Python Scripting
Extend SeqMaster with custom Python plugins, script steps, and hardware drivers
Overview
SeqMaster supports Python scripting at multiple levels. You can write plugins for reusable test logic, script steps for inline code within sequences, and custom drivers for unsupported hardware.
Scripting Capabilities
- • Plugins — Reusable Python functions callable from any sequence step
- • Script steps — Inline Python code executed during test runs
- • Custom drivers — Python classes for unsupported hardware
- • Statement steps — Python expressions for variable manipulation
- • Full access to hardware capabilities, variables, and test context
Plugins
Plugins are Python files placed in the ~/.seqmaster/plugins/ directory.
They are auto-loaded at startup and their functions become available as step actions in sequences.
Creating a Plugin
"""
Plugin: Custom Calculations
Adds reusable test calculations.
"""
def calculate_efficiency(input_power: float, output_power: float) -> dict:
"""Calculate power efficiency and return result."""
efficiency = (output_power / input_power) * 100 if input_power > 0 else 0
return {
"value": round(efficiency, 2),
"unit": "%",
"status": "PASS" if efficiency >= 85 else "FAIL"
}
def check_tolerance(measured: float, nominal: float, tolerance_pct: float) -> bool:
"""Check if a measurement is within tolerance."""
deviation = abs(measured - nominal) / nominal * 100
return deviation <= tolerance_pct
Using Plugins in Sequences
Call plugin functions from YAML sequences using the plugin action:
steps:
- name: Measure Input Power
type: measurement
instrument: DMM
function: DC_VOLTAGE
save_as: input_power
- name: Measure Output Power
type: measurement
instrument: DMM
function: DC_VOLTAGE
save_as: output_power
- name: Calculate Efficiency
type: action
plugin: my_calculations.calculate_efficiency
args:
input_power: ""
output_power: ""
Script Steps
For one-off logic that doesn't justify a plugin, use script steps
to run inline Python directly in your sequence:
steps:
- name: Custom Validation
type: script
language: python
code: |
# Access variables from previous steps
vcc = float(variables['vcc_measurement'])
icc = float(variables['current_measurement'])
power = vcc * icc
# Set a new variable
variables['power_consumption'] = round(power, 3)
# Return a result
result.value = power
result.unit = 'W'
result.status = 'PASS' if power < 2.5 else 'FAIL'
💡 Script vs Plugin?
- • Use scripts for quick, one-off calculations in a single sequence
- • Use plugins for reusable logic shared across multiple sequences
- • Plugins are version-controlled separately and easier to test
Execution Context
Scripts and plugins have access to these objects during execution:
| Object | Type | Description |
|---|---|---|
| variables | dict | Read/write access to all sequence variables |
| result | StepResult | Set value, unit, status, and message for the step |
| hardware | HardwareManager | Access detected instruments and send commands |
| log | Logger | Write to the test execution log |
| station_globals | dict | Station-wide configuration values |
code: |
# Get a specific instrument
dmm = hardware.get('DMM')
# Send a SCPI command
voltage = dmm.query('MEASURE:VOLTAGE:DC?')
# Use the capability tree
psu = hardware.get('PSU')
psu.set_voltage(channel=1, voltage=5.0)
psu.set_output(channel=1, enabled=True)
Custom Drivers
Write Python classes to add support for hardware not included with SeqMaster.
Drivers implement the BaseDriver interface and are
auto-discovered at startup.
from seqmaster.drivers import BaseDriver
class MyCustomDMM(BaseDriver):
"""Driver for Custom Instruments Model 1234."""
# Auto-detection identifiers
VENDOR_ID = "0x1234"
PRODUCT_ID = "0x5678"
IDN_MATCH = "Custom Instruments,Model 1234"
# Capability tree
CAPABILITIES = {
"type": "DMM",
"functions": ["DC_VOLTAGE", "AC_VOLTAGE", "RESISTANCE"],
"ranges": ["AUTO", "100mV", "1V", "10V", "100V"],
}
def connect(self):
"""Initialize connection to the instrument."""
self.resource = self.open_visa(self.address)
self.resource.write("*RST")
def measure(self, function: str, **kwargs) -> float:
"""Take a measurement."""
cmd = {
"DC_VOLTAGE": "MEAS:VOLT:DC?",
"AC_VOLTAGE": "MEAS:VOLT:AC?",
"RESISTANCE": "MEAS:RES?",
}[function]
return float(self.resource.query(cmd))
def disconnect(self):
"""Clean up."""
self.resource.close()
See Hardware Drivers for more details on the driver interface and capability tree.
Examples
Barcode-Driven Testing
Start a test by scanning a barcode/QR code that contains the serial number:
steps: - name: Scan Serial Number type: prompt message: "Scan the barcode on the PCB" input_type: barcode save_as: serial_number - name: Validate Serial Format type: script code: | sn = variables['serial_number'] valid = sn.startswith('SN-') and len(sn) == 14 result.status = 'PASS' if valid else 'FAIL' result.message = f'Serial: {sn}'
Multi-Instrument Calibration Check
Use a plugin to compare readings from two instruments:
def cross_check(dmm_reading: float, reference_reading: float,
max_deviation_pct: float = 0.5) -> dict:
"""Compare a DMM reading against a reference standard."""
deviation = abs(dmm_reading - reference_reading) / reference_reading * 100
return {
"value": round(deviation, 3),
"unit": "%",
"status": "PASS" if deviation <= max_deviation_pct else "FAIL",
"message": f"Deviation: {deviation:.3f}%"
}