文档库
NodeHexa UART v2协议
本章依据相机v1.1.0与主控v3.0.1源码。UART为115200、8N1、3.3 V;相机TX=GPIO1、RX=GPIO2,接主控RX=GPIO16、TX=GPIO17。
二进制帧
| 偏移 | 字节数 | 内容 |
|---|---|---|
| 0 | 2 | 帧头A5 4E |
| 2 | 1 | 版本02 |
| 3 | 1 | 消息类型 |
| 4 | 1 | flags |
| 5 | 2 | sequence,小端uint16 |
| 7 | 2 | JSON载荷字节数N,小端uint16 |
| 9 | N | UTF-8 JSON,不带末尾NUL、不带换行 |
| 9+N | 2 | CRC,小端uint16 |
总长N+11,N最大512。长度按UTF-8字节数计算,不按汉字个数计算。
CRC为CRC16-CCITT:多项式0x1021、初值0xFFFF,不反射、不作最终异或;覆盖偏移2开始的7+N字节,不含帧头及CRC。可复用源码examples/nodehexa-video/main/uart_protocol.{h,cpp},避免自己拼出不同变体。
| 类型值 | 名称 | 用途 |
|---|---|---|
| 1 | HELLO | 发现和能力声明 |
| 2 | REQUEST | 配网、档位或运动请求 |
| 3 | RESPONSE | 对应请求的回执,匹配sequence |
| 4 | EVENT | 异步状态、低电量、动作完成 |
| 5 | HEARTBEAT | 存活查询 |
现有发送端常用请求/HELLO flags=0x02,主控回执flags=0x01,相机回执为0。接收时应首先按消息类型和序号分流,不因两端flags差异丢弃合法回执。flags不是加密或鉴权字段。
相机解析器相邻字节间隔超过250 ms会放弃未完成帧;错误CRC或超长载荷也会丢弃。UART读取可能只有半帧或同时包含多帧,必须增量解析,不能假定一次read就是一帧。
可校验的停止帧示例
下面是人为构造的REQUEST:flags=0x02、sequence=0x1234、JSON为{"stop":true},载荷13字节,总长24字节。十六进制中的34 12是小端序号,不是两个独立序号。
A5 4E 02 02 02 34 12 0D 00 7B 22 73 74 6F 70 22 3A 74 72 75 65 7D 62 89
将其作为编码器单元测试向量;不要把十六进制字符文本直接作为UART二进制帧发送。
HELLO与主控响应
图传相机发送的载荷结构:
{"device":"camera","deviceId":"aabbccddeeff","firmware":"1.1.0","protocols":[2],"capabilities":["mjpeg","camera_status","wifi_provision","camera_binding_v1"]}
主控RESPONSE包含device=nodehexa、protocol=2、固件信息、能力和power.lowBatteryLatched。纯运动上位机可声明device=generic_host,不冒充camera触发不需要的配网流程。本手册按键实操采用这一方式。
图传相机在未完成主控握手时每2秒HELLO,完成后每10秒HELLO,并每2秒上报cameraStatus。不要把某个HELLO响应当成后续所有请求都成功。
相机配网与档位请求
旧单机兼容载荷:
{"cameraProvision":{"ssid":"robot-network","password":"example-password","streamPort":81}}
绑定模式另外包含bssid、robotId、session。ssid最大32字节,password允许空或8–63字节;bssid为小写十六进制冒号分隔单播MAC,robotId为12个小写十六进制字符,session为32个。绑定请求必须有session。使用主控实际产生的字段,不复制示例身份。
当前相机监听端口固定81,虽然主控载荷带streamPort字段,相机并没有据此改端口。 配网ACK表示已进入处理队列,保存、连接与归属验证结果要看cameraStatus或/status。
{"camera":{"op":"setProfile","profile":"qvga"}}
profile仅接受小写qvga或vga。响应成功表示接受切换,完成以framesize、profilePending及相机状态确认。错误包括invalid JSON、invalid camera request、unsupported request等;不要把错误当成自动重试动作的理由。
运动控制载荷
相机板作为上位机向NodeHexa发送REQUEST,JSON由主控运动入口解析。入门优先使用有边界的动作:
{"sequenceId":1001,"sequence":[{"movementMode":"forward","cycles":1,"speedOverride":0.25}],"append":false}
主控RESPONSE匹配UART sequence;status=success表示已接受。完成后EVENT载荷形如:
{"event":"sequenceComplete","sequenceId":1001}
UART sequence与业务sequenceId是两种编号:前者关联报文回执,后者关联动作完成。 不能用收到ACK代替等待动作完成。sequence数组允许1–5段,主控队列最大8段;append=false覆盖已有动作队列,不能与另一个遥控源同时争用。
停止请求:
{"stop":true}
连续基础控制中movementMode是bitmask:1待机、2前进、8后退、16左转、32右转;高级动作含cycles/steps/distance/angle等字段时建议使用字符串,避免数字索引与bitmask歧义。首次实验不要使用无限连续前进。
ACK丢失时不要盲目重发运动请求,当前实现不能假定有业务去重保障。可记录故障并发stop;即使stop发送失败,也禁止产生下一动作。拔断串口后上位机无法保证stop送达,因此实操用有限周期动作,并保留机器人实体断电手段。
低电量与链路状态
主控HELLO和HEARTBEAT响应包含低电量锁存字段;EVENT可能报告lowBattery。收到低电量时终止策略,不尝试关闭保护来继续动作。当前主控把外设在线状态与收到的v2帧关联,离线判定不是运动指令租约:不要假定UART断线会自动停止任意连续运动。
旧版主控还支持$开头、换行结尾的JSON文本,但新图传相机解析器只实现v2。本教程统一用v2,新代码不在同一链路上混发两种协议。