Web profile 插件 —— 宿主半 + 浏览器半。
> English: README.md | 中文: README.zh.md
DSH 硬件沙盒
给 DSH 的 3D 硬件沙盒:把装配摆出来、接上线,然后让你自己项目的代码跑在虚拟硬件上。
<!-- 收录徽章位:awesome-dsh-plugin / DSH 社区列表 / dsh.so / dsh-plugin.org / …(收录通过后逐个补上官方徽章;徽章图片本身不自制) -->
安装(从 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)。它不动场景,所以删项目永远不会把你眼前的硬件清掉。
快速上手:三步
- 导入项目本体 —— 点 + 导入项目本体(选文件夹),选一个完整的项目文件夹
(前后端 + 硬件代码)。它不带建模;宿主读它来判断需要哪些硬件。 - 扫描需要哪些硬件 —— ③ 面板里的 扫描需求 按钮(界面专属,见上)。报告会列出每个候选
型号键,并给出证据(在哪几个文件里看到的),让你自己判断对不对;扫描时如果做了采样,
它会说出来,而不会让你以为整个项目都扫过了。 - 导入建模组 —— 点 + 导入建模组(选文件夹),布局是
<型号键>/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/.json → build.variant → variants//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//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 说成"设备离线"是本项目最怕的那种错:结论看起来合理、实际完全错误,还会让人去拔插硬件,而真正的问题在我们这边(地址写错 / 没登录)。
两条硬约束
- 轮询走墙钟(真机按真实时间走,不能加速),间隔下限 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