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

~/.seqmaster/plugins/my_calculations.py
"""
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:

sequence.yaml
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:

Inline script step
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
Using hardware from a script step
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.

~/.seqmaster/drivers/my_custom_dmm.py
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:

calibration_plugin.py
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}%"
    }