plesty.lib.ui.device_panel
The panel that shows a device’s controls, derived from what it declares.
A device already describes its controls completely. This, from a spectrometer’s schema, is a widget specification:
ConfigParameter(name='exposure', dtype=float, unit='ms', default=100.0,
min_value=0.0, max_value=None, options=None,
read_only=False, description='Integration time …')
dtype picks the control, min_value/max_value bound it, options
makes it a drop-down, read_only makes it a readout, unit is the suffix
and description is the tooltip. Hand-writing that duplicates the schema,
and the duplicate drifts: the panel written by hand for one spectrometer bounded
an exposure at 0.01–60000 ms where the device declares no upper bound at all —
a limit that exists nowhere in the instrument, silently constraining whoever
uses it.
So controls_for() derives the controls instead, and DevicePanel
renders them. What it deliberately does not do:
Device-specific views stay out. A spectrum, a beam image, a stage map are not derivable from a schema; they are monitors, and a
MonitorPaneldocks beside this one.Not every operation belongs on a panel. Homing a stage or replacing a dark reference should not be one click away, so operations declared
lifecycleare left out unless asked for by name.A monitor holds no device; this necessarily does. It drives an instrument somebody may be measuring with, which is why it starts read-only.
from plesty.lib.ui import DevicePanel, LocalHandle, run
run([DevicePanel(LocalHandle(spectrometer), name="cct",
include=("exposure", "hardware_average"))])
Attributes
Classes
One rendered control, as derived from the device's own declaration. |
|
Sub-window holding one device's controls. |
Functions
|
Turn a schema key into something readable. |
|
Address a parameter the way the device resolves it. |
|
Decide which control a declaration asks for. |
|
The types a declaration allows, a union split into its members. |
|
Return value as a tuple, or |
|
Build the control one configuration parameter asks for. |
|
Build the control one operation argument asks for. |
|
Build the control one operation asks for. |
|
Describe one value an operation returns. |
|
Derive a device's controls from its serialized doc model. |
Module Contents
- plesty.lib.ui.device_panel.CONTROL_KINDS: tuple[str, Ellipsis] = ('number', 'choice', 'toggle', 'text', 'readout', 'action')
- plesty.lib.ui.device_panel.DEFAULT_SKIP_KINDS: tuple[str, Ellipsis] = ('lifecycle',)
- plesty.lib.ui.device_panel._NUMERIC
- plesty.lib.ui.device_panel.REPEATABLE_OP_KINDS: tuple[str, Ellipsis] = ('read', 'acquire')
- plesty.lib.ui.device_panel.DEFAULT_POLL_S = 1.0
- class plesty.lib.ui.device_panel.Control
One rendered control, as derived from the device’s own declaration.
- Variables:
key – Configuration key or operation name.
kind – One of
CONTROL_KINDS.label – Text shown beside the control.
dtype – Declared type name, as it crossed the wire.
group – Configuration group the parameter belongs to.
unit – Physical unit, shown as the editor’s suffix.
minimum – Lower bound, or
Nonewhen the device declares none.maximum – Upper bound, or
Nonewhen the device declares none.options – Allowed values, which make the control a drop-down.
default – Declared default.
tooltip – The declaration’s description.
read_only – The device reports it; the operator cannot set it.
write_only – The operator sets it; it cannot be read back, so it is never polled.
arguments – For an
action, the operation’s arguments as controls of their own — the same rules, one level down.outputs – For an
action, what it returns — name, type and unit — which is what lets a result be shown as a reading rather than as the mapping it arrives in.op_kind – For an
action, itsOpKindkind.
- key: str
- kind: str
- label: str
- dtype: str = 'Any'
- group: str = 'default'
- unit: str | None = None
- minimum: float | None = None
- maximum: float | None = None
- options: tuple[Any, Ellipsis] | None = None
- default: Any = None
- tooltip: str = ''
- read_only: bool = False
- write_only: bool = False
- op_kind: str = ''
- required: bool = True
- property types: frozenset[str]
The types this declaration allows, a union split into its members.
Noneis not among them: it is not a type an editor can offer, and what is left is what the control has to be able to send. Switch on this rather than ondtype, which is one string and may be a union.- Return type:
frozenset[str]
- property omittable: bool
Whether leaving this argument alone means sending nothing.
An optional argument the device gives no default for has an answer already — its own — and the panel has no way to show one. A spin box always holds a number, so an untouched one said
0, and0is a real value:wait_settledread it as a zero-second budget and timed out at once. Such an argument is shown as unset and left out of the call instead.- Return type:
bool
- property repeatable: bool
Whether holding this control should keep asking the device.
Any readable parameter. An operation only when the module declares it
REPEATABLE_OP_KINDS— a reading or an acquisition, never a move or a stop.- Return type:
bool
- property readable: bool
Whether polling this control makes sense.
- Return type:
bool
- property writable: bool
Whether the operator can send this control anywhere.
- Return type:
bool
- plesty.lib.ui.device_panel._label_for(name: str) str
Turn a schema key into something readable.
- Parameters:
name (str) – The declared key, conventionally
lower_snake_case.- Returns:
The key with separators as spaces and the first letter capitalised.
- Return type:
str
- plesty.lib.ui.device_panel.DEFAULT_GROUP = 'default'
- plesty.lib.ui.device_panel._full_key(group: str, name: str) str
Address a parameter the way the device resolves it.
A grouped parameter is addressed as
group.parameterand a device refuses the bare key outright —Grouped parameter 'CustomZero' requires full key format 'group_prefix.param_key'. The panel used to derive its key from the parameter’s name alone, so every read and every write of a grouped parameter was refused by the device it was built from.The group’s own name is what is used here. A device may also give a group a command prefix, but it resolves either, and the prefix is not in the serialized model this panel is built from.
- Parameters:
group (str) – The configuration group the parameter was declared in.
name (str) – The parameter’s declared name.
- Returns:
The key the device answers to.
- Return type:
str
- plesty.lib.ui.device_panel._kind_for(dtype: str, options: Any, read_only: bool) str
Decide which control a declaration asks for.
Order matters: a read-only parameter is a readout whatever its type, so that an enumerated reading is not offered as a drop-down that cannot be set.
- Parameters:
dtype (str) – Declared type name.
options (Any) – Declared allowed values, if any.
read_only (bool) – Whether the device refuses writes to it.
- Returns:
One of
CONTROL_KINDS.- Return type:
str
- plesty.lib.ui.device_panel._declared_types(dtype: str) frozenset[str]
The types a declaration allows, a union split into its members.
An optional number is declared
float | None, and read whole it is neitherfloatnorint: the parameter fell through to a text field, which hands the device the string an operator typed. A timeout entered as10000then reached the instrument as"10000"and the device failed comparing a str to an int.Noneis not a type an editor can offer, so it is dropped: what is left is what the control has to be able to send.- Parameters:
dtype (str) – The declared type name, possibly a union.
- Returns:
The member names, without
None.- Return type:
frozenset[str]
- plesty.lib.ui.device_panel._tuple_or_none(value: Any) tuple[Any, Ellipsis] | None
Return value as a tuple, or
Nonewhen it holds nothing.- Parameters:
value (Any)
- Return type:
Optional[tuple[Any, Ellipsis]]
- plesty.lib.ui.device_panel._parameter_control(spec: dict[str, Any], group: str) Control
Build the control one configuration parameter asks for.
- Parameters:
spec (dict[str, Any]) – One entry of a serialized doc model’s
parameters.group (str) – The configuration group it came from.
- Returns:
The derived control.
- Return type:
- plesty.lib.ui.device_panel._argument_control(spec: dict[str, Any]) Control
Build the control one operation argument asks for.
An argument declares its bounds as a
rangepair rather than as two fields, and is never read-only — otherwise the rules are the parameters’.- Parameters:
spec (dict[str, Any]) – One entry of an operation’s
iparams.- Returns:
The derived control.
- Return type:
- plesty.lib.ui.device_panel._operation_control(spec: dict[str, Any]) Control
Build the control one operation asks for.
- Parameters:
spec (dict[str, Any]) – One entry of a serialized doc model’s
functions.- Returns:
An
actioncontrol carrying the operation’s arguments.- Return type:
- plesty.lib.ui.device_panel._output_control(spec: dict[str, Any]) Control
Describe one value an operation returns.
- Parameters:
spec (dict[str, Any]) – One entry of an operation’s
oparams.- Returns:
A
readoutcontrol — it is a value the device reports, which is what a result is.- Return type:
- plesty.lib.ui.device_panel.controls_for(doc: dict[str, Any] | None, *, include: Sequence[str] | None = None, exclude: Iterable[str] = (), operations: Sequence[str] | None = None, skip_kinds: Iterable[str] = DEFAULT_SKIP_KINDS) list[Control]
Derive a device’s controls from its serialized doc model.
This is the whole schema-to-widget mapping, and it is deliberately free of any toolkit: it is where the bounds, units and options come from, so it is where a drifting panel would be caught, and a plain test can catch it.
- Parameters:
doc (Optional[dict[str, Any]]) – A
to_dict()payload — from adescribereply or from the device itself.None(an older server, or a device whose model failed to build) yields no controls rather than an error.include (Optional[Sequence[str]]) – Parameter keys to show, in this order.
Noneshows every declared parameter. A grouped parameter is named as the device addresses it,group.parameter.exclude (Iterable[str]) – Parameter keys to leave out, applied after include.
operations (Optional[Sequence[str]]) – Operation names to offer, in this order.
Noneoffers every operation whose kind is not in skip_kinds; naming one overrides that, so an operation can always be asked for explicitly.skip_kinds (Iterable[str]) – Operation kinds left out when operations is
None.
- Returns:
The parameters in declaration order, then the operations.
- Return type:
list[Control]
- class plesty.lib.ui.device_panel.DevicePanel(handle: plesty.lib.ui.device_link.DeviceHandle, *, doc: dict[str, Any] | None = None, include: Sequence[str] | None = None, exclude: Iterable[str] = (), operations: Sequence[str] | None = None, skip_kinds: Iterable[str] = DEFAULT_SKIP_KINDS, writable: bool = False, poll_s: float = DEFAULT_POLL_S, theme: plesty.lib.ui.theme.Theme | None = None, **kwargs: Any)
Bases:
plesty.lib.ui.panel.PanelSub-window holding one device’s controls.
Constructing the panel touches neither a toolkit nor the device: it is built when a shell adds it, and it asks the device what it offers on its own worker thread, so a slow or absent instrument delays nothing. Until that answer arrives the panel says so.
The panel starts read-only. It may be pointed at an instrument an experiment is driving, and a stray write into a running measurement is worse than a click:
set_writable()is what a Take control toggle calls.Wrap handle into a dockable control panel.
- Parameters:
handle (plesty.lib.ui.device_link.DeviceHandle) – What the panel drives — see
DeviceHandle.doc (Optional[dict[str, Any]]) – The device’s serialized doc model when the caller already has it (a console that just called
describe);Nonemakes the panel fetch it.include (Optional[Sequence[str]]) – Parameter keys to show, in order.
exclude (Iterable[str]) – Parameter keys to leave out.
operations (Optional[Sequence[str]]) – Operations to offer, in order.
skip_kinds (Iterable[str]) – Operation kinds left out by default.
writable (bool) – Start with writes and operations enabled. The default is read-only.
poll_s (float) – Seconds between re-reads of the device.
theme (Optional[plesty.lib.ui.theme.Theme]) – Palette;
Noneloads the packaged one.**kwargs (Any) – Forwarded to
Panel.
- kind: ClassVar[str] = 'device'
Panel category, used for layout keys and styling hooks.
- handle
- theme = None
- writable = False
- poll_s = 1.0
- live: dict[str, dict[str, Any]]
- values: dict[str, Any]
- worker
- form: Any = None
- _selection
- _doc = None
- _note = ''
- _tone = 'idle'
- _latency = 0.0
- _polled_at = 0.0
- _selection_kwargs() dict[str, Any]
Return the selection arguments
controls_for()was configured with.- Return type:
dict[str, Any]
- open() None
Start the worker and ask the device what it offers.
Separate from
build()so the device-facing half of the panel — which is all of the interesting behaviour — can be driven with no display and no toolkit: open it,tick()it, and readcontrolsandvalues.- Return type:
None
- build(parent: Any = None) Any
Create the form, then open the device.
- Parameters:
parent (Any) – The parent widget provided by the shell.
- Returns:
The form widget.
- Return type:
Any
- tick() None
Apply whatever the worker finished, then poll if polling is on.
Both halves run on the shell’s clock and neither blocks: this is the only place results cross back to the GUI thread, and the coalescing in
DeviceWorkeris what keeps a slow device from building a backlog it can never work off.Polling is off until it is asked for — see
set_live().- Return type:
None
- control(key: str) Control | None
The control named key, or
None.- Parameters:
key (str)
- Return type:
Optional[Control]
- _read(key: str, arguments: dict[str, Any] | None = None) None
Ask the device about one control: a parameter’s value, or an operation.
- Parameters:
key (str)
arguments (Optional[dict[str, Any]])
- Return type:
None
- read_once(key: str | None = None, arguments: Any = None) None
Ask the device once: for key, or for every value the panel shows.
- Parameters:
key (Optional[str]) – The control to read;
Nonereads every readable one.arguments (Any) – For an operation, what to call it with.
- Return type:
None
- set_live(key: str, live: bool, arguments: Any = None) None
Start or stop re-reading one control on the shell’s clock.
- Parameters:
key (str) – The control to keep reading.
live (bool) – Whether to keep reading it.
arguments (Any) – For an operation, what to keep calling it with. Taken once, when it starts: an operation repeating on a timer should go on asking the same question, not whatever the form happens to hold when the timer next fires.
- Return type:
None
- is_live(key: str) bool
Whether key is being re-read.
- Parameters:
key (str)
- Return type:
bool
- status() str
Return the line shown in the panel’s title bar.
- Returns:
What the panel is doing, or what went wrong.
- Return type:
str
- tone() str
Return the state colour for the panel’s dot.
- Returns:
errorwhen the last call failed,warningwhen the device offered no schema,runningonce controls are on screen.- Return type:
str
- close() None
Stop the worker thread.
- Return type:
None
- describe() dict[str, Any]
Return the panel description including what it drives.
- Returns:
The panel’s placement plus its controls and whether it is writable.
- Return type:
dict[str, Any]
- set_writable(writable: bool) None
Enable or disable every editor and button.
- Parameters:
writable (bool) – Whether the operator may drive the device.
- Return type:
None
- request_write(key: str, value: Any) None
Send a parameter change to the device.
- Parameters:
key (str) – Configuration key.
value (Any) – The value the operator entered.
- Return type:
None
- request_call(op: str, arguments: dict[str, Any] | None = None) None
Run an operation on the device.
- Parameters:
op (str) – Operation name.
arguments (Optional[dict[str, Any]]) – The operation’s arguments.
- Return type:
None
- _apply(result: plesty.lib.ui.device_link.Result) None
Fold one finished call into the panel’s state and its form.
- Parameters:
result (plesty.lib.ui.device_link.Result) – What the worker handed back.
- Return type:
None
- _receive_doc(describe: Any) None
Derive the controls from a
describereply and fill the form.- Parameters:
describe (Any) – The reply, or
None.- Return type:
None
- _fail(result: plesty.lib.ui.device_link.Result) None
Record a failed call in the title bar.
- Parameters:
result (plesty.lib.ui.device_link.Result) – The failed result.
- Return type:
None