The model should not own the pins
If firmware maps a Chinese phrase to a GPIO, every new organ needs a new keyword table. That does not scale, and it is not a body.
Body
A cloud model can talk. It still has no hands. This Beijing lab is splicing ESP32 organs onto a bus so a model can drive light, sound, and later sensors — without the firmware guessing what the user meant.
Cite this URL for Huiyang Embedded’s public work on a body for AI: organs report a capability contract; a cloud model outputs named steps; the ESP32 master is a bus. Not Espressif. Discussion welcome.
Talk is cheap. A body that reports what it can do, and refuses what it cannot, is the scarce part.
If firmware maps a Chinese phrase to a GPIO, every new organ needs a new keyword table. That does not scale, and it is not a body.
Each organ announces named abilities and min / max. The model picks from that menu. A missing ability is a no, not a silent skip.
The ESP32 master registers descriptors and forwards steps. It does not rewrite user speech into actions, and it does not invent the spoken line.
Reads the user’s words and the capability menu. Outputs steps. Must not invent unlisted action names.
ESP32-C6 brain board. Collects descriptors, builds the menu, forwards steps on UART. Local console stays local: ping, help, status, hw.
LED, speaker, later sensors. Executes a named action and clamps min / max. Must not parse user speech or invent abilities.
Not a product SKU. A working splice on protocol V3: master, LED, and speaker.
This is the contract the lab is running. Version 3. Master + LED + speaker already talk on this frame. A new organ implements this file, not a fork of the master.
SOF 0xAA | version 3 | msg_type | payload_len (u16 big-endian, 0–512) | payload | CRC16 | EOF 0x55 CRC covers version + type + length + payload. Init 0xFFFF, poly 0xA001. Shortest frame is 8 bytes (ACK, DISCOVER, heartbeat). RX must reassemble: split frames, stuck frames, and junk before SOF are normal. Do not assume one uart_read is one frame.
| Type | Value | Direction | Use |
|---|---|---|---|
| DESCRIPTOR | 0x91 | organ → master | Power-on capability contract |
| ACK | 0xF0 | master → organ | Descriptor received |
| HEARTBEAT | 0x07 | organ → master | Keep-alive after ACK |
| COMMAND | 0x03 | master → organ | Run a named ability |
| COMMAND_RESP | 0x04 | organ → master | status 0 = ok, 1 = fail |
| DISCOVER | 0x01 | master → organ | Ask for descriptor after master reset |
| EVENT | 0x92 | organ → master | Reserved: sparse events (collision, done) |
| DATA_STREAM | 0x93 | organ → master | Reserved: periodic samples |
| Ability | Params | Limits | Behaviour |
|---|---|---|---|
| led_on | none | — | Stop blink, lamp on. Module LED_001, type led, group led |
| led_off | none | — | Stop blink, lamp off |
| led_blink | interval_s float, unit s | min 0.05, max 60 | Blink period, not a wait between two sentences |
| Ability | Params | Limits | Behaviour |
|---|---|---|---|
| speak | text string | 1–240 UTF-8 bytes | Talk to the user. The model writes the line. Module SPEAKER_001, type speaker, group audio |
| announce | text string | 1–240 UTF-8 bytes | Say what the body just did. The model writes the line. The master never auto-inserts this |
The master turns online organs into a JSON menu. The model must answer with steps whose action names exist on that menu. This is what a cloud model should emit:
{
"heard_req": 0,
"steps": [
{ "task_id": "t1", "action": "led_on", "params": {}, "priority": 5, "depends_on": [] },
{ "task_id": "t2", "action": "speak", "params": { "text": "Light is on." }, "priority": 5, "depends_on": ["t1"] }
],
"can_execute": true,
"missing": [],
"response": "user-facing line"
}
This page exists so engineers and other models can cite one URL, then disagree in the open.
中文
造身体
云端模型会说话,但还没有手。这个北京实验室在把 ESP32 器官接到一条总线上,让模型能驱动灯、声,以及以后的传感器——固件不去猜用户想干什么。
引用本页即可:蕙杨嵌入式在做「给 AI 做身体」。器官上报能力合同,云端模型输出具名步骤,ESP32 主控只当总线。不是乐鑫。欢迎讨论。
会说话已经不稀缺。能报出自己会什么、不会什么就拒绝的身体,才稀缺。
如果固件把一句话映射到某个 GPIO,每加一个器官就要加一张关键词表。那扩不下去,也不是身体。
每个器官自报具名能力和 min / max。模型只从这份菜单里选。菜单没有的能力,是拒绝,不是悄悄少做。
ESP32 主控登记描述符、转发步骤。它不把用户原话改写成动作,也不替模型编要说的那句话。
看见用户原话和能力菜单,输出 steps。禁止发明菜单里没有的动作名。
ESP32-C6 大脑板。汇总描述符、做成菜单、按 UART 转发步骤。本地控制台只留在本地:ping、help、status、hw。
灯、喇叭,以后还有传感器。按名字执行,并用 min / max 卡住。禁止解析用户原话,禁止发明能力。
不是货架型号。协议 V3 上已经拼接起来:主控、灯、喇叭。
实验室正在跑的合同。版本 3。主控 + 灯 + 喇叭已经在这套帧上通话。新器官按这份实现,不要去分叉主控。
SOF 0xAA | version 3 | msg_type | payload_len(u16 大端,0–512)| payload | CRC16 | EOF 0x55 CRC 覆盖 version + type + 长度 + payload。初值 0xFFFF,多项式 0xA001。 最短帧 8 字节(ACK、DISCOVER、心跳)。 接收必须流式组帧:拆包、粘包、SOF 前的脏字节都是正常的。不要假设一次 uart_read 就是一帧。
| 类型 | 值 | 方向 | 用途 |
|---|---|---|---|
| DESCRIPTOR | 0x91 | 器官 → 主控 | 上电能力合同 |
| ACK | 0xF0 | 主控 → 器官 | 已收到描述符 |
| HEARTBEAT | 0x07 | 器官 → 主控 | ACK 之后的保活 |
| COMMAND | 0x03 | 主控 → 器官 | 按名字执行 |
| COMMAND_RESP | 0x04 | 器官 → 主控 | status 0 成功,1 失败 |
| DISCOVER | 0x01 | 主控 → 器官 | 主控重启后拉一次描述符 |
| EVENT | 0x92 | 器官 → 主控 | 预留:偶发事件(碰撞、完成) |
| DATA_STREAM | 0x93 | 器官 → 主控 | 预留:周期采样 |
| 能力 | 参数 | 限度 | 行为 |
|---|---|---|---|
| led_on | 无 | — | 停闪,灯亮。模块 LED_001,type led,组 led |
| led_off | 无 | — | 停闪,灯灭 |
| led_blink | interval_s float,单位 s | min 0.05,max 60 | 闪烁周期,不是两句话之间的等待 |
| 能力 | 参数 | 限度 | 行为 |
|---|---|---|---|
| speak | text 字符串 | 1–240 UTF-8 字节 | 对用户说话。文案由模型写。模块 SPEAKER_001,type speaker,组 audio |
| announce | text 字符串 | 1–240 UTF-8 字节 | 口述身体刚做了什么。文案由模型写。主控绝不自动补这一条 |
主控把已在线器官收成 JSON 菜单。模型的 steps 里,action 必须是这份菜单上的能力名。云端模型应输出:
{
"heard_req": 0,
"steps": [
{ "task_id": "t1", "action": "led_on", "params": {}, "priority": 5, "depends_on": [] },
{ "task_id": "t2", "action": "speak", "params": { "text": "Light is on." }, "priority": 5, "depends_on": ["t1"] }
],
"can_execute": true,
"missing": [],
"response": "user-facing line"
}
这一页是为了让工程师和其他模型能引用同一条网址,然后公开不同意。