plesty.lib.device.base_device_sync

Base synchronous device model for Plesty.

Attributes

DATA_ROOT_VAR

DATA_PATH_VAR

DATA_SHARE_VAR

Classes

BaseDeviceSyncModel

Abstract base class for synchronous device models in Plesty.

Module Contents

plesty.lib.device.base_device_sync.DATA_ROOT_VAR = 'DEVICE_DATA_ROOT'
plesty.lib.device.base_device_sync.DATA_PATH_VAR = 'DEVICE_DATA_PATH'
plesty.lib.device.base_device_sync.DATA_SHARE_VAR = 'PLESTY_DATA'
class plesty.lib.device.base_device_sync.BaseDeviceSyncModel(id: str, op_schema=None, param_schema=None, op_solver=None, cmd_solver=None, data_path: str | os.PathLike[str] | None = None, allowed_data_roots: collections.abc.Iterable[str | os.PathLike[str]] | None = None, **kwargs)

Bases: abc.ABC, plesty.lib.device.params.ConfigSystem, plesty.lib.device.funcs.FunctionSystem, plesty.lib.device.telemetry.TelemetrySystem

Abstract base class for synchronous device models in Plesty.

Initialize the device model with optional schemas and solvers.

Parameters:
  • id (str) – Device identifier, combined with the host IP into the resource key.

  • op_schema – Optional operation schema for the function system.

  • param_schema – Optional parameter schema for the configuration system.

  • op_solver – Optional operation solver bound to the function system.

  • cmd_solver – Optional command solver bound to the configuration system.

  • data_path (str | os.PathLike[str] | None) – Default directory for acquired data, used until a client calls set_data_path(). Device modules read it from their own configuration source (YAML, .env) and pass it in; None takes DEVICE_DATA_PATH, which the bench sets to the device’s folder.

  • allowed_data_roots (collections.abc.Iterable[str | os.PathLike[str]] | None) – Directories that client-supplied paths must resolve under; the first is the one a client’s relative path is taken from. None takes DEVICE_DATA_ROOT — the bench’s folder on the Plesty share — else [data_path], so a device can only be redirected inside its own data directory.

  • **kwargs – Extra keyword arguments, accepted and ignored so subclasses can pass their own construction options up the chain.

Raises:

ValueError – If data_path does not resolve under allowed_data_roots.

id
_resources: list[Any] = []
_resource_manager: plesty.lib.service.resource_manager.ResourceManager | None = None
_data_share: pathlib.Path | None
_allowed_data_roots: list[pathlib.Path]
_root_spellings: list[tuple[pathlib.Path, pathlib.Path]]
_default_data_path: pathlib.Path | None = None
_data_path: pathlib.Path | None = None
_data_path_display: pathlib.Path | None
register_resources(resources: list[Any]) → None

Declare the physical resources this device owns.

Extends the device’s resource inventory and registers the same set with the embedded ResourceManager so that direct (non-TCP) clients can request exclusive access via allocate_resource().

Parameters:

resources (list[Any]) – Resource identifiers in any format accepted by ResourceManager (flat list, set, or nested dict tree).

Return type:

None

allocate_resource(resources: Any, client_id: str) → bool

Request exclusive access to one or more device resources.

Delegates to the embedded ResourceManager. Returns True if the allocation succeeded, False if any requested resource is already held by another client. When no resources have been registered, all requests are granted.

Parameters:
  • resources (Any) – Resource identifiers (str, list, or dict tree).

  • client_id (str) – Stable identifier for the requesting client.

Return type:

bool

release_resource(client_id: str) → None

Release all resources previously allocated to client_id.

Parameters:

client_id (str) – Identifier of the client whose allocation should be freed.

Return type:

None

client_resources(client_id: str) → list[str]

Return the canonical resource paths currently allocated to client_id.

Parameters:

client_id (str) – Identifier of the client to query.

Return type:

list[str]

_is_allowed_data_path(path: pathlib.Path) → bool

Return whether path resolves under one of the allowed data roots.

Parameters:

path (pathlib.Path)

Return type:

bool

_display_path(resolved: pathlib.Path) → pathlib.Path

Re-base resolved onto the configured spelling of its data root.

Path.resolve() rewrites a mapped Windows drive to its UNC form, which the operator never configured and some vendor software rejects. A path under a known root is expressed under that root’s configured spelling; anything else is returned as it came.

Parameters:

resolved (pathlib.Path)

Return type:

pathlib.Path

_resolve_data_path(path: str | os.PathLike[str]) → pathlib.Path

Resolve path, relative to the device’s data root, to an absolute directory.

The root is the first allowed one — the bench’s folder on the Plesty share — so a client routes a run with set_data_path("pol-pl/run-7") without knowing how this machine mounts the share.

Parameters:

path (str | os.PathLike[str]) – Directory supplied by the caller, relative to the data root.

Raises:

ValueError – If path is absolute, or names a drive or a home directory — every machine spells those differently.

Return type:

pathlib.Path

_reported_path(resolved: pathlib.Path) → str

resolved as a client is told it: relative to the Plesty share.

Every machine mounts the share under its own path, so a path from the share is the one spelling all of them read back. Without a share it is relative to the data root it lies under.

Parameters:

resolved (pathlib.Path)

Return type:

str

set_data_path(path: str | os.PathLike[str], **kwargs) → str

Set the directory where this device stores acquired data.

The path is taken relative to the device’s data root — the bench’s folder on the Plesty share (DEVICE_DATA_ROOT) — resolved (.. collapsed, symlinks followed) and must stay under it; it is created if missing. All clients of a device server share one data path — the last call wins, matching the single-operator resource-lock model.

Parameters:
  • path (str | os.PathLike[str]) – Target directory relative to the data root, any depth (pol-pl/run-7/raw/cam). An absolute path is refused: every machine mounts the share under its own.

  • **kwargs – Optional extras such as resources, ignored here.

Returns:

The directory now in effect, relative to the Plesty share (PLESTY_DATA) — platform-a/pol-pl/run-7/raw/cam — which any machine joins to its own mount of the share. Relative to the data root when the device is not told where the share is.

Raises:

ValueError – If the path is absolute or leaves the data root, or the device has no data path configured at all.

Return type:

str

get_data_path(**kwargs) → str

Return the directory currently used for acquired data.

Device code that writes acquisition files should read the directory from here rather than from its own configuration, so client-set paths take effect.

Parameters:

**kwargs – Optional extras such as resources, ignored here.

Returns:

the last one set by a client, or the device’s configured default. The directory is created if missing — the configured default is otherwise never created anywhere (construction is side-effect-free), and vendor software validating the directory rejects one that does not exist (plesty-lib#31).

Return type:

The directory in effect, in its configured spelling

Raises:

RuntimeError – If the device has no data path configured.

abstractmethod init(main=None) → None

Initialize the device. This should be called before any other operations.

Return type:

None

property is_operatable: bool

Check if the device is currently operatable (connected and responsive).

Return type:

bool

property state: dict

Return a dictionary representing the current device state with all parameters.

Return type:

dict

get_state() → dict[str, Any]

Return a dictionary of all configuration parameters and their current values.

Return type:

dict[str, Any]

synchronize_param_from_device(keys: list[str] | None = None, sync_constraints: bool = False) → None

Synchronizing the model’s configuration with the actual device.

Parameters:
  • keys (list[str] | None)

  • sync_constraints (bool)

Return type:

None

doc_model() → plesty.lib.device.doc.DeviceDocModel

Return this device’s structured, render-agnostic documentation model.

The model bundles the configuration parameters (grouped) and every callable operation (schema-registered plus @expose_to_api custom methods, unified). Documentation renderers consume it instead of re-deriving the metadata.

Returns:

The DeviceDocModel for this device.

Return type:

plesty.lib.device.doc.DeviceDocModel

summary(style: str = 'short', filename=None) → str

Summarize both configuration parameters and registered functions.

Parameters:
  • style (str) – One of short, md, or google.

  • filename – Optional file path to save the rendered summary.

Returns:

A rendered summary string combining parameter and function sections.

Return type:

str

get_query_command(key: str) → str | None

Return the command that would be sent to query key, sending nothing.

An accessor, not an action — which is the whole difference from query(). query(key) performs the exchange, consumes the reply and returns the value; this returns the command, touches no I/O and leaves the session exactly as it found it. The naming follows the split already in this class: get_* reads what the device declares, a bare verb talks to it.

It exists so a caller can transmit a query without reading its answer — the orphaned reply the buffer-drain gate needs. That takes the command and the transport’s orphan(), and the command is the half only the device knows.

Parameters:

key (str) – Configuration key.

Returns:

The command, or None when this device cannot produce one — the base implementation, since a solver is bound by the subclass. The drain gate then skips and says so.

Return type:

str | None

query_param_range(key: str) → tuple[float | int | None, float | int | None]

Query the device for the valid range of a numeric parameter.

Parameters:

key (str)

Return type:

tuple[float | int | None, float | int | None]

query_param_options(key: str) → list[Any] | None

Query the device for the valid options of a categorical parameter.

Parameters:

key (str)

Return type:

list[Any] | None

abstractmethod connect() → Any

Placeholder method for connecting to the device.

Return type:

Any

abstractmethod disconnect() → Any

Placeholder method for disconnecting from the device.

Return type:

Any

abstractmethod identity() → str

Query the device for its identity string.

Return type:

str

preflight() → dict[str, Any]

Report what this device’s transport sees, without opening anything.

Exposed to the API on purpose: a composite on another machine can only see that a server does or does not answer, and “the server answers but its stage is not on the bus” is a different problem with a different fix. Asking the server lets that reach the experiment that asked, instead of stopping at the machine the instrument is attached to.

Returns:

to_dict() of the transport’s report — serial numbers dropped, because this crosses a machine boundary and may be written down on the other side. A device with no transport, or one not connected yet, answers unknown rather than claiming anything.

Return type:

dict[str, Any]

abstractmethod check_errors() → list[str]

Query the device for any error messages.

Return type:

list[str]

abstractmethod check_operatability() → bool

Check if the device is currently operatable (connected and responsive).

Return type:

bool

__enter__()

Enter the device context: acquire the resource lock, init and connect.

__exit__(exc_type, exc_value, tb)

Exit the device context, disconnecting and releasing resources.

The resource lock is released even when disconnect() fails. A transport that throws on the way out — a clear refused by a wedged session, a close on an already-dead handle — must not leave the device marked as in use: every later attempt then fails with “Resource already in use” against an instrument nothing is holding, and the process has to be restarted to clear it.

response_to_target_type(key, response) → Any

Parse a raw device response into the target type for the given parameter key.

Return type:

Any

_user_permission_check(user_resources: list[Any], key: str) → bool

Check if the client is permitted to access key given its allocated resources.

The default implementation is permissive (returns True). Subclasses should override this method with device-specific logic, and may call client_resources() or inspect _resource_manager to query current allocations.

Parameters:
  • user_resources (list[Any]) – Resources reported by the client for this request.

  • key (str) – The configuration parameter or function key being accessed.

Return type:

bool

write(key: str, value: str | float | int | bool, **kwargs) → bool

Write a configuration parameter value to the device after validation.

Parameters:
  • key (str) – Name of the parameter to write (as listed on the Parameters page).

  • value (str | float | int | bool) – New value; validated against the parameter’s type and range before being sent to the device.

  • **kwargs – Optional extras such as resources for per-client access control.

Returns:

True if the write succeeded, False otherwise (e.g. a permission check failed).

Return type:

bool

query(key: str, **kwargs) → Any

Query a configuration parameter value from the device.

Parameters:
  • key (str) – Name of the parameter to read (as listed on the Parameters page).

  • **kwargs – Optional extras such as resources for per-client access control.

Returns:

The parameter’s current value, converted to its declared type.

Return type:

Any

abstractmethod _write_(key: str, value: str | float | int | bool) → bool

Placeholder method for writing a command to the device.

Parameters:
  • key (str)

  • value (str | float | int | bool)

Return type:

bool

abstractmethod _query_(key: str) → str

Placeholder method for querying the device.

Parameters:

key (str)

Return type:

str

reset() → None

Reset the device to a known state.

Return type:

None

clear() → None

Clear the device state.

Return type:

None