Comprehensive test suite for SCPI command parsing: - SCPICommand dataclass tests (creation, keyword property) - Parser tests for queries, commands, arguments - IEEE 488.2 common command tests (*IDN?, *RST, etc.) - Edge cases (whitespace, empty strings) - Instrument-specific command tests Also fixed bug where is_query was determined from command string ending rather than header ending (handles queries with arguments).
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
"""SCPI command parsing.
|
|
|
|
This module provides SCPI (Standard Commands for Programmable Instruments)
|
|
command parsing for instrument communication. It handles IEEE 488.2 common
|
|
commands (*IDN?, *RST, etc.) and instrument-specific commands.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass
|
|
class SCPICommand:
|
|
"""Parsed SCPI command.
|
|
|
|
Attributes:
|
|
header: The command header (e.g., "TEMP:SETPOINT" or "*IDN").
|
|
arguments: List of command arguments (e.g., ["85.0"]).
|
|
is_query: True if the command ends with '?' (query command).
|
|
"""
|
|
|
|
header: str
|
|
arguments: list[str]
|
|
is_query: bool
|
|
|
|
@property
|
|
def keyword(self) -> str:
|
|
"""Return the command keyword without '?'.
|
|
|
|
For query commands like "TEMP:SETPOINT?", returns "TEMP:SETPOINT".
|
|
For regular commands like "VOLT", returns "VOLT".
|
|
"""
|
|
return self.header.rstrip("?")
|
|
|
|
|
|
class SCPIParser:
|
|
"""Parse SCPI command strings.
|
|
|
|
Handles both IEEE 488.2 common commands (e.g., *IDN?, *RST) and
|
|
instrument-specific commands (e.g., VOLT 3.3, TEMP:SETPOINT?).
|
|
|
|
Examples:
|
|
>>> parser = SCPIParser()
|
|
>>> cmd = parser.parse("*IDN?")
|
|
>>> cmd.header, cmd.is_query
|
|
('*IDN?', True)
|
|
>>> cmd = parser.parse("VOLT 3.3")
|
|
>>> cmd.header, cmd.arguments
|
|
('VOLT', ['3.3'])
|
|
"""
|
|
|
|
def parse(self, command_string: str) -> SCPICommand:
|
|
"""Parse a SCPI command string.
|
|
|
|
Args:
|
|
command_string: The raw SCPI command string to parse.
|
|
|
|
Returns:
|
|
SCPICommand with parsed header, arguments, and query flag.
|
|
|
|
Examples:
|
|
"*IDN?" -> SCPICommand("*IDN?", [], True)
|
|
"VOLT 3.3" -> SCPICommand("VOLT", ["3.3"], False)
|
|
"TEMP:SETPOINT?" -> SCPICommand("TEMP:SETPOINT?", [], True)
|
|
"CONF:VOLT:DC 10,0.001" -> SCPICommand("CONF:VOLT:DC", ["10", "0.001"], False)
|
|
"""
|
|
command_string = command_string.strip()
|
|
if not command_string:
|
|
return SCPICommand(header="", arguments=[], is_query=False)
|
|
|
|
# Split into header and arguments on first whitespace
|
|
parts = command_string.split(None, 1)
|
|
header = parts[0]
|
|
arguments: list[str] = []
|
|
|
|
if len(parts) > 1:
|
|
# Parse comma-separated arguments
|
|
arg_string = parts[1]
|
|
arguments = [arg.strip() for arg in arg_string.split(",")]
|
|
|
|
# Query is determined by whether the header ends with '?'
|
|
is_query = header.endswith("?")
|
|
|
|
return SCPICommand(
|
|
header=header,
|
|
arguments=arguments,
|
|
is_query=is_query,
|
|
)
|