Data model
Data flow
The flow chart below shows the data flow from the host to the device using the
set_temperature()
method for the
IKA RCT Digital hotplate:
_recv()
is usually called
automatically based on whether reply is expected from device according to the
command definition (see below). However, _recv()
can be called
explicitly to acquire the data from underlying connection object. The data flow
inside _recv()
is presented in the next flow chart:
Timeouts
There are several timeouts implemented to facilitate flow control and avoid deadlocking on the connection level:
- command_delay
This is the delay between two sequential commands sent to ensure the device has enough time to process the first one before getting another one. This delay is maintained inside the repspective connection class
transmit()
method.- receive_timeout
This is the delay for the underlying connection’s
receive()
method.- transmit_timeout
This is the delay for the underlying connection’s
send()
method.- receiving_interval
This is the delay for the connection listener loop to sleep between reading from the underlying connection object.
The default values for the timeouts can be found in the
DEFAULT_CONNECTION_PARAMETERS
dictionary of the AbstractConnection
class. They can be accessed and
overriden through the LabDevice.connection
object:
>>> LabDevice.connection.command_delay = 2 # Sets inter-command delay to 2 seconds
Device command structure
Command set for every device is grouped into a helper class acting purely as a command container. Each command definition is a simple Python dictionary. The command dictionary contains all the necessary information related to any particular command including:
Argument value type.
Allowed argument values range/set.
Information whether a reply is expected after issuing the command.
Rules for reply parsing.
Below is an example of command definitions with flattened dictionary structure for visual compactness:
class RCTDigitalHotplateCommands(LabDeviceCommands):
"""Collection of command definitions for for IKA RCT Digital stirring hotplate.
"""
...
# Get internal hotplate sensor temperature
GET_TEMP = {"name": "IN_PV_2", "reply": {"type": float, "parser": parser.slicer, "args": [-2]}}
# Set temperature
SET_TEMP = {"name": "OUT_SP_1", "type": int, "check": {"min": 20, "max": 310}}
...
The principal structure of the dictionary is as follows:
cmd_name = {"name": "cmd_string",
"type": None,
"check": {},
"reply": {}
}
- cmd_name
A human-understandable command name used in function calls. Must be capitalized. Mandatory.
- cmd_string
An actual command sent to the device, according to device specs. Unused fields should be either assigned to None or skipped. Mandatory.
- type
One of the standard Python types that the command parameter has to be casted to before sending. Optional.
- check
A sub-dictionary defining command parameter checking rules (see below). Optional.
- reply
A sub-dictionary defining command reply handling (see below). Optional.
Warning
PyLabware check the presence of reply
key in the command
dictionary to judge whether a reply should be expected from the device
after sending a command.
As there is no universal way to map the reply to the preceding
command, an unsolicited reply would be processed by PyLabware and
given out next time _recv()
is called, which would break
the data flow.
Thus, even if no reply is desired, but the device still sends it
back, an empty reply: {}
key should be included in the command
definition.
The shortest command specification without any parameter value checking and no reply expected would be:
TEST_COMMAND = {"name":"T"}
The shortest command definition with reply expected would be:
TEST_COMMAND = {"name":"T", "reply":{}}
Value checking
There are two basic options for value checking: built in - min/max check and value in range check. Custom workflows for value checking are not supported.
Min/max check
Example:
SET_TEMP = {"name":"ST",
"type":int,
"check": {"min": 20,
"max": 180
}
}
- min
Minimum allowed value for the command parameter.
- max
Maximum allowed value for the command parameter.
The example above defines a command with an integer
argument having minimum
allowed value of 20
and maximum allowed value of 180
. Upon invoking
device.send(SET_TEMP, 52.5)
, the following would happen:
The value
52.2
would be cast toSET_TEMP["type"]
, being transformed to 52.A check
is 52 > 20
would be performed, if not,SLDeviceCommandError()
exception would be raised.A check
is 52 < 180
would be performed, if not,SLDeviceCommandError
exception would be raised.The checked parameter would be glued together with the
SET_TEMP["name"]
along with the proper termination.The resulting message (e.g.
ST 52\r\n
) would be sent to the device after whichdevice.send()
would return as no reply is expected according to the command definition.
Value in range check
Example:
SET_ROTATION_DIR = {"name":"SRD",
"type":str,
"check": {"values": ["CW", "CCW", "cw", "ccw"]
}
}
- values
Any iterable holding discrete values that the command parameter can take.
The execution flow for this command would be similar to the one above with the
min/max check being replaces by the parameter in values
check.
Built-in reply parsers
As it was explained above, PyLabware checks for the presence of the reply
key in
the command definition to figure out whether it should wait for the device reply
after sending a command. The contents of the reply
define the further
processing of the obtained reply:
GET_TEMP = {"name": "IN_PV_2",
"reply": {"type": float,
"parser": parser.slicer,
"args": [-2]
}
}
- type
One of the standard Python types that the reply would be cast to. Optional.
Note
To provide compatibility with more complex processing, type casting only takes place if the original reply type is one of the basic Python types - int, float, str or bool.
- parser
Any callable to pass the reply to for the further parsing. See the section on custom parsers below for the detailed description.
- args
A list of positional arguments to pass to the parser.
A few simple parsers that are used most often are provided in the
PyLabware.parsers
module.
Making custom parsers
To make a custom parser when developing your own device driver,
simply define a function taking reply
as a first argument:
def my_parser(reply, dofourtytwo=0):
if dofourtytwo == 42:
return 42
else:
return reply
Then provide it as a parser in the command definition:
MY_AWESOME_CMD = {cmd_name="DO", type: str, "reply":{"parser": my_parser, "args":[42]}}
Note the following:
The absence of quotes around
my_parser
in the command definition - it has to be passed by reference, not by calling it.Even when we pass only a single argument, it still has to be passed as a list.
Device reply structure
- class PyLabware.models.LabDeviceReply(body='', content_type='text', parameters=None)[source]
This class defines the data model for a device reply for all transport types (plain text, HTTP REST, …)
- content_type
Content type is a literal defining how the reply body should be treated. Currently, only two types are supported - text for plain-text replies and json for JSON replies.
- parameters
Optional Python dictionary holding any required reply parameters.
- body
The reply body.
Abstract classes hierarchy
The abstract classes hierarchy is one of the core concepts in PyLabware. It ensures, that all the devices are classified into a limited number of basic types, according to their capabilities. Every device type provides a uniform set of methods irrespective of the particular device’s internal implementation.
This is maintained by a set of hierarchical abstract classes branching down from
Python’s standard ABC
. Each abstract class presents a limited set of
basic abstract methods that must be implemented by all children along with
any additional methods to provide extra functionality.
- class PyLabware.models.AbstractLabDevice[source]
Bases:
ABC
Base abstract class for all labware devices.
- abstract check_errors()[source]
Gets errors from the device (if the device supports it) and raises SLDeviceInternalError with error-specific message if any errors are present.
- abstract execute_when_ready(action, *args, check_ready)[source]
Acquires device lock, waits till device is ready and runs device method.
- Parameters:
action –
- A function to run when the device is ready
args: List of arguments for the method to run
- check_ready: A method to use for checking whether
the device is ready or not.
- abstract initialize_device()[source]
Many devices require hardware initialization before they can be used. This method should run hardware initialization/resetting or setting all the necessary parameters.
This method has to be redefined in child classes.
- abstract is_connected()[source]
Checks if connection to the device is active. This method should issue a device-specific command (e.g. status/info command) and check the reply from the device to figure out whether the device is actually responsive, and not just returns the state of the connection itself (e.g. underlying connection object is_connection_open() method).
This method has to catch all potential exceptions and always return either True or False.
- Returns:
Whether device is connected or not.
- Return type:
(bool)
- abstract is_idle()[source]
Checks whether the device is in idle state. The idle state is defined as following:
The device has just been powered on AND is_connected() is True
OR if device has an idle status indication - if device.status == idle
OR if device doesn’t have an idle status indication - after device.stop() was called
This method has to execute device-specific command, if possible, to check whether a device is in idle state as defined above.
If there’s no command to get device status, an internal flag self._running has to be used. If the device has multiple activities (e.g. hotplate), an appropriate set of flags has to be used (idle == not (self._stirring or self._heating))
This method has to catch all potential exceptions and always return either True or False.
This method has to be redefined in child classes.
- Returns:
Device ready status
- Return type:
(bool)
- abstract property simulation
Determines whether the device behaves as as a real or simulated one. Simulated device just logs all the commands.
- abstract start()[source]
Main method that starts device’s intended activity. E.g if it’s a stirrer, starts stirring. If it’s a stirring hotplate - starts both stirring and heating. For granular control child classes for the devices capable of multiple activities (e.g. stirring hotplate) must implement separate methods defined in the respective derivate abstract classes.
- class PyLabware.controllers.AbstractTemperatureController(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
Any device capable of heating or cooling with temperature regulation.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- abstract get_temperature(sensor=0)[source]
Gets the actual temperature.
- Parameters:
sensor (int) – Specify which temperature probe the setpoint applies to. Default (0) is the internal probe.
- Return type:
float
- abstract get_temperature_setpoint(sensor=0)[source]
Gets desired temperature setpoint.
- Parameters:
sensor (int) – Specify which temperature probe the setpoint applies to. Default (0) is the internal probe.
- Return type:
float
- class PyLabware.controllers.AbstractPressureController(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
Any device capable of regulating the pressure.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- class PyLabware.controllers.AbstractStirringController(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
Any device capable of stirring.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- abstract get_speed_setpoint()[source]
Gets desired stirring speed setpoint, in RPM.
- Return type:
int
- class PyLabware.controllers.AbstractDispensingController(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
Any device capable of withdrawing and dispensing the material.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- abstract dispense(amount)[source]
Dispenses the defined amount of material.
- Parameters:
amount (int) –
- Return type:
None
- class PyLabware.controllers.AbstractHotplate(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
AbstractTemperatureController
,AbstractStirringController
A typical hotplate capable of heating and stirring simultaneously.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- class PyLabware.controllers.AbstractSyringePump(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
AbstractDispensingController
A syringe pump device.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- class PyLabware.controllers.AbstractDistributionValve(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
A 1-to-N distribution device.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- abstract get_valve_position()[source]
Gets currently selected distribution valve output.
- Return type:
Any
- class PyLabware.controllers.AbstractRotavap(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
AbstractTemperatureController
,AbstractStirringController
A typical rotary evaporator, without integrated vacuum controller/pump.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.
- abstract start_bath()[source]
Human-friendly wrapper for the start_temperature_regulation() of the parent class.
- Return type:
None
- abstract start_rotation()[source]
Human-friendly wrapper for the start_stirring() of the parent class.
- Return type:
None
- start_stirring()[source]
Mandatory method inherited from the AbstractStirringController.
- Return type:
None
- start_temperature_regulation()[source]
Mandatory method inherited from the AbstractTemperatureController.
- Return type:
None
- abstract stop_bath()[source]
Human-friendly wrapper for the stop_temperature_regulation() of the parent class.
- Return type:
None
- abstract stop_rotation()[source]
Human-friendly wrapper for the stop_stirring() of the parent class.
- Return type:
None
- class PyLabware.controllers.AbstractFlashChromatographySystem(device_name=None, connection_mode=None, connection_parameters=None)[source]
Bases:
LabDevice
A flash chromatography system.
Default constructor. This function performs object initialization. All device-specific hardware initialization procedures should be inside the
initialise_device()
method.This method has to be redefined in child classes.
- Parameters:
device_name (
str
) – Device name (for logging purposes).connection_mode (
str
) – Physical connection mode (defines the connection adapter used).connection_parameters (
Dict
) – Dictionary with connection-specific settings. These vary depending on the connection_mode.