Parameter System

The parameter system is the metadata and validation layer used by device classes in PlestyLib.

It defines what can be queried or written, validates user input, stores cached values, and supports typed parsing of device responses.

Core implementation: ConfigSystem in plesty.lib.device.params.

What It Does

ConfigSystem provides:

  1. Parameter registration through code (register_config) or schema (register_params_from_schema).

  2. Optional grouping of parameters (register_config_group).

  3. Validation for type, range, and categorical options.

  4. Read/write permission flags (read_only, write_only).

  5. Current value cache per parameter (value).

  6. Automatic response casting through AutoResponseParser.

  7. Response parsers named from the schema (parser), including a device’s own.

  8. Human-readable summaries (param_summary).

Main Data Structures

ConfigParameter

Represents one parameter, including:

  1. name

  2. dtype

  3. default and current value

  4. unit

  5. read_only, write_only

  6. min_value, max_value

  7. options

  8. command (string or Command object)

  9. parser (optional custom response parser)

  10. description

ConfigGroup

Represents a parameter group with:

  1. Group name

  2. Group parameter dictionary

  3. Optional command prefix

  4. Optional list of group-level allowed commands

  5. Group description

Command

Command metadata object used in grouped schemas:

  1. name

  2. command # some devices may provide command to query or set parameters in this group

  3. required_params # defines required parameters for the group command

  4. optional_params # defines optional paramters for the group command

  5. mode (write or query)

  6. description

Register Parameters in Code

from plesty.lib.device.base_visa_scpi_device import BaseVisaScpiDevice


class PowermeterDevice(BaseVisaScpiDevice):
		def __init__(self, address: str):
				super().__init__(address)

				self.register_config("POWER", dtype=float, unit="watt", read_only=True, command="MEAS:SCAL:POW")
				self.register_config("WAVELENGTH", dtype=int, unit="nm", min_value=400, max_value=1100, command="SENS:CORR:WAV")
				self.register_config("AUTO_RANGE", dtype=bool, default=True, command="SENS:POW:RANG:AUTO")

Register Parameters from Schema

register_params_from_schema supports loading from a JSON file in the following shape:

{
	"ROSCillatorFrequency": {
		"type": "float",
		"command": "SOUR:ROSC:FREQ",
		"description": "Set or query the frequency of the internal reference oscillator."
	},
	"SyncClockoutValue": {
		"type": "integer",
		"command": "SYNC:VAL",
		"read_only": true
	}
}

Load schema from file path or dictionary:

from plesty.lib.device.params import ConfigSystem

cfg = ConfigSystem(param_schema="param_schema.json")
# or
# cfg = ConfigSystem(param_schema=schema_dict)

Load schema directly in a base-device constructor (BaseDeviceSyncModel):

from plesty.lib.device.base_device_sync import BaseDeviceSyncModel


class MyDevice(BaseDeviceSyncModel):
	def __init__(self, id: str, param_schema):
		super().__init__(id=id, param_schema=param_schema)

	def connect(self):
		...

	def disconnect(self):
		...

	def check_errors(self) -> list[str]:
		return []

	def check_operatability(self) -> bool:
		return True

	def _write_(self, key, value) -> bool:
		return True

	def _query_(self, key) -> str:
		return "0"


dev_a = MyDevice(id="dev-1", param_schema="param_schema.json")
# or
# dev_b = MyDevice(id="dev-2", param_schema=schema_dict)

Validation Rules

Validation happens at registration and at write-time checks.

  1. Type checks: values must match dtype.

  2. Numeric range checks: min_value <= value <= max_value.

  3. Option checks: categorical values must be in options.

  4. Permission checks: cannot write read_only parameters.

Important behavior:

  1. Registration errors raise exceptions.

  2. check_write_config raises on invalid values.

  3. set_config_value attempts auto-casting before range/options validation.

Query/Write Flow Integration

In synchronized devices, high-level methods in BaseDeviceSyncModel use ConfigSystem as follows:

  1. write(key, value)

  2. check_write_config(key, value) validates the request

  3. Device-specific _write_ sends command to hardware

  4. On success, set_config_value updates cached value

For queries:

  1. query(key)

  2. check_query_config(key) validates access

  3. Device-specific _query_ gets raw response

  4. response_to_target_type parses/casts response

  5. set_config_value stores parsed value

Response Parsing

By default, AutoResponseParser tries to cast response text to the parameter dtype.

Examples:

  1. "42" -> int

  2. "3.14" -> float

  3. "true"/"1" -> bool (via basic cast helper)

  4. Delimited strings for typed list/tuple annotations

A reply the auto parser cannot cast is returned unchanged. When the value has to be pulled out of the reply first, name a parser in the parameter’s parser key.

Naming a parser from a schema

parser takes a kind name, or a mapping of a kind and its configuration:

{
	"wavelength": {
		"type": "float",
		"unit": "nm",
		"command": "?NM",
		"parser": {"kind": "regex", "pattern": "(-?\\d+\\.\\d+) nm"}
	}
}

The kind is resolved to a ResponseParser when the parameter is registered, so a misspelled kind, an invalid pattern or an out-of-range capture group fails while the schema is being loaded rather than on the first query.

Built-in kinds:

kind

configuration

reply → value

auto

—

"3.14" → 3.14 (the default)

regex

pattern, group (default 1, a number or a group name), encoding

"605.334 nm  ok" → 605.334

delimited

index (default 0, negatives count from the end), separator (default ,), encoding

"IDN,ACME,X100,1.2.3" with index: 2 → "X100"

Both parsers hand their result to AutoResponseParser, so the parameter’s type still performs the conversion — the pattern or the separator only decides which characters are the value. A bytes reply is decoded with encoding (default utf-8) first.

Use delimited when the reply carries several fields and the parameter is one of them. When the parameter’s dtype is itself a list or tuple, auto already splits the reply and no parser is needed.

Registering a device’s own parser

Binary frames and replies keyed by a mnemonic are the device’s own business. The module registers a parser once and every parameter then names it from the schema:

from plesty.lib.device.device_utils import ResponseParser
from plesty.lib.device.params import register_parser_kind


class AptStatusParser(ResponseParser):
	def __init__(self, field: str):
		self.field = field

	def parse(self, response, param=None, **kwargs):
		return decode_apt_status(response)[self.field]


register_parser_kind("apt_status", AptStatusParser)
{
	"position": {
		"type": "float",
		"command": "MGMSG_MOT_REQ_STATUSUPDATE",
		"parser": {"kind": "apt_status", "field": "position"}
	}
}

Schema keys other than kind are passed to the factory as keyword arguments. Registering the same name twice with the same factory is fine; a different factory under a name already in use raises. parser_kinds() lists what is currently nameable.

Passing a ResponseParser instance directly to register_config(parser=...) from Python keeps working unchanged.

Useful API Methods

  1. register_config(...)

  2. register_config_group(...)

  3. register_params_from_schema(...)

  4. get_config(key, group="default")

  5. get_config_list(group="default")

  6. get_group_list()

  7. set_config_value(key, value)

  8. get_config_value(key)

  9. set_config_min_max(key, min_value, max_value)

  10. param_summary(constraints=True, description=False)

  11. register_parser_kind(name, factory) / parser_kinds() (module-level)

Minimal End-to-End Example

from plesty.lib.device.params import ConfigSystem

system = ConfigSystem()

system.register_config(
		key="GAIN",
		dtype=int,
		min_value=1,
		max_value=10,
		default=5,
		command="CONF:GAIN",
)

system.check_write_config("GAIN", 7)
system.set_config_value("GAIN", "7")  # auto-cast to int

print(system.get_config_value("GAIN"))
print(system.param_summary())

Best Practices

  1. Always set dtype for robust validation and auto-casting.

  2. Prefer explicit min_value and max_value for numeric parameters.

  3. Use options for categorical values.

  4. Keep command strings in parameter metadata so solver logic stays generic.

  5. Use schema-driven registration for larger devices.

  6. Include description and unit fields to improve generated summaries and docs.