Plesty Documentation

Parameter and Operation Schemas

Define device capabilities in schema_param.json and schema_func.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.

schema_param.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

schema_func.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.

Where the files live

The schemas ship inside the package, beside the code that loads them:

plesty/power_meter/
├── __init__.py
├── base_device.py
├── device.py
├── schema_param.json     # parameters
└── schema_func.json      # operations

plesty init does not create them — you add them during implementation — but the location matters: a file outside plesty/<module>/ is not part of the wheel, so the module would import on your machine and fail on a bench. Keeping them in the package means they install with it.

The names follow the reference devices (schema_param.json, schema_func.json). Nothing in the platform hard-codes either name — you pass the path — but a device that spells them differently is one more thing the next reader has to look up.

Loading schemas in the device class

Resolve the path relative to the module file, never the working directory:

from pathlib import Path

from plesty.lib.device.base_device_sync import BaseDeviceSyncModel

_SCHEMA_DIR = Path(__file__).parent


class MyDevice(BaseDeviceSyncModel):
    def __init__(self, address: str):
        super().__init__(
            id=address,
            param_schema=str(_SCHEMA_DIR / "schema_param.json"),
        )
        self.register_from_op_schema(str(_SCHEMA_DIR / "schema_func.json"))

param_schema takes a path or an already-loaded dict, so a device that has to adjust the schema before registering it can load the JSON itself and pass the object.

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 schema_param.json directly from the vendor manual.