loading…
Search for a command to run...
loading…
AI-native oscilloscope control. A pure-Python MCP service that lets LLM agents control Keysight EDUX1052G oscilloscopes via a compact DSL.
AI-native oscilloscope control. A pure-Python MCP service that lets LLM agents control Keysight EDUX1052G oscilloscopes via a compact DSL.
AI-native oscilloscope control. A pure-Python MCP service that lets LLM agents control Keysight EDUX1052G oscilloscopes via a compact DSL.
┌─────────────┐ DSL/SCPI ┌──────────────┐ USBTMC ┌──────────┐
│ AI Agent │ ─────────────────→│ keyscope │ ─────────────→│ EDUX1052G│
│ (Claude/…) │←─ waveform/data───│ -mcp │←─ screenshot──│ │
└─────────────┘ └──────────────┘ └──────────┘
scope_exec) — ~800 tokens context vs 3000+ for 12 separate tools1V, 10ms, 1kHz)continue_on_error for batch jobsv0.1.0v0.1.x:scope_exec(script, continue_on_error?)v0.2.0+trig? works while chan 1? may be inconsistent depending on parser path)pip install keyscope-mcp
# or from source
pip install -e .
cd /root/keyscope-mcp
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .
Verify install:
keyscope --list-commands
python -m keyscope_mcp --list-commands
# Auto-detects USB device
python -m keyscope_mcp
# Or specify address explicitly
python -m keyscope_mcp --address "USB0::10893::923::CN60121247::0::INSTR"
# Interactive REPL
keyscope -i
# One-shot command
keyscope -c "idn"
# Execute script file
keyscope examples/power_ripple.dsl
# Dry-run (parse only, no hardware)
keyscope -n -c "rst; chan 1 on 1V dc; time 1ms"
# List all commands
keyscope --list-commands
# List VISA devices
keyscope --list-devices
from keyscope_mcp.executor import execute
result = execute("""
chan 1 on 1V dc
time 1ms
trig edge chan1 0.5 pos
wgen on sin 10k 3.3 0
run
meas 1 freq vpp
""")
print(result["results"][-1]["value"])
# → {"freq": 10000.0, "vpp": 3.28}
Use OpenCode interactive MCP setup:
opencode mcp add
When prompted:
keyscopelocal/root/keyscope-mcp/.venv/bin/python -m keyscope_mcpCheck registration:
opencode mcp list
PYTHONPATH=. .venv/bin/pytest tests/test_dsl.py -q
python -m keyscope_mcp -n -c "idn; chan 1 on 1V dc; time 1ms; meas 1 vpp"
In an OpenCode chat, run:
Use MCP tool `keyscope.scope_exec` with script:
help
Expected: command reference text is returned.
Use MCP tool `keyscope.scope_exec` with script:
idn
Expected: Keysight ID string (for example, Keysight Technologies,EDUX1052G,...).
chan 1 on 1V dc # CH1 on, 1V/div, DC coupling
chan 2 on 500mV ac bw20 # CH2 on, 500mV/div, AC, 20MHz BW limit
chan 1 off # Turn off
time 1ms # 1ms/div
time 10us -5ms main # 10us/div, -5ms offset, main mode
trig edge chan1 1.65 pos # Edge trigger on CH1, 1.65V, positive slope
trig auto # Auto trigger mode
trig holdoff 100ns # 100ns holdoff
run # Continuous
sing # Single shot
stop # Stop
acq norm # Normal mode
acq aver 16 # Average 16 samples
meas 1 freq vpp # Frequency and Vpp on CH1
meas 1 all # All measurements
meas clear # Clear all
wave 1 10k word # 10k points, WORD format
wave 1 max asc # Max points, ASCII (slow but human-readable)
wgen on sin 10k 3.3 0 # Sine 10kHz 3.3Vpp 0V offset
wgen on dc 1.65 # DC offset only (no frequency)
wgen on squ 1M 5 0 # Square 1MHz 5Vpp
wgen off # Turn off
math fft 1 10kHz 100kHz hann # FFT of CH1, center 10kHz, span 100kHz
math sub 1 2 # CH1 - CH2 waveform subtraction
math off # Turn off math
curs on chan1 # Enable cursors on CH1
curs x 0us 50us # Set X cursors
curs y -1V 1V # Set Y cursors
curs? # Read cursor values
curs off # Disable
save baseline # Save current setup
load baseline # Restore setup
list # List saved snapshots
shot png # Capture PNG screenshot
shot bmp # Capture BMP (larger, faster)
idn # Query identity
rst # Reset to factory defaults
opc # Wait for operation complete
err # Check error queue
auto # Autoscale
help # Show help
help chan # Help for specific command
When a measurement cannot be made (e.g., frequency with no signal), Keysight returns ~9.9e37. keyscope-mcp normalizes this to:
{"freq": null, "freq_invalid": true}
This lets AI agents distinguish "no measurement" from "zero" or invalid data.
See examples/ directory:
power_ripple.dsl — Switching regulator ripple measurementdigital_si.dsl — Clock signal integrity analysisfft_spectrum.dsl — Harmonic content analysisfrequency_sweep.py — Frequency response (Bode plot approximation)PC USB ───→ EDUX1052G (USBTMC)
WaveGen OUT ──balun──→ CH1 (10x probe)
PC Audio tip (left) ──→ CH1 (1x probe, 100-200mV/div)
PC Audio ring (right) ──→ CH2 (1x probe, 100-200mV/div)
PC Audio sleeve (gnd) ──→ scope ground
| Model | Bandwidth | WaveGen | Verified |
|---|---|---|---|
| EDUX1052G | 50–200MHz | 100Hz–12MHz | ✓ |
| DSOX1102G | 70–100MHz | 100Hz–12MHz | ✓* |
| Other InfiniiVision | — | — | Likely* |
*Compatible SCPI command set; may need capability flags for advanced features.
keyscope_mcp/
├── __main__.py # CLI entry point (MCP/REPL/script)
├── server.py # MCP server (stdio/sse)
├── dsl.py # DSL lexer, parser, 24-command registry
├── executor.py # Fail-fast script engine
├── scope.py # VISA connection, binary I/O, SCPI errors
├── units.py # Human-readable unit parsing (1V → 1.0, 1ms → 0.001)
├── persist.py # LMDB snapshot save/load with IDN validation
├── repl.py # Interactive REPL
└── help.py # Help text generation
# Unit tests (no hardware required)
PYTHONPATH=. .venv/bin/pytest tests/test_dsl.py -v
# Device integration tests (requires EDUX1052G)
PYTHONPATH=. .venv/bin/pytest tests/test_device.py -v
# Stereo dual-channel device test only
PYTHONPATH=. .venv/bin/pytest tests/test_device.py -k stereo_audio_inputs -vv
# Manual stereo validation script
PYTHONPATH=. .venv/bin/python test_dual_channel.py
# XY oscilloscope music demo
PYTHONPATH=. .venv/bin/python examples/oscilloscope_music_demo.py --scale 50mV
Safety note:
chan settings or demo --scale) to improve visibility.# Check USB connection
lsusb | grep Keysight
# → Bus 001 Device 002: ID 2a8d:039b Keysight Technologies, Inc.
# Check kernel module
lsmod | grep usbtmc
# If present, may conflict with pyvisa-py backend:
sudo rmmod usbtmc
Physical replug required. Scope may need to re-initialize USBTMC state.
Fixed by read_bytes() with precise length parsing instead of read_raw(). See scope.py:89-120.
MIT
Bug reports and PRs welcome. See PLAN.md for detailed architecture and SCPI taxonomy.
keyscope-mcp is a small support asset for SiliconAIO / Noema / Autopilot, not a platform branch.
scope_exec, backed by a compact DSL.python -m keyscope_mcppython3 -m venv .venv && . .venv/bin/activate && python -m pip install -e ./root/keyscope-mcp/.venv/bin/python -m keyscope_mcpPYTHONPATH=. .venv/bin/pytest tests/test_dsl.py -q
python -m keyscope_mcp -n -c "idn; chan 1 on 1V dc; time 1ms; meas 1 vpp"keyscope.scope_exec with helpkeyscope.scope_exec with idn.raw is CLI-only expert path.Run in your terminal:
claude mcp add keyscope-mcp -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.