Plesty Documentation

TCP Server and Remote Client

Run a device as a ZMQ TCP server and connect to it from experiments and analyzers.

Once a device class is implemented, exposing it over TCP is a single function call.

Starting the server

import asyncio
from plesty.power_meter import PowermeterDevice
from plesty.lib.service import build_server


async def main() -> None:
    device = PowermeterDevice(
        address="USB0::0x1313::0x8078::P0000001::INSTR",
        sensor_type="S155C",
    )
    server = build_server(device, fixed_threading=False, address="tcp://*:5555")
    await server.run()


asyncio.run(main())

The generated __main__.py in the scaffold already includes this pattern. Run it with:

python -m plesty.power_meter
# or
uv run python -m plesty.power_meter

build_server parameters

Parameter Default When to change
device required Your device instance
fixed_threading False Set True for high-frequency calls (dedicated worker thread)
address "tcp://*:5555" Change port or bind to specific interface

Connecting a client

From any machine that can reach the server:

from plesty.lib.service import build_client

client = build_client(address="tcp://192.168.1.10:5555", timeout=5000)

The client exposes the exact same interface as the local device:

# Query a parameter
wavelength = client.query("WAVELENGTH")

# Write a parameter
client.write("WAVELENGTH", 1064)

# Get all parameter names
params = client.get_config_list()

# Get device identity string
idn = client.identity()

# Get a snapshot of all parameter values
state = client.state

# Call a registered operation
result = client.run_operation("measure_power_sequence", count=10, delay_ms=50)

Connection lifecycle

The client manages connection automatically. To explicitly control the connection:

client = build_client(address="tcp://host:5555")
client.connect()

try:
    data = client.query("POWER")
finally:
    client.disconnect()

Resource locking (shared devices)

When a device is shared by multiple clients, use the resources argument to request exclusive access to specific hardware channels:

# Experiment A locks two lines
client_a = build_client(
    address="tcp://daq-host:5555",
    resources=["Dev1/port0/line0", "Dev1/port0/line1"],
    client_id="experiment-A",
)

# Experiment B tries to lock the same line — will be rejected
client_b = build_client(
    address="tcp://daq-host:5555",
    resources=["Dev1/port0/line0"],  # conflict with client_a
    client_id="experiment-B",
)

The server enforces exclusive access. Client B receives a rejection until client A disconnects and releases its resources.

Running server at startup

For persistent instrument servers, write a __main__.py that the systemd service or Docker container launches:

# plesty/power_meter/__main__.py
import asyncio
import argparse
from plesty.power_meter import PowermeterDevice
from plesty.lib.service import build_server


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--address", default="USB0::0x1313::0x8078::P0000001::INSTR")
    parser.add_argument("--sensor", default="S155C")
    parser.add_argument("--port", default=5555, type=int)
    args = parser.parse_args()

    async def run() -> None:
        device = PowermeterDevice(args.address, sensor_type=args.sensor)
        server = build_server(device, address=f"tcp://*:{args.port}")
        await server.run()

    asyncio.run(run())


if __name__ == "__main__":
    main()