dbay-client/README.md
DBay Client (Unified Dual-Mode)
DBay provides a unified Python client (DBayClient) for interacting with DBay hardware in two distinct ways:
- GUI (stateful) mode – Talks to the DBay GUI backend over lab-link WebSocket sync, pulling the authoritative system state and sending backend commands.
- Direct (stateless) mode – Sends low-level ASCII commands directly to the mainframe over UDP or Serial without maintaining any shadow state.
Both modes share the same object model and method names where feasible. You choose the mode once at construction via a simple string: mode="gui" or mode="direct".
Installation
Requires Python 3.11 or newer.
Install from PyPI (package name placeholder shown below; adjust if different):
pip install dbay
Or install from source (editable):
git clone <repository-url>
cd dbay/device-bay-client
pip install -e .
Quick Start
GUI Mode (Stateful)
from dbay import DBayClient
client = DBayClient(mode="gui", server_address="192.168.0.50", port=8345)
client.list_modules()
dac16 = client.module(1, expected="dac16D")
dac16.set_voltage(0, 1.2, activated=True)
dac16.set_voltage_shared(0.5) # sets all channels to 0.5 V
Direct Mode (UDP)
from dbay import DBayClient, dac4D, dac16D
client = DBayClient(mode="direct", direct_host="192.168.0.108", direct_port=8880)
# Direct mode requires explicit attachment (no discovery)
dac4 = client.attach_module(0, dac4D)
dac4.set_voltage(0, 5.0)
dac4.set_voltage_diff(1, -2.0)
dac16 = client.attach_module(1, dac16D)
dac16.set_voltage(10, -1.25)
dac16.set_bias(3.0)
print("Raw read: ", dac16.read())
Direct Mode (Serial)
from dbay import DBayClient, HIC4, ADC4D
client = DBayClient(
mode="direct",
direct_transport="serial",
serial_port="/dev/ttyUSB0",
baudrate=115200,
timeout=1.0,
)
h = client.attach_module(2, HIC4)
h.set_voltage(0, 0.75)
Raw Command (Direct Only)
client.direct_send("DAC16D VS 1 5 2.5")
Reacting to State Changes (GUI Only)
The GUI backend broadcasts every state change to all connected clients. Rather
than polling snapshot(), subscribe:
from dbay import DBayClient
client = DBayClient(mode="gui", server_address="192.168.0.50")
def on_change(event):
# The snapshot is already up to date — the client applied the patch before
# calling back, so you never apply patch operations yourself.
if event.origin_client_id == client_id_of_mine:
return # ignore echoes of our own commands
print("version", event.version, client.snapshot()["data"][0])
unsubscribe = client.on_patch(on_change)
client.on_snapshot(lambda e: print("resync at version", e.version))
...
unsubscribe()
client.state_version is bumped on every applied patch, so a consumer holding a
derived view (e.g. a typed model validated from the snapshot) can cache it and
recompute only when the version changes:
class MirroredState:
def __init__(self, model_cls, client):
self._model_cls, self._client = model_cls, client
self._cached, self._version = None, -1
def current(self):
if self._client.state_version != self._version:
self._cached = self._model_cls.model_validate(self._client.snapshot())
self._version = self._client.state_version
return self._cached
Both subscription methods connect the sync transport on demand, so they work on
a client built with load_state=False. Callbacks run on the lab-link client's
own thread — guard any state they touch accordingly.
State Schema
dbay.state is the single definition of what a rack's state looks like. The GUI
backend binds it to lab-link and mutates it; clients validate the snapshot they
receive against it. Both sides import the same classes.
from dbay import DBayClient
from dbay.state import SystemState, Dac4DState
client = DBayClient(mode="gui", server_address="192.168.0.50")
state = SystemState.model_validate(client.snapshot())
module = state.data[1]
if isinstance(module, Dac4DState):
print(module.vsource.channels[0].bias_voltage)
A module type this build doesn't recognize validates into UnknownModuleState
rather than failing the whole snapshot — core stays typed and unrecognized
fields are preserved, so a consumer older than the rack still reads the slots it
understands. A malformed module still raises.
A backend registering custom modules extends the union:
from dbay.state import build_system_state_model
SystemState = build_system_state_model(MyModuleState) # module_type must be a Literal
Module Methods (Summary)
| Module | Methods (Common) | GUI Support | Direct Support |
|---|---|---|---|
| dac4D | set_voltage, set_voltage_diff | voltage only (diff TBD) | yes |
| dac16D | set_voltage, set_voltage_diff, set_voltage_shared, set_bias, read | voltage/shared | yes |
| FAFD | set_voltage, read | pending | yes |
| HIC4 | set_voltage | pending | yes |
| ADC4D | read_diff | yes | yes |
| DAC4ETH | set_voltage, set_voltage_diff | pending | yes |
| Empty | placeholder | n/a | n/a |
GUI “pending” indicates the sync command and data models are not yet implemented; calls will raise NotImplementedError.
Migration from Legacy DBay
Old (HTTP-only):
from dbay.client import DBay
client = DBay("192.168.0.50")
client.modules[0].voltage_set(0, 1.0)
New unified API:
from dbay import DBayClient
client = DBayClient(mode="gui", server_address="192.168.0.50")
mod = client.module(0, expected="dac4D")
mod.set_voltage(0, 1.0)
Key changes:
- Class name changed to
DBayClient. - Methods standardized to
set_voltage(legacyvoltage_setstill available as alias). - Access modules via
client.module(slot)instead ofclient.modules[index]. - Direct mode now uses class-based
attach_module(slot, ModuleClass)for better type checking.
Design Notes
- GUI mode uses lab-link WebSocket sync and Pydantic models. By default module changes are retained (
retain_changes=True). Opt out withretain_changes=Falseif you want automatic revert for supported modules. - Direct mode is intentionally stateless: it sends commands and returns raw responses (no caching). Activation flags are ignored in direct mode.
- Some sense and supply-voltage features may be absent in GUI until sync commands are added.
- Class-based attachment uses each module's
CORE_TYPEattribute to set hardware identity. - New modules (FAFD, HIC4, ADC4D, DAC4ETH) raise
NotImplementedErrorfor GUI operations for now. - Direct connection layer can be injected: pass an existing
DeviceConnection(or subclass) viaconnection=at construction to integrate with higher-level proxy frameworks (e.g., Pyro5 remote transport).
Injecting a Custom Direct Connection
Direct mode can accept an existing DeviceConnection (or subclass) via the connection= parameter instead of constructing one from host/port or serial arguments. If connection is provided it takes precedence; otherwise you must supply either (direct_host + direct_port) for UDP or serial_port for serial.
State Retention & Optional Reversion (retain_changes)
Default behavior now: hardware settings you apply in GUI mode remain in effect after your script ends. This matches the intuitive expectation that "what I last set is what the GUI (and hardware) keeps". The flag retain_changes therefore defaults to True.
If you prefer a session-style workflow where temporary changes are rolled back automatically for supported modules (currently the DAC family), start the client with retain_changes=False:
from dbay import DBayClient
client = DBayClient(
mode="gui",
server_address="192.168.0.50",
retain_changes=False, # auto-revert DAC channels on cleanup
)
Behavior matrix:
| Mode | retain_changes | Cleanup action | Notes |
|---|---|---|---|
| GUI | True (default) | No revert | Final commanded values persist in GUI |
| GUI | False | Revert (DACs) | Attempts to restore initial snapshot for DAC modules |
| Direct | (any) | No revert | Direct mode never auto-reverts; flag currently inert |
Details & caveats:
- Reversion relies on destructor timing; abrupt termination (e.g.
kill -9) skips cleanup. - Only modules that captured an initial state support revert (DAC family). Others have nothing to restore.
- A future enhancement may add an explicit
revert_all()to avoid relying on GC.
Recommendation: leave the default (True) for user-facing or interactive tooling; use False in automated measurement scripts that must leave hardware in a known pre-run state.
Limitations / Not Implemented Yet
dac16D.read()in GUI mode.- Batch / shared voltage command optimization in direct mode (currently sequential sends).
- Websocket streaming for sense channels (explicitly out of scope for now).
Contributing
Contributions are welcome! Feel free to open issues or PRs for:
- Adding GUI endpoints/models for the new modules.
- Extending test coverage for direct command generation.
- Documentation improvements.
License
MIT License. See LICENSE.
Metadata
Release files for dbay 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dbay-0.6.0.tar.gz | 26.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dbay-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.7 kB
Release files / dbay-0.6.0.tar.gz
| Download URL | dbay-0.6.0.tar.gz |
|---|---|
| Size | 26.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bd97a1c3931b32a4184f8dabde7166b4ec23cf4d4e58040a58b48ccdfffe8eca
|
|
BLAKE2b-256 checksum How to use checksums |
561c36885816d6c77514e403e9e7d85b5a9524800fd9e7ac75e82070332d4d2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency logRelease files / dbay-0.6.0-py3-none-any.whl
| Download URL | dbay-0.6.0-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b13e4dc8827c0a99fa58af7f93e1972fadd691de6dcf75cf93288ffcab7d876b
|
|
BLAKE2b-256 checksum How to use checksums |
ac5cfb2eeb8876a0803b2d64152e4800aa7b1aafdd9bd94c6edfb55e89c856bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency log