Command Solver
Command solvers translate standardized device operations into protocol-specific command payloads, and optionally help decode protocol responses.
In PlestyLib, command solving is separated from transport I/O:
Device class defines high-level operations (
query,write, function calls).Command solver converts these operations to wire commands.
Traffic manager sends commands and receives raw responses.
This separation keeps each layer focused and easier to test.
What It Does
A command solver typically handles:
Command string construction from parameter metadata.
Value serialization rules (for example, bool to
1and0in SCPI).Query command formatting.
Optional response decoding/validation helpers.
Solver Types in PlestyLib
The base interfaces are in plesty.lib.solver:
CmdSolver: parameter-centric command generation.OpSolver: operation/message-centric translation.
CmdSolver Interface
CmdSolver defines:
get_write_cmd(cfg_param, value) -> str | Noneget_query_cmd(cfg_param) -> str | None
Use this for command families based on registered parameters (ConfigParameter.command, dtype, ranges, and so on).
OpSolver Interface
OpSolver defines:
solve_request(request: dict) -> dict
Use this for protocol layers that operate on operation messages rather than direct parameter commands.
Built-in Example: SCPISolver
SCPISolver (in plesty.lib.solver.scpi) is a CmdSolver implementation used by SCPI devices.
Behavior highlights:
get_write_cmdbuilds<CMD> <VALUE>.get_query_cmdbuilds<CMD>?.Supports scalar and list/tuple serialization.
Additional helpers:
<CMD>:MIN?and<CMD>:MAX?.solve_funcdispatches schema-driven operations viaFuncMeta.
solve_func — schema-driven operation dispatch
SCPISolver.solve_func(op_name, send_fn, func_meta) -> dict | None
Sends the SCPI command stored in a FuncMeta object, casts the raw string response to func_meta.output_dtype, and returns {output_key: cast_value}.
Returns None when func_meta is None, signalling that the caller must handle the operation through its own custom logic.
Parameter |
Type |
Description |
|---|---|---|
|
|
PLESTY operation name — used only in error messages |
|
|
Sends a raw SCPI string and returns the device response (e.g. |
|
|
Injected by |
Raises ValueError if the response cannot be cast to func_meta.output_dtype.
Example — wiring solve_func in a custom OpSolver:
from plesty.lib.solver.scpi import SCPISolver
from plesty.lib.solver import OpSolver
class MySCPIOpSolver(OpSolver):
def __init__(self, send_fn):
self._send = send_fn
self._scpi = SCPISolver()
def solve_request(self, request: dict) -> dict:
op_name = request["op"]
func_meta = request.get("func_meta") # injected by FunctionSystem
result = self._scpi.solve_func(op_name, self._send, func_meta)
if result is not None:
return result
# fallback: custom dispatch for complex operations
raise NotImplementedError(f"No handler for operation '{op_name}'")
How It Integrates with Base Devices
For SCPI devices, base classes call solver methods internally:
BaseVisaScpiDevice._write_and_query_useself.scpi_solver.get_write_cmd(...)andget_query_cmd(...).BaseTCPScpiDevice._write_and_query_follow the same pattern.
This means your concrete device class usually only needs to register parameters and choose the correct base device.
Demo 1: Use SCPISolver Directly
from plesty.lib.solver.scpi import SCPISolver
from plesty.lib.device.params import ConfigParameter
solver = SCPISolver()
cfg = ConfigParameter(
name="WAVELENGTH",
dtype=int,
command="SENS:CORR:WAV",
)
write_cmd = solver.get_write_cmd(cfg, 1064)
query_cmd = solver.get_query_cmd(cfg)
min_cmd = solver.get_param_min_cmd(cfg)
max_cmd = solver.get_param_max_cmd(cfg)
print(write_cmd) # SENS:CORR:WAV 1064
print(query_cmd) # SENS:CORR:WAV?
print(min_cmd) # SENS:CORR:WAV:MIN?
print(max_cmd) # SENS:CORR:WAV:MAX?
Demo 2: Create a Custom CmdSolver
Use a custom solver when your protocol syntax is not SCPI-compatible.
from typing import Any
from plesty.lib.solver import CmdSolver
from plesty.lib.device.params import ConfigParameter
class KVTextSolver(CmdSolver):
"""Example protocol: SET <key>=<value> and GET <key>."""
def get_write_cmd(self, cfg_param: ConfigParameter, value: Any) -> str | None:
if not cfg_param.command:
return None
return f"SET {cfg_param.command}={value}"
def get_query_cmd(self, cfg_param: ConfigParameter) -> str | None:
if not cfg_param.command:
return None
return f"GET {cfg_param.command}"
Demo 3: Operation-Style Solver Example
ICEBLOCKSolver is an operation-style solver that builds compact JSON messages with transmission IDs and includes helpers for response decoding and status checks.
from plesty.lib.solver.iceblock import ICEBLOCKSolver
solver = ICEBLOCKSolver()
msg = solver.get_startLink_cmd("192.168.1.20")
print(msg)
ok = solver.status_ok('{"message":{"parameters":{"status":"ok"}}}')
print(ok) # True
Best Practices
Keep transport details in traffic managers, not in solvers.
Keep device business logic in device classes, not in solvers.
Put all protocol text formatting in the solver layer.
Return
Nonefor unsupported/missing command metadata and let caller handle it.Keep serialization deterministic for easier testing.