plesty.server.services.control ============================== .. py:module:: plesty.server.services.control .. autoapi-nested-parse:: The console's connection to a running device server. The console manages device servers; this is the one place it *talks* to one. A :class:`DeviceLink` satisfies plesty-lib's ``DeviceHandle`` — ``describe``, ``query``, ``write``, ``call`` — so a ``DevicePanel`` built on it renders the controls the device declares, and the console needs no schema of its own. Two things it does deliberately: **It connects on first use, not when it is made.** ``DeviceTCPIPClient`` handshakes and describes inside its constructor, and a ZMQ socket belongs to the thread that created it. A panel runs its calls on a worker thread of its own, so building the client there — on the first ``describe`` — keeps the socket on one thread for its whole life, instead of making it on the console's pool and migrating it. **It asks for no resources.** The connection handshake can claim an allocation, and the device server enforces it. A console panel is a second client on a device an experiment may be driving: it takes nothing, so opening the controls can never be what makes a measurement fail. Writes are gated in the panel instead, behind *Take control*. Attributes ---------- .. autoapisummary:: plesty.server.services.control.DEVICE_TIMEOUT_MS Classes ------- .. autoapisummary:: plesty.server.services.control.DeviceLink Module Contents --------------- .. py:data:: DEVICE_TIMEOUT_MS :value: 15000 .. py:class:: DeviceLink(name: str, address: str, timeout_ms: int = DEVICE_TIMEOUT_MS, resolve: collections.abc.Callable[[], str | None] | None = None) A lazy :class:`~plesty.lib.ui.device_link.DeviceHandle` over one device server. Prepare a link; nothing is connected yet. :param name: The device's fleet name, for messages. :param address: ZMQ endpoint of its server, ``tcp://host:port``. :param timeout_ms: Reply timeout for one call. :param resolve: Returns where the server is now, or ``None`` when it is not running; raises when that is not known. A restarted server can come back on another port; a link that kept the first address drove a port nobody listened on, and the panel's controls silently did nothing (#22). .. py:attribute:: name .. py:attribute:: address .. py:attribute:: timeout_ms :value: 15000 .. py:attribute:: _resolve :value: None .. py:attribute:: _client :type: Any :value: None .. py:attribute:: _held :value: False .. py:method:: hold() -> None Refuse every call until :meth:`release`: the server is being stopped. A call sent to a server that is shutting down, or to the port it left, gets no reply and waits out the whole timeout — and every call after it waits behind it on the panel's one worker. Restarting on a new port left the panel dead for up to fifteen seconds that way (#22). Held, a call fails at once and sends nothing. .. py:method:: release() -> None Let calls through again after :meth:`hold`. .. py:method:: _follow() -> None Move to the server's current address, dropping a client on the old one. :raises ConnectionError: When the server is not running, rather than send a call to an address nothing answers on. .. py:method:: client() -> Any Return the connected client, building it on first use. :returns: The ``DeviceTCPIPClient``. :raises ConnectionError: When the link is held or the server is not running. .. py:method:: describe() -> dict[str, Any] Return the device's methods and its serialized doc model. .. py:method:: query(key: str) -> Any Read configuration parameter *key*. .. py:method:: write(key: str, value: Any) -> Any Write *value* to configuration parameter *key*. .. py:method:: call(op: str, **kwargs: Any) -> Any Run operation *op*. .. py:method:: close() -> None Release the connection; a later call reconnects. Never raises: this runs when a panel closes and a window is going away, and a server that already died must not be what stops it.