mcpField
H
hwcontract

hwcontract

本地服务
github.com/MohibShaikh开发工具01 周前更新

关于 hwcontract

hwcontract 按明确的契约检查硬件采样和串口日志,并返回 PASS、MARGINAL、FAIL 或 MISSING 及测量证据。它面向固件开发者和 Agent,用于验证实际时序与事件行为,并支持命令行、MCP 和 CI。

主要功能

  1. 01检查时序、串口和事件契约
  2. 02实时采集 WS2812 和 DShot600 信号
  3. 03提供证据哈希和测量判定
  4. 04附带 28 个契约模板
  5. 05支持命令行、MCP、pytest 和 GitHub Action

适用场景

  1. 01根据逻辑分析仪采样验证 WS2812 时序
  2. 02检查 DShot600 信号和串口启动日志
  3. 03发现引脚事件的顺序或时序故障
  4. 04用基于采样的 CI 检查固件变更

MCP 相关

提供列出契约、判定测量值、串口日志和事件,以及采集或检查受支持硬件信号的 MCP 工具。服务器默认使用 stdio,也可选用 HTTP 传输和 Bearer 认证。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"mohibshaikh-hwcontract": {
"command": "uvx",
"args": [
"hwcontract"
]
}
}
}
启动命令uvx hwcontract

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypihwcontract0.6.1

工具

7

收录的工具列表 · 更新于 1 周前

参数类型必填说明
observationsarray是
contract_pathstring是

详细介绍

hwcontract

你的固件纸面上正确,实际信号却不对。

编码 Agent 编写 WS2812 驱动、ESC 位流和启动日志,通过了审查,却在信号接入真实芯片时立刻失败。hwcontract 闭合了这个反馈环路。它会采集硬件实际产生的结果,并给出可据此采取行动的判定:

  • PASS:符合规格
  • MARGINAL:符合规格,但太接近边界。在你的工作台上能运行,装到现场的冷板上就会失效。这会导致判定不通过:裁决器不会放行。
  • FAIL:超出规格,并给出测量值和偏差幅度
  • MISSING:缺少必需的观测结果;这也会导致判定不通过

绿色判定除了表格中的结果,还能带来两点:

  • 每个脉冲都会接受判定,而不只是看中位数。 采集结果包含完整的脉冲分布;中位数会掩盖的故障脉冲尾部,会被判为临界或失败,并在提示中注明违规脉冲数量。
  • 判定精度不会高于采集能够达到的精度。 从采样点测得的脉宽,其精度仅为一个采样周期。要判为 FAIL,信号必须超出规格的幅度大于该周期;要判为 PASS,信号必须至少在规格范围内一个该周期的距离;更接近的情况会判为 MARGINAL,并提示以更高采样率采集。
  • 每次判定都附有证据: 合约哈希、采集哈希、采集参数、工具版本和时间戳。CI 中的绿色构建可以追溯到生成它的确切字节数据。

手边没有硬件?下面的演示会用真实录制的信号运行完整流程,让你在接线前就能确切了解结果。

hwcontract 对真实的 WS2812B 采集结果、DMA 故障的 SPI 波形和串口启动日志进行判定

回放真实采集结果

pip install hwcontract
python3 -m hwcontract.demo ws2812b

首次运行时,这会从 sigrok 下载一份录制的 24-LED NeoPixel 采集结果(约 109 KiB),校验其 SHA256,然后缓存到 $XDG_CACHE_HOME/hwcontract 或 ~/.cache/hwcontract。首次运行需要网络连接;后续运行可使用 --offline。可以使用 --cache-dir DIR 或 --capture PATH 指定同一份固定采集结果的副本。无需安装 sigrok,也无需硬件。采集来源和许可证。

这份采集结果会依据两份合约进行判定。同一信号,两种判定(输出摘录;每次判定还会打印其证据哈希):

measured on the real WS2812B signal (300000 samples @24MHz):
  T0H 333 ns   T1H 833 ns   T1L 417 ns   T0L 917 ns   RESET 992250 ns

=== generic WS2812 contract -> FAIL ===
  T0H     350   333  PASS
  T0L     800   917  MARGINAL  within one sample (41.7ns) of max; 1842 of 4275
                               pulses within one sample of the window
  T1H     700   833  MARGINAL  within one sample (41.7ns) of max
  T1L     600   417  MARGINAL  33ns past min, within one sample (41.7ns);
                               capture at a higher samplerate to resolve
  RESET 50000 992250 PASS

=== matching WS2812B contract -> PASS ===
  (all five edges PASS)

同一硬件,两份合约:通用合约判定失败,芯片专用合约判定通过。通用合约中的各项判定为 MARGINAL,而非 FAIL,因为在 24MHz 下,每个采样间隔为 41.7ns,且每项偏差都小于这个值:采集结果表明信号太接近 WS2812 的限值,但无法确定它确实已经超出限值。WS2812B 不等于 WS2812。测量真实信号,依据规格进行检验,并让合约匹配实际芯片。

查看时序引擎如何捕获 DMA 故障

python3 -m hwcontract.demo spi

以 100MHz 采集 100 个合成 SPI 帧的原始 CS/SCK/MOSI 波形,提取引脚边沿,并依据随附的 spi-frame 合约进行判定。第 77 帧存在模拟的 DMA 顺序故障:时钟启动后片选才拉高。第 42 帧的 MOSI 信号在采样边沿前 10ns 稳定。两者都会返回精确时间戳。另有一次人工构造的 B/E jsontrace 往返测试,用于测试导入器;这些标注不是真实的 sigrok SPI 解码器输出:

cs-precedes-first-clock  800  1  FAIL  trigger at 1540310ns: no gpio.cs.falling
                                          in [1530310ns, 1540310ns] (first of 1)
mosi-setup               800  1  FAIL  forbidden spi.mosi.* at 843300ns is 10ns
                                          before spi.sck.rising at 843310ns

这是一个离线、确定性的引脚时序演示。它不会解码 SPI 负载字节,也不会运行回环测试。python3 -m hwcontract.demo serial 也可离线运行,使用合成的正常启动日志和触发崩溃的启动日志。仓库中的 python3 demo/spi_dma_temporal.py 等命令仍作为兼容性封装保留。所有演示在预期的 PASS/FAIL 结果出现时都会以零状态码退出;生产环境检查请使用裁决器 CLI 或 MCP 判定。

你将获得什么

  • 28 个内置契约模板,包括 WS2812 系列 LED、DShot、伺服/I2C/传感器/步进电机时序、串行启动日志和 SPI 引脚顺序。这些是规格库,并非 28 个可直接使用的采集集成。请根据具体部件及其数据手册核对模板。
  • **可用的采集路径:**WS2812 系列脉冲宽度分桶、通过 check_dshot 检查 DShot600,以及串行日志。DShot150/300/1200 需要针对速率设置适配器阈值。伺服、I2C、HC-SR04 和其他时序模板需要自定义观测适配器,以生成契约中命名的测量值。
  • **对规范化事件进行时序断言。**原始引脚转换会馈入 spi-frame;解码器注释需要与解码器实际使用的数据源和事件类型匹配的契约。
  • **当现有适配器会生成所需观测数据时,新规格只需编写 YAML。**新协议也可能需要适配器代码,以及已知正常/异常的采集数据。
  • 一个供 Agent 调用的 MCP 服务,也可以手动运行普通 CLI 命令。
  • **每项判定均附带证据:**契约哈希、采集哈希、采集参数、工具版本、时间戳。绿色构建可追溯到确切的字节数据。
  • **默认设置合理:**所有时序边沿均以纳秒为单位测量,串行契约使用 Python 正则表达式,判定结果会返回测量值和偏差,让 Agent 清楚知道需要修正什么。

安装

pip install hwcontract              # judge + logic-analyzer adapter
pip install "hwcontract[serial]"    # + live serial capture (pyserial)
pip install "hwcontract[untrusted]" # + google-re2 (ReDoS-immune, for untrusted contracts)
pip install "hwcontract[all]"       # everything

实时逻辑分析仪采集(check_ws2812 / check_dshot)还需要确保 sigrok-cli 位于 PATH 中。仅进行判定的工具(judge_contract、judge_serial)不需要额外依赖。

hwcontract --help
hwcontract --version
hwcontract --list-contracts         # names, kinds, capture requirements
hwcontract --list-contracts --json  # also includes absolute installed paths

这些命令会立即退出。直接运行 hwcontract 会启动 MCP stdio 服务,并等待从 stdin 接收 JSON-RPC 请求。

将其接入 Agent

每个客户端添加一个配置段,只需添加一次。安装后,hwcontract 命令会位于 PATH 中。

Claude Code

claude mcp add hwcontract -- hwcontract

Codex CLI: ~/.codex/config.toml

[mcp_servers.hwcontract]
command = "hwcontract"

opencode / Cursor / Gemini / 任何 stdio MCP 客户端

{ "mcpServers": { "hwcontract": { "command": "hwcontract" } } }

默认传输方式为 stdio(本地,无需认证)。对于仅支持远程的客户端(例如 ChatGPT 连接器),运行 hwcontract --http 8791,并通过隧道公开服务,同时设置 HWCONTRACT_TOKEN 以启用 bearer 身份验证。

支持 MCP 2026-07-28,即无状态修订版:每个请求都带有 _meta、server/discover,且无需握手。仍以 initialize 开头的客户端会收到旧版结构。每个请求都会选择自己的版本,因此无需配置。

如果客户端找不到 hwcontract(PATH 问题)

GUI 应用和部分 Agent 不会继承你的 shell PATH,因此直接运行 hwcontract 可能会因“找不到命令”而失败。以下两种方法较为可靠:

  • 使用绝对路径:运行 which hwcontract,然后将完整路径填入 command。
  • 或通过 Python 调用(无需通过 PATH 查找脚本):command: "python3",args: ["-m", "hwcontract.server"]。安装后可从任何目录运行。

**契约路径:**MCP 工具接受诸如 ws2812b、boot 或 spi-frame 这样的内置名称,也接受完整文件名(ws2812b.contract.yaml)。这些名称与 pytest 插件和 Action 中使用的名称一致;DShot600 的名称为 dshot,迷你 LED 的名称为 ws2812b_mini。如果存在同名项目文件,则优先使用项目文件。相对路径会在 HWCONTRACT_ROOT 下解析(默认值:服务的工作目录)。任意绝对路径和符号链接都必须位于该根目录内。只有 list_contracts 返回的确切已安装路径才能引用根目录之外的内置契约;这不会授权访问其他软件包文件。底层的 judge 和 temporal 模块 CLI 仍接受文件路径。

工具

工具需要硬件?功能
list_contracts否列出全部 28 个模板、类型、安装路径和采集要求。
judge_contract否根据时序契约判定给定的观测结果。用于回放/测试。
judge_serial否根据串口契约的 expect/forbid 判定给定的日志字符串。
judge_events否根据时间断言(when/require/within、forbid/while/before)判定已解码的事件。
check_ws2812是实时采集 WS2812 信号线 并 判定结果,一次调用即可完成。
check_dshot是同上,用于 DShot600 ESC 信号。
capture_ws2812是仅采集 → 生成观测结果(不进行判定)。
check_serial是读取串口 N 秒,并判定日志。

事件契约会检查每次出现的事件关系,并提供延迟分布和首次失败时间戳。随附的 spi-frame 契约要求原始引脚边沿包括 gpio.cs.falling/rising、spi.sck.rising 和 spi.mosi.*。sigrok SPI 的 Data 注释不会提供这些事件。请按照完整的多通道 CSV 到边沿操作指南判定实际采集结果。语义解码器检查使用单独的契约,匹配导入的解码器行。

请根据契约类型使用相应的判定工具:时序、串口或事件。调用类型不匹配时会报错。超过 1,000,000 个字符的串口日志会被拒绝,不会静默截断。CSV 输入必须包含有效的数字采样;格式错误的行和空采集都属于错误,不能作为设备通过的证据。

与 MCP 相比,你更倾向于使用普通 pytest?pytest-hwcontract 是一个将判定结果转化为测试的插件:FAIL、MARGINAL 或 MISSING 边沿都会导致测试失败,并在消息中附上判定结果表,同时包含 JUnit。

将其纳入 CI 门禁

仓库附带一个 GitHub Action,因此提交到仓库中的采集结果会在每个 PR 上接受判定:

- uses: MohibShaikh/hwcontract@action-v0
  with:
    timing: "ws2812b=captures/strip.csv"     # contract=capture-glob, bundled names work
    serial: "boot=logs/boot.log"
    samplerate: 24000000                     # for CSV captures (0/1 per line)
    junit: hwcontract-junit.xml              # shows in the tests tab

如果出现 FAIL、MARGINAL 或 MISSING 边沿,步骤就会失败、标注失败行并写入 JUnit。时序 CSV 会根据 WS2812 系列阈值进行分类,因此该 Action 只判定 WS2812 系列的采集结果;对于 DShot 或其他时序协议,请将相应阈值传给 pytest 插件的 capture_csv。该 Action 会在每次向此仓库推送时进行自测,使用一份正常采集结果和一份故意破坏的采集结果。

回放未更改的录制采集结果,只会根据录制的字节检查判定器和契约;它不会测试新构建的固件。要将固件更改纳入门禁,请刷入该构建版本,采集新的信号/日志并进行判定;同时保留固件修订版本、采集参数和判定证据。

工作原理

  observers (capture)                  judge (this repo)
  ─────────────────────                ─────────────────
  logic analyzer  ─ pulse widths ─┐
  serial port     ─ log text ─────┼─►  contract × observation  ─►  PASS/MARGINAL/FAIL/MISSING
  sigrok jsontrace ─ events ──────┘         (judge.py / temporal.py)
  • judge.py。 用于时序和串口的纯判定器,以及契约验证。无需硬件或框架,已缓存。
  • temporal.py。 跨事件时间断言:选择器、有符号窗口、延迟分布、首次失败时间戳。
  • jsontrace.py。 将 sigrok-cli 的 Google Trace Event JSON 导入为规范化事件。
  • sigrok_adapter.py。 将逻辑分析仪采集结果转换为 WS2812 和 DShot 的脉冲宽度分布。
  • serial_adapter.py。 采集串口日志,或回放已保存的日志。
  • server.py。 MCP 服务,仅使用标准库,支持 stdio 和 HTTP JSON-RPC。
  • *.contract.yaml。 定义“正确”的样子。可由人工编辑,同时也用作回归测试。

契约格式

时序(ws2812.contract.yaml、dshot.contract.yaml):以 ns 为单位的脉冲宽度

contract: ws2812
headroom_pct: 20         # in-spec but within 20% of a rail => "marginal"
edges:
  - {name: T0H, min: 200, typ: 350, max: 500}   # '0' bit high time

串口(boot.contract.yaml)使用 Python 正则表达式:

contract: boot
kind: serial
expect: ["IMU init OK", "boot v\\d+"]
forbid: ["panic", "Guru Meditation", "\\bnan\\b"]

事件(spi-frame.contract.yaml)断言已解码事件之间的关系——对原始引脚边沿或 sigrok 注释进行 SVA 风格的时间检查:

contract: spi-frame
kind: events
assertions:
  - {name: cs-precedes-first-clock, when: spi.sck.rising,
     require: gpio.cs.falling, within: [-10us, 0ns]}
  - {name: mosi-setup, when: spi.sck.rising,
     forbid: spi.mosi.*, before: 20ns}

新契约需要匹配的观测结果。如果现有采集路径不会输出这些结果,请添加适配器;仅添加 YAML 不会增加协议支持。

紧急停用开关

立即停用所有会接触硬件的工具(采集工具),同时保留纯判定工具正常工作:

export HWCONTRACT_SAFE=1

安全性

每个工具参数都被视为不可信,因为调用方是可能遭受提示词注入的 LLM。任意合约路径都被限制在服务端工作目录内,HWCONTRACT_ROOT 可覆盖此目录;精确匹配的内置合约已加入允许列表。driver/channel/port 会进行字符集校验,samples/seconds/samplerate 会进行范围限制,sigrok-cli 运行时设有超时,YAML 使用 safe_load 加载。除非添加身份验证,否则请勿将此服务暴露到网络上。

自测:无需硬件,可从任意位置运行

hwcontract --selftest                       # full MCP round-trip
python3 -m hwcontract.judge --demo           # tiny synthetic self-test, includes MISSING RESET
python3 -m hwcontract.sigrok_adapter --demo
python3 -m hwcontract.serial_adapter --demo
pytest                                      # the tests/ suite; pip install -e .[dev]

CI 会在每次推送和 PR 时运行测试套件,PyPI 发布任务会等待测试套件完成。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。