跳转至

UART 层

UART 模块实现 ESP32(Link)和 RP2040(Controller)之间的双向二进制帧协议。用于双 MCU 设备:iDryer LINK、iHeater LINK。

Storage Link 不使用 UART 模块 — 它是没有第二个 MCU 的独立 ESP32-C3 设备。

单独 include:

#include <idryer_uart.h>

物理层

  • UART 8N1,115200 baud(默认),无硬件流控。
  • 最大帧 payload:200 字节。
  • 每帧 CRC-16/CCITT(poly 0x1021,init 0xFFFF)。

帧结构

byte 0  : SOF = 0xAA
byte 1  : version = 1
byte 2  : flags   (ACK required | is ACK | error | fragment | last fragment)
byte 3  : message kind (UartMsgKind)
byte 4  : sequence number (0..255, wrap)
byte 5  : payload length
payload : data (0..200 bytes)
crc16   : CRC, low byte + high byte

UartBridge

模块主类。逐字节处理传入流、构建帧、校验 CRC、管理 ACK/重试,并把帧分发给注册的 callback。

UartBridge bridge;
bridge.begin(&Serial1, 115200);

// Register handlers before begin()
bridge.setHelloHandler([&](const UartHelloPayload& p, const UartFrameHeader&) {
    cloud.setMcuSerial(p.mcuSerial);
    UartHelloAckPayload ack{};
    bridge.sendHelloAck(ack);
});

// in loop():
bridge.loop();

发送方法分为两组:

  • ESP32 → RP2040:sendHelloAcksendCommandsendProfileCommandsendHeartbeatsendClaimStatussendClaimCompletesendWsStatussendTelemetryAcksendCommandAcksendConfigAck
  • RP2040 → ESP32(或测试用):sendHellosendTelemetrysendStatussendWeightssendRfid

ACK/重试:带 UART_FLAG_ACK_REQ 标志的帧最多重试 3 次,超时 700 ms。如果没有收到 ACK,send* 返回 false

消息类型

Kind Code Direction Purpose
Hello 0x01 RP2040 → ESP32 启动通告;包含 MCU 序列号、设备类型、能力
HelloAck 0x02 ESP32 → RP2040 带 IP 地址和 SSID 的响应
Telemetry 0x10 RP2040 → ESP32 温度、湿度、加热功率
Weights 0x12 RP2040 → ESP32 称重读数
Status 0x13 RP2040 → ESP32 当前干燥模式、会话进度
Rfid 0x14 RP2040 → ESP32 RFID 事件(标签检测/移除)
Command 0x20 ESP32 → RP2040 来自后端的命令(start、stop、find...)
ConfigPush 0x30 ESP32 → RP2040 配置(简单或分块)
Heartbeat 0x40 ESP32 → RP2040 Uptime、RSSI、云端状态
Error 0x50 both 协议错误
ClaimStart..Complete 0x70–0x72 both Claim 生命周期
WsEnable..StatusRequest 0x73–0x76 both RP2040 上的 WebSocket 服务器控制

ConfigReceiver / ConfigSender

用于通过 UART 分片传输大 JSON 配置的辅助类(每片数据 ≤ 194 字节)。

// Receive (ESP32 ← RP2040)
ConfigReceiver rx;
bridge.setConfigChunkHandler([&rx, &mqtt](const UartConfigChunkPayload& p, uint8_t len, const UartFrameHeader& hdr) {
    if (rx.processFragment(p, len, hdr.flags) == ConfigFragResult::Complete) {
        mqtt.publishConfigRaw(rx.getJson(), rx.getLength());
        rx.reset();
    }
});

// Send (ESP32 → RP2040)
ConfigSender tx;
uint16_t tid = ConfigSender::generateTransferId();
tx.send(json, length, tid, [&](const UartConfigChunkPayload& p, uint8_t payloadLen, uint8_t flags) {
    return bridge.sendConfigPushChunk(p, payloadLen, flags);
});

与 CloudStateMachine 的集成

双 MCU 设备在 provisioning 前需要先拿到 RP2040 的序列号:

idryer::cloud::CloudConfig cfg;
cfg.waitForMcuSerial = true;
idryer::cloud::CloudStateMachine cloud(..., cfg);

bridge.setHelloHandler([&](const UartHelloPayload& p, const UartFrameHeader&) {
    cloud.setMcuSerial(p.mcuSerial);
    // ...
});

状态机会停留在 WaitingForMcuSerial,直到调用 setMcuSerial()