generated from ImperialCollegeLondon/poetry_template_2
-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add developer documentation for hardware framework #379
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
684859d
Add developer documentation for hardware framework
alexdewar 730da46
markdown-link-checker: Ignore links to API docs
alexdewar b4d5d88
"temperature monitor" => "temperature controller"
alexdewar 847393a
Update for changes to hardware code
alexdewar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"ignorePatterns": [ | ||
{ | ||
"pattern": "^\\.\\./reference/" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# Hardware | ||
|
||
To work, FINESSE requires connections to a number of devices. At a minimum, this must | ||
include: | ||
|
||
- An interferometer | ||
- A stepper motor for orienting the interferometer's mirror | ||
- Temperature controllers for getting and setting the temperature of the hot and cold | ||
black bodies | ||
- A separate temperature monitor with sensors recording from various angles around the | ||
mirror | ||
|
||
All the code for interfacing with the hardware lives in the | ||
[`finesse.hardware`](../reference/finesse/hardware) module. | ||
|
||
## Plugin architecture | ||
|
||
Code in the `finesse.hardware` module is not imported into the frontend code | ||
([`finesse.gui`](../reference/finesse/gui)) directly. Instead, messages are passed back | ||
and forth using the [PyPubSub](https://pypi.org/project/PyPubSub/) package. | ||
|
||
As we want the user to be able to select which devices to use at runtime, the FINESSE | ||
hardware framework is designed to be modular. This is achieved via a plugin system. Each | ||
device type and device base type (explained below) is represented by a plugin class | ||
residing somewhere in the | ||
[`finesse.hardware.plugins`](../reference/finesse/hardware/plugins) module. To add a new | ||
plugin, it is sufficient just to define this class in a `.py` file and put it somewhere | ||
in the plugins directory hierarchy. | ||
|
||
### Creating a new device type | ||
|
||
A device base type is a class providing a common interface for similar device types | ||
(e.g. a stepper motor). Each device base class must inherit from `Device` and each | ||
device class must inherit from a device base class. | ||
|
||
You can create a new device base like so: | ||
|
||
```py | ||
class MyBaseType( | ||
Device, is_base_type=True, name="my_base_type", description="Example base type" | ||
): | ||
# ... | ||
``` | ||
|
||
The `is_base_type=True` is required to register the class as a device base type. `name` | ||
is the short name for the base type and is used in the topic for PyPubSub messages (see | ||
more below). `description` provides a human-readable name for the base type, which will | ||
be displayed in the GUI. It is additionally possible to provide a list of possible names | ||
for instances of the device, but this is currently only used for temperature controllers | ||
(to distinguish between the hot and cold black body controllers). | ||
|
||
You can create a concrete implementation of `MyBaseType` like so: | ||
|
||
```py | ||
class MyDevice1(MyBaseType, description="An example device"): | ||
# ... | ||
``` | ||
|
||
You may optionally provide a `dict` specifying which parameters should be passed when | ||
the device object is constructed, along with a human-readable description. This provides | ||
a mechanism by which the frontend can know what parameters it can provide for a given | ||
device type, as well as information about their type and default value (if any). Here is | ||
an example: | ||
|
||
```py | ||
class MyDevice2( | ||
MyBaseType, | ||
description="An example device", | ||
parameters={"my_param": "An example parameter"} | ||
): | ||
def __init__(self, my_param: int = 42) -> None: | ||
# ... | ||
``` | ||
|
||
In this case, the frontend will be informed that `MyDevice2` has a parameter, `my_param` | ||
of type `int` with a default value of `42`. The user can then alter this value via a | ||
text box in the GUI. Note that if a default value were *not* provided for this | ||
parameter, the user would be forced to enter one. Subclasses inherit their parents' | ||
device parameters (but can add more of their own). As a result, they *must* also include | ||
these parameters for their constructors. | ||
|
||
You can also provide a `Sequence` of possible values that a parameter can take, e.g.: | ||
|
||
```py | ||
class MyDevice3( | ||
MyBaseType, | ||
description="An example device", | ||
parameters={"my_param": ("An example parameter", range(10))} | ||
): | ||
def __init__(self, my_param: int = 5) -> None: | ||
# ... | ||
``` | ||
|
||
In this case, `my_param` must be a number in the range 0 to 9. The user will be able to | ||
select from among these options in a dropdown box. | ||
|
||
Subclasses can provide different default values for device parameters than their | ||
parents, simply by providing a different default value in their constructors. This is | ||
used by device classes for USB serial devices to choose a default baud rate. For | ||
example: | ||
|
||
```py | ||
class MyUSBDevice(SerialDevice, MyBaseType, description="A USB serial device"): | ||
def __init__(self, port: str, baudrate: int = 9600) -> None: | ||
# ... | ||
``` | ||
|
||
Note that the constructor must have both the `port` and `baudrate` parameters as they | ||
are defined as device parameters by the `SerialDevice` base class. The `SerialDevice` | ||
class must be listed before `MyBaseType` unless `MyUSBDevice` defines its own `close()` | ||
method, otherwise you will get an error about this abstract method not being | ||
implemented. | ||
|
||
### Communicating with devices via PyPubSub | ||
|
||
Many messages for communicating with devices include a string indicating which device | ||
the communication is intended for (prefixed by `device.`). This is composed of the | ||
device base type's name and, if provided, the device's name. For example, this could be | ||
`stepper_motor` for the stepper motor and `temperature_controller.hot_bb` for the hot | ||
black body temperature controller. | ||
|
||
To connect to a device, the frontend should send a `device.open` message, indicating | ||
which device type should be opened, along with any device parameters. If the connection | ||
is successful, a `device.opening.*` message is sent, followed by a `device.opened.*` | ||
one. If the connection fails, a `device.error.*` message is sent instead. | ||
(`device.error.*` messages can also be sent at any point during the device's lifetime to | ||
indicate that an error has occurred.) Similarly, the `device.close` method is used to | ||
close a connection to a device. | ||
|
||
If the frontend sends a `device.list.request` message all of the plugins are loaded and | ||
information about each device type (grouped by base type) is sent to the frontend with | ||
the `device.list.response` message. Note that this step is not required in order to open | ||
a device: if the name of the plugin and values for parameters are known (e.g. if the | ||
user is connecting to a predefined hardware set), it is sufficient to just send the | ||
`device.open` message. | ||
|
||
Device types also need to define their own message types for communication. For example, | ||
the `StepperMotorBase` class allows for setting the current angle of the stepper motor | ||
with a `device.stepper_motor.move.begin` message. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be useful, I think, to indicate somewhere the list of all possible messages a particular device type handles - either sending or receiving. In the end, pubsub topics are implicit function calls with their own parameters and they should be documented in detail as we do with any other function.
We have never done it before, but given the complexity of the messaging system of FINESSE, it might be worth considering doing something like this https://pypubsub.readthedocs.io/en/v4.0.3/usage/usage_advanced_maintain.html#specify-topic-tree-def, although as most topics are created implicitely based on the device names, I'm not sure how far we can go down this route...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
The doc you link to looks interesting. I'll see if we can incorporate that way of doing things 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good idea, but I think we should do it later. I've opened an issue for it: #432