lewis.core.adapters

This module contains Adapter, which serves as a base class for concrete adapter implementations in lewis.adapters. It also contains AdapterCollection which can be used to store multiple adapters and manage them together.

Members

Adapter

Base class for adapters

AdapterCollection

A container to manage the adapters of a device

NoLock

A dummy context manager that raises a RuntimeError when it's used.

class lewis.core.adapters.Adapter(options: dict[str, Any] | None = None)[source]

Bases: object

Base class for adapters

This class serves as a base class for concrete adapter implementations that expose a device via a certain communication protocol. It defines the minimal interface that an adapter must provide in order to fit seamlessly into other parts of the framework (most importantly Simulation).

Sub-classes should re-define the protocol-member to something appropriate. While it is explicitly supported to modify it in concrete device interface implementations, it is good to have a default (for example epics or stream).

An adapter should provide everything that is needed for the communication via the protocol it defines. This might involve constructing a server-object, configuring it and starting the service (this should happen in start_server()). Due to the large differences between protocols it is very hard to provide general guidelines here. Please take a look at the implementations of existing adapters (EpicsAdapter, StreamAdapter),to get some examples.

In principle, an adapter can exist on its own, but it only really becomes useful when a device is bound to it. To do this, assign an object derived from lewis.core.devices.DeviceBase to the device-property. Sub-classes have to implement _bind_device() to achieve actual binding behavior.

It is possible to pass a dictionary with configuration options to Adapter. The keys of the dictionary are accessible as properties of the _options-member. Only keys that are in the default_options member of the class are accepted. Inheriting classes must override default_options to be a dictionary with the possible options for the adapter.

Each adapter has a lock member, which contains a NoLock by default. To make device access thread-safe, any adapter should acquire this lock before interacting with the device (or interface). This means that before starting the server component of an Adapter, a proper Lock-object needs to be assigned to lock.

Parameters:

options – Configuration options for the adapter.

property documentation: str

This property can be overridden in a sub-class to provide protocol documentation to users at runtime. By default it returns the indentation cleaned-up docstring of the class.

handle(cycle_delay: float = 0.1) None[source]

This function is called on each cycle of a simulation. It should process requests that are made via the protocol that exposes the device. The time spent processing should be approximately cycle_delay seconds, during which the adapter may block the current process. It is desirable to stick to the provided time, but deviations are permissible if necessary due to the way the protocol works.

Parameters:

cycle_delay – Approximate time spent processing requests.

property interface: InterfaceBase | None

The device property contains the device-object exposed by the adapter.

The property can be set from the outside, at that point the adapter will call _bind_device() (which is implemented in each adapter sub-class) and thus re-bind its commands etc. to call the new device.

property is_running: bool

This property indicates whether the Adapter’s server is running and listening. The result of calls to start_server() and stop_server() should be reflected as expected.

start_server() None[source]

This method must be re-implemented to start the infrastructure required for the protocol in question. These startup operations are not supposed to be carried out on construction of the adapter in order to preserve control over when services are started during a run of a simulation.

Note

This method may be called multiple times over the lifetime of the Adapter, so it is important to make sure that this does not cause problems.

See also

See stop_server() for shutting down the adapter.

stop_server() None[source]

This method must be re-implemented to stop and tear down anything that has been setup in start_server(). This method should close all connections to clients that have been established since the adapter has been started.

Note

This method may be called multiple times over the lifetime of the Adapter, so it is important to make sure that this does not cause problems.

class lewis.core.adapters.AdapterCollection(*args: Adapter)[source]

Bases: object

A container to manage the adapters of a device

This container is designed to keep all adapters that expose a device in one place and interact with them in a uniform way.

Adapters can be passed as arguments upon construction or added later on using add_adapter() (and removed using remove_adapter()). The available protocols can be queried using the protocols() property.

Each adapter can be started and stopped separately by supplying protocol names to connect() and disconnect(), both methods accept an arbitrary number of arguments, so that any subset of the stored protocols can be handled at any time. Supplying no protocol names at all will start/stop all adapters. These semantics also apply for is_connected() and documentation.

This class also makes sure that all adapters use the same Lock for device interaction.

Parameters:

args – List of adapters to add to the container

add_adapter(adapter: Adapter) None[source]

Adds the supplied adapter to the container but raises a RuntimeError if there’s already an adapter registered for the same protocol.

Parameters:

adapter – Adapter to add to the container

configuration(*args: str) dict[str | None, dict[str, Any]][source]

Returns a dictionary that contains the options for the specified adapter. The dictionary keys are the adapter protocols.

Parameters:

args – List of protocols for which to list options, empty for all adapters.

Returns:

Dict of protocol: option-dict pairs.

connect(*args: str) None[source]

This method starts an adapter for each specified protocol in a separate thread, if the adapter is not already running.

Parameters:

args – List of protocols for which to start adapters or empty for all.

property device_lock: allocate_lock

This lock is passed to each adapter when it’s started. It’s supposed to be used to ensure that the device is only accessed from one thread at a time, for example during network IO. Simulation uses this lock to block the device during the simulation cycle calculations.

disconnect(*args: str) None[source]

Stops all adapters for the specified protocols. The method waits for each adapter thread to join, so it might hang if the thread is not terminating correctly.

Parameters:

args – List of protocols for which to stop adapters or empty for all.

documentation(*args: str) str[source]

Returns the concatenated documentation for the adapters specified by the supplied protocols or all of them if no arguments are provided.

Parameters:

args – List of protocols for which to get documentation or empty for all.

Returns:

Documentation for all selected adapters.

is_connected(*args: str) bool | dict[str | None, bool][source]

If only one protocol is supplied, a single bool is returned with the connection status. Otherwise, this method returns a dictionary of adapter connection statuses for the supplied protocols. If no protocols are supplied, all adapter statuses are returned.

Parameters:

args – List of protocols for which to start adapters or empty for all.

Returns:

Boolean for single adapter or dict of statuses for multiple.

property protocols: list[str]

List of protocols for which adapters are registered.

remove_adapter(protocol: str) None[source]

Tries to remove the adapter for the specified protocol, raises a RuntimeError if there is no adapter registered for that particular protocol.

Parameters:

protocol – Protocol to remove from container

set_device(new_device: DeviceBase) None[source]

Bind the new device to all interfaces managed by the adapters in the collection.

class lewis.core.adapters.NoLock[source]

Bases: object

A dummy context manager that raises a RuntimeError when it’s used. This makes it easier to detect cases where an Adapter has not received the proper lock-object to make sure that device/interface access is synchronous.