Instrument Server CLI Usage Guide¶
Complete guide to using the instrument-server command-line interface.
Table of Contents¶
- Instrument Server CLI Usage Guide
- Table of Contents
- Overview
- Daemon Management
- Instrument Management
- Measurements
- Testing
- Plugin Management
- Configuration Validation
- Logging
- Complete Workflow Examples
- Exit Codes
- Environment Variables
- See Also
Overview¶
The instrument-server command provides a unified interface for all instrument server operations.
All commands follow the pattern:
instrument-server <command> [subcommand] [options]
Command Categories¶
| Category | Commands | Description |
|---|---|---|
| Daemon | daemon start/stop/status |
Manage server daemon |
| Instruments | start, stop, status, list |
Manage instruments |
| Measurements | measure <script> |
Run measurement scripts |
| Testing | test <config> <verb> |
Test instrument commands |
| Plugins | plugins, discover |
Manage plugins |
| Validation | validate config/api <file> |
Validate configuration files |
Global Options¶
--log-level <level> Set logging level (debug|info|warn|error)
--help, -h Show help message
Daemon Management¶
The server daemon is a background process that manages the instrument registry and coordinates all operations.
Start Daemon¶
instrument-server daemon start [--log-level <level>]
Example:
# Start with default logging (info)
instrument-server daemon start
# Start with debug logging
instrument-server daemon start --log-level debug
Output:
Server daemon started (PID: 12345)
Daemon running in background
Notes:
- Must be running before any instrument operations
- Only one daemon instance can run at a time
- Daemon persists until explicitly stopped
- On Linux: PID file stored in
/tmp/instrument-server-$USER/server. pid - On Windows: PID file stored in
%LOCALAPPDATA%\InstrumentServer\server.pid
Stop Daemon¶
instrument-server daemon stop
Example:
instrument-server daemon stop
Output:
Stopping server daemon (PID: 12345)...
Server daemon stopped
Notes:
- Stops all running instruments gracefully
- Cleans up IPC resources
- Removes PID file
Check Daemon Status¶
instrument-server daemon status
Example:
instrument-server daemon status
Output (if running):
Server daemon is running (PID: 12345)
Runtime directory: /tmp/instrument-server-user/server. pid
Output (if not running):
Server daemon is not running
Exit codes:
0: Daemon is running1: Daemon is not running
Instrument Management¶
Start Instrument¶
instrument-server start <config> [--plugin <path>] [--log-level <level>]
Arguments:
<config>: Path to instrument configuration YAML file--plugin <path>: Optional custom plugin (. so on Linux, .dll on Windows)--log-level <level>: Logging level (default: info)
Examples:
# Start instrument with discovered plugin
instrument-server start configs/dmm1.yaml
# Start with custom plugin
instrument-server start configs/custom_instrument.yaml --plugin ./my_plugin.so
# Start with debug logging
instrument-server start configs/dac1.yaml --log-level debug
# Start multiple instruments
instrument-server start configs/dac1.yaml
instrument-server start configs/dac2.yaml
instrument-server start configs/dmm1.yaml
Output:
Started instrument: DMM1
Requirements:
- Server daemon must be running
- Configuration file must exist and be valid
- Plugin for the protocol type must be available (unless --plugin specified)
Stop Instrument¶
instrument-server stop <name>
Arguments:
<name>: Instrument name (from config file)
Example:
instrument-server stop DMM1
Output:
Stopped instrument: DMM1
Check Instrument Status¶
instrument-server status <name>
Arguments:
<name>: Instrument name
Example:
instrument-server status DMM1
Output:
Instrument: DMM1
Status: RUNNING
Commands sent: 150
Commands completed: 148
Commands failed: 0
Commands timeout: 2
Status Fields:
- Status: RUNNING or STOPPED
- Commands sent: Total commands dispatched
- Commands completed: Successfully executed commands
- Commands failed: Commands that returned errors
- Commands timeout: Commands that exceeded timeout
List All Instruments¶
instrument-server list
Example:
instrument-server list
Output:
Running instruments:
DAC1 [RUNNING]
DAC2 [RUNNING]
DMM1 [RUNNING]
Scope1 [STOPPED]
Notes:
- Shows all instruments registered with the daemon
[RUNNING]: Worker process is active[STOPPED]: Worker process has died or been stopped
Measurements¶
Run Lua measurement scripts that control running instruments.
Measure Command¶
instrument-server measure <script> [--globals <string>] [--block_inject_globals] [--context_schema_version <x.y.z>] [--json] [--log-level <level>]
Arguments:
<script>: Path to Lua measurement script--globals <string>: Optional json containing global variables for the measurement script. Keys are the names in the global namespace, Values are the values.--block_inject_globals: Optional block of the globals entering the lua namespace.--context_schema_version <x.y.z>: Optional allows validating versions of json globals for compatibility.--json: Output results in JSON format (default: text format)--log-level <level>: Logging level (default: info)
Requirements:
- Server daemon must be running
- Required instruments must be started
- Script file must exist and be valid Lua
Examples:
# Run measurement script with text output
instrument-server measure scripts/iv_curve.lua
# Get results in JSON format for programmatic parsing
instrument-server measure scripts/iv_curve.lua --json
# With debug logging
instrument-server measure scripts/test.lua --log-level debug
# Save JSON output to file
instrument-server measure scripts/sweep.lua --json > results.json
Automatic Result Collection¶
All context:call() operations are automatically collected with full metadata, including:
- Command ID and execution timestamp
- Instrument name and verb (command)
- Parameters passed to the command
- Return value and type
- Large data buffer references (for waveforms, arrays, etc.)
Results are displayed after script execution in execution order, providing complete traceability of all measurements.
Text Output Format¶
By default, results are displayed in a human-readable format:
Running measurement...
Measurement complete
=== Script Results ===
[0] MockInstrument1:1.SET(5.0) -> [bool] true
[1] MockInstrument1:2.SET(3.0) -> [bool] true
[2] MockInstrument1:1.GET() -> [double] 5.0
[3] MockInstrument1:2.GET() -> [double] 3.0
[4] Scope1.CAPTURE() -> [buffer] buf_abc123 (10000 elements, float32)
======================
Each line shows:
- Index: Sequential number of the call
- Instrument and Command: Full command with channel if applicable
- Parameters: Values passed to the command
- Return Value: Type in brackets, followed by the value
For large data buffers (waveforms, large arrays), the output shows a reference with:
- buffer_id: Unique identifier for accessing the data
- element_count: Number of data points
- data_type: Type of data (float32, float64, int32, etc.)
JSON Output Format¶
Use --json flag to get structured output for automation and data processing:
instrument-server measure script.lua --json
Output structure:
{
"status": "success",
"script": "iv_curve.lua",
"results": [
{
"index": 0,
"instrument": "MockInstrument1:1",
"verb": "SET",
"params": {"value": 5.0},
"executed_at_ms": 1704720615123,
"return": {
"type": "bool",
"value": true
}
},
{
"index": 4,
"instrument": "Scope1",
"verb": "CAPTURE",
"params": {},
"executed_at_ms": 1704720615127,
"return": {
"type": "buffer",
"buffer_id": "buf_abc123",
"element_count": 10000,
"data_type": "float32"
}
}
]
}
JSON Schema: The output conforms to the JSON schema at schemas/measurement_results.schema.json for validation and automated parsing.
Return Types:
double: Floating-point numberint64: Integer valuestring: Text valuebool: Boolean (true/false)array: Array of numbersbuffer: Reference to large data buffervoid: Command with no return value
Script Structure¶
All scripts have access to a global context object:
-- context:call(command, args...) - Execute instrument command (returns MeasurementResponse)
-- context:parallel(function) - Synchronized parallel execution
-- context:log(message) - Log message
context:log("Script starting")
-- Your measurement logic here
local resp = context:call("DMM1.Measure")
local value = resp:value() -- Extract actual value
print(value)
context:log("Script complete")
MeasurementResponse Return Type¶
All context:call() operations return MeasurementResponse objects that wrap the measurement value with metadata:
local response = context:call("DMM.MEASURE")
-- Access metadata
print(response:instrument()) -- "DMM"
print(response:verb()) -- "MEASURE"
print(response:type()) -- "float"|"integer"|"string"|"boolean"|"buffer"
-- Extract the actual value
local value = response:value()
-- Perform math operations (returns new MeasurementResponse)
local adjusted = response:add_offset(-0.5) -- Add offset
local scaled = adjusted:multiply_gain(2.0) -- Multiply by gain
local final_value = scaled:value() -- Extract result
-- For arrays/buffers:
local array_resp = context:call("Scope.GET_WAVEFORM")
local buffer = array_resp:buffer() -- Get BufferHandle
buffer:add_offset(-0.5) -- Offset all elements
buffer:multiply_gain(10.0) -- Gain all elements
MeasurementResponse Methods:
- instrument() → string - Returns instrument name
- verb() → string - Returns command/verb name
- type() → string - Returns value type
- value() → any - Returns actual value (number, string, boolean, or BufferHandle)
- add_offset(offset) → MeasurementResponse - Adds offset to numeric values
- multiply_gain(gain) → MeasurementResponse - Multiplies numeric values by gain
- buffer() → BufferHandle - For array types, returns buffer handle
BufferHandle Methods (for arrays):
- id() → string - Buffer ID
- size() → integer - Number of elements
- type() → string - Data type
- add_offset(offset) → boolean - Apply offset to all elements
- multiply_gain(gain) → boolean - Apply gain to all elements
Command Format¶
-- Basic: InstrumentName.CommandVerb
context:call("DAC1.SetVoltage", 5.0)
-- With channel: InstrumentName:Channel.CommandVerb
context:call("DAC1:1.SetVoltage", 3.3)
-- Return value extraction
local voltage_resp = context:call("DMM1.MeasureVoltage")
local voltage = voltage_resp:value()
Example Scripts¶
Simple sweep with value extraction:
for v = 0, 5, 0.1 do
context:call("DAC1.Set", v)
local i_resp = context:call("DMM1.Measure")
local i = i_resp:value()
print(string.format("%.3f,%.6e", v, i))
end
Using built-in math operations:
for v = 0, 5, 0.1 do
context:call("DAC1.Set", v)
local i_resp = context:call("DMM1.Measure")
-- Apply offset and gain corrections
local corrected = i_resp:add_offset(-0.001):multiply_gain(1.05)
print(string.format("%.3f,%.6e", v, corrected:value()))
end
Parallel execution:
context:parallel(function()
context:call("DAC1.Set", 1.0)
context:call("DAC2.Set", 2.0)
end)
-- Both DACs set simultaneously
2D measurement:
for x = 0, 10 do
context:call("DAC_X.Set", x * 0.1)
for y = 0, 10 do
context:parallel(function()
context:call("DAC_Y.Set", y * 0.05)
end)
local z_resp = context:call("DMM1.Measure")
local z = z_resp:value()
print(string.format("%d,%d,%.6e", x, y, z))
end
end
Building Measurement Libraries¶
Create reusable Lua modules for common measurement patterns:
# Directory structure
measurements/
├── dc_sweep.lua
├── waveform_1d.lua
└── stability_diagram.lua
-- measurements/dc_sweep.lua
local M = {}
function M.sweep(setter, getter, v_start, v_stop, v_step)
local data = {}
local v = v_start
while v <= v_stop do
context:call(setter, v)
os.execute("sleep 0.01")
local measured_resp = context:call(getter)
local measured = measured_resp:value()
table.insert(data, {v, measured})
v = v + v_step
end
return data
end
return M
Use it:
package.path = package.path .. ";./measurements/?. lua"
local dc_sweep = require("dc_sweep")
local results = dc_sweep.sweep("DAC1.SetVoltage", "DMM1.Measure", 0, 5, 0.1)
for _, point in ipairs(results) do
print(string.format("%. 3f,%.6e", point[1], point[2]))
end
Integration with Higher-Level Software¶
The generic measure command allows integration with any high-level software:
Python example:
import subprocess
import tempfile
# Generate Lua script
lua_script = """
context:log("Starting measurement")
for v = 0, 5, 0.1 do
context:call("DAC1.Set", v)
local i = context:call("DMM1.Measure")
print(string.format("%.3f,%.6e", v, i))
end
"""
# Write to temp file
with tempfile.NamedTemporaryFile(mode='w', suffix='.lua', delete=False) as f:
f.write(lua_script)
script_path = f.name
# Run measurement
result = subprocess.run(
['instrument-server', 'measure', script_path],
capture_output=True,
text=True
)
# Parse results
for line in result.stdout.split('\n'):
if line.strip():
voltage, current = map(float, line.split(','))
print(f"V={voltage}V, I={current}A")
This architecture keeps the instrument server simple and generic, while allowing arbitrarily complex measurement logic in higher-level frameworks!
Testing¶
Test individual instrument commands without writing full scripts.
Test Command¶
instrument-server test <config> <verb> [param=value ... ] [--plugin <path>] [--log-level <level>]
Arguments:
<config>: Path to instrument configuration file<verb>: Command verb from API definitionparam=value: Command parameters (key=value pairs)--plugin <path>: Optional custom plugin--log-level <level>: Logging level
Examples:
# Test identity query
instrument-server test configs/dmm1.yaml IDN
# Test with parameters
instrument-server test configs/dac1.yaml SET_VOLTAGE channel=1 voltage=5.0
# Test with custom plugin
instrument-server test configs/custom. yaml MEASURE --plugin ./my_plugin.so
# Test with debug logging
instrument-server test configs/scope1.yaml TRIGGER --log-level debug
Output:
Testing instrument: DMM1
Executing command: IDN
Result:
Success: YES
Response: Keithley Instruments Inc., Model 2400, 1234567, v1.0
Notes:
- Creates temporary instrument instance for testing
- Instrument is automatically stopped after test
- Useful for verifying plugin functionality
- Does not require daemon (starts temporary instance)
Plugin Management¶
Discover and manage instrument driver plugins.
List Available Plugins¶
instrument-server plugins
Example:
instrument-server plugins
Output:
Available plugins:
VISA -> /usr/local/lib/instrument-plugins/visa_builtin.so
SimpleSerial -> /usr/local/lib/instrument-plugins/simple_serial_plugin.so
MockTest -> ./mock_plugin.so
Total: 3 plugin(s)
Notes:
- Searches standard directories:
/usr/local/lib/instrument-plugins//usr/lib/instrument-plugins/./plugins/.(current directory)
Discover Plugins¶
instrument-server discover [path1] [path2] ...
Arguments:
[paths]: Optional directories to search (uses defaults if none provided)
Example:
# Discover in default locations
instrument-server discover
# Discover in custom directories
instrument-server discover /opt/custom-plugins ./local-plugins
Output:
Discovering plugins in:
/opt/custom-plugins
./local-plugins
Found 2 plugin(s):
Protocol: CustomDAQ
Path: /opt/custom-plugins/custom_daq.so
Name: Custom DAQ Plugin
Version: 2.1.0
Description: High-speed data acquisition plugin
Protocol: MySerial
Path: ./local-plugins/my_serial.so
Name: My Serial Driver
Version: 1.0.0
Description: Custom serial protocol implementation
Configuration Validation¶
Validate configuration files against JSON schemas before using them.
Validate Command¶
# Validate instrument configuration
instrument-server validate config <file>
# Validate API definition
instrument-server validate api <file>
Examples:
# Validate instrument configuration
instrument-server validate config examples/instrument-configurations/agi_34401_config.yaml
# Validate API definition
instrument-server validate api examples/instrument-apis/agi_34401a.yaml
Output (success):
✓ Configuration is valid
Output (error):
✗ Validation failed:
- Field 'name' must match pattern ^[A-Z][A-Z0-9_]*$
- Missing required field 'io_config'
Notes:
- Validates against JSON schemas in
schemas/directory - Checks required fields, data types, and constraints
- Use before starting instruments to catch configuration errors early
Logging¶
All commands support logging configuration via --log-level.
Log Levels¶
| Level | Description | Use Case |
|---|---|---|
error |
Errors only | Production, minimal output |
warn |
Warnings and errors | Production |
info |
Informational messages | Normal operation (default) |
debug |
Detailed debugging | Development, troubleshooting |
trace |
Very detailed trace | Deep debugging |
Log Files¶
Main log: instrument_server.log
- Contains server daemon and command logs
- Location: Current directory when command executed
Worker logs: worker_<instrument_name>.log
- One log per instrument worker process
- Contains plugin execution details
- Location: Current directory
Example log locations:
./instrument_server.log
./worker_DMM1.log
./worker_DAC1.log
./worker_Scope1.log
Viewing Logs¶
# View main log
tail -f instrument_server.log
# View specific worker log
tail -f worker_DMM1.log
# Search for errors
grep ERROR *.log
# Search for specific instrument
grep "DMM1" instrument_server.log
Complete Workflow Examples¶
Example 1: Basic Measurement¶
# 1. Start daemon
instrument-server daemon start
# 2. Start instruments
instrument-server start configs/dac1.yaml
instrument-server start configs/dmm1.yaml
# 3. Verify instruments are running
instrument-server list
# 4. Run measurement
instrument-server measure scripts/iv_curve.lua
# 5. Check instrument status
instrument-server status DMM1
# 6. Stop instruments
instrument-server stop DAC1
instrument-server stop DMM1
# 7. Stop daemon
instrument-server daemon stop
Example 2: Development Workflow¶
# 1. Start daemon with debug logging
instrument-server daemon start --log-level debug
# 2. Validate configuration before using
instrument-server validate config configs/test_instrument.yaml
instrument-server validate api apis/test_api.yaml
# 3. Test instrument with custom plugin
instrument-server test configs/test_instrument.yaml IDN --plugin ./my_plugin. so
# If test succeeds, start instrument
instrument-server start configs/test_instrument.yaml --plugin ./my_plugin.so
# 4. Run test measurement with debug logging
instrument-server measure scripts/test_measurement.lua --log-level debug
# 5. Check logs for issues
tail -f instrument_server.log
tail -f worker_TestInstrument.log
# 6. Stop and restart instrument if needed
instrument-server stop TestInstrument
instrument-server start configs/test_instrument.yaml --plugin ./my_plugin.so
# 7. Cleanup
instrument-server daemon stop
Example 3: Multi-Instrument Setup¶
# 1. Start daemon
instrument-server daemon start
# 2. Discover available plugins
instrument-server plugins
# 3. Start multiple instruments
instrument-server start configs/dac1.yaml
instrument-server start configs/dac2.yaml
instrument-server start configs/dac3.yaml
instrument-server start configs/dmm1.yaml
instrument-server start configs/dmm2.yaml
instrument-server start configs/scope1.yaml
# 4. Verify all running
instrument-server list
# 5. Check individual status
for inst in DAC1 DAC2 DAC3 DMM1 DMM2 Scope1; do
echo "=== $inst ==="
instrument-server status $inst
done
# 6. Run complex measurement with parallel execution
instrument-server measure scripts/stability_diagram.lua
# 7. Selective shutdown
instrument-server stop Scope1
# 8. Continue with remaining instruments
instrument-server measure scripts/final_measurement.lua
# 9. Complete shutdown
instrument-server daemon stop
Example 4: Troubleshooting¶
# 1. Check daemon status
instrument-server daemon status
# If not running, start it
if [ $? -ne 0 ]; then
instrument-server daemon start
fi
# 2. Validate configuration files
instrument-server validate config configs/problematic_instrument.yaml
instrument-server validate api apis/problematic_api.yaml
# 3. Try starting instrument with debug logging
instrument-server start configs/problematic_instrument.yaml --log-level debug
# 4. Check logs immediately
tail -20 instrument_server.log
# 5. Test specific command
instrument-server test configs/problematic_instrument.yaml IDN
# 6. If plugin issue, try with explicit plugin path
instrument-server start configs/problematic_instrument.yaml \
--plugin /usr/local/lib/instrument-plugins/visa_builtin.so \
--log-level debug
# 7. Monitor worker log
tail -f worker_ProblematicInstrument.log
# 8. Check IPC issues (Linux)
ls -la /tmp/instrument-server-$USER/
# 9. Clean up if needed
instrument-server daemon stop
rm -rf /tmp/instrument-server-$USER/
Exit Codes¶
All commands return exit codes for scripting:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error |
Example script:
#!/bin/bash
instrument-server daemon start
if [ $? -ne 0 ]; then
echo "Failed to start daemon"
exit 1
fi
instrument-server start configs/dmm1.yaml
if [ $? -ne 0 ]; then
echo "Failed to start DMM1"
instrument-server daemon stop
exit 1
fi
instrument-server measure scripts/measurement.lua
result=$?
instrument-server daemon stop
exit $result
Environment Variables¶
INSTRUMENT_SCRIPT_SERVER_RPC_PORT¶
Type: Integer (1-65535)
Default: 8555
Description: Port number for the HTTP RPC server on localhost
The RPC server provides programmatic API access for embedding and automation.
INSTRUMENT_SCRIPT_SERVER_OPT_LUA_LIB¶
Type: Path
Default: ``
Description: Sets the path for an optional lua library to load for interpreting measurement scripts
This supports either the directory of a larger package or just a file with registering modules.
See Also¶
- Main README - Getting started and overview
- Configuration Guide - Writing configuration files
- Architecture - System design and components
- Plugin Development - Writing instrument plugins
- Synchronization Protocol - Parallel execution details