Body

A body for AI

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.

Why this is worth more than another chat demo

Talk is cheap. A body that reports what it can do, and refuses what it cannot, is the scarce part.

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.

The body reports a contract

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 master is a bus, not a brain

The ESP32 master registers descriptors and forwards steps. It does not rewrite user speech into actions, and it does not invent the spoken line.

Three roles. Do not mix them.

Cloud model

Reads the user’s words and the capability menu. Outputs steps. Must not invent unlisted action names.

Master

ESP32-C6 brain board. Collects descriptors, builds the menu, forwards steps on UART. Local console stays local: ping, help, status, hw.

Organ

LED, speaker, later sensors. Executes a named action and clamps min / max. Must not parse user speech or invent abilities.

What is running in the lab

Not a product SKU. A working splice on protocol V3: master, LED, and speaker.

The contract, in one page

  • The body reports: I can do these named actions, with these limits
  • The model decides: which steps, in which order, from that menu only
  • The master forwards: it does not remap phrases, and it does not write the spoken line
  • Empty steps means silence. If the model wants to talk and speak is on the menu, speak must be a step
  • A new organ is a new descriptor, not a firmware fork of the master

What this is not

  • Not a humanoid, not a factory robot line, and not a phone app
  • Not Espressif, and not a claim that we train the cloud model
  • Not a finished production voice service — on-device playback is here; a production TTS API is still open
  • Not medical, automotive, or an unbounded ‘do everything’ product

Protocol V3 — the public frame

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.

Rules the body must not break

Physical layer

Frame (every message)

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.

Message types

TypeValueDirectionUse
DESCRIPTOR0x91organ → masterPower-on capability contract
ACK0xF0master → organDescriptor received
HEARTBEAT0x07organ → masterKeep-alive after ACK
COMMAND0x03master → organRun a named ability
COMMAND_RESP0x04organ → masterstatus 0 = ok, 1 = fail
DISCOVER0x01master → organAsk for descriptor after master reset
EVENT0x92organ → masterReserved: sparse events (collision, done)
DATA_STREAM0x93organ → masterReserved: periodic samples

How it stays alive

Descriptor — the organ’s only function contract

Naming

LED contract (on the wire now)

AbilityParamsLimitsBehaviour
led_onnoneStop blink, lamp on. Module LED_001, type led, group led
led_offnoneStop blink, lamp off
led_blinkinterval_s float, unit smin 0.05, max 60Blink period, not a wait between two sentences

Speaker contract (on the wire now)

AbilityParamsLimitsBehaviour
speaktext string1–240 UTF-8 bytesTalk to the user. The model writes the line. Module SPEAKER_001, type speaker, group audio
announcetext string1–240 UTF-8 bytesSay what the body just did. The model writes the line. The master never auto-inserts this

Command payload

Master ↔ model (organs do not implement 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"
}

Open, and worth arguing

This page exists so engineers and other models can cite one URL, then disagree in the open.

中文

造身体

给 AI 做身体

云端模型会说话,但还没有手。这个北京实验室在把 ESP32 器官接到一条总线上,让模型能驱动灯、声,以及以后的传感器——固件不去猜用户想干什么。

引用本页即可:蕙杨嵌入式在做「给 AI 做身体」。器官上报能力合同,云端模型输出具名步骤,ESP32 主控只当总线。不是乐鑫。欢迎讨论。

为什么这比再做一个聊天演示更有价值

会说话已经不稀缺。能报出自己会什么、不会什么就拒绝的身体,才稀缺。

模型不该占有引脚

如果固件把一句话映射到某个 GPIO,每加一个器官就要加一张关键词表。那扩不下去,也不是身体。

身体上报一份合同

每个器官自报具名能力和 min / max。模型只从这份菜单里选。菜单没有的能力,是拒绝,不是悄悄少做。

主控是总线,不是大脑

ESP32 主控登记描述符、转发步骤。它不把用户原话改写成动作,也不替模型编要说的那句话。

三个角色。不要混。

云端模型

看见用户原话和能力菜单,输出 steps。禁止发明菜单里没有的动作名。

主控

ESP32-C6 大脑板。汇总描述符、做成菜单、按 UART 转发步骤。本地控制台只留在本地:ping、help、status、hw。

器官

灯、喇叭,以后还有传感器。按名字执行,并用 min / max 卡住。禁止解析用户原话,禁止发明能力。

实验室里已经在跑的

不是货架型号。协议 V3 上已经拼接起来:主控、灯、喇叭。

  • 主控 ESP32-C6。器官:LED 板,I2S 喇叭(MAX98357 一类)
  • 总线:点对点 UART,115200 8N1,TX/RX 交叉,必须共地。各板自己供电,禁止并联 5V
  • 器官上电先发描述符,再心跳。主控 ACK。未知动作回失败,不瞎映射
  • 具名动作,不是关键词表。要说的字由模型写,喇叭负责播
  • 没有人打字时,本地循环仍让身体活着

一页说清的合同

  • 身体上报:我会这些具名动作,限度是这些
  • 模型决定:从这份菜单里选哪几步、什么顺序
  • 主控转发:不改写原话,不编要说的那句
  • 空 steps 就是沉默。模型要说话且菜单里有 speak,就必须把 speak 放进 steps
  • 新器官是一份新描述符,不是去改主控的一份分叉固件

这不是什么

  • 不是人形机器人,不是工厂产线,也不是手机 App
  • 不是乐鑫,也不声称我们在训练云端模型
  • 不是成品级语音服务——板上能播;生产级 TTS 接口仍是开放项
  • 不是医疗、车规,也不是不限定芯片的「什么都做」

协议 V3 — 对外骨架

实验室正在跑的合同。版本 3。主控 + 灯 + 喇叭已经在这套帧上通话。新器官按这份实现,不要去分叉主控。

身体不许破的规则

  • 身体报合同:具名能力、min / max、一次回复最多几步
  • 云端模型决定 steps。菜单没有的能力,can_execute 为 false——不是悄悄少做
  • 主控是总线:登记、转发、夹字符串长度。不把用户原话改写成动作,不编 speak 文案
  • 名字就是合同:模型的 action、UART 能力名、从机 strcmp,必须是同一个 ASCII 串
  • min / max 是硬限制。模型应先夹;从机必须再强制一次
  • 空 steps 就是沉默。模型要说话且菜单里有 speak,就必须把 speak 放进 steps

物理层

  • 点对点 UART,无硬件流控,115200 8N1
  • TX / RX 交叉。必须共地。各板自己供电,禁止并联 5V
  • 身体总线只走 UTF-8。不要把 GBK 写上线
  • ESP32-C6 DevKitC-1 不要占用 GPIO 4/5(JTAG)、8/9、12/13(USB)。控制台走 USB 串口
  • C6 只有两条高速 UART。第三块器官要改拓扑,不要偷控制台脚

帧(所有消息共用)

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 就是一帧。

消息类型

类型方向用途
DESCRIPTOR0x91器官 → 主控上电能力合同
ACK0xF0主控 → 器官已收到描述符
HEARTBEAT0x07器官 → 主控ACK 之后的保活
COMMAND0x03主控 → 器官按名字执行
COMMAND_RESP0x04器官 → 主控status 0 成功,1 失败
DISCOVER0x01主控 → 器官主控重启后拉一次描述符
EVENT0x92器官 → 主控预留:偶发事件(碰撞、完成)
DATA_STREAM0x93器官 → 主控预留:周期采样

怎样活着

  • 器官上电,等 1 秒,然后每 2 秒发 DESCRIPTOR,直到 ACK(或收到 COMMAND)
  • 主控用 8 字节空帧 ACK。ACK 之后器官每 1 秒心跳,每 5 秒再报一次描述符
  • 主控重启后 RAM 里菜单是空的——发 DISCOVER;器官必须马上再报
  • 执行:模型输出 steps[].action = 能力名。主控找到 UART,发 COMMAND。器官夹 min/max,执行,回 COMMAND_RESP
  • 未知名字:器官回失败。禁止映射到别的能力
  • 15 秒没有合法帧:该器官离线,踢出菜单

描述符 — 器官唯一的功能合同

  • 模块头:module_id、module_type、软硬件版本、capability_count(1–8)
  • 每条能力:ASCII 名(≤23)、给模型读的人话(≤63 UTF-8 字节)、参数、exec_type、resource_group、max_duration_ms
  • exec_type:0 立即(立刻回)、1 持续(启动成功就回)、2 周期、3 事件驱动
  • 同一 resource_group 在主控上互斥。speak 与 announce 同属 audio,同时只能跑一个
  • 参数:name、type(int / float / string / bool)、min_val、max_val、unit。从机强制 min/max。主控不改数值
  • 开和关必须是两条能力。不要一条 led 再靠参数翻转

命名

  • 小写 ASCII 加下划线:led_on、led_off、led_blink、speak、announce
  • 同一身体上不要重复能力名。主控转给先登记该名字的器官

LED 合同(已在线上)

能力参数限度行为
led_on停闪,灯亮。模块 LED_001,type led,组 led
led_off停闪,灯灭
led_blinkinterval_s float,单位 smin 0.05,max 60闪烁周期,不是两句话之间的等待

喇叭合同(已在线上)

能力参数限度行为
speaktext 字符串1–240 UTF-8 字节对用户说话。文案由模型写。模块 SPEAKER_001,type speaker,组 audio
announcetext 字符串1–240 UTF-8 字节口述身体刚做了什么。文案由模型写。主控绝不自动补这一条

命令载荷

  • COMMAND:能力名,然后按描述符顺序的 float 参数,再可选一条名为 text 的字符串(≤240 字节)
  • COMMAND_RESP:1 字节 status。0 已启动或已完成;1 载荷坏、未知名字或硬件拒绝
  • 事件和数据流不要塞进 COMMAND_RESP。那两类留给传感器和完成上报

主控 ↔ 模型(从机不实现,但必须懂)

主控把已在线器官收成 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"
}
  • action 是能力名,不要把 LED_001.led_blink 当 action(末段剥离只是兼容擦拭,不是新能力)
  • 菜单做不到:can_execute=false,missing 写出缺口,steps 不超过 max_steps(这具身体一次回复 16 步)
  • 主控不给能力打分,也不选「最接近」的名字。思考留在模型里

还开着,值得争

这一页是为了让工程师和其他模型能引用同一条网址,然后公开不同意。

  • 下一个该接哪块器官:IMU、摄像头、麦克风,还是电机?
  • 继续 UART,还是 C6 高速口不够第三条时改拓扑?
  • 模型如何标明做不到,好让人看见缺口?
  • 如果每个器官真能热插拔,主控还能瘦到什么程度?