Plesty Documentation

Parameter and Operation Schemas

Define device capabilities in param_schema.json and op_schema.json.

PLESTY devices define their capabilities in two JSON schema files. These schemas are the source of truth for parameter validation, documentation generation, and test automation.

param_schema.json

Defines all readable/writable device parameters.

Flat schema (simple devices)

{
  "WAVELENGTH": {
    "type": "int",
    "command": "SENS:CORR:WAV",
    "unit": "nm",
    "min": 400,
    "max": 1700,
    "description": "Measurement wavelength in nm"
  },
  "POWER": {
    "type": "float",
    "command": "MEAS:SCAL:POW",
    "unit": "watt",
    "read_only": true,
    "description": "Measured optical power"
  },
  "AUTO_RANGE": {
    "type": "bool",
    "command": "SENS:POW:RANG:AUTO",
    "default": true,
    "description": "Enable auto-range mode"
  },
  "AVERAGES": {
    "type": "int",
    "command": "SENS:AVER:COUN",
    "min": 1,
    "max": 1000,
    "default": 100,
    "description": "Number of averages per measurement"
  }
}

Grouped schema (multi-channel devices)

{
  "Channel1": {
    "command_prefix": "CH1",
    "parameters": {
      "POWER": {
        "type": "float",
        "command": "MEAS:POW",
        "unit": "watt",
        "read_only": true
      },
      "WAVELENGTH": {
        "type": "int",
        "command": "SENS:WAV",
        "unit": "nm"
      }
    }
  }
}

Grouped parameters resolve to keys like CH1.POWER and CH1.WAVELENGTH.

Supported fields

Field Required Type Description
type yes string One of: int, float, str, bool
command yes string Protocol command string
unit no string Physical unit label
min no number Minimum allowed value
max no number Maximum allowed value
options no array Allowed categorical values
default no any Default value
read_only no bool If true, write raises an error
write_only no bool If true, query raises an error
description no string Human-readable description

op_schema.json

Defines device operations (functions with inputs and outputs beyond simple param read/write).

{
  "measure_power": {
    "command": "MEAS:SCAL:POW?",
    "iparams": {},
    "oparams": {
      "power": {
        "type": "float",
        "description": "Measured optical power in watts"
      }
    },
    "description": "Take a single power measurement"
  },
  "measure_power_sequence": {
    "iparams": {
      "count": {
        "type": "int",
        "required": true,
        "description": "Number of measurements to take"
      },
      "delay_ms": {
        "type": "float",
        "default": 100.0,
        "description": "Delay between measurements in milliseconds"
      }
    },
    "oparams": {
      "powers": {
        "type": "float",
        "description": "Array of measured power values in watts"
      },
      "timestamps": {
        "type": "float",
        "description": "Unix timestamps for each measurement"
      }
    },
    "description": "Take a sequence of power measurements with a fixed delay"
  }
}

Each operation has iparams (inputs) and oparams (outputs), and optionally a command:

  • command links the operation to its raw device-protocol command. The FunctionSystem builds a FuncMeta from it and injects it into the request, so a generic solver (e.g. SCPISolver) can dispatch a single-command, single-scalar-output operation with no hand-written dispatch logic — like measure_power above.
  • Complex operations that need multi-step exchanges or custom response parsing omit command and implement the logic in a custom OpSolver — like measure_power_sequence above.

iparams entries support the same constraint fields as parameters (required, default, unit, options, range, shape).

Registered operations are callable directly as methods on the device (device.measure_power()) — there is no _call wrapper. Gate d1's test_funcs_mock verifies that all registered operations return a dict with the correct output keys.

Loading schemas in the device class

The simplest way is to pass the schema path to the base class constructor:

from plesty.lib.device.base_device_sync import BaseDeviceSyncModel

class MyDevice(BaseDeviceSyncModel):
    def __init__(self, address: str):
        super().__init__(
            id=address,
            param_schema="assets/param_schema.json",
            op_schema="assets/op_schema.json",
        )

Or register parameters in code (useful for dynamic ranges that depend on constructor arguments):

class PowermeterDevice(BaseDeviceSyncModel):
    def __init__(self, address: str, sensor_type: str):
        super().__init__(id=address)
        min_wl, max_wl = SENSOR_RANGES[sensor_type]
        self.register_config("WAVELENGTH", dtype=int, unit="nm",
                             min_value=min_wl, max_value=max_wl,
                             command="SENS:CORR:WAV")

Tip: Prefer schema-driven registration for devices with many parameters — an LLM can generate param_schema.json directly from the vendor manual.