# 状态与交接(一页纸) > **用途**:会话上下文压缩 / 新会话接手。读完这一份即可继续,不必回溯历史。 > **更新**:2026-10-08 ┃ **项目根**:`D:\dsh-breadboard\dsh-hardware-sandbox` --- ## 1. 一句话状态 **两类虚拟硬件都已端到端跑通并上线,DS 能亲手驱动它们。** 总线设备(I2C:真 Python 跑在虚拟硬件上)+ **联网设备**(主动向外部系统发 HTTP 的设备模拟器, 已在真实项目智座上验证:插件工具 → 设备 → HTTP → 智座状态机 → 数据库,全链路)。 3D 场景能连、装配状态与虚拟设备是同一份真相、用户能导入自己的模型。 **健康度**:297 项测试全绿 ┃ typecheck exit 0 ┃ 构建通过 ┃ 插件线上 `[active]` **实测指纹**(2026-10-08,构建 `08:52:12Z`): ```jsonc // GET /@noob-stupid/dsh-breadboard/api/capabilities { "build": "2026-10-08T07:36:30.132Z", "simTools": ["hw_run_simulation","hw_inject_fault","hw_get_sim_log","hw_get_assembly", "hw_network_add","hw_network_devices","hw_network_control","hw_network_set_reading"], "pythonPath": "python", "externalProcess": true, "chat": true } ``` - `hw_run_simulation` 拉真 Python 3.14.2 跑 `test_bme280_project.py`:3.030 虚拟秒 / 283ms 真实,读到 `chipId=96, 24.99°C` - `hw_network_*` 驱动真实智座:seat 6 `free → occupied → free`,服务端日志节拍稳定 ~1.01s - `POST /api/chat` **端到端实证**:投递一条到本会话 → 返回 `{ok:true, sessionId, messageId}` → **消息真的到达并驱动了一轮**(不是只看 `ok:true`)。 ⚠️ 这两件事**不是一回事**:`ok:true` 只说明 `followup()` 没抛; 「消息到达且开了一轮」才是通道通了的证据。**先记的是前者,后补的是后者。** - `POST /api/chat` 未指定 sessionId 且有多会话时**拒绝并列出候选**(实测本机 3 个会话,拒绝了) --- ## 2. 工作线状态 | # | 线 | 状态 | 证据 | |---|---|---|---| | ① | 虚拟时钟 + 确定性设备 | ✅ | T1/T2/T3/T5/T6 全绿;T6 实测 205.7ms→16.2ms | | ② | BridgeServer + Python shim | ✅ | **真 Python 进程**跑在虚拟硬件上,T1/T3 跨进程通过 | | ③ | BME280 寄存器级模型 | ✅ | 数据手册算例锚点 + 三向往回一致;**项目自己算补偿** | | ④ | 真实项目适配 | ✅ | 智座跑通(见 ⑩);I2C 形态的项目仍待一个 | | ⑤ | SimEngine + 工具注册 | ✅ | 4 个工具线上注册;`hw_run_simulation` 实测驱动真 Python | | ⑥ | SSOT + 前后端桥 | ✅ | 端到端验证;线上运行中 | | ⑦ | 3D 场景 + 交互层 | ✅ | 用户确认可见可拖;含组件面板/磁吸/拔线 | | ⑧ | 模型导入(step.parts) | ✅ | 搜索 + 一键导入 Pi 4B 实测(STEP 50.1MB → GLB 5,978,508B) | | ⑨ | 模型库 spike | ✅ 收口 | 结论「不采集;改用户导入」,见 `docs/03`、`docs/05` | | ⑩ | **联网设备(第二类虚拟硬件)** | ✅ | **见 `docs/07`**;智座实测全链路;4 个工具 | | ⑪ | **面板浮层输入框 → 会话** | ✅ | `POST /api/chat`;**端到端实证**(消息到达并驱动一轮)。前端已接(40f8f716),**只剩图标待换** | | ⑫ | **钉住组件**(右键菜单) | ✅ | `ComponentSpec.pinned` + `set_pinned` action;**拦截在 SSOT**(实测走 action 路由被拒)。前端已接(40f8f716) | | ⑬ | **真实几何进场景** | ✅ | 导入 `rpi-4b`(2067 节点) / `mpl3115a2` / `mpr121`;`ModelStore` 导入时记录 `nodes` | | ⑭ | **端口位置对齐真连接器** | ✅ | 排针在 **−z 边**(原铺在对侧空边);`pitch` 收到 **2.54×2 = 5.08mm** 使六个端口全落在排针 x ∈ [−22, +14] 内。**实测六个端口命中连续编号的引脚 mesh(−18/−20/−22/−24/−26/−29)、同一高度 −3.8mm、距离 0.23–0.90mm** | > ★ ⑭ 由**四个独立证据**共同确证:并行会话的顶点分箱 / 本仓库的高处顶点分布 / > **用户直接在渲染图上圈出排针位置** / 射线沿 x 扫描定出的排针两端。 > 工具 `tools/_port_on_model.py`:距离 + 偏移向量 + 竖直射线 + 命中 mesh + 镜像复核 + 高处顶点分布。 > ⚠️ **判据是「连续编号的一组 mesh」,不是「同一个 mesh」** —— 一排引脚一个 mesh。 > ⚠️ **射线答不了"排针在哪"**(它是分布问题;零宽度的线会从两根针之间穿过)—— 用分布来答。 ### ★ ⑪ 的通道很容易找错(我第一遍就找错了,记下来免得再犯) | 看起来像"发消息"的 | 实际是 | |---|---| | `commands.execute(agent, line, …)` | 文档原文 **"execute a known command *without sending it to the model*"** —— 斜杠命令,**不产生对话轮次** | | `Session.append('user/message', …)` | 只往**日志**写事件,**不驱动 agent loop** | | **`Agent.followup(input)`** | ✅ `send(input,'next-turn',true)` —— **就是"用户发消息、开新一轮"** | 前两个做出来的是「**看着像发了消息、其实模型没收到**」。出处 `packages/core/agent-loop/src/agent.ts`。 **两条硬约束**(都已实现并有测试): 1. **绝不猜目标会话** —— 未指定 sessionId 且多会话时**拒绝并列出候选**。猜错会把消息发进别人的会话且无提示。 2. **拿不到 `agents` 就不注册路由** —— 且 `capabilities.chat` 缺省,前端据此**隐藏**输入框。 ⚠️ `createChatSender` **总是返回函数**,所以入口必须判**服务**在不在、不能判 sender 在不在(判错了条件恒真)。 --- ## 3. 关键决策(压缩版,理由见详细文档) | 决策 | 一句话理由 | |---|---| | **三 realm**:宿主(SSOT) / 浏览器(投影) / Python(代理) | 文档原以为是两 realm;SSOT 必须在宿主(BridgeServer 在那、面板关了也要跑) | | **tick 只写 SSOT,不逐 tick 推前端** | `advance(1.0)`=1000 片,逐片推会打死链路(已用节流验证:<100 条且累计量守恒) | | **`advance()` 必须让出事件循环** | 同步忙循环会饿死 DSH 宿主本身(新增判据 T6) | | **整数微秒为时间权威** | `0.001` 累加 1000 次浮点 ≠ 1.0,会让"确定性读数"多跳一格 | | **原点 = 包围盒中心** | 旋转枢轴:角原点会让组件绕角甩出去 | | **`size` 是 3D 包络,不是 PCB 封装** | 树莓派 PCB 1.6mm vs 含接口 17mm,用错会让板子陷进地面 | | **端口位置用声明式 `portLayout`** | 手打 12 个坐标已出过错;规则让等距由推导保证(已验精确等价 3.5e-18) | | **两处真相必除**:歧义两侧都抛错 | 既无位置也无规则⇒抛;两者都有⇒抛。加载期告警等于没告警 | | **不采集厂商数据,改用户导入** | 条款约束的是**我们的**批量采集;用户自己下不在射程内 | | **step.parts 是唯一可直连来源** | 公共 API + 免鉴权 + **每件都有 glbUrl**(GLB 免转换)+ 覆盖板卡模块 | | **导入只收 partId,不收 URL** | glbUrl 由宿主从 API 取 ⇒ **SSRF 面为零** | | **几何可由用户给,语义不能** | 端口语义跨两生态证明买不到,必须自产(DS 辅助读**原厂手册**) | | **`tools`/`webServer`/`subprocess` 用硬 `inject`,不用 `ctx.get`** | 三者缺失都是**静默归零**(工具没注册/界面空白/仿真起不来)。硬依赖语义是"等到就绪再激活"⇒ 要么可用、要么不激活,**没有"激活了但功能悄悄没了"这一档** | | **工具的 `parameters` 与 `output.schema` 是两套方言** | `parameters` 是给人写的源方言(属性内 `required: true`,由 `defineTool` 编译);`output.schema` 是**已编译**形态(对象级 `required: [...]`)。我们不用 `defineTool`(零宿主依赖)⇒ **两边都必须自己写成标准 JSON Schema** | | **不编造拿不到的字段** | 宿主刻意不暴露进程 pid("provider-private")⇒ `pid` 改为可选,拿不到就不打印。编个 `-1` 只会让日志**看着像诊断、实际是垃圾** | --- ## 4. 文件地图 ``` src/contracts/ 冻结契约(我独有):time device assembly library model-sources protocol ipc src/core/ vclock/ 虚拟时钟(T6 让出、跳空、整数微秒) devices/ deterministic(验收用)、bme280(寄存器级) state/ assembly-state(SSOT) events.ts 事件桥 + sim_tick 节流合并 bridge/server.ts BridgeServer(TCP + token + NDJSON + 虚拟时间预算) sim/ device-registry(装配↔设备)、engine(spawn+预算+故障注入) network-registry(装配↔**联网设备**,与上面并列的第二类) models/ store(持久化+目录穿越防护+原子写)、step-parts(API 客户端) devices/ deterministic · bme280 · **networked(联网设备运行时)** protocols/zhizuo(智座座位传感器协议适配器) src/host/ routes.ts(HTTP/WS/模型/搜索/导入) tools.ts(4 个 hw_* 仿真工具) network-tools.ts(4 个 hw_network_* 工具) subprocess-spawner.ts(宿主 subprocess → SimSpawner + Python 探测) fetch-transport.ts(宿主 fetch → NetworkTransport) src/index.ts 宿主入口:内核 + 路由 + 引擎/工具注册 + **联网设备注册** + 自检 src/client/ src/scene/ 前端半(并行会话所有) project-shim/ Python 侧:bridge_client / shim(smbus,RPi.GPIO) / sitecustomize ★ build.sh 会把它复制到 lib/project-shim(package.json 的 files 只有 lib) examples/ zhizuo-virtual-esp32/(独立脚本版虚拟 ESP32,未接插件时用) tests/ 266 项(含 Python 侧 test_*.py,由 python-shim / sim-engine 两个 .ts 驱动) docs/ 00 宿主API核实 · 01 项目定位 · 02 步骤① · 03 模型库验证 · 04 失败族 05 模型来源 · **06 本文** · **07 联网设备(第二类虚拟硬件)** ``` --- ## 5. ★ 智座(`D:\MAX_xiangmu`):不适配 I2C 形态,但指向第二类虚拟硬件 **它是什么**:Flask + SocketIO + MySQL + Redis 的「智能选座与导航一体化系统」,带 APK。 **硬件**:ESP32 + 双红外/PIR/超声波 → **WiFi/HTTP 上报**给 Flask。 **Python 侧一行硬件调用都没有**(全项目无 `smbus`/`I2C`/`GPIO`/`serial`)⇒ **我们的 I2C shim 拦不到任何东西**。 **它要的三个接口**(这就是它的"硬件接口"): ```json POST /api/sensor/report { "seat_id": 12, "ir_front": 0, "ir_back": 1, "device_id": "AA:…" } POST /api/sensor/device/register // 设备以 WiFi MAC 注册 GET /api/sensor/device_config // 设备拉配置(绑定座位/间隔/类型/ir_active_high) ``` **✅ 已完成**:做成插件里的**第二类虚拟硬件**(联网设备),4 个 `hw_network_*` 工具, 智座实测全链路通过(seat 6 `free → occupied → free`)。**详见 `docs/07`。** **它揭示的结构性事实**(这条比智座本身重要): > 沙盒原来只做**协议虚拟化**(拦 I2C,设备被动等被读); > 智座需要的是**设备模拟器**(模拟联网设备的主动行为)。 > **同一命题换协议,而且简单一个量级。** **⚠️ 它自带一个进程内模拟器**(`_build_sensor_simulator()`),但**绕过 HTTP/注册/配置下发** —— 与我们做的**互补而非重复**(它测状态机,我们测设备接入链路)。 **环境**:本机 MySQL(3306) + Redis(6379) **本来就在跑**,12 个依赖已装,**不需要 Docker**。 起法(从项目根):`python app.py 127.0.0.1 5800`。 --- ## 6. 待办(按建议优先级) | # | 事 | 备注 | |---|---|---| | 1 | **★ 模型尺寸 vs 几何保真:二选一** | 模型包围盒 92.86×19.89×58.5 vs 契约 85×17×56,**比例差 4%–17%** ⇒ 逐轴缩放到声明尺寸**必然使模型变形**(标准 2.54mm 排针间距被压成 **2.32mm**,85/92.86=0.9153)。**两条路**:① 保留真机尺寸 ⇒ 模型轻微变形,端口按变形后间距对齐(**现在走这条**)② `size` 取模型包围盒 ⇒ 几何 1:1 不变形,但声明的不是真树莓派尺寸 | | 2 | 剩下 **4/40** 引脚落空 | 命中 PCB 而非引脚;平均距离 0.443mm、命中 13–29 连续 17 列。继续收敛得再精调 `offset` / `rowPitch` | | 3 | **ESP32 供电模式** | 现在有 30 个引脚(含 VIN/GND),但 `powerDrawW: 0`(自供电)。**若用户真把 VIN 接到装配的 5V,预算不会算进去** —— 要修得区分"自供电/装配供电" | | 4 | `mpl3115a2` 的朝向歧义 | 长/宽 19.05 vs 17.78(差 7%)⇒ "哪条是长"是**约定不是测量**。已标 `ambiguous`,靠 UI 的 info 提示 | | 5 | 联网设备进 3D 场景 | 已进 SSOT(`transport:'network'`)但前端还没渲染 | | 6 | 界面:改成"先搜模型再选型号" | 用户反馈"为啥把这几行单独列出来" | | 7 | I2C 形态的真实项目适配 | ④ 只完成了联网那一半;智座不适配 I2C | | 7 | 渲染统计尺子(默认关) | 见 `protocol.ts` 的 `nodes` 注释:**用户报卡时先量 `renderer.info.render.calls`**。40f8f716 待定 | | 8 | **告警详情带多少**(工程决定,已部分解决) | ~~"用户看不到告警"~~ **那句不成立** —— `sync.applyWarnings()` 对 `warn`/`error` **会在组件上画 3D 告警框**(就地、不打断),`info` 按定义不打扰。**⇒ 用户看的是空间化告警,不是列表,那是个站得住的设计。**
真正缺的是 **AI 看不到详情** —— 已在 `host/tools.ts` 修:告警现在连 `message` 一起印(截断 120 字符),不再只有 `severity/code`。
**仍待定**:`refs` 要不要带(可能几百个)、要不要按 severity 分层输出 |不会被自动裁剪**(2026-10-08 确证) > > ``` > include:compaction-basic enabled=false fiberPhase=null > include:command-compact enabled=false fiberPhase=null > include:tool-result-pruner enabled=false fiberPhase=null > ``` > **三个上下文管理插件全是 disabled。** ⇒ 会话上下文只涨不降,**长会话最终会被提供方拒收** > (实测症状:`400 INVALID_REQUEST`,**无 body**,看起来像插件炸了,实际与会话体积有关)。 > > ★ **这正是本文档存在的理由** —— 它是唯一的保险。**开工前先读它**; > 长任务要主动**分段落盘**(把结论写进 `docs/`),别指望环境替你记。 > > ⚠️ 保留一句:`disabled` 只证明"上下文没被裁剪",**不能区分**"累积上下文超限"与 > "提供方对单次请求体积的限制" —— 两者都会给同样的 400。所以上面的因果是**方向性判断,不是定论**。 ```powershell # 测试(无需编译、无需 bash) npm test # node --test,228 项 # 构建(需 bash 与 checkout)—— 会顺带把 project-shim 复制进 lib/ $env:DSH_CHECKOUT='D:\dsh\deepseek-harness'; & 'E:\Git\bin\bash.exe' scripts/build.sh # 部署 dev_install_package {dir, profile:'desktop'} # ★ 不是 dev_inject_plugin(它默认 web profile,会白跑) dev_reload_package {packageName:'dsh-breadboard'} ``` **线上自检**:`GET /@noob-stupid/dsh-breadboard/api/capabilities` · **`build`** = 当前活着的构建时间戳(热重载可能报成功但模块没换,靠它判别) · **`simTools`** = **实际注册成功**的工具名;**`simNote`** = 没注册成的具体原因 (工具注册失败是完全静默的 —— 模型只会说"我没这个能力",界面上看不出任何异常) **Python 探测的坑**:本机 `python` = `E:\python314\python.exe`(真,3.14.2); `python3` = `%LOCALAPPDATA%\Microsoft\WindowsApps\python3.exe`(**应用商店存根**, 能"解析成功"但一跑就失败)。⇒ 探测必须**真跑一次 `--version`**,不能只看解析结果。 **存储**:用户导入的模型在 `$DSH_HOME/dsh-hardware-sandbox/models//{model.bin,meta.json}`。 **已知坑(详见 `docs/04-失败族`,16 例)**:`npm install` 会清 junction(已 postinstall 自愈); 路由前缀**不能带尾斜杠**;测试替身的匹配规则必须逐字复刻生产; `Readable` 的 `'data'` 是异步投递的(`push()` 后立刻断言会看到空数组); Windows 上环境变量叫 `Path` 不叫 `PATH`。 --- ## 7. ★ 下一步的主线:打通 GPIO 链("能不能真测遮挡"的答案) **用户要测的"真问题"是:人坐下 → 遮挡 PIR → 固件读 GPIO 变高 → 上报 → 座位变 occupied。** **现在这条路走不通,卡点已定位到具体一行:** ``` ① 项目代码 GPIO.input(23) ② shim project-shim/shim/RPi/GPIO.py → raise NotImplementedError ← ★★★ 断在这里 ③ bridge project-shim/bridge_client.py gpio_read()/gpio_write() ✅ 已实现 ④ 契约 src/contracts/ipc.ts gpio_read / gpio_write ✅ 已声明 ⑤ 宿主 src/core/bridge/server.ts(i2c_read 在 L295) ❓ 待确认有无 gpio 分支 ``` ★ 关键:**协议链已经做了 3/4**(ipc 声明了、bridge_client 实现了), 只有 `RPi/GPIO.py` 那个壳在抛异常。**这不是架构问题,是三个待补环节。** | # | 要做 | 现状 | |---|---|---| | 1 | `server.ts` 补 `gpio_read` / `gpio_write` 分支 | 待确认 | | 2 | `RPi/GPIO.py` 的 `input()`/`output()` 接到 bridge | **就是这一行抛异常** | | 3 | **引脚电平的来源**:PIR 之类要作为虚拟设备参与装配、按端口寻址 | `hc-sr501` **没有虚拟设备** | | 4 | **遮挡注入**:能设某个引脚的电平 | 现在只能设"网络设备读数",绕过了硬件 | | 5 | 一个**读 GPIO 的虚拟固件**(读 GPIO23/27 → 上报智座) | 现有示例是纯 HTTP,不碰 GPIO | **做完这五步,"测遮挡"全程没有一步是假装的。** ★ **与模型精细度完全无关** —— 模型粗不影响这条链。 --- ## 8. ESP32 模型:为什么用**手搓的**而不是真模型(已决策,勿反复) step.parts 上唯一的真 ESP32 **开发板**模型是 `Arduino Nano ESP32`(2.8MB,已导入验证过)。 **但它只有 D0–D13 / A0–A7,没有 GPIO23 / GPIO27**,而智座(`D:/MAX_xiangmu`)的 固件与接线文档用的正是这两个脚 ⇒ **换真模型会让那两根线没有落点,接法不再忠实**。 ⇒ **用回手搓的 DevKit 模型**(真机尺寸 55.25×12.60×28mm;PCB + 双排 15 针 + USB + WROOM 屏蔽罩 + 两个按键)。 ★ step.parts 的覆盖边界(实测多次): **有** 开发板 / 机械件 / PCB 封装 / Adafruit breakout(88 个) **没有** 面包板(`breadboard`/`solderless`/`protoboard`/`perfboard` **全 0**)、 PIR(`pir`/`hc-sr501`/`motion sensor`/`infrared sensor` **全 0**)、 BME280(`bme`/`bmp280` **全 0**) --- ## 9. 硬件在环日程(**终版,已排期未开工**) 见 `docs/08-硬件在环日程.md`。顺序(用户 2026-10-14 拍定): ``` P0 加型号引脚定义(不需任何外部条件,可立即开始) P1 ESP32 服务端回读 → P2 ESP32 串口回读 → P3 ESP32 烧录 P4 ★ 抽出通用「硬件适配器」接口(真正的交付物) P5 STM32 验收(最后)—— 只写适配器,看不改核心能不能跑通 ``` ★ **验收判据(就是"到时候硬件驱动是不是基本都能搞"这句怎么算成立)**: 接 STM32 只新增适配器、**不需要改装配层/校验层/工具表/面板** ⇒ 抽象成立; 被迫改核心 ⇒ 回头补 P4。**这就是 STM32 排最后的原因:它是验收,不是目标。** ★ 已核实(2026-10-14 实测): - **ESP32 三要件齐备** —— 板子(用户有)· 工程(`D:\MAX_xiangmu`,已在插件里建模)· 工具链(PlatformIO Core 6.1.19 已装但不在 PATH + `pyserial` 已装) ⇒ 所以 ESP32 先行。且固件 `PIN 23/27` 与插件建模 `E4/E19` **完全一致**。 - **STM32 三要件全无** —— `STM32_Programmer_CLI`/CubeMX/`arm-none-eabi-gcc`/`openocd`/`make` 全不在 PATH,常见安装目录也不存在 ⇒ P5 阻塞在"等硬件 + 装哪套工具链"。 - **本机当前没有任何 COM 口** ⇒ P2/P3 需要先把板子插上。