plestylib.service#
Submodules#
Classes#
ZeroMQ-based client for remote device control. |
|
ZeroMQ-based asynchronous device server. |
|
Asynchronous wrapper for synchronous device objects using thread offloading. |
|
Asynchronous wrapper for synchronous device objects using a dedicated worker thread. |
Functions#
|
Factory function to create a DeviceTCPIPServer instance. |
|
Factory function to create a DeviceTCPIPClient instance. |
Package Contents#
- class plestylib.service._DeviceTCPIPClient(address='tcp://localhost:5555', timeout=5000)#
ZeroMQ-based client for remote device control.
This client provides a transparent interface to a remote device server. Methods can be invoked as if the device were local.
- Features:
Standard query/write interface
Arbitrary remote function calls
Automatic method discovery and binding
Timeout support
Structured error handling
Initialize the device client.
- Parameters:
address – Server address.
timeout – Receive timeout in milliseconds.
- ctx#
- socket#
- _description#
- _send(payload)#
Send a request to the server and wait for response.
- Parameters:
payload – Dictionary payload.
- Returns:
Result from server.
- Return type:
Any
- Raises:
RuntimeError – If server returns an error.
TimeoutError – If no response is received.
- query(param, timeout=None)#
Query a device parameter.
- Parameters:
param – Parameter name.
timeout – Optional timeout in seconds (server-side).
- Returns:
Parameter value.
- Return type:
Any
- write(param, value, timeout=None)#
Write a device parameter.
- Parameters:
param – Parameter name.
value – Value to set.
timeout – Optional timeout in seconds (server-side).
- Returns:
Result from device.
- Return type:
Any
- call(func, *args, timeout=None, **kwargs)#
Call a remote device function.
- Parameters:
func – Function name.
args – Positional arguments.
timeout – Optional timeout in seconds (server-side).
kwargs – Keyword arguments.
- Returns:
Function result.
- Return type:
Any
- describe()#
Retrieve available device methods from server.
- Returns:
Device description.
- Return type:
dict
- _build_methods()#
Dynamically attach remote methods as local methods.
- _make_proxy(name)#
Create a proxy method for a remote function.
- Parameters:
name – Function name.
- Returns:
Proxy method.
- Return type:
callable
- class plestylib.service._DeviceTCPIPServer(device, wrapper_cls, address='tcp://*:5555')#
ZeroMQ-based asynchronous device server.
This server exposes a synchronous device over TCP/IP using a JSON-based RPC protocol. The device is wrapped using an async wrapper (e.g., AsyncWrapperSafe or AsyncDeviceThread) to ensure safe, serialized access.
The server supports multiple concurrent clients via a ROUTER socket.
Protocol:
Request payload:
{ "type": "query | write | call | help | describe", "timeout": 3.0 }
Response payload:
{"status": "ok", "result": "..."}
Error response payload:
{"status": "error", "error": "message", "type": "ExceptionType"}
Initialize the device server.
- Parameters:
device – Synchronous device instance.
wrapper_cls – Async wrapper class (e.g., AsyncDeviceThread).
address – ZMQ bind address.
- device#
- ctx#
- socket#
- property address#
Get the server’s bind address.
- property is_running#
Check if the server is currently running.
- async _execute(coro, timeout=None)#
Execute a coroutine with optional timeout.
- Parameters:
coro – Coroutine to execute.
timeout – Timeout in seconds.
- Returns:
Result of coroutine.
- Raises:
asyncio.TimeoutError – If timeout is exceeded.
- async _handle(request_str)#
Handle a single client request.
- Parameters:
request_str – JSON-encoded request string.
- Returns:
JSON-serializable response.
- Return type:
dict
- async _describe()#
Introspect the device and list available methods.
- Returns:
Available callable methods.
- Return type:
dict
- async _help()#
- async run()#
Run the server loop indefinitely.
This method listens for incoming requests and processes them sequentially. Each request is handled asynchronously.
- async shutdown()#
Gracefully shutdown the server.
- class plestylib.service._AsyncWrapperSafe(obj)#
Bases:
AsyncWrapperBaseAsynchronous wrapper for synchronous device objects using thread offloading.
This class converts all callable attributes of a synchronous object into asynchronous methods by executing them in a background thread via asyncio.to_thread. A per-instance asyncio lock ensures that only one operation is executed at a time, making it safe for thread-unsafe hardware interfaces.
The wrapper is transparent: methods can be called using await without modifying the original device implementation.
A global weak registry ensures that only one wrapper exists per device instance, preventing accidental concurrent access through multiple wrappers.
Example
sync_device = Monochromator(...) device = AsyncWrapperSafe(sync_device) await device.goto(532) await device.set_grating(1)
- Internal State:
Uses an internal asyncio lock to serialize access and a class-level weak registry to maintain one wrapper instance per wrapped object.
Notes
All wrapped methods are executed in a thread using asyncio.to_thread, which avoids blocking the event loop.
Calls are serialized using an asyncio lock, ensuring safe access to non-thread-safe hardware.
If the wrapped object already provides async methods, they are returned unchanged and not wrapped again.
Non-callable attributes are forwarded directly.
Warning
Do not mix synchronous and asynchronous access to the same device instance, as this can lead to race conditions or hardware conflicts.
Creating multiple wrappers for different instances controlling the same physical hardware (e.g., same COM port or TCP endpoint) is not prevented by this class. Use an external resource registry if needed.
Frequent high-rate calls may incur overhead due to thread creation; consider a dedicated worker thread model in such cases.
- Raises:
RuntimeError – May propagate exceptions raised by the underlying device methods during execution.
- When to Use:
When you need a quick, low-overhead way to integrate synchronous device APIs into an asyncio-based application.
When device calls are relatively infrequent and simplicity is preferred over maximum performance.
- When Not to Use:
For high-frequency polling or streaming applications.
When strict single-thread execution is required (use a dedicated worker-thread model instead).
- _lock#
- async _call(func, *args, **kwargs)#
Execute a synchronous function asynchronously. Must be implemented by subclasses.
- class plestylib.service._AsyncDeviceThread(obj)#
Asynchronous wrapper for synchronous device objects using a dedicated worker thread.
This class provides a transparent async interface for a synchronous device by executing all method calls in a single background thread. Calls are queued and processed sequentially (FIFO), ensuring strict ordering and eliminating concurrent access to the underlying device.
Unlike thread-per-call approaches, this design avoids repeated thread creation and is well-suited for high-frequency or continuous device interactions.
The wrapper is transparent: callable attributes of the underlying device can be accessed as async methods using await.
Example
sync_device = Wavemeter(...) device = AsyncDeviceThread(sync_device) wavelength = await device.get_wavelength() await device.set_mode("fast")
- Internal State:
Uses a per-instance call queue plus a dedicated worker thread and a class-level weak registry to maintain one wrapper per object.
Notes
All device operations are executed in a single dedicated thread, guaranteeing thread safety for non-thread-safe hardware APIs.
Calls are processed in FIFO order, ensuring deterministic execution.
Results and exceptions are safely communicated back to the asyncio event loop using Futures.
If the wrapped object already provides async methods, they are returned unchanged and not wrapped again.
Non-callable attributes are forwarded directly.
Warning
Do not mix synchronous and asynchronous access to the same device instance, as this may lead to race conditions or undefined behavior.
Creating multiple device instances for the same physical hardware (e.g., same COM port or TCP endpoint) is not prevented by this class. Use a resource registry or device server for distributed coordination.
Long-running or blocking device calls will block the worker thread and delay subsequent queued operations.
- Raises:
RuntimeError – May propagate exceptions raised by the underlying device methods during execution.
asyncio.TimeoutError – If a timeout is specified and exceeded.
- When to Use:
When working with thread-unsafe hardware (serial, TCP, DLL).
For high-frequency polling or continuous control loops.
When strict ordering and single-thread execution are required.
When performance matters and thread creation overhead must be avoided.
- When Not to Use:
For simple or infrequent device interactions where a lightweight wrapper is sufficient.
When the underlying API is already fully asynchronous.
- _registry#
- _queue#
- _thread#
- _worker()#
Worker thread: executes device calls sequentially.
- async _call(func, *args, **kwargs)#
Schedule a function call in the worker thread and await result.
- plestylib.service.build_server(device, fixed_threading=False, address='tcp://*:5555')#
Factory function to create a DeviceTCPIPServer instance.
- Parameters:
device – Synchronous device instance.
fixed_threading – If True, use AsyncDeviceThread; otherwise, use AsyncWrapperSafe.
address – ZMQ bind address.
- Returns:
DeviceTCPIPServer instance.
- plestylib.service.build_client(address='tcp://localhost:5555', timeout=5000)#
Factory function to create a DeviceTCPIPClient instance.
- Parameters:
address – Server address.
timeout – Receive timeout in milliseconds.
- Returns:
DeviceTCPIPClient instance.