DSH 嵌入式硬件沙盒插件:3D 装配协作感知 + 项目级硬件在环仿真
30.7/100
综合分
1
GitHub 星标
285
npm 周下载
—/5
用户评分
数据截至 2026/10/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @noob-stupid/dsh-breadboardnpm 源 · @noob-stupid/dsh-breadboard 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · tool
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包@noob-stupid/dsh-breadboard @ 0.4.1
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/14 00:35:26
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 22 处,展示前 22 条)
- 读取本地文件scripts/prepare.mjs:42
if (existsSync(entry)) { - 执行外部命令固定命令scripts/prepare.mjs:49
const result = spawnSync('bash', [script], { cwd: root, stdio: 'inherit', shell: false }) - 读取本地文件scripts/prepare.mjs:51
if (result.status === 0 && existsSync(entry)) { - 读取本地文件lib/core/projects/store.js:194
names = await readdir(this.bodiesRoot); - 读取本地文件lib/core/projects/store.js:205
const text = await readFile(path.join(this.bodiesRoot, name), 'utf8'); - 读取本地文件lib/core/projects/store.js:216
const text = await readFile(path.join(this.bodiesRoot, `${id}.json`), 'utf8'); - 写入本地文件lib/core/projects/store.js:225
await mkdir(this.bodiesRoot, { recursive: true }); - 写入本地文件lib/core/projects/store.js:226
await writeFile(path.join(this.bodiesRoot, `${body.id}.json`), `${JSON.stringify(body, null, 2)}\n`, 'utf8'); - 删除本地文件lib/core/projects/store.js:234
await rm(path.join(this.bodiesRoot, `${id}.json`)); - 读取本地文件lib/core/projects/store.js:258
names = await readdir(this.root); - 读取本地文件lib/core/projects/store.js:278
const text = await readFile(file, 'utf8'); - 写入本地文件lib/core/projects/store.js:307
await mkdir(this.root, { recursive: true }); - 写入本地文件lib/core/projects/store.js:309
await writeFile(file, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); - 删除本地文件lib/core/projects/store.js:318
await rm(file); - 写入本地文件lib/core/models/seed.js:76
const copyFileImpl = deps.copyFile ?? ((from, to) => copyFile(from, to)); - 写入本地文件lib/core/models/seed.js:77
const mkdirImpl = deps.mkdir ?? ((dir) => mkdir(dir, { recursive: true })); - 读取本地文件lib/core/models/seed.js:80
const entries = await readdir(dir, { withFileTypes: true }); - 读取本地文件lib/core/models/seed.js:86
await access(file); - 发起网络请求lib/core/models/step-parts.js:75
const response = await this.#fetch(url, { signal: controller.signal }); - 发起网络请求lib/core/models/step-parts.js:134
const response = await this.#fetch(candidate.glbUrl, { signal: controller.signal }); - 读取本地文件scripts/test-inject-words.mjs:44
manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件scripts/test-inject-words.mjs:58
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'))
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-llm@deepseek-ai/dsh-tools@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
最近更新
在 GitHub 查看全部 →v0.6.02026-10-16
· 0.6.0
本轮把"加一块新硬件"从"改代码"变成"从权威来源推导",并让真机状态进入 3D 场景。
· 新能力
· hw_scan_board_pins —— 从本机 PlatformIO 的权威文件推导引脚表草稿:
boards/.json → build.variant → variants//pins_arduino.h
(编译器实际用的那份映射)。list:true 列出本机能找到的板子(本机实测 242 块)。
★★ 它给的是草稿 + 缺口清单(缺:物理排布 / 电气语义 / 丝印),不是成品。
· esp32-s3-devkitc-1 型号定义(44 脚) —— 由 Espressif 官方文档机械提取(脚位/丝印/电气类型/GPIO)。
· 真机状态画进 3D 场景:被观察的器件按状态着色高亮 —— 🟢 在线 / 🟠 真机离线 / 🔴 我们查不到 / 🔵 服务端没有这台设备。
· 两个维护工具随包发:tools/crosscheck-model-pins.mjs(型号 ↔ 权威 variant 一致性检查)、tools/extract-devkit-header.mjs(官方文档 → 型号定义,换块 devkit 直接能用)。
· 修复 / 加固
· LICENSE(BSD-3-Clause) —— 之前只在 package.json 里声明,仓库没有许可证文件(GitHub 因此显示"无许可证")。
· 定义只能存在一份:包内有了就删掉用户目录副本,否则每次启动都会报"与另一条外部定义重复"。
· 已知限制(如实)
· esp32-s3-devkitc-1 的 size 是按排针几何推算的,不是官方外形尺寸(官方只有 DXF/PDF:DXF 里外形不是 LINE/LWPOLYLINE 实体、产品页 403)。推算过程与"这不是官方值"写在定义的 _source 字段里。
· 型号定义的引脚表来源需要人/AI 去查;插件不替来源负责(错的引脚表比没有更坏)。
· 真机观察的真机验证仍需真板 + 服务端(本机目前无 COM 口、服务端未起)。
· 第三方模型仍不分发;随包只有自产 4 个模型。
测试:356 项全绿;host / client tsc --noEmit 均通过。
v0.5.02026-10-15
· 0.5.0
两块新能力,都是让插件的硬件覆盖面可以扩展,而不是写死的。
· 型号定义:现在可以让 agent 来写
· 新工具 hw_save_model_definition —— agent 能直接写并装上一条型号定义(校验通过才落盘,落盘后立即生效)。
· 界面 ③ 面板新增 ✎ 让 agent 帮我写(填型号名即可),提示词走官方聊天通道(与你自己敲一句同一条路)。
· ★★ 纪律写死在工具描述与提示词里:引脚表必须有权威来源(厂商数据手册 / PlatformIO 的 variants//pins_arduino.h / 项目自己的 platformio.ini、.ioc、sdkconfig);找不到就停下来问用户,不许凭记忆编。
理由:手打错的引脚表比没有更坏 —— 它会产出"看起来对但接错脚"的校验,而没人能发现。
· 安装逻辑与界面路由共用同一段实现(不会出现"界面能装、agent 报错方式不同")。
· 真机观察:界面上看得见
· 新面板 ④ 真机观察:显示正在观察的真机、五种状态分开措辞、读数、轮询/失败次数,并能从界面开始/停止。
· 轮询真机的只有宿主(墙钟、间隔下限 2 秒),面板只读宿主缓存 ⇒ 不会把对方服务器打两遍。
· 文档
· README.zh.md / README.md:新增两节详解 —— 外观与引脚数据是两件事(模型库给外观,引脚数据库里没有:连 SnapEDA 的符号库都"不含总线协议与电压");error 与 stale 为什么不能混。
· skill/SKILL.md:同样内容给 agent(随插件注册的技能)。
· 悬浮提示改成具体而不是更长。
· 已知限制(未变)
· 真机观察的真机验证需要真板 + 服务端(本机目前无 COM 口、服务端未起)。
· 型号定义的引脚表来源需要人/AI 去查;插件不会替来源负责。
· 项目列表默认是空的;第三方模型仍不分发(随包只有自产 4 个)。
v0.4.22026-10-14
· 0.4.2
修社区列表维护者复核 npm 安装路径时发现的安装阻塞。
· 修复
· 去掉发布包里的 postinstall —— 维护者在隔离 profile 里实跑
dsh plugin --profile web add @noob-stupid/dsh-breadboard 得到 exit 1:
pnpm 拦下了这个包的 postinstall,往 profile 的 pnpm-workspace.yaml 写了一条待决的
allowBuilds,用户手工放行之前这条命令不会成功;未决期间该 profile 的后续插件安装也会一起失败。
他抽了索引里 13 个 npm 包,只有我们这个带 postinstall。
这个脚本只做一件事:把宿主依赖 junction 进 node_modules —— 那是源码检出开发期的便利
(scripts/build.sh 自己就会跑同一段逻辑),而发布包里 lib/ 已预构建,装上它没有任何收益。
⇒ 删除;源码检出流程改为在 README 里写明(npm install 后手动 npm run link)。
· 验证
· 解开 tgz 确认 scripts 里已无 postinstall。
· 其余不变:dependencies 只有 ws(没有未发布的包)。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成Web profile 插件 —— 宿主半 + 浏览器半。
English: README.md | 中文: README.zh.md
DSH 硬件沙盒
给 DSH 的 3D 硬件沙盒:把装配摆出来、接上线,然后让你自己项目的代码跑在虚拟硬件上。
安装(从 GitHub 源码装;本包未发布到 npm)
dsh plugin --profile web add github:Noob-stupid/dsh-breadboard
装完刷新页面即可。沙盒注册进 main 槽(3D 视图)与 sidebar.panellist 槽(各面板); skill/SKILL.md 随插件一起注册,装了这个插件的 agent 已经知道该怎么用它。
喜欢的话:在插件管理里打开这个插件的详情页,内容最下方靠右有一行小字,可以到 GitHub 点个 Star。它就是透明的一行字—— 没有底色、没有边框、没有阴影,也只在本插件自己的页面上出现。
展开完整文档(README 剩余 1 节)
Web profile 插件 —— 宿主半 + 浏览器半。
English: README.md | 中文: README.zh.md
DSH 硬件沙盒
给 DSH 的 3D 硬件沙盒:把装配摆出来、接上线,然后让你自己项目的代码跑在虚拟硬件上。
安装(从 GitHub 源码装;本包未发布到 npm)
dsh plugin --profile web add github:Noob-stupid/dsh-breadboard
装完刷新页面即可。沙盒注册进 main 槽(3D 视图)与 sidebar.panellist 槽(各面板); skill/SKILL.md 随插件一起注册,装了这个插件的 agent 已经知道该怎么用它。
喜欢的话:在插件管理里打开这个插件的详情页,内容最下方靠右有一行小字,可以到 GitHub 点个 Star。它就是透明的一行字—— 没有底色、没有边框、没有阴影,也只在本插件自己的页面上出现。
这一行能生效靠三件事,三件都在本仓库里:
| 要求 | 位置 | 说明 |
| 仓库 topic dsh-plugin | GitHub 仓库 topics | 社区列表据此发现本仓库 |
| dsh.bundle 清单 | package.json → "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } | 没有它 dsh plugin add 装不进任何东西 —— 而且界面上的症状极具误导性:插件列表说「没有声明组合包」,真正被挡住的却是装配本身,于是所有 /api/ 返回 404,面板干脆不出现 |
| 导出 apply(ctx) | src/index.ts(宿主半)/ src/client/index.ts(浏览器半) | 宿主半注册路由、工具与仿真引擎;浏览器半注册上面那两个槽 |
这是什么 / 解决什么问题
一个软硬件项目里,只有真机能试的那几段,是接线本身、传感器在寄存器层的语义、以及出错时的处理。
本插件把它们搬进一个真能跑起来的虚拟场景:
- 3D 装配 —— 从内置模型库放器件(ESP32、树莓派 4B、BME280、HC-SR501 PIR、LED 模块、 MPL3115A2、面包板),按端口逐个连线,得到一份活的 SSOT(唯一真相)装配状态,带拓扑与电气校验。
- 项目级硬件在环 —— 一个真实 Python 进程跑你自己项目的代码。Python shim 拦截它的 smbus/I2C 访问,由虚拟设备应答,所以被测项目读到的是寄存器级的值(例如它自己拿虚拟芯片的数据做真正的 BME280 补偿计算)。
- 联网设备模拟(并列的第二类虚拟硬件)—— 一台按真实节拍主动向你的外部 HTTP 后端上报的设备:注册 → 拉配置 → 周期上报。已在真实的智能选座系统上全链路验证(设备 → HTTP → 服务端状态机 → 数据库)。
- 故障注入 —— 拔线、返回 NACK、强制读数、让设备掉线,然后看项目的异常处理到底抓没抓住。
- 项目级便利 —— 扫描一个真实项目文件夹,得出它需要哪些硬件;再一键换掉整套硬件。
依赖 DSH 的 web profile 与 webServer、tools、subprocess 三个服务。宿主半把它们声明为 硬 inject 依赖(刻意如此):缺任何一个,插件根本不激活,而不是"激活了但工具没注册 / 界面空白 / 仿真起不来"。
没有真实项目也想看效果
examples/zhizuo-virtual-esp32/ 是虚拟 ESP32 座位传感器的独立脚本版。
hw_list_projects 会列出内置演示项目(zhizuo-seat-node)以及它当前是否软硬件连通; hw_import_project 一次把它的器件、连线与联网绑定摆好。
界面导览
界面目前只有中文,下表用的就是屏幕上那行字。
| 面板 | 作用 |
| ③ 项目(一键换整套硬件) | 主工作流。每个项目本体显示:文件数(采样跳过时为「N 个文件(采样时跳过 M 个)」)、扫描需求、删除;底部还有:+ 导入项目本体(选文件夹)、⤓ 导出建模组(存成一个 .json)、+ 导入建模组(选 .json)、+ 添加型号定义(选 .json)、✎ 让 agent 帮我写 |
| ① 添加器件 | 点一下型号键,把它放进场景。只列有真几何的型号(默认收起) |
| ② 换装模型 | 搜模型 / 导入文件,给某个型号换装;没有几何的型号渲染为占位盒。外部(数据驱动)型号带「[数据驱动]」标记(默认收起) |
| ④ 真机观察 | 显示真实硬件的状态(只读),并能从界面开始/停止观察(默认收起) |
| 统计 | 渲染统计(绘制次数 / 三角形数 / 帧率)—— 纯读取,默认关 |
| 中央 3D 视图 | 已放置器件(带世界变换)、端口之间的蓝色连线、在带 warn/error 告警的器件上就地面出的告警框、变换手柄 / 右键菜单(钉住、删除)、端口悬浮提示 |
| 面板浮层输入框 | 不离开面板就能给当前会话发消息;宿主没提供 agents 服务时该输入框直接隐藏(capabilities.chat 缺省) |
其中两条要点名:
- 「扫描需求」只是界面按钮。 它没有对应的 agent 工具 —— agent 侧没有这一步。agent 能做的是它后面的事:摆器件、连线、建项目图纸、跑仿真。
- 删除项目只删掉项目定义(那一份 JSON)。它不动场景,所以删项目永远不会把你眼前的硬件清掉。
快速上手:三步
1. 导入项目本体 —— 点 + 导入项目本体(选文件夹),选一个完整的项目文件夹 (前后端 + 硬件代码)。它不带建模;宿主读它来判断需要哪些硬件。
2. 扫描需要哪些硬件 —— ③ 面板里的 扫描需求 按钮(界面专属,见上)。报告会列出每个候选型号键,并给出证据(在哪几个文件里看到的),让你自己判断对不对;扫描时如果做了采样,它会说出来,而不会让你以为整个项目都扫过了。
3. 导入建模组 —— 点 + 导入建模组(选文件夹),布局是 <型号键>/model.glb。
没补的型号继续渲染为占位盒。
或者干脆跳过 1–3:把系统用话讲给 agent,让它用 hw_save_project 写成图纸,再用 hw_import_project 一次把器件、连线、联网绑定全部摆好。
Agent 工具:16 个 hw_
以下是实际注册的清单(未增未删)。「什么时候用」这一列才是表格的意义 —— 这些工具并不通用互换。
项目
| 工具 | 作用 | 什么时候用 |
| hw_list_projects | 列内置软硬件项目:器件、接线、联网配置,以及当前是否软硬件连通 | 在手工摆器件之前先用它 —— 一张图纸是一次调用;手工摆是四步,少一步就不通且不报错 |
| hw_import_project | 导入项目:先清空当前装配,再摆器件、连线、挂绑定 | 换掉整套硬件时用;注意它是替换,不是往上加 |
| hw_save_project | 新建或覆盖项目图纸(存进 $DSH_HOME/dsh-hardware-sandbox/projects/) | 用户用话描述了一个系统 —— 先写成图纸,再导入 |
装配
| 工具 | 作用 | 什么时候用 |
| hw_get_assembly | 读 SSOT 摘要:revision、组件、连线、告警(连 message 一起给,不只是码)、供电、虚拟设备 | 每次改动前都先读 —— 你需要真实的端口 id;端口 id 是引脚身份(E4),不是丝印名(GPIO23) |
| hw_edit_assembly | 一个工具覆盖契约里的 ClientAction 联合:放器件 / 移动 / 旋转 / 连线 / 拔线 / 删除 / 钉住 | 任何对场景的修改。它收一个 action 对象,先读工具描述里的样例 |
仿真
| 工具 | 作用 | 什么时候用 |
| hw_run_simulation | 在虚拟硬件上运行真实的 Python 项目;duration 是虚拟秒数;返回项目输出与虚拟/真实耗时 | 真正的硬件在环那一跑。accelerated(默认)远快于真实时间;realtime 按真实节拍限速,便于观察 |
| hw_get_sim_log | 读最近一次(或进行中)那跑的项目输出,可只取 tail 行 | 当 hw_run_simulation 的输出被截断时 |
| hw_inject_fault | 对设备 id 或 型号:地址 目标注入 disconnect / nack / busy / set_reading / clear | 验证项目的异常处理真的看到了故障(例如断线后的 OSError),而不是假定它会看到 |
联网设备
| 工具 | 作用 | 什么时候用 |
| hw_network_add | 添加一台联网设备(协议适配器 + endpoint + 设备自报身份)。它不会启动 | 第一次把设备模拟器接上你的后端 |
| hw_network_devices | 列设备:协议、外部系统地址、在线状态、上报/失败次数、当前读数、服务端下发的配置 | 确认设备到底连上没有、有没有在真的上报 —— 否则"已添加但停着"看起来也很正常 |
| hw_network_control | start / stop / disconnect(掉线故障)/ clear | 只有 start 会让请求真的发出去 —— 这是刻意的,默认不该有外部副作用;disconnect 用来验对面能否识别离线 |
| hw_network_set_reading | 设置物理读数(如 { ir_front: true, ir_back: true },true = 被遮挡) | 单独验协议层对不对,与硬件通路无关。电平由协议按配置(如 ir_active_high)翻译,极性搞反的责任留在协议侧,不在调用方 |
| hw_set_occlusion | 遮挡 / 移开装配里的某个器件,并沿实际接线把影响传导到联网设备的读数上 | 验硬件通路:PIR 没接上、或接错了脚,就什么都不会发生 —— 真机就是这样 |
型号定义(数据驱动)
| 工具 | 作用 | 什么时候用 |
| hw_scan_board_pins | 从本机 PlatformIO 的权威文件推导引脚表草稿:boards/<board>.json → build.variant → variants/<variant>/pins_arduino.h(编译器实际用的那份映射)。list:true 列出本机能找到的板子 | 要加一块新板子时先跑它。★★ 它给的是草稿 + 缺口清单(缺:物理排布 / 电气语义 / 丝印),不是成品 —— 缺口按数据手册补齐后再用 hw_save_model_definition |
| hw_save_model_definition | 保存一条型号定义(引脚表:portId/丝印/协议/电压/方向 + 尺寸 + 排布)。校验通过才落盘,落盘后立即生效 | 需要一块插件还不认识的板子时。引脚表必须有权威来源(厂商数据手册 / PlatformIO 的 variant 文件 / 项目自己的工程文件);找不到就问用户,不要凭记忆编 |
真机在环(只读观察)
| 工具 | 作用 | 什么时候用 |
| hw_watch_device | 观察一台真机:定时只读查询外部系统(智座用 GET /api/admin/sensor/devices),把真机状态映射到场景器件上。action = start / stop / status | 想知道"真机现在到底连没连上"。⚠️ 它与 hw_network_add 方向相反:那个是我们冒充设备发请求,这个是我们去问真机(只读、无对外副作用) |
原因码与对策
装配操作的失败都带原因码(契约的 WarningCode,共 12 项,见 src/contracts/assembly.ts)。
照着码改对应的那件事。不要盲试。
| 原因码 | 含义 | 该做什么 |
| bad_params | 动作参数不可用(如旋转角里带 NaN / Infinity) | 改数值 —— 问题在值上,不在组件 id 上 |
| direction_conflict | 两端电流方向冲突(输出对输出 / 两个供电接在一起) | 按电流方向重排:至少一端要变成输入 |
| protocol_mismatch | 两端口信号种类不同 | 用匹配的端口对;确认那个脚到底支持哪种信号 |
| port_occupied | 这个端口被别的线占了 | 拔掉那根线(用它的 cableId 做 disconnect),或换脚 |
| voltage_mismatch | 端口电压不兼容 | 接电压兼容的端口,或加电平转换 |
| power_exceeded | 装配的供电需求超出供给预算 | 增加供电或减小功耗 |
| i2c_address_conflict | 总线上两个器件地址撞了 | 改其中一个的地址(SDO/AD0 脚) |
| i2c_pullup_missing | I2C 总线缺上拉 | 给 SDA/SCL 加上拉 |
| unconnected_port | 有端口一根线都没接 | 按需连接 —— 并检查是不是忘了成对的那一根 |
| unknown_hardware_model | 找不到这个型号键 | 用 hw_get_assembly 看现有组件里的真实键 |
| component_pinned | 组件被钉住了,移动被明确拒绝 | 先解开钉住(右键菜单,或 set_pinned: false) |
| already_connected | 就是这一对端口已经连了 | 别去找一根不存在的线;想换线先拔这一根 |
三条值得留下的说明:
- port_occupied 与 already_connected 是刻意分开的两个码。 合成一个,用户会去找一根不存在的线。
- 多个不同端口接到同一个端口是合法的(那是 I2C 总线),不报 already_connected。
- ok: true 不等于效果发生了。 每次写入之后都应该再读一次装配确认。
型号定义:让插件认识一块新硬件
先说清两件不同的事(这是最容易混的一处):
| | 给的是 | 从哪来 |
| 建模组(.glb) | 外观 —— 场景里长得像真板子 | 模型库(step.parts / GrabCAD / SnapEDA…,大部分板子有)或你自己导入 |
| 型号定义(.json) | 引脚数据 —— 有几个脚、每个脚是什么 | 模型库里没有:连 SnapEDA 的符号库都"不含总线协议与电压",而语义正是我们要校验的东西 |
⇒ 外观来自库;引脚数据靠型号定义。 没有定义,插件不认识这块板子(摆上去也只是占位方块)。
一条定义里写什么
{
"key": "stm32f103c8t6", // 型号键(小写字母/数字/连字符);不能与内置型号同名 "label": "STM32F103C8T6(Blue Pill)",
"size": { "x": 0.0533, "y": 0.011, "z": 0.0229 }, // 包围盒,单位【米】
"powerDrawW": 0, "powerSupplyW": 0,
"ports": [
// portId = 身份(连线用它,必须唯一);name = 丝印(人看的那行字,可重复) { "portId": "P1", "name": "VBAT", "protocol": "power", "voltage": 3.3, "direction": "in" }, { "portId": "P2", "name": "PC13", "protocol": "gpio", "voltage": 3.3, "direction": "io" } ], // 排布:贴哪条边、间距、几排(双排排针用 rows/rowPitch)
"portLayout": { "edge": "+z", "pitch": 0.00254, "count": 40, "inset": 0.001, "rows": 2, "rowPitch": 0.02292 }
}
三条容易踩的:
1. portId 是身份、name 是丝印 —— 一块板有多个 GND,用功能名当身份会撞号,而撞号的后果是静默的(两根线分不清)。
2. voltage: 0 是合法的 —— 那是 GND 的电压,不是"没填"。
3. 端口要么每个给 position、要么给 portLayout —— 两者都缺会被拒绝(不是给个默认值假装能用)。
三条获取路径(按可靠性排序)
| 路径 | 怎么做 | 说明 |
| ① 扫描/推导(主路径) | 从工程文件推:platformio.ini 的 board = 认出型号;framework 的 variants/<board>/pins_arduino.h(编译器实际用的那份映射)拿到引脚;固件源码 #define IR_SENSOR_A_PIN 23 看出本项目实际用了哪些脚;STM32 的 .ioc 有完整外设→引脚分配 | 覆盖大部分;推不出电气语义(3.3V/5V、I2C、上拉)与物理尺寸 —— 那部分要手册 |
| ② AI 一键写 | ③ 面板里填型号名 → ✎ 让 agent 帮我写(或让 agent 直接调 hw_save_model_definition) | agent 会先找权威来源;找不到就来问你,不许凭记忆编 |
| ③ 手动/用户提供 | ③ 面板 + 添加型号定义(选 .json) | 你自己有手册/接线表时最快 |
★★ 一条纪律(写进工具描述里的)
引脚表必须有权威来源(厂商数据手册 / PlatformIO variant / CubeMX 数据库 / 项目自己的工程文件)。
手打错的引脚表比没有更坏 —— 它会产出"看起来对但接错脚"的校验,而没人能发现。
⇒ 所以插件宁可拒绝、宁可让你补来源,也不给一个"看起来合理"的默认值。
内置型号不可被覆盖:撞名会被拒绝并说明原因 —— 否则别人发一个 rpi-4b 定义就能悄悄改掉所有用户的引脚表。
真机观察:把真机状态读进场景
让场景里的器件反映真实硬件的状态:真机 → 服务端 → 插件(只读)。
hw_watch_device({ action:'start', componentId:'c1', deviceId:'AA:BB:CC:11:22:33',
endpoint:'http://127.0.0.1:5800', intervalMs:5000, cookie:'…' })
hw_watch_device({ action:'status' }) // 看现在是什么状态
hw_watch_device({ action:'stop', componentId:'c1' })
界面上对应 ④ 真机观察 面板(填器件 / 真机标识 / 地址 / 间隔 → 开始观察(只读))。
★★ 五种状态分开(这是这一层最重要的设计)
| 状态 | 含义 | 该去查什么 |
| ok | 查到了,数据是新的 | —— |
| stale | 查到了,但数据很旧 | 真机确实离线 → 查硬件(供电/网络)|
| error | 我们查不到(网络/鉴权/解析失败)| 地址与鉴权,与硬件无关 |
| not_found | 服务端答得好好的,但没有这台设备 | deviceId 是否写错;或它还没上过线 |
| idle | 还没查过 | —— |
⚠️ 把 error 说成"设备离线"是本项目最怕的那种错:结论看起来合理、实际完全错误,还会让人去拔插硬件,而真正的问题在我们这边(地址写错 / 没登录)。
两条硬约束
1. 轮询走墙钟(真机按真实时间走,不能加速),间隔下限 2 秒 —— 这是保护对方服务器的硬约束,不是建议值。
2. 只有宿主在轮询:界面只读宿主的缓存(不会把对方服务器打两遍,也不会出现两份不一致的状态)。
⚠️ 一个真实的陷阱(源码里挖出来的)
不要用 GET /api/sensor/device_config 做观察 —— 智座那条路由会把 last_seen 刷成当前时间(它本来是给设备拉配置用的)。
用它做观察 = 我们一查,离线设备就变在线 ⇒ 得出"设备在线"而设备根本没插电。
⇒ 观察只用 /api/admin/sensor/devices(只读),这条有断言钉死。
建模组格式
交给 + 导入建模组 的文件夹(也是导出与导入往返的同一套布局)是:
<型号键>/ model.glb 模型本体 meta.json 可选元信息
- 渲染只加载 GLB / GLTF。 导入侧的格式判定认内容不认扩展名,接受 glb / gltf / stl / obj / ply / 3mf / dae / fbx / wrl;但 STEP / IGES 会被明确拒绝,而不是静默存下 —— three.js 没有对应 loader,存下来的 STEP 永远渲染不出来。
- 内部每个模型是一个目录 <型号键>/{model.bin, meta.json},model.bin 装几何字节(meta.json 记 format / nodes / bytes / generator)。用中性文件名存字节是刻意的:布局里没有扩展名歧义,meta 与它的字节绑在一起。导出的建模组用上面的 <型号键>/model.glb 形式,所以导出与导入是同一套布局。
- 随包分发(启动时补进用户模型目录,只补缺的键 —— 你自己导入过的模型绝不会被覆盖):
| 型号键 | 来源 | 在包里 | 为什么 |
| bme280 | 自产 | 有 | 寄存器级设备模型 + 演示装配要用 |
| led-5v | 自产 | 有 | 最简单的输出器件 |
| hc-sr501 | 自产 | 有 | 演示装配要用两个(PIR) |
| esp32-seat-sensor | 自产 | 有 | 联网设备演示要用 |
| rpi-4b | 从 step.parts 导入(第三方) | 没有 | 第三方几何 —— 本插件不分发;需要时从界面上自己导入 |
| mpl3115a2 | 从 step.parts 导入(第三方) | 没有 | 同上 |
| breadboard | 自产(无端口、无源) | 无几何 | 渲染为按契约尺寸生成的占位盒 |
为什么这么分:四个自产模型是我们的,可以随包发,所以插件开箱即用 (演示装配就是 esp32-seat-sensor + 2 × hc-sr501,全在包里)。第三方模型是别人的资产、有它自己的条款,所以插件不分发它们 —— 界面给搜索入口,文件你自己导。端口语义(I2C 地址、协议、电压、方向)一律自产,因为没有哪个模型库卖这个 —— 详见 docs/05。
墙钟陷阱:联网设备为什么不用虚拟时间
虚拟时钟是加速的:advance(60) 会把 60 个虚拟秒在几十毫秒内跑完。被动等被读的总线设备挂上去没问题
(确定性、纯同步);联网设备不行 —— 它是主动周期上报的,一次快进会朝别人的后端打出几十次 真实 HTTP 请求,把对方打爆。
所以联网设备一律按墙钟(真实节拍)走,与虚拟时钟解耦;它的快照里带 wallClockDriven: true,加速模式对它们没有意义。两个推论:
- 加速模式对联网设备不适用。 它们始终按真实间隔上报。
- 设备被添加后停在 idle,必须显式 start —— 默认完全不产生外部副作用。
这是本插件的一条结构性纪律:默认行为不许伸到机器外面去。
构建与开发
1. 安装(同时链接构建所需的框架 peer)
npm install
2. 构建:src/ -> lib/(需要 bash 与一份 DSH 源码 checkout) $env:DSH_CHECKOUT='D:\dsh\deepseek-harness'; bash scripts/build.sh
3. 测试与类型检查(2026-10-11 实测)
npm test # node --test,308 项,0 失败
npm run typecheck # tsc -p tsconfig.json --noEmit -> exit 0
npm run typecheck:client # tsc -p tsconfig.client.json --noEmit -> exit 0
scripts/build.sh 除了编译还做三件事:链接构建依赖(scripts/link-deps.mjs,同时挂在 postinstall 上,因为 npm install 会清掉它不认识的 junction)、把 project-shim/ 复制进 lib/project-shim/、写出 lib/build-stamp.json。那次复制是功能性步骤,不是打包细节 —— SimEngine 把 Python 进程的 PYTHONPATH 指向 shim(smbus + RPi.GPIO + sitecustomize),而 package.json 的 files 只发 lib,不做这一步就等于发布一个跑不起来的包。
scripts/prepare.mjs 让 git 装法也能用:lib/ 被 gitignore,所以 git 装出来会没有入口。
它只在 lib/index.js 缺失时才构建,而且构建失败只警告(exit 0) ——构建失败大概率是这台机器缺 bash 或缺 devDependencies,让整个 install 失败只会把锅甩给 npm。
发布产物:npm pack → 一个 .tgz,内含 lib、cordis.patch.yml、skill、docs、models、scripts。
活体自检:线上跑的是你以为的那次构建吗?
curl http://127.0.0.1:<端口>/@noob-stupid/dsh-breadboard/api/capabilities
所有界面与 API 都挂在路由前缀 /@noob-stupid/dsh-breadboard/ 下。GET …/api/capabilities 返回 build(当前活着的那份代码的构建时间戳)与 simTools(实际注册成功的工具名)。
觉得哪里不对就先看它:工具注册失败是完全静默的 —— 模型只会说"我没这个能力",界面上看不出任何异常;而热重载可能报成功、实际还是旧模块在服务。
已知限制
诚实清单;下面没有一条被"看着像默认值"的东西掩盖。
- 本包未发布到 npm。 从 GitHub 装。有一个预构建的 tgz 作为发布产物,但没有 npm install 这一行可给。
- 界面只有中文。 面板里今天没有语言切换,也没有英文界面文案可以承诺。
- 项目 Python 代码读 GPIO 尚未实现。 hw_set_occlusion 会把遮挡沿实际接线传导进联网设备的读数,但反方向 —— 项目代码经 shim 调 RPi.GPIO.input() —— 会抛 NotImplementedError,桥接层的 gpio_read / gpio_write 在契约里已声明、行为未实现("已定义协议但行为模型尚未实现")。
所以"固件轮询引脚再上报"这条路今天还走不通;"固件直接 HTTP 上报"这条路可以。
- 联网设备还没画进 3D 场景。 它们已在 SSOT 里(transport: 'network')、工具也完全能驱动,但前端还没渲染它们。
- 只声称验证过的路径。 联网设备那条链在真实后端上验证过;I2C 形态的真实项目还没有适配过 —— I2C 那一半目前由本仓库自带的测试项目验证。
- 模型尺寸 vs 几何保真是待决的二选一(见 docs/06 §6):把导入模型缩放到声明的真板 size 会让它轻微变形。今天的做法是保留真机尺寸、端口按变形后的间距对齐。
- hw_set_occlusion 只够得着联网设备。 若被遮挡的器件接到的对端不是带 pinMap 的联网设备,什么都传导不过去 —— 这时工具会在 reason 里说出来,而不是静默成功。
- 部分导入模型的端口数还在收敛(例如树莓派排针有几个脚落在 PCB 上而非引脚 mesh 上)。
端口可用,但少数锚点有零点几毫米的偏差。
文档
| 文件 | 内容 |
| docs/00-宿主API核实.md | 宿主 API 核实(slot 树、路由注册、坑位) |
| docs/01-项目定位.md | 项目定位、锁定范围 |
| docs/02-步骤①-虚拟时钟内核.md | 虚拟时钟内核 |
| docs/03-模型库可行性验证.md | 模型库可行性验证(为什么不做采集器) |
| docs/04-失败族-看起来正常实际全错.md | 失败族「看起来正常、实际全错」(20 例) |
| docs/05-模型来源与导入方案.md | 模型来源与导入方案决策 |
| docs/06-状态与交接.md | 状态与交接(一页纸) |
| docs/07-联网设备-第二类虚拟硬件.md | 联网设备,第二类虚拟硬件 |
这些文档是中文的工作文档(翻译它们会多出一份需要同步的副本)。本文件与 README.md 是成对维护的。
许可证
BSD-3-Clause。
dsh-breadboard预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Noob-stupid/dsh-breadboard
DSH 嵌入式硬件沙盒插件:3D 装配协作感知 + 项目级硬件在环仿真
相关插件 · 工具 / 效率
破甲管控台kingcharleslzy-ai/dsh-purge
dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚。基于小杨 / YuJunZhiXue 的 dsh-purge。
★ 4,173已汉化DeepSeek 客户端
74.4
网文长卷lingfengQAQ/webnovel-writer
基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200…
★ 7,636已汉化DSH
72.2
多智能体协作HKUDS/AutoSwarm
AutoSwarm: Unify Your Agents. Unify Your Context (Codex · Claude Code · DeepSeek Harness · Muse · Grok · WorkBuddy· nanobot · and more)
★ 2,197已汉化DSH
71.7
Vox 拼贴视频生成Alisa0808/vox-director
🎬 Vox Director拼贴动效导演
★ 2,129已汉化DSH
71.4
Agent 演化引擎Q00/ouroboros
智能体 OS:智能体自己变聪明,我们只守住底线:面试准入、分阶段评估、带预算的进化循环。MCP 服务器,14 种运行时:Claude Code、Codex CLI、Gemini CLI、OpenCode、Copilot、Kiro 等。
★ 6,308已汉化DSH
71.3
智能体运行框架路由HarnessRouter/harnessrouter
智能体运行框架的统一接口。
★ 3,897已汉化DSH
71.2