Skip to content

Commit

Permalink
Add graphical documentation of a setup.
Browse files Browse the repository at this point in the history
  • Loading branch information
BenediktBurger committed May 14, 2024
1 parent 5235b4d commit 92520de
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 30 deletions.
113 changes: 111 additions & 2 deletions GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,12 @@ This example is the code behind the call of the director example.
For a program which runs happily in the background listening for commands, and executing them, you can base your code on the [`MessageHandler`](pyleco/utils/message_handler.py) (in `utils` directory).
The MessageHandler will listen to incoming messages and handle them in a continuous loop.
The `listen` method has three parts:

1. The method `_listen_setup`, where you can specify, what to do at the start of listening,
2. the method `_listen_loop_element`, which is executed in the loop until the `stop_event` is set.
1. the method `_listen_loop_element`, which is executed in the loop until the `stop_event` is set.
Typically, it listens for incoming messages and handles them,
3. the method `_listen_close`, which finishes after having been told to stop.
1. the method `_listen_close`, which finishes after having been told to stop.


If you want to make a method available for RPC calls, you can use the `register_rpc_method` method.
That allows any Component to use that method via `ask_rpc`.
Expand Down Expand Up @@ -257,3 +259,110 @@ In this example, the message handler will offer the `my_method` method via RPC (
However, that method will be executed in the message handler thread!
You can send messages via the message handler by using the listener's `communciator`.
In this example the director uses this communicator instead of creating its own one with its own name.



## Graphical overview

Here a graphical overview over an experimental setup.
Solid lines indicate data flow, dashed lines decisions/additional actions.
Round fields participate in the LECO network.
Diamonds are Coordinators.
Note that the control protocol (with the _COORDINATOR_) is symmetric, while the data protocol (_proxy_server_, bold lines) is one way.

```mermaid
flowchart TD
COM2 <--> C{"COORDINATOR
(control protocol)"}
PUB([DataPublisher]) ==> Proxy
Proxy{"proxy_server
(data protocol)"} ==> SUB([Subscriber])
Proxy ==>SUBD
subgraph DataLogger
SUBD([Subscriber])-->STO[(Storage)]
end
subgraph Jupyter Notebook
Script <--> Director
Director -.->|creates|COM2([Communicator])
Director <-->COM2
Script{{Script}} <--> Director2 <--> COM2
COM2
subgraph Director2
end
end
C <--> MessageHandler
subgraph "`Actor`"
MessageHandler([MessageHandler]) <--> InstrumentDriver[Instrument Driver]
MessageHandler ==> PUB2([DataPublisher])
end
InstrumentDriver <--> |control|Instrument{{Measurement Instrument}}
PUB2 ==> Proxy
subgraph GUI-App
subgraph Main Thread
Listener-.->|generates|ListenerCOM(Listener Communicator)
GUI{{GUI}}
Director5
end
subgraph Listener Thread
ListenerEMH([ExtendedMessageHandler])
end
subgraph Another Thread
Director6[Director]<-->LC2(Listener Communicator)
end
LC2<-->ListenerEMH
Listener-.->|generates|LC2
Listener-.->|starts|ListenerThread
ListenerCOM<-->ListenerEMH
GUI<-->ListenerCOM
Director5[Director]<-->ListenerCOM
GUI<-->Director5
ListenerEMH-->|notifies|GUI
end
ListenerEMH <---> C
Proxy ==> ListenerEMH
```
The _Actor_ contains a message handler handling incoming messages in order to control a hardware measurement instrument via a _Driver_. It also has a _DataPublisher_ to publish measurement data regularly.

A script uses _Directors_ to send messages via a _Communicator_ (generated by the first _Director_) to that measurement instrument.

The GUI-App has a _Listener_ in its main thread, which starts an _ExtendedMessageHandler_ in another thread and offers its _Listener Communicator_ in order to communicate.
The GUI can then communicate via that Communicator to the network, either directly or using _Directors_.
Also other threads can use their own Communicator for communication.
It can send commands to the measurement instrument via the _Actor_.
It can also subscribe to the data published by the _Actor_.

The _DataLogger_ collects the data published via the data protocol (_proxy_server_).


This is an overview over the extended message handler
```mermaid
flowchart TD
subgraph ExtendedMessageHandler
subgraph listen-loop
L1[Read control]-.-> L2{Message?}-.->|yes|L3[Process command]
L2-.->|no|L4
L3 -.-> L4[Read broadcast]
L4 -.->L5{Message?}
L5-.->|yes|L6[Process broadcast]-.->L7
L5-.->|no|L7
L7[restart loop]-.->L1
end
end
C{COORDINATOR} --> L1
L3-->|respond| C
P[proxy_server] --> L4
```
61 changes: 33 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ For a tutorial on how to get started, see [GETTING_STARTED.md](https://github.co
## Quick Start

1. Install Python,
2. Execute `pip install pyleco`,
3. Import `pyleco` in your python scripts.
2. install PyLECO with `pip install pyleco` or `conda install conda-forge::pyleco`,
3. import the package `pyleco` in your python scripts,
4. and use it as desired.


## LECO Overview
Expand All @@ -33,20 +34,20 @@ PyLECO is an implementation of LECO, for the full protocol specifications please
LECO offers a protocol for data exchange, for example for laboratory experimental control.

There exist two different communication protocols in LECO.
1. The control protocol allows to exchange messages between any two Components in a LECO network, which is useful for controlling devices.
1. The control protocol allows to exchange messages between any two _Components_ in a LECO network, which is useful for controlling devices.
2. The data protocol is a broadcasting protocol to send information to all those, who want to receive it, which is useful for regular measurement data or for log entries.

A LECO network needs at least one Coordinator (server), which routes the messages among the connected Components.
A LECO network needs at least one _Coordinator_ (server), which routes the messages among the connected Components.

Each Component has a name unique in the network.
This name consists in the name of the Coordinator they are connected to and their own name.
For example `N1.component1` is the full name of `component1` connected to the Coordinator of the Namespace `N1`.
The Coordinator istelf is always called `COORDINATOR`.
Each _Component_ has a name unique in the network.
This name consists in the name of the _Coordinator_ they are connected to and their own name.
For example `N1.component1` is the full name of `component1` connected to the _Coordinator_ of the _Namespace_ `N1`.
That _Coordinator_ itself is called `N1.COORDINATOR`, as _Coordinators_ are always called `COORDINATOR`.

### Remote Procedure Calls

The default messaging content of the control protocol are remote procedure calls (RPC) according to jsonrpc.
With these RPCs you execute a method on the remote Component.
The default messaging content of the control protocol are _remote procedure calls_ (RPC) according to [JSON-RPC](https://www.jsonrpc.org/specification).
RPC means, that you execute a method (or procedure) on a remote _Component_.
For example you have an Actor, which is for example a Component controlling a measurement instrument.
In order to set the output of that measurement instrument, you want to call the `set_output` method of that instrument.
For that purpose, you send a message which encodes exactly that (via jsonrpc): the method to call and the parameters of that method.
Expand All @@ -57,11 +58,11 @@ For that purpose, you send a message which encodes exactly that (via jsonrpc): t
### Minimum Setup

For a minimum setup, you need:
* a Coordinator (just execute `coordinator` in your terminal or run the `coordinator.py` file with your Python interpreter)
* one Component
* a _Coordinator_ (just execute `coordinator` in your terminal or run the `coordinator.py` file with your Python interpreter)
* one _Component_

For example, you can use a `Communicator` instance to send/receive messages via LECO protocol.
The following example requests the list of Components connected to the Coordinator:
The following example requests the list of _Components_ connected currently to the _Coordinator_:

```python
from pyleco.utils.communicator import Communicator
Expand All @@ -76,7 +77,7 @@ print(connected_components)
Let's say you have an instrument with a pymeasure driver `Driver`, which you want to control.

You need to start (in different threads):
* a Coordinator (as shown above).
* a _Coordinator_ (as described above).
* an `Actor` instance listening to commands and controlling the instrument: `actor = Actor(name="inst_actor", cls=Driver)`.
For an example see the `pymeasure_actor.py` in the examples folder.
* a `TransparentDirector`: `director=TransparentDirector(actor="inst_actor")`. The `actor` parameter has to match the Actor's `name` parameter.
Expand All @@ -93,26 +94,30 @@ Afterwards you can use these methods transparently similar to the property shown

## Overview of Offered Packages and Modules

See the docstrings of the individual classes for more information and for examples.
PyLECO offers the following subpackages and modules.
For more information and for examples see the docstrings of the relevant methods and classes.

* The `core` subpackage contains elements necessary for implementing LECO, especially the `Message` class, which helps to create and interpret LECO messages.
* The `core` subpackage contains elements necessary for implementing LECO and for interacting with PyLECO, for example:
* The `Message` and `DataMessage` class help to create and interpret LECO messages for the control and broadcasting protocol, respectively.
* The `leco_protocols` module contains _Protocol_ classes for the different LECO _Components_, in order to test, whether a _Component_ satisfies the LECO standard for communicating with other programs.
* The `internal_protocols` module contains _Protocol_ classes which define the API access to PyLECO.
* The `utils` subpackage contains modules useful for creating LECO Components.
* The`Communicator` can send and receive messages, but neither blocks (just for a short time waiting for an answer) nor requires an extra thread.
It is useful for usage in scripts.
* The `MessageHandler` handles incoming messages in a continuous loop (blocking until stopped).
It is useful for creating standalone scripts, like tasks for the Starter.
* The `Listener` offers the same interface as the Communicator, but listens in an extra thread for incoming messages.
It satisfies the `CommunicatorProtocol` and is useful in scripts.
* The `MessageHandler` also satisfies the `CommunicatorProtocol`, but handles incoming messages in a continuous loop (blocking until stopped).
It is useful for creating standalone scripts, like tasks for the _Starter_.
* The `Listener` offers an interface according to the `CommunicatorProtocol`, but listens at the same time in an extra thread for incoming messages.
It is useful if you want to react to incoming messages (via data or control protocol) and if you want to send messages of your own accord, for example for GUI applications.
* The `coordinators` subpackage contains the differenc Coordinators.
* `Coordinator` is the Coordinator for the control protocol (exchanging messages).
* `proxy_server` is the Coordinator for the data protocol (broadcasting).
* The `actors` subpackage contains Actor classes to control devices.
* The `management` subpackage contains Components useful for experiment management
* The `coordinators` subpackage contains the different _Coordinators_.
* `Coordinator` is the _Coordinator_ for the control protocol (exchanging messages).
* `proxy_server` is the _Coordinator_ for the data protocol (broadcasting).
* The `actors` subpackage contains _Actor_ classes to control devices.
* The `management` subpackage contains _Components_ useful for experiment management
* The `Starter` can execute tasks in separate threads.
A task could be an Actor controlling some Device.
* The `DataLogger` listens to published data (data protocol) and collects them.
A task could be an _Actor_ controlling some _Device_.
* The `DataLogger` listens to published data (via the data protocol) and collects them.
* The `directors` subpackage contains Directors, which facilitate controlling actors or management utilities.
* For example the `CoordinatorDirector` has a method for getting Coordinators and Components connected to a Coordinator.
* For example the `CoordinatorDirector` has a method for getting _Coordinators_ and _Components_ connected to a _Coordinator_.
* The `TransparentDirector` reads / writes all messages to the remote actor, such that you use the director's `device` as if it were the instrument itself.

### PyLECO extras
Expand Down

0 comments on commit 92520de

Please sign in to comment.