plesty.server.view.control
The Control window: one device panel per running device, in one shell.
Several devices get driven at once — a stage and the meter reading it — so the
controls cannot be a modal dialog: it would freeze the console’s table, its log
tail and its job list for as long as it is open, which is the reason
docs/design/console.md already refuses modals for failures. Nor is it one loose window
per device, which nothing arranges and nothing remembers.
It is a Shell instead — plesty-lib’s docking window,
opened inside the console’s own application (Shell takes a parent and
starts no event loop of its own). Docking, tabbing, dragging a panel to a
second screen and the arrangement surviving a restart all come with it, and one
clock ticks every panel. The console’s main window keeps its own layout: this
is a separate window, not a change to it.
Each panel holds its own DeviceLink and its
own worker thread, so a device server that stopped answering costs its panel
and nothing else.
Attributes
Classes
A shell holding one |
Functions
|
Close the link of an already-closed panel. |
Module Contents
- plesty.server.view.control.TITLE = 'PLESTY Control'
- plesty.server.view.control.INTERVAL_MS = 500
- plesty.server.view.control.EMPTY_HINT = Multiline-String
Show Value
"""No device controls open. Controls need a running device server: start one from the console, then open it here with Device > Control... (Ctrl+D)."""
- class plesty.server.view.control.ControlWindow(bench: plesty.server.presenter.Bench, parent: PySide6.QtWidgets.QWidget | None = None, title: str | None = None)
Bases:
plesty.lib.ui.qt.shell.ShellA shell holding one
DevicePanelper device being controlled.Create the window; it holds no panels until one is opened.
- Parameters:
bench (plesty.server.presenter.Bench) – The presenter, asked for a device’s link.
parent (Optional[PySide6.QtWidgets.QWidget]) – The console’s main window. Qt keeps a parented window on top of it and closes it with it, which is what stops a control window outliving the console that opened it.
title (Optional[str]) – What the window is called. The bench’s own wall takes the default; a window opened for one device is named for it, so two of them on a second screen can be told apart.
- bench
- closed = False
- _placeholder = False
- say_empty(text: str) None
Fill an empty window with text instead of leaving it blank.
A
Shellhas no central widget — its panels are the layout — so one holding no panels paints a menu bar, a status bar and nothing at all. Opening it and being shown an empty frame reads as a broken window, not as an empty one, which is exactly how it was first reported.- Parameters:
text (str) – What to show; replaces any previous placeholder.
- Return type:
None
- _sync_placeholder() None
Show the placeholder when there are no panels, and never beside one.
- Return type:
None
- open_device(name: str, writable: bool = False) plesty.lib.ui.DevicePanel
Show the controls of name, or focus them if they are already up.
Asking twice for one device must not give it two panels: the second would poll the same instrument on a second connection and show its own idea of the values.
- Parameters:
name (str) – The device to control.
writable (bool) – Start with writes enabled. The default is read-only.
- Returns:
The panel showing that device.
- Raises:
BenchError – When the device is not in a state to be controlled.
- Return type:
plesty.lib.ui.DevicePanel
- open_handle(name: str, handle: object, host: str = '', writable: bool = False) plesty.lib.ui.DevicePanel
Show the controls of name on an already-built handle.
What a panel drives is a protocol —
describe,query,write,call— and a device on a node satisfies it through the agent’s relay exactly as one on this bench satisfies it directly. So a remote device needs no panel of its own, only its own handle and its own key: two benches may each declare apm100d, and one panel must not stand for both.- Parameters:
name (str) – The device to control.
handle (object) – What the panel drives.
host (str) – The node it runs on, empty for this bench’s own.
writable (bool) – Start with writes enabled. The default is read-only.
- Returns:
The panel showing that device.
- Return type:
plesty.lib.ui.DevicePanel
- open_devices(names: collections.abc.Sequence[str]) list[tuple[str, str]]
Open a panel for each of names, skipping the ones that refuse.
One device that cannot be controlled must not cost the operator the others: a bench of four with one server down should show three panels and say what happened to the fourth.
- Parameters:
names (collections.abc.Sequence[str]) – The devices to open.
- Returns:
(name, reason)for each device that could not be opened.- Return type:
list[tuple[str, str]]
- close_device(name: str) bool
Remove the panel of name, releasing its connection.
- Parameters:
name (str) – The device whose panel to drop.
- Returns:
Whether there was one.
- Return type:
bool
- property devices: list[str]
The devices that currently have a panel, in the order they were opened.
- Return type:
list[str]
- reread(name: str) bool
Have the panel of name read its device again;
Falsewhen it has none.After the server is started or restarted the panel has made no call, so the error banner of the last one that failed stayed up over a device that now answers (#22). A fresh read replaces the values the old server reported and, when it succeeds, clears the banner.
- Parameters:
name (str)
- Return type:
bool
- hold(name: str) bool
Stop the panel of name calling its server while it is stopped.
Released by
reread()once the server is back. A handle that cannot be held — a node’s relay — is left as it is.- Returns:
Whether there was a panel to hold.
- Parameters:
name (str)
- Return type:
bool
- closeEvent(event: Any) None
Stop every panel’s worker, then release the connections.
- Parameters:
event (Any) – The Qt close event.
- Return type:
None
- plesty.server.view.control._release(panel: Any) None
Close the link of an already-closed panel.
Order matters, which is why this is never called before the panel is: the link’s socket was opened on the panel’s worker thread, and a ZMQ socket may only move to another thread once nothing else can touch it. Closing the panel joins that worker, and the join is the barrier that makes closing the socket here safe.
A worker that did not stop — a call still stuck in an instrument past the join’s timeout — keeps its socket. Closing it under a thread that is still using it is the one thing that could crash the console, and the cost of leaving it is a socket on a daemon thread that the process exit reclaims.
- Parameters:
panel (Any) – The panel whose handle to release.
- Return type:
None