Files
trail-mate/docs/C6.md
T
2026-06-11 10:00:29 +08:00

33 KiB
Raw Blame History

2026-06-09 C6 Phase Override

This section supersedes the older staged plan below for the current development slice.

The current target is no longer "HostLink bring-up first, then decide whether to build the C6 firmware". The current target is:

1. Build the Trail-Mate ESP32-C6 companion firmware as the first full wireless
   facade firmware.
2. Flash the C6 externally through its UART ROM bootloader path.
3. Boot the ESP32-P4 application normally.
4. Verify that P4 and C6 complete HostLink over SDIO.
5. Verify BLE, ESP-NOW, Wi-Fi management, and diagnostics as wireless surfaces.

External first flash is the validation route for this slice. Runtime slave OTA and P4-forced recovery remain future capabilities and must not be treated as available product behavior in this phase.

Current full-function C6 firmware means:

Implemented on C6:
  - HostLink HELLO / PING / CONFIG_SET / diagnostics routing over SDIO.
  - BLE GAP/GATT facade for Meshtastic, MeshCore/NUS, and Trail-Mate private.
  - BLE pairing/passkey enforcement as wireless access control only; PIN policy
    still comes from P4 and C6 must not persist it as product identity.
  - ESP-NOW raw Team transport send/receive facade.
  - Wi-Fi management facade: scan, STA connect/disconnect/get IP, AP start/stop.
  - C6 diagnostics and service state reports.

Still owned by P4:
  - LoRa, MeshCore meaning, Meshtastic meaning, Team state, keys, NodeDB,
    messages, GPS/maps/UI/storage, and all product configuration authority.

Not claimed in this slice:
  - C6 runtime OTA.
  - P4 forcing C6 into ROM bootloader.
  - C6-side MeshCore/Meshtastic parsing.
  - Wi-Fi data server or general TCP/UDP business bridge.

The C6 partition table uses a 4MB flash assumption with a single 3MB factory app partition. This is intentional for the full wireless facade firmware. The first validation firmware is flashed externally, so no OTA partitions are reserved in this phase.

The external flash path requires a 3.3V USB-UART connection to C6 UART0 plus the board's C6 BOOT/EN controls. A Qwiic-shaped connector that exposes 3V3, C6_U0TXD, C6_U0RXD, and GND is not a normal I2C Qwiic cable; it needs a USB-UART adapter wired for UART, with GPIO9/BOOT held low during reset to enter the ESP32-C6 ROM bootloader.

Trail-Mate T-Display-P4 / ESP32-C6 Companion 固件开发方案

0. 总目标

为 Trail-Mate 的 T-Display-P4 目标开发一套自研 ESP32-C6 companion 固件,使 ESP32-C6 不再只是板厂无线固件,而是 Trail-Mate 的近场无线协处理器。

最终系统分工如下:

ESP32-P4:
  - Trail-Mate 主业务唯一真相源
  - UI / 地图 / GPS / 存储 / LoRa / MeshCore / Meshtastic / Team Mode
  - 管理所有业务配置、节点状态、频道、队伍、消息、密钥
  - 通过 HostLink 控制 ESP32-C6

ESP32-C6:
  - Wi-Fi / BLE / ESP-NOW / 近场无线入口
  - BLE GATT Server
  - ESP-NOW discovery / pairing transport
  - P4-C6 数据桥
  - 不拥有 Trail-Mate 主业务状态

核心原则:

P4 owns meaning.
C6 owns wireless surfaces.

也就是:

P4 负责“这条数据是什么意思”
C6 负责“这条数据通过 BLE / ESP-NOW / Wi-Fi 如何进出设备”

不得把 MeshCore、Meshtastic、Team Mode 的核心状态机搬到 C6。


1. 明确不做什么

C6 固件不得实现以下内容:

1. 不管理 LoRa。
2. 不驱动 SX1262。
3. 不管理地图。
4. 不管理 GPS。
5. 不管理 NodeDB。
6. 不管理 MeshCore routing。
7. 不管理 Meshtastic routing。
8. 不管理 Channel / PSK / LoRa region。
9. 不管理 Team Mode 的最终队伍状态。
10. 不保存 Trail-Mate 主业务密钥。
11. 不作为第二套 Trail-Mate 业务系统。

C6 可以保存的状态只包括:

1. BLE bond 信息。
2. C6 自身 NVS 配置,例如日志等级、调试开关。
3. 最近一次 P4 下发的无线 facade runtime config。
4. ESP-NOW 运行时 peer cache。
5. HostLink 会话状态。

C6 不允许保存的状态:

1. Meshtastic channel key。
2. MeshCore private key。
3. Team key。
4. Trail-Mate 用户身份主密钥。
5. NodeDB。
6. 消息历史。
7. 地图缓存。
8. LoRa 配置的权威副本。

如果 BLE 或 ESP-NOW 需要使用某些临时密钥,必须由 P4 下发,C6 只在 RAM 中保存;除非 P4 明确发送 ALLOW_PERSIST 标志,否则 C6 不得写入 NVS。


2. 工程目录建议

在 Trail-Mate 仓库内新增 C6 companion 工程,而不是新建完全独立仓库。原因是 P4-C6 协议必须和 P4 固件同步演进。

推荐目录:

trail-mate/
  firmware/
    c6_companion/
      CMakeLists.txt
      sdkconfig.defaults
      partitions.csv
      main/
        app_main.c
        tm_c6_config.h
        tm_c6_tasks.c
      components/
        tm_hostlink/
          include/tm_hostlink.h
          tm_hostlink.c
          tm_hostlink_frame.c
          tm_hostlink_crc.c
          tm_hostlink_sdio.c
          tm_hostlink_uart_debug.c
        tm_ble/
          include/tm_ble.h
          tm_ble.c
          tm_ble_gap.c
          tm_ble_gatt.c
          tm_ble_profiles.c
          tm_ble_meshtastic.c
          tm_ble_meshcore.c
          tm_ble_trailmate.c
        tm_espnow/
          include/tm_espnow.h
          tm_espnow.c
          tm_espnow_discovery.c
          tm_espnow_pairing.c
        tm_wifi/
          include/tm_wifi.h
          tm_wifi.c
        tm_diag/
          include/tm_diag.h
          tm_diag.c
        tm_proto/
          include/tm_c6_proto.h
          tm_c6_proto.c
  src/
    idf/
      wireless_companion/
        WirelessCompanion.hpp
        C6Companion.hpp
        C6Companion.cpp
        C6HostLink.hpp
        C6HostLink.cpp
        C6Frame.hpp
        C6Frame.cpp
        C6BleBridge.hpp
        C6BleBridge.cpp
        C6EspNowBridge.hpp
        C6EspNowBridge.cpp
        C6WifiBridge.hpp
        C6WifiBridge.cpp

如果现有工程中没有 firmware/ 目录,可以使用:

companion/esp32c6/

但无论使用哪个路径,P4 和 C6 必须共享同一份协议头文件生成源,避免协议漂移。

推荐共享协议源:

shared/c6_link/tm_c6_protocol.yaml
shared/c6_link/generated/c/tm_c6_protocol.h
shared/c6_link/generated/cpp/TmC6Protocol.hpp
tools/gen_c6_protocol.py

协议头文件不要手写两份。


3. 构建系统

C6 companion 使用 ESP-IDF,不使用 Arduino。

新增构建命令:

idf.py -B build.c6_companion -C firmware/c6_companion set-target esp32c6
idf.py -B build.c6_companion -C firmware/c6_companion build
idf.py -B build.c6_companion -C firmware/c6_companion -p COMx flash
idf.py -B build.c6_companion -C firmware/c6_companion -p COMx monitor

Windows PowerShell 推荐增加脚本:

tools/vscode/run_c6_task.ps1

参数:

-Action build | flash | monitor | erase | menuconfig
-Port COMx

不要把 C6 flash 动作混进 P4 flash 动作中。P4 和 C6 必须可以分别构建、分别刷写、分别监控。

新增 VS Code task

C6 Companion: Reconfigure
C6 Companion: Build
C6 Companion: Flash
C6 Companion: Monitor

4. C6 固件总体模块

C6 固件由五个模块组成:

tm_hostlink:
  P4-C6 通信层

tm_ble:
  BLE GAP / GATT / profile facade

tm_espnow:
  ESP-NOW 发现、配对、短消息入口

tm_wifi:
  Wi-Fi station / AP / provisioning / diagnostic

tm_diag:
  日志、版本、运行状态、heap、任务状态、错误码

主任务拓扑:

app_main
  ├─ init_nvs()
  ├─ init_event_loop()
  ├─ init_hostlink()
  ├─ wait_for_p4_hello()
  ├─ apply_runtime_config_from_p4()
  ├─ start_ble_if_enabled()
  ├─ start_espnow_if_enabled()
  ├─ start_wifi_if_enabled()
  └─ start_diag_task()

所有 BLE / ESP-NOW / Wi-Fi 的 enable 状态默认关闭。C6 启动后必须等待 P4 下发配置。C6 不得在没有 P4 配置的情况下主动开启 Trail-Mate 业务广播。

唯一例外是调试模式:

CONFIG_TM_C6_STANDALONE_DEBUG=y

该模式只允许用于开发板 bring-up,不允许作为 release 默认配置。


5.1 传输层

正式目标传输层:

SDIO

原因:

T-Display-P4 板级资料中 P4-C6 通信协议为 SDIO。

开发期间可以保留 UART debug transport,但必须满足以下条件:

1. UART debug transport 只用于本地调试。
2. 默认 release 不启用。
3. 不能把 UART debug transport 写死成正式协议。
4. 所有上层模块只能依赖 tm_hostlink,不得直接依赖 SDIO 或 UART。

HostLink 传输接口:

typedef struct {
    esp_err_t (*init)(void);
    esp_err_t (*send)(const uint8_t *data, size_t len, uint32_t timeout_ms);
    esp_err_t (*recv)(uint8_t *data, size_t max_len, size_t *out_len, uint32_t timeout_ms);
    esp_err_t (*reset)(void);
} tm_hostlink_transport_t;

P4 侧也必须有等价接口:

class C6Transport {
public:
    virtual bool begin() = 0;
    virtual bool send(const uint8_t* data, size_t len, uint32_t timeoutMs) = 0;
    virtual bool recv(uint8_t* data, size_t maxLen, size_t& outLen, uint32_t timeoutMs) = 0;
    virtual void reset() = 0;
    virtual ~C6Transport() = default;
};

5.2 帧格式

所有 P4-C6 消息都使用统一二进制帧。

帧头固定小端序:

#define TM_C6_MAGIC 0x36434D54u  // ASCII little-endian: "TMC6"
#define TM_C6_PROTO_VERSION 1

typedef struct __attribute__((packed)) {
    uint32_t magic;
    uint8_t  version;
    uint8_t  header_len;
    uint8_t  frame_type;
    uint8_t  channel;
    uint16_t flags;
    uint16_t seq;
    uint16_t ack;
    uint16_t payload_len;
    uint32_t crc32;
} tm_c6_frame_header_t;

字段定义:

magic:
  固定 0x36434D54

version:
  初始为 1

header_len:
  sizeof(tm_c6_frame_header_t)

frame_type:
  见 5.3

channel:
  见 5.4

flags:
  bit0 = ACK_REQUIRED
  bit1 = IS_ACK
  bit2 = IS_FRAGMENT
  bit3 = FRAGMENT_START
  bit4 = FRAGMENT_END
  bit5 = ERROR
  bit6 = RESERVED
  bit7 = RESERVED

seq:
  发送方递增序号,0 可以跳过,从 1 开始

ack:
  当 IS_ACK=1 时表示被确认的 seq

payload_len:
  payload 字节数,不包括 header

crc32:
  对 header 中 crc32 字段置 0 后的 header + payload 计算 CRC32

最大 payload

TM_C6_MAX_PAYLOAD = 1024

如果 BLE MTU、SDIO buffer 或内存条件要求更小,可以降低,但 P4 和 C6 必须通过 HELLO 协商。

禁止发送超过协商 max_payload 的单帧。

5.3 frame_type

enum tm_c6_frame_type {
    TM_C6_FRAME_HELLO              = 0x01,
    TM_C6_FRAME_HELLO_ACK          = 0x02,
    TM_C6_FRAME_PING               = 0x03,
    TM_C6_FRAME_PONG               = 0x04,
    TM_C6_FRAME_ACK                = 0x05,
    TM_C6_FRAME_ERROR              = 0x06,

    TM_C6_FRAME_CONFIG_SET         = 0x10,
    TM_C6_FRAME_CONFIG_GET         = 0x11,
    TM_C6_FRAME_CONFIG_REPORT      = 0x12,

    TM_C6_FRAME_BLE_UPLINK         = 0x20,
    TM_C6_FRAME_BLE_DOWNLINK       = 0x21,
    TM_C6_FRAME_BLE_EVENT          = 0x22,
    TM_C6_FRAME_BLE_CONTROL        = 0x23,

    TM_C6_FRAME_ESPNOW_UPLINK      = 0x30,
    TM_C6_FRAME_ESPNOW_DOWNLINK    = 0x31,
    TM_C6_FRAME_ESPNOW_EVENT       = 0x32,
    TM_C6_FRAME_ESPNOW_CONTROL     = 0x33,

    TM_C6_FRAME_WIFI_CONTROL       = 0x40,
    TM_C6_FRAME_WIFI_EVENT         = 0x41,
    TM_C6_FRAME_WIFI_DATA          = 0x42,

    TM_C6_FRAME_DIAG_REQUEST       = 0x50,
    TM_C6_FRAME_DIAG_REPORT        = 0x51,
    TM_C6_FRAME_LOG                = 0x52
};

5.4 channel

enum tm_c6_channel {
    TM_C6_CH_CONTROL               = 0x00,
    TM_C6_CH_BLE_MESHTASTIC        = 0x01,
    TM_C6_CH_BLE_MESHCORE          = 0x02,
    TM_C6_CH_BLE_TRAILMATE         = 0x03,
    TM_C6_CH_ESPNOW_TEAM           = 0x04,
    TM_C6_CH_WIFI_MGMT             = 0x05,
    TM_C6_CH_WIFI_DATA             = 0x06,
    TM_C6_CH_DIAG                  = 0x07
};

不得使用一个“通用 BLE channel”承载所有协议。必须保留 channel id,让 P4 可以明确区分数据来自哪个无线入口。

5.5 启动握手

C6 启动后进入:

WAIT_P4_HELLO

P4 启动后执行:

1. reset C6, if board reset GPIO exists and is configured
2. init SDIO transport
3. send HELLO
4. wait HELLO_ACK
5. send CONFIG_SET
6. wait CONFIG_REPORT
7. start services

HELLO payload

typedef struct __attribute__((packed)) {
    uint16_t proto_version_min;
    uint16_t proto_version_max;
    uint32_t p4_firmware_version;
    uint32_t requested_features;
    uint16_t preferred_mtu;
    uint16_t max_payload;
} tm_c6_hello_t;

HELLO_ACK payload

typedef struct __attribute__((packed)) {
    uint16_t selected_proto_version;
    uint32_t c6_firmware_version;
    uint32_t supported_features;
    uint16_t selected_mtu;
    uint16_t max_payload;
    uint32_t c6_free_heap;
} tm_c6_hello_ack_t;

feature bits

#define TM_C6_FEATURE_BLE_MESHTASTIC  (1u << 0)
#define TM_C6_FEATURE_BLE_MESHCORE    (1u << 1)
#define TM_C6_FEATURE_BLE_TRAILMATE   (1u << 2)
#define TM_C6_FEATURE_ESPNOW_TEAM     (1u << 3)
#define TM_C6_FEATURE_WIFI_STA        (1u << 4)
#define TM_C6_FEATURE_WIFI_AP         (1u << 5)
#define TM_C6_FEATURE_DIAG_LOG        (1u << 6)

如果 P4 请求的 feature C6 不支持,C6 不得失败启动。C6 应返回 supported_featuresP4 根据 capability 降级 UI 和业务入口。


6. BLE 设计

6.1 BLE 总原则

C6 负责 BLE GAP / GATTP4 负责 payload 语义。

C6 要实现三个 BLE facade

1. Meshtastic-compatible BLE facade
2. MeshCore-compatible BLE facade
3. Trail-Mate private BLE facade

三者必须独立 enable / disable。

不得把三个协议混进同一个 GATT characteristic。

6.2 BLE profile 配置

P4 下发:

typedef struct __attribute__((packed)) {
    uint8_t ble_enabled;
    uint8_t meshtastic_enabled;
    uint8_t meshcore_enabled;
    uint8_t trailmate_enabled;
    uint8_t pairing_mode;
    uint8_t fixed_pin_enabled;
    char    fixed_pin[8];
    char    device_name[32];
    uint16_t preferred_mtu;
} tm_c6_ble_config_t;

pairing_mode

enum tm_c6_pairing_mode {
    TM_C6_PAIRING_DISABLED = 0,
    TM_C6_PAIRING_RANDOM_PIN = 1,
    TM_C6_PAIRING_FIXED_PIN = 2,
    TM_C6_PAIRING_NO_PIN_DEBUG_ONLY = 3
};

release 固件不得默认使用 TM_C6_PAIRING_NO_PIN_DEBUG_ONLY

6.3 Meshtastic BLE facade

C6 只做 transport,不解析 Meshtastic protobuf。

行为:

Phone writes ToRadio bytes to Meshtastic GATT characteristic.
C6 wraps bytes into TM_C6_FRAME_BLE_UPLINK with channel TM_C6_CH_BLE_MESHTASTIC.
P4 receives bytes and passes them into existing Meshtastic Client API input path.
P4 produces FromRadio bytes.
P4 sends TM_C6_FRAME_BLE_DOWNLINK with channel TM_C6_CH_BLE_MESHTASTIC.
C6 notifies phone using Meshtastic GATT notify characteristic.

C6 禁止解析:

MeshPacket
NodeInfo
Channel
Config
Position
User

C6 只允许知道:

which connection
which characteristic
raw bytes
fragmentation state
notification queue state

6.4 MeshCore BLE facade

C6 只做 MeshCore companion transport bridge,不实现 MeshCore mesh 逻辑。

行为:

MeshCore App writes bytes to MeshCore GATT characteristic.
C6 wraps bytes into TM_C6_FRAME_BLE_UPLINK with channel TM_C6_CH_BLE_MESHCORE.
P4 routes bytes to MeshCore adapter.
P4 returns MeshCore response bytes.
C6 notifies MeshCore App.

如果当前代码库中还没有 MeshCore BLE UUID / characteristic 定义,则实现时必须:

1. 新增 tm_ble_meshcore.c。
2. 提供 compile-time disabled stub。
3. 在 tm_c6_ble_config_t 中保留 meshcore_enabled。
4. 当 meshcore_enabled=1 但 UUID 未配置时,C6 返回 TM_C6_ERROR_PROFILE_NOT_CONFIGURED。
5. 不得随意发明 MeshCore UUID。

6.5 Trail-Mate private BLE facade

Trail-Mate private BLE 用于 Trail-Mate 自己的 companion app、调试工具、配置工具。

推荐用途:

1. 查看设备状态。
2. 查看 GPS 状态。
3. 查看 LoRa 状态。
4. 查看 C6 状态。
5. 修改非敏感 UI 配置。
6. 触发 Team Mode pairing。
7. 读取诊断日志。

Trail-Mate private BLE 不得绕过 P4 直接修改核心业务状态。所有写入必须转发给 P4,由 P4 决定是否接受。


7. ESP-NOW / Team Mode 设计

Team Mode 的业务核心在 P4。C6 只负责 ESP-NOW 发现和短消息传输。

7.1 ESP-NOW 数据流

Nearby device sends ESP-NOW discovery packet.
C6 receives packet.
C6 wraps packet into TM_C6_FRAME_ESPNOW_UPLINK with channel TM_C6_CH_ESPNOW_TEAM.
P4 evaluates Team Mode business rule.
P4 sends response through TM_C6_FRAME_ESPNOW_DOWNLINK.
C6 sends ESP-NOW response.

7.2 C6 ESP-NOW 不得做的事情

C6 不得:

1. 决定是否加入队伍。
2. 决定谁是 leader。
3. 生成最终 team key。
4. 保存 team key。
5. 修改队伍成员列表。
6. 触发 LoRa 消息。

C6 可以:

1. 扫描附近 Trail-Mate 设备。
2. 广播 P4 下发的 discovery payload。
3. 接收 ESP-NOW payload。
4. 转发到 P4。
5. 根据 P4 指令发送 ESP-NOW response。
6. 维护短期 peer MAC cache。

7.3 ESP-NOW payload

C6 不理解 payload 内部格式。C6 只处理:

typedef struct __attribute__((packed)) {
    uint8_t peer_mac[6];
    uint8_t rssi_valid;
    int8_t  rssi;
    uint8_t payload_len;
    uint8_t payload[240];
} tm_c6_espnow_packet_t;

如果 ESP-NOW payload 超过 240 字节,必须拒绝发送并向 P4 返回 TM_C6_ERROR_PAYLOAD_TOO_LARGE


8. Wi-Fi 设计

Wi-Fi 分阶段实现。

8.1 Phase A:不实现 Wi-Fi,仅保留接口

第一版 C6 companion 以 BLE / ESP-NOW 为核心目标。Wi-Fi 可以先不实现,但必须在协议里保留:

TM_C6_FEATURE_WIFI_STA
TM_C6_FEATURE_WIFI_AP
TM_C6_CH_WIFI_MGMT
TM_C6_CH_WIFI_DATA
TM_C6_FRAME_WIFI_CONTROL
TM_C6_FRAME_WIFI_EVENT
TM_C6_FRAME_WIFI_DATA

C6 如果不支持 Wi-Fi,则 HELLO_ACK 中不设置对应 feature bit。

P4 UI 必须显示:

C6 Wi-Fi: unsupported by current companion firmware

而不是显示:

Wi-Fi hardware missing

8.2 Phase BWi-Fi 管理能力

实现:

1. scan
2. connect
3. disconnect
4. get_ip
5. softAP start/stop
6. Wi-Fi event report

P4 仍是配置源。C6 不保存 Wi-Fi SSID/password,除非 P4 明确要求。

8.3 Phase CWi-Fi 数据能力

可选实现 TCP/UDP bridge 或 HTTP diagnostic server。

注意:

Wi-Fi 数据面不是第一阶段目标。
不要因为 Wi-Fi 数据面拖慢 BLE / ESP-NOW 的落地。

9. C6 内存和资源约束

C6 无 PSRAM。必须按小内存设备设计。

硬性限制:

1. 不分配大 buffer。
2. 单个 HostLink payload 默认不超过 1024 字节。
3. BLE notify queue 默认不超过 8 个 packet。
4. ESP-NOW rx queue 默认不超过 8 个 packet。
5. 日志环形缓冲默认不超过 8KB。
6. 禁止在 C6 上保存消息历史。
7. 禁止在 C6 上保存 NodeDB。
8. 禁止在 C6 上保存地图、轨迹或文件。

推荐配置:

#define TM_C6_HOSTLINK_RX_QUEUE_LEN      8
#define TM_C6_HOSTLINK_TX_QUEUE_LEN      8
#define TM_C6_BLE_NOTIFY_QUEUE_LEN       8
#define TM_C6_ESPNOW_RX_QUEUE_LEN        8
#define TM_C6_LOG_RING_SIZE              8192
#define TM_C6_MAX_PAYLOAD                1024

如果 heap 低于阈值:

free_heap < 32KB

C6 必须上报 warning event

TM_C6_EVENT_LOW_MEMORY

如果 heap 低于:

free_heap < 16KB

C6 必须停止接受新的 BLE write / ESP-NOW rx queue 入队,并返回 busy/error。


10. P4 固件必须调整的内容

按照这个方案,P4 固件必须调整。原因是 C6 不再是透明板厂无线固件,而是 Trail-Mate 的 wireless companion。P4 必须新增一个明确的 companion 抽象层。

10.1 新增 WirelessCompanion 抽象

新增接口:

class WirelessCompanion {
public:
    virtual bool begin() = 0;
    virtual bool isPresent() const = 0;
    virtual uint32_t capabilities() const = 0;

    virtual bool configureBle(const BleCompanionConfig& config) = 0;
    virtual bool configureEspNow(const EspNowCompanionConfig& config) = 0;
    virtual bool configureWifi(const WifiCompanionConfig& config) = 0;

    virtual bool sendBleDownlink(BleProfile profile, const uint8_t* data, size_t len) = 0;
    virtual bool sendEspNow(const EspNowPacket& packet) = 0;

    virtual void poll() = 0;

    virtual ~WirelessCompanion() = default;
};

新增实现:

class C6Companion final : public WirelessCompanion {
public:
    bool begin() override;
    bool isPresent() const override;
    uint32_t capabilities() const override;

    bool configureBle(const BleCompanionConfig& config) override;
    bool configureEspNow(const EspNowCompanionConfig& config) override;
    bool configureWifi(const WifiCompanionConfig& config) override;

    bool sendBleDownlink(BleProfile profile, const uint8_t* data, size_t len) override;
    bool sendEspNow(const EspNowPacket& packet) override;

    void poll() override;

private:
    C6HostLink link_;
    uint32_t caps_ = 0;
    bool present_ = false;
};

10.2 P4 不得直接假设本机有 BLE

T-Display-P4 目标中,BLE 不是 P4 本机 BLE,而是 C6 BLE facade。

因此 P4 侧代码必须区分:

LocalBleAdapter:
  用于 ESP32-S3 / nRF52 等本机有 BLE 的设备

C6BleAdapter:
  用于 T-Display-P4BLE 在 C6 上

新增枚举:

enum class BleBackend {
    None,
    Local,
    C6Companion
};

T-Display-P4 target 设置:

BleBackend::C6Companion

普通 ESP32 / nRF52 目标保持原状。

10.3 P4 新增 BLE profile router

P4 必须根据 HostLink channel 把 C6 上来的 BLE 数据路由到不同业务模块:

void onC6BleUplink(BleProfile profile, const uint8_t* data, size_t len) {
    switch (profile) {
        case BleProfile::Meshtastic:
            meshtasticClientApi.handleToRadio(data, len);
            break;

        case BleProfile::MeshCore:
            meshcoreCompanion.handleUplink(data, len);
            break;

        case BleProfile::TrailMate:
            trailMatePrivateApi.handleBleRequest(data, len);
            break;
    }
}

P4 业务模块产生响应时:

wirelessCompanion.sendBleDownlink(BleProfile::Meshtastic, data, len);
wirelessCompanion.sendBleDownlink(BleProfile::MeshCore, data, len);
wirelessCompanion.sendBleDownlink(BleProfile::TrailMate, data, len);

10.4 P4 新增 ESP-NOW Team Mode bridge

P4 新增:

class TeamModeNearFieldBridge {
public:
    void onEspNowDiscovery(const EspNowPacket& packet);
    void sendDiscovery(const TeamDiscoveryPayload& payload);
    void sendPairingResponse(const TeamPairingResponse& response);
};

C6 收到 ESP-NOW 后只转发,P4 负责 Team Mode 状态机。

现有 Team Mode 如果直接假设 ESP-NOW 在当前 MCU 上运行,需要改成:

TeamModeCore
  不依赖 ESP-NOW API

TeamModeTransport
  抽象接口

LocalEspNowTransport
  用于本机 Wi-Fi/BLE MCU

C6EspNowTransport
  用于 T-Display-P4

接口:

class TeamModeTransport {
public:
    virtual bool startDiscovery(const TeamDiscoveryConfig& config) = 0;
    virtual bool stopDiscovery() = 0;
    virtual bool sendToPeer(const uint8_t mac[6], const uint8_t* data, size_t len) = 0;
    virtual ~TeamModeTransport() = default;
};

10.5 P4 新增 C6 状态 UI

T-Display-P4 的设置页 / diagnostics 页需要新增:

C6 Companion
  - present / missing
  - firmware version
  - protocol version
  - free heap
  - supported features
  - BLE enabled
  - Meshtastic BLE enabled
  - MeshCore BLE enabled
  - Trail-Mate BLE enabled
  - ESP-NOW Team enabled
  - Wi-Fi supported / unsupported
  - last error

如果 C6 不存在或协议不兼容,UI 必须显示明确错误:

C6 companion not detected
C6 protocol mismatch
C6 firmware does not support requested BLE profile
C6 firmware does not support ESP-NOW Team Mode

不要静默隐藏。

10.6 P4 新增配置源

P4 是权威配置源。新增配置结构:

struct C6CompanionSettings {
    bool enabled = true;

    struct {
        bool enabled = true;
        bool meshtasticEnabled = true;
        bool meshcoreEnabled = true;
        bool trailmateEnabled = true;
        PairingMode pairingMode = PairingMode::FixedPin;
        char fixedPin[8] = "123456";
        char deviceName[32] = "TrailMate";
    } ble;

    struct {
        bool enabled = false;
        bool teamDiscoveryEnabled = false;
    } espnow;

    struct {
        bool enabled = false;
        bool staEnabled = false;
        bool apEnabled = false;
    } wifi;
};

T-Display-P4 启动时:

C6CompanionSettings settings = loadFromP4Storage();
wirelessCompanion.begin();
wirelessCompanion.configureBle(settings.ble);
wirelessCompanion.configureEspNow(settings.espnow);
wirelessCompanion.configureWifi(settings.wifi);

C6 不从自己的 NVS 读取业务默认值。


11. 错误码

统一错误码:

enum tm_c6_error_code {
    TM_C6_OK = 0,
    TM_C6_ERROR_BAD_MAGIC = 1,
    TM_C6_ERROR_BAD_VERSION = 2,
    TM_C6_ERROR_BAD_CRC = 3,
    TM_C6_ERROR_PAYLOAD_TOO_LARGE = 4,
    TM_C6_ERROR_UNSUPPORTED_FRAME = 5,
    TM_C6_ERROR_UNSUPPORTED_CHANNEL = 6,
    TM_C6_ERROR_UNSUPPORTED_FEATURE = 7,
    TM_C6_ERROR_PROFILE_NOT_CONFIGURED = 8,
    TM_C6_ERROR_NOT_CONNECTED = 9,
    TM_C6_ERROR_QUEUE_FULL = 10,
    TM_C6_ERROR_LOW_MEMORY = 11,
    TM_C6_ERROR_TIMEOUT = 12,
    TM_C6_ERROR_INTERNAL = 13
};

C6 返回 ERROR frame 时 payload

typedef struct __attribute__((packed)) {
    uint16_t error_code;
    uint16_t related_seq;
    uint8_t  related_frame_type;
    uint8_t  related_channel;
    char     message[64];
} tm_c6_error_t;

P4 必须把 ERROR frame 记录到日志,并更新 C6 diagnostics UI。


12. 开发阶段

Phase 0:协议和空实现

目标:

1. 添加 shared protocol 定义。
2. 添加 P4 C6Companion stub。
3. 添加 C6 companion ESP-IDF 工程。
4. C6 可以编译、刷写、启动。
5. P4 可以在没有 C6 时正常运行。
6. P4 diagnostics 显示 C6 missing。

验收:

1. P4 build 通过。
2. C6 build 通过。
3. 不影响现有 tlora_pager / tdeck / gat562 构建。
4. T-Display-P4 在无 C6 companion 时不崩溃。

目标:

1. P4 和 C6 完成 HELLO / HELLO_ACK。
2. P4 可以 PINGC6 返回 PONG。
3. C6 上报 firmware version / free heap。
4. P4 diagnostics 显示 C6 present。

Implementation note for the current Trail-Mate slice:

P4 transport:
  ESP-IDF esp_serial_slave_link over SDMMC slot 1.

C6 transport:
  ESP-IDF esp_driver_sdio slave packet mode.

Present condition:
  P4 sends HELLO, C6 returns HELLO_ACK, P4 sends PING, C6 returns PONG.

Failure condition:
  P4 must continue booting and report missing/error with detail. It must not
  mark C6 present from template logs, board capability, or factory firmware
  assumptions.

OTA:
  C6 image transfer is not part of this Phase 1 slice. Runtime slave OTA remains
  gated on a present, versioned transport and a separately implemented image
  write/verify/end sequence.

验收:

1. 连续 ping 1000 次无 CRC 错误。
2. C6 reset 后 P4 能重新握手。
3. P4 reset 后 C6 能重新进入 WAIT_P4_HELLO。
4. 协议版本不匹配时显示明确错误。

Phase 2Trail-Mate private BLE

目标:

1. C6 开启 Trail-Mate private BLE service。
2. 手机 BLE 调试工具可以连接。
3. 手机写入 private characteristic。
4. P4 收到 BLE_UPLINK。
5. P4 返回 BLE_DOWNLINK。
6. 手机收到 notify。

验收:

1. 20 次连接/断开无死锁。
2. 1KB payload 可以分片传输。
3. notify queue 满时返回 backpressure,不丢失状态。
4. P4 UI 可显示 BLE connected / disconnected。

Phase 3Meshtastic BLE facade

目标:

1. C6 暴露 Meshtastic-compatible BLE profile。
2. 手机 Meshtastic App 能发现设备。
3. App 写入的 ToRadio bytes 能到 P4。
4. P4 生成的 FromRadio bytes 能通知回 App。

验收:

1. Meshtastic App 能连接。
2. 基本配置读取路径可通。
3. 文本消息发送路径可通。
4. 断线重连后不需要重启 P4 或 C6。

注意:

如果 Meshtastic App 需要严格的 service UUID / characteristic UUID / advertisement 格式,必须从现有 Meshtastic 实现或项目内已有代码读取,不得猜测。

Phase 4MeshCore BLE facade

目标:

1. C6 暴露 MeshCore companion profile。
2. MeshCore app 或调试工具能连接。
3. MeshCore uplink/downlink raw bytes 能通过 P4。

验收:

1. MeshCore BLE profile 未配置 UUID 时,编译可通过但运行时返回 PROFILE_NOT_CONFIGURED。
2. 配置 UUID 后,MeshCore raw transport 可通。
3. 不影响 Meshtastic BLE profile。

Phase 5ESP-NOW Team Mode

目标:

1. C6 可以发送 ESP-NOW discovery。
2. C6 可以接收 ESP-NOW discovery。
3. P4 Team Mode 可以通过 C6EspNowTransport 处理近场发现。
4. C6 不保存 team state。

验收:

1. 两台设备可以互相发现。
2. P4 diagnostics 能看到 peer MAC / RSSI。
3. Team Mode 状态变化只发生在 P4。
4. C6 reset 后 Team Mode 状态不会在 C6 恢复,必须由 P4 重新下发。

Phase 6Wi-Fi 恢复

目标:

1. C6 支持 Wi-Fi scan。
2. C6 支持 station connect。
3. C6 上报 IP。
4. P4 UI 可以显示 Wi-Fi 状态。

验收:

1. BLE 开启时 Wi-Fi scan 不导致 BLE 断连。
2. Wi-Fi 连接失败有明确错误码。
3. Wi-Fi unsupported 时 P4 正确降级。

13. 回归测试要求

每次修改 C6 protocol 必须执行:

1. P4 build: t_display_p4_tft
2. P4 build: t_display_p4_amoled
3. C6 build: esp32c6 companion
4. 至少一个现有 PlatformIO 主目标 build
5. HostLink frame encode/decode unit test
6. CRC error injection test
7. payload too large test
8. unsupported feature test

HostLink 单元测试至少覆盖:

1. 正常 frame encode/decode。
2. magic 错误。
3. version 错误。
4. crc 错误。
5. payload_len 超限。
6. ACK_REQUIRED / IS_ACK。
7. channel routing。
8. fragment start/end。

14. 日志规范

C6 日志 tag

C6_MAIN
C6_HOSTLINK
C6_BLE
C6_BLE_MT
C6_BLE_MC
C6_BLE_TM
C6_ESPNOW
C6_WIFI
C6_DIAG

P4 日志 tag

P4_C6
P4_C6_LINK
P4_C6_BLE
P4_C6_ESPNOW
P4_C6_WIFI
P4_C6_DIAG

所有 HostLink 错误必须打印:

seq
frame_type
channel
error_code
payload_len
free_heap if C6 side

禁止在日志中打印:

1. channel key
2. team key
3. private key
4. fixed PIN
5. Wi-Fi password

15. Release 策略

P4 和 C6 必须有独立版本号,但需要协议兼容声明。

例如:

Trail-Mate P4 firmware: 0.1.30
Trail-Mate C6 companion: 0.1.0
C6 protocol version: 1

P4 固件中维护兼容范围:

#define TM_C6_PROTO_MIN 1
#define TM_C6_PROTO_MAX 1

C6 固件中维护兼容范围:

#define TM_C6_PROTO_MIN 1
#define TM_C6_PROTO_MAX 1

如果协议不兼容:

P4 不得继续启用 BLE / ESP-NOW。
P4 UI 显示 C6 protocol mismatch。
C6 保持 WAIT_P4_HELLO 或 SAFE_MODE。

16. 最终验收标准

第一版可接受目标:

1. P4 和 C6 可以稳定握手。
2. P4 diagnostics 能显示 C6 状态。
3. Trail-Mate private BLE 可用。
4. Meshtastic BLE facade 可用或至少 raw path 可通。
5. MeshCore BLE facade 有明确 stub,不会误报可用。
6. ESP-NOW Team Mode transport 可跑最小发现流程。
7. C6 reset 不会拖死 P4。
8. P4 reset 后 C6 能重新连接。
9. 没有 C6 固件时,P4 仍可运行 LoRa / UI / GPS / 地图。
10. C6 不保存 Trail-Mate 主业务状态。

不作为第一版验收目标:

1. 完整 Wi-Fi 数据面。
2. Web server。
3. OTA。
4. 文件传输。
5. C6 上的复杂协议解析。
6. C6 上的消息历史。
7. C6 上的 MeshCore / Meshtastic 核心实现。

17. 给编码 AI 的强制要求

实现时必须遵守以下规则:

1. 不要把业务核心搬到 C6。
2. 不要让 C6 直接驱动 LoRa。
3. 不要让 C6 保存业务密钥。
4. 不要猜测 MeshCore UUID;没有来源就做 disabled stub。
5. 不要猜测 Meshtastic UUID;必须从项目已有实现或上游协议定义确认。
6. 不要让 T-Display-P4 的 P4 固件直接依赖本机 BLE。
7. T-Display-P4 的 BLE backend 必须是 C6Companion。
8. C6 所有无线服务必须由 P4 下发配置后才启动。
9. P4 没有检测到 C6 时必须降级运行,而不是崩溃。
10. 所有 P4-C6 数据都必须走 HostLink frame。
11. 所有 HostLink frame 必须校验 CRC。
12. 所有 HostLink channel 必须显式路由。
13. 所有错误必须可诊断。
14. 所有新增代码必须不破坏现有 tdeck / tlora_pager / gat562 构建。

总结

本方案不是把 ESP32-C6 做成第二个 Trail-Mate,而是把它做成 Trail-Mate 的无线边缘适配器。

最终边界:

P4:
  Trail-Mate domain core

C6:
  BLE / ESP-NOW / Wi-Fi wireless facade

HostLink:
  P4-C6 之间唯一可信数据通道

按照这个边界实现,Trail-Mate 可以同时支持 Meshtastic、MeshCore、Trail-Mate private companion、Team Mode,而不会把业务状态拆散到两颗 MCU 上。