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:

  1. Device class defines high-level operations (query, write, function calls).

  2. Command solver converts these operations to wire commands.

  3. 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:

  1. Command string construction from parameter metadata.

  2. Value serialization rules (for example, bool to 1 and 0 in SCPI).

  3. Query command formatting.

  4. Optional response decoding/validation helpers.

Solver Types in PlestyLib#

The base interfaces are in plestylib.solver:

  1. CmdSolver: parameter-centric command generation.

  2. OpSolver: operation/message-centric translation.

CmdSolver Interface#

CmdSolver defines:

  1. get_write_cmd(cfg_param, value) -> str | None

  2. get_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:

  1. 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 plestylib.solver.scpi) is a CmdSolver implementation used by SCPI devices.

Behavior highlights:

  1. get_write_cmd builds <CMD> <VALUE>.

  2. get_query_cmd builds <CMD>?.

  3. Supports scalar and list/tuple serialization.

  4. Additional helpers: <CMD>:MIN? and <CMD>:MAX?.

How It Integrates with Base Devices#

For SCPI devices, base classes call solver methods internally:

  1. BaseVisaScpiDevice._write_ and _query_ use self.scpi_solver.get_write_cmd(...) and get_query_cmd(...).

  2. 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 plestylib.solver.scpi import SCPISolver
from plestylib.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 plestylib.solver import CmdSolver
from plestylib.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 plestylib.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#

  1. Keep transport details in traffic managers, not in solvers.

  2. Keep device business logic in device classes, not in solvers.

  3. Put all protocol text formatting in the solver layer.

  4. Return None for unsupported/missing command metadata and let caller handle it.

  5. Keep serialization deterministic for easier testing.