Skip to main content
Glama
Xingwen37

instrument_mcp

by Xingwen37
README.md
# 仪器 MCP

通过 MCP(Model Context Protocol)让 AI 控制两台台式仪器:RIGOL DHO/HDO 示波器与
SIGLENT SDG1000X 信号发生器。主入口是一个本地 stdio MCP server,AI 客户端
(Claude Desktop、Cursor 等)可直接调用;同时保留薄 CLI 便于人工排错和硬件验证。

## 测试环境

- 电脑通过 USB 分别连接示波器(RIGOL DHO/HDO)和信号发生器(SIGLENT SDG1000X)。
- 信号发生器 CH1 → **低通滤波器衰减网络** → 示波器 CH1,构成闭环验证回路。
- 三本编程手册(PDF)随仓库提供,SCPI 语法以手册为准。

## 架构

```
仪器MCP/
├── instruments/            共享核心库(包)
│   ├── __init__.py         导出 Scope / Generator / SessionManager / InstrumentError
│   ├── base.py             VISA 底层 + SessionManager(按厂商ID懒加载、缓存连接)
│   ├── validation.py       通道/枚举/数值范围校验 + InstrumentError
│   ├── scope.py            Scope 类:RIGOL 方言(:CHANnel / :MEASure:ITEM? / :AUToset)
│   └── generator.py        Generator 类:SIGLENT 方言(C{n}:BSWV / C{n}:OUTP)
├── server.py               FastMCP stdio server(@mcp.tool() 包装 instruments/*)
├── scope_cli.py            示波器薄 CLI(替换原 scope_control.py)
├── generator_cli.py        信号发生器薄 CLI(替换原 generator_control.py)
├── scope_idn.py            连通性冒烟测试 CLI;VISA 底层迁入 instruments.base 后改为再导出
├── requirements.txt        pyvisa>=1.14, mcp>=1.27,<2
├── README.md / AGENTS.md
└── *.pdf                   三本仪器手册
```

两台仪器使用不同 SCPI 方言,切勿混用:

|  | RIGOL DHO/HDO 示波器 | SIGLENT SDG1000X 信号发生器 |
|---|---|---|
| 脚本 | `scope_cli.py` | `generator_cli.py` |
| USB 厂商 ID | `0x1AB1` | `0xF4EC` |
| 自动选择 | `choose_scope_resource()` | `choose_generator_resource()` |
| 通道前缀 | `:CHANnel{n}`(前导冒号),通道 1–4 | `C{n}`(无前导冒号),通道 1–2 |
| 输出使能 | 不适用 | **需显式 `C{n}:OUTP ON`**,仅设置波形参数不会出信号 |

## 前置条件

1. 示波器与信号发生器后面板的 `USB DEVICE` 口分别连接到电脑。
2. 安装 RIGOL Ultra Sigma,或直接安装 NI-VISA(提供 VISA runtime)。
3. 重新插拔 USB 线,确认 Windows 不再把仪器设备显示为错误状态。
4. 安装 Python 依赖(Python 3.10+):

```powershell
python -m pip install -r requirements.txt
```

## 故障排查

检查 PyVISA 是否能找到已安装的 VISA runtime:

```powershell
pyvisa-info
```

若 `Backends > ivi > Binary library` 显示 `Not found`,说明 Python 依赖就绪但
NI/IVI VISA runtime 未安装。请安装 RIGOL Ultra Sigma,重新连接仪器,再运行
`python scope_idn.py --list`。

检查 Windows 是否识别到仪器:

```powershell
Get-PnpDevice -PresentOnly | Where-Object {
  $_.FriendlyName -match 'RIGOL|DHO|HDO|SIGLENT|SDG|USBTMC|VISA|Instrument' -or
  $_.InstanceId -match '1AB1|F4EC|RIGOL|SIGLENT'
} | Select-Object Status,Class,FriendlyName,InstanceId
```

## 作为 MCP server 接入 AI 客户端

`server.py` 默认以 stdio 传输启动:

```powershell
python server.py
```

### Claude Desktop

在 Claude Desktop 配置文件(`claude_desktop_config.json`)中添加:

```json
{
  "mcpServers": {
    "bench-instruments": {
      "command": "D:\\anaconda3\\python.exe",
      "args": ["D:\\project\\仪器MCP\\server.py"]
    }
  }
}
```

### Cursor

在 Cursor 的 `~/.cursor/mcp.json`(或项目级 `.cursor/mcp.json`)中:

```json
{
  "mcpServers": {
    "bench-instruments": {
      "command": "D:\\anaconda3\\python.exe",
      "args": ["D:\\project\\仪器MCP\\server.py"]
    }
  }
}
```

请按本机实际 Python 路径替换 `command`,并把 `args` 里的路径改成本机
`server.py` 的实际位置。路径分隔符在 JSON 里需转义为 `\\`(Windows)或用
正斜杠 `/`。

### Claude Code(CLI)

Claude Code 用命令行注册 stdio server:

```powershell
claude mcp add bench-instruments -- "D:\anaconda3\python.exe" "D:\project\仪器MCP\server.py"
```

注册后用 `claude mcp list` 确认。Claude Code 会以 stdio 方式拉起该进程,
工具自动出现在会话中。

### opencode / 其他 stdio MCP 客户端

任何遵循 MCP stdio transport 的客户端,原理一致:告诉客户端用哪个命令
启动 server 进程。以 opencode 的 `opencode.json` 为例:

```json
{
  "mcp": {
    "bench-instruments": {
      "type": "local",
      "command": ["D:\\anaconda3\\python.exe", "D:\\project\\仪器MCP\\server.py"]
    }
  }
}
```

若客户端按项目级配置识别,把文件放在工程根目录;若按全局识别,放在其
用户配置目录。具体字段名以该客户端文档为准,核心都是 `command` + `args`
(或等价的命令数组)指向 `python server.py`。

### 用 MCP Inspector 调试(不依赖 AI 客户端)

无需任何 AI 客户端即可验证工具契约:Inspector 会以 stdio 拉起 server,
列出 15 个工具并手动调用。

```powershell
uv run mcp dev server.py
```

然后在浏览器打开 Inspector 提示的地址,连接 → List Tools → 逐个 Call Tool
查看返回。这最适合在接入客户端前先排查工具签名/返回结构问题。

### 注意事项

- server 进程**首次调用某仪器时**才打开并缓存 USB 连接(懒加载),所以
  第一个工具调用会比后续慢。AI 看到 `list_resources` 能列出两台仪器即说明
  VISA runtime 正常。
- VISA runtime / USB 驱动缺失时,工具会返回 `InstrumentError`(把 setup
  提示作为工具错误结果报告给 AI),而非让 server 进程崩溃。AI 应据此
  提示用户安装驱动/重插线缆。
- server 是**长驻进程**,两台仪器连接跨工具调用复用;客户端关闭时
  lifespan 自动 `close_all()`。

### 可用 MCP 工具

发现类:

- `list_resources` — 列出主机可见的 USB VISA 资源
- `idn(instrument)` — 查询 `*IDN?`,`instrument` 取 `scope` 或 `generator`

示波器类:

- `scope_status` — IDN、触发状态、各通道设置
- `scope_autoset` / `scope_run` / `scope_stop` / `scope_single` / `scope_force_trigger`
- `scope_channel_config(channel, display?, scale?, offset?, coupling?)`
- `scope_measure(channel, item)` — 单项测量,item 如 `VPP`/`FREQUENCY`/`VRMS` 等
- `scope_measure_all(channel)` — 默认项集 VPP/FREQUENCY/VRMS/VMAX/VMIN

信号发生器类:

- `generator_status` — IDN、两通道波形/输出状态、系统错误
- `generator_set_waveform(channel, wave?, freq?, amp?, offset?, phase?)` — 至少给一个参数;**不会自动开输出**
- `generator_output(channel, state)` — `state` 取 `on`/`off`,需显式开启才会出信号
- `generator_query_channel(channel)`

所有工具返回结构化 dict,便于 AI 解析。参数越界(通道号、枚举、频率/幅度范围)会在
发往硬件前被 `instruments.validation` 拦截并报错。

## CLI 排错用法

### 示波器 `scope_cli.py`

```powershell
python scope_cli.py list
python scope_cli.py idn
python scope_cli.py status
python scope_cli.py auto
python scope_cli.py run
python scope_cli.py stop
python scope_cli.py single
python scope_cli.py force
python scope_cli.py ch 1 status
python scope_cli.py ch 1 on
python scope_cli.py ch 1 --scale 0.5 --offset 0 --coupling DC
python scope_cli.py measure 1 VPP
python scope_cli.py measure-demo
```

### 信号发生器 `generator_cli.py`

```powershell
python generator_cli.py list
python generator_cli.py idn
python generator_cli.py status
python generator_cli.py ch 1
python generator_cli.py set 1 --wave SINE --freq 1000 --amp 2 --offset 0
python generator_cli.py output 1 on
python generator_cli.py output 1 off
```

### 连通性冒烟测试

```powershell
python scope_idn.py --list
python scope_idn.py
```

多仪器时可用 `-r` 显式指定资源字符串:

```powershell
python scope_cli.py -r "USB0::0x1AB1::0x0610::HDO4A264201814::INSTR" status
python generator_cli.py -r "USB0::0xF4EC::0x1103::SDG1XCAQ2R1765::INSTR" status
```

## 端到端验证回路

利用 generator CH1 → 低通滤波衰减网络 → scope CH1 的闭环,可让 AI 跑一个验证任务:

1. `generator_set_waveform(1, wave="SINE", freq=1000, amp=2)` 设 1kHz 正弦 2Vpp
2. `generator_output(1, "on")` 开启输出
3. `scope_autoset` 自动设置示波器
4. `scope_measure_all(1)` 测 CH1 的 VPP/FREQUENCY/VRMS/VMAX/VMIN
5. 比对测量值与设定值(受衰减网络影响,幅度应小于设定值),确认链路正常