Skip to content

Commit

Permalink
Update for changes to hardware code
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdewar committed Nov 23, 2023
1 parent b4d5d88 commit 847393a
Showing 1 changed file with 60 additions and 19 deletions.
79 changes: 60 additions & 19 deletions docs/hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,38 +52,72 @@ for instances of the device, but this is currently only used for temperature con
You can create a concrete implementation of `MyBaseType` like so:

```py
class MyDevice(MyBaseType, description="An example device"):
class MyDevice1(MyBaseType, description="An example device"):
# ...
```

Certain device base types require extra parameters. For example, to create a USB serial
device type which also inherits from `MyBaseType`, you can do:
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 MySerialDevice(
SerialDevice, MyBaseType, description="An example device", default_baudrate=9600
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:
# ...
```

### Device parameters
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.

Devices can also require additional parameters to be specified by the user, such as port
and baudrate for serial devices. Details about these parameters are set in an
`__init_subclass__()` method for the class. These parameters will be passed as arguments
to the device type's constructor when it is opened.
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. 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.

When the main program window has loaded, the plugins are dynamically loaded and
information about each device type (grouped by base type) is sent to the frontend with
the `device.list` message.
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
Expand All @@ -93,6 +127,13 @@ one. If the connection fails, a `device.error.*` message is sent instead.
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.

0 comments on commit 847393a

Please sign in to comment.