Files
trail-mate/docs/C6.md
T

35 KiB

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 firmware development plan

0. Overall goal

Develop a set of self-developed ESP32-C6 companion firmware for Trail-Mate's T-Display-P4 target, so that ESP32-C6 is no longer just a manufacturer's wireless firmware, but a Trail-Mate A near-field wireless coprocessor.

The final system division of labor is as follows:

ESP32-P4:
 - Trail-Mate main business sole source of truth
 - UI / Map / GPS / Storage / LoRa / MeshCore / Meshtastic / Team Mode
 - Manage all business configurations, node status, channels, teams, messages, keys
 - Control ESP32-C6 via HostLink

ESP32-C6:
 - Wi-Fi / BLE / ESP-NOW / Near Field Wireless Portal
  - BLE GATT Server
  - ESP-NOW discovery / pairing transport
 - P4-C6 Data Bridge
 - Do not own Trail-Mate Main business status

Core principles:

P4 owns meaning.
C6 owns wireless surfaces.

That is:

P4 is responsible for "what does this data mean"
C6 is responsible for "how this data enters and exits the device through BLE/ESP-NOW/Wi-Fi"

The core state machines of MeshCore, Meshtastic, and Team Mode must not be moved to C6.


1. Be clear about what not to do

C6 firmware must not implement the following:

1. Do not manage LoRa.
2. Does not drive SX1262.
3. Does not manage the map.
4. Does not manage GPS.
5. NodeDB is not managed.
6. Do not manage MeshCore routing.
7. Does not manage Meshtastic routing.
8. Does not manage Channel / PSK / LoRa region.
9. Do not manage the final team status of Team Mode.
10. Do not save the Trail-Mate master business key.
11. Not used as the second Trail-Mate business system.

The states that can be saved by C6 only include:

1. BLE bond information.
2. C6's own NVS configuration, such as log level and debugging switch.
3. The latest wireless facade runtime config issued by P4.
4. ESP-NOW runtime peer cache.
5. HostLink session status.

C6 does not allow saving status:

1. Meshtastic channel key.
2. MeshCore private key.
3. Team key.
4. Trail-Mate User Identity Master Key.
5. NodeDB.
6. Message history.
7. Map cache.
8. Authoritative copy of LoRa configuration.

If BLE or ESP-NOW needs to use some temporary keys, they must be issued by P4, and C6 is only saved in RAM; unless P4 explicitly sends the ALLOW_PERSIST flag, C6 must not be written to NVS.


2. Project directory suggestions

Add a new C6 companion project to the Trail-Mate repository instead of creating a completely independent repository. The reason is that the P4-C6 protocol must evolve simultaneously with the P4 firmware.

Recommended directory:

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

If there is no firmware/ directory in the existing project, you can use:

companion/esp32c6/

But no matter which path is used, P4 and C6 must share the same protocol header file generation source to avoid protocol drift.

It is recommended to share the protocol source:

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

Do not write two copies of the protocol header file by hand.


3. Build System

C6 companion uses ESP-IDF, not Arduino.

New build command:

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 recommends adding script:

tools/vscode/run_c6_task.ps1

Parameters:

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

Do not mix C6 flash action into P4 flash action. P4 and C6 must be able to be built separately, flashed separately, and monitored separately.

New VS Code task:

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

4. C6 firmware overall module

C6 firmware consists of five modules:

tm_hostlink:
 P4-C6 communication layer

tm_ble:
  BLE GAP / GATT / profile facade

tm_espnow:
 ESP-NOW discovery, pairing, short message entrance

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

tm_diag:
 Log, version, running status, heap, task status, error code

Main task topology:

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()

The enable state of all BLE / ESP-NOW / Wi-Fi is turned off by default. After C6 starts, it must wait for P4 to deliver the configuration. C6 shall not actively enable Trail-Mate service broadcast without P4 configuration.

The only exception is the debug mode:

CONFIG_TM_C6_STANDALONE_DEBUG=y

This mode is only allowed for development board bring-up and is not allowed as the default configuration for release.


5.1 Transport layer

Official target transport layer:

SDIO

Reason:

The P4-C6 communication protocol in the T-Display-P4 board-level information is SDIO.

The UART debug transport can be retained during development, but the following conditions must be met:

1. The UART debug transport is only used for local debugging.
2. Release is not enabled by default.
3. The UART debug transport cannot be hard-coded into a formal protocol.
4. All upper-layer modules can only rely on tm_hostlink and must not directly rely on SDIO or UART.

HostLink transport interface:

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;

The P4 side must also have an equivalent interface:

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 Frame format

All P4-C6 messages use unified binary frames.

Frame header fixed little endian:

#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;

Field definition:

magic:
 Fixed 0x36434D54

version:
 Initially 1

header_len:
  sizeof(tm_c6_frame_header_t)

frame_type:
 See 5.3

channel:
 See 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:
 The sender increments the sequence number, 0 can be skipped, starting from 1

ack:
 When IS_ACK=1, it means the number of confirmed seq

payload_len:
 payload bytes, excluding header

crc32:
 Calculate the maximum CRC32 for the header + payload after the crc32 field in the header is set to 0

payload:

TM_C6_MAX_PAYLOAD = 1024

If the BLE MTU, SDIO buffer or memory conditions require smaller, it can be reduced, but P4 and C6 must be negotiated through HELLO.

Do not send a single frame exceeding the negotiated 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
};

One "universal BLE channel" must not be used to carry all protocols. The channel id must be preserved so that P4 can clearly distinguish which wireless portal the data came from.

5.5 Startup handshake

Enter after C6 starts:

WAIT_P4_HELLO

Execute after P4 starts:

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)

If the feature requested by P4 is not supported by C6, C6 must not fail to start. C6 should return supported_features, and P4 downgrades the UI and business entrance based on capability.


6. BLE design

6.1 General principles of BLE

C6 is responsible for BLE GAP / GATT; P4 is responsible for payload semantics.

C6 needs to implement three BLE facades:

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

The three must be enabled/disabled independently.

Do not mix three protocols into the same GATT characteristic.

6.2 BLE profile configuration

P4 release:

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 firmware must not use TM_C6_PAIRING_NO_PIN_DEBUG_ONLY by default.

6.3 Meshtastic BLE facade

C6 only does transport and does not parse Meshtastic protobuf.

Behavior:

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 prohibits parsing:

MeshPacket
NodeInfo
Channel
Config
Position
User

C6 is only allowed to know:

which connection
which characteristic
raw bytes
fragmentation state
notification queue state

6.4 MeshCore BLE facade

C6 only does MeshCore companion transport bridge and does not implement MeshCore mesh logic.

Behavior:

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.

If there is no MeshCore BLE UUID / characteristic definition in the current code base, the implementation must:

1. Add tm_ble_meshcore.c.
2. Provide compile-time disabled stub.
3. Keep meshcore_enabled in tm_c6_ble_config_t.
4. When meshcore_enabled=1 but UUID is not configured, C6 returns TM_C6_ERROR_PROFILE_NOT_CONFIGURED.
5. MeshCore UUIDs are not allowed to be invented at will.

6.5 Trail-Mate private BLE facade

Trail-Mate private BLE is used in Trail-Mate's own companion app, debugging tools, and configuration tools.

Recommended use:

1. Check the device status.
2. Check GPS status.
3. Check LoRa status.
4. Check C6 status.
5. Modify non-sensitive UI configuration.
6. Trigger Team Mode pairing.
7. Read the diagnostic log.

Trail-Mate private BLE must not bypass P4 to directly modify the core business status. All writes must be forwarded to P4, which decides whether to accept them.


7. ESP-NOW/Team Mode Design

The business core of Team Mode is P4. C6 is only responsible for ESP-NOW discovery and short message transmission.

7.1 ESP-NOW Data Flow

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 MUST NOT DO

C6 MUST NOT:

1. Decide whether to join the team.
2. Decide who is the leader.
3. Generate the final team key.
4. Save team key.
5. Modify the team member list.
6. Trigger LoRa message.

C6 can:

1. Scan for nearby Trail-Mate devices.
2. Broadcast the discovery payload delivered by P4.
3. Receive ESP-NOW payload.
4. Forward to P4.
5. Send ESP-NOW response according to P4 command.
6. Maintain short-term peer MAC cache.

7.3 ESP-NOW payload

C6 does not understand the payload internal format. C6 only handles:

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;

If the ESP-NOW payload exceeds 240 bytes, it must refuse to send and return TM_C6_ERROR_PAYLOAD_TOO_LARGE to P4.


8. Wi-Fi design

Wi-Fi is implemented in stages.

8.1 Phase A: Wi-Fi is not implemented, only the interface is retained

The first version of C6 companion has BLE/ESP-NOW as its core target. Wi-Fi does not need to be implemented yet, but it must be retained in the protocol:

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 If Wi-Fi is not supported, the corresponding feature bit will not be set in HELLO_ACK.

P4 UI must display:

C6 Wi-Fi: unsupported by current companion firmware

Instead of displaying:

Wi-Fi hardware missing

8.2 Phase B: Wi-Fi management capabilities

Implementation:

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

P4 is still the configuration source. C6 does not save Wi-Fi SSID/password unless explicitly requested by P4.

8.3 Phase C: Wi-Fi data capability

Optional implementation of TCP/UDP bridge or HTTP diagnostic server.

Note:

The Wi-Fi data plane is not the first phase goal.
Don't let the Wi-Fi data plane slow down the implementation of BLE/ESP-NOW.

9. C6 Memory and Resource Constraints

C6 has no PSRAM. Must be designed for small memory devices.

Hard restrictions:

1. Do not allocate large buffers.
2. A single HostLink payload does not exceed 1024 bytes by default.
3. The BLE notify queue does not exceed 8 packets by default.
4. ESP-NOW rx queue does not exceed 8 packets by default.
5. The log ring buffer does not exceed 8KB by default.
6. Disable message history saving on C6.
7. Disable saving NodeDB on C6.
8. Saving of maps, tracks or files on C6 is prohibited.

Recommended configuration:

#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

If the heap is lower than the threshold:

free_heap < 32KB

C6 must report warning event:

TM_C6_EVENT_LOW_MEMORY

If the heap is lower than:

free_heap < 16KB

C6 must stop accepting new BLE write / ESP-NOW rx queue entries and return busy/error.


10. What must be adjusted in the P4 firmware

According to this plan, the P4 firmware must be adjusted. The reason is that the C6 is no longer a transparent board factory wireless firmware, but a wireless companion for Trail-Mate. P4 must add an explicit companion abstraction layer.

10.1 New WirelessCompanion abstraction

New interface:

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;
};

New implementation:

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 must not directly assume that the machine has BLE

In the T-Display-P4 target, BLE is not P4 native BLE, but the C6 BLE facade.

Therefore the P4 side code must be distinguished:

LocalBleAdapter:
 For devices such as ESP32-S3 / nRF52 that have BLE natively

C6BleAdapter:
 For T-Display-P4, BLE on C6

New enumeration:

enum class BleBackend {
    None,
    Local,
    C6Companion
};

T-Display-P4 target settings:

BleBackend::C6Companion

Normal ESP32 / nRF52 target remains as is.

10.3 P4 added BLE profile router

P4 must route the BLE data from C6 to different business modules according to the HostLink channel:

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;
    }
}

When the P4 business module responds:

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

10.4 P4 added ESP-NOW Team Mode bridge

P4 added:

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

C6 receives ESP-NOW After only forwarding, P4 is responsible for the Team Mode state machine.

If you directly assume that ESP-NOW is running on the current MCU in the existing Team Mode, it needs to be changed to:

TeamModeCore
 Does not depend on ESP-NOW API

TeamModeTransport
 Abstract interface

LocalEspNowTransport
 For native Wi-Fi/BLE MCU

C6EspNowTransport
 For T-Display-P4

Interface:

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 added C6 status UI

The settings page/diagnostics page of T-Display-P4 needs to be added:

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

If C6 does not exist or the protocol is incompatible, the UI must display an explicit error:

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

Do not hide silently.

10.6 P4 new configuration source

P4 is the authoritative configuration source. New configuration structure:

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 startup:

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

C6 does not read business defaults from its own NVS.


11. Error code

Uniform error code:

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
};

Payload when C6 returns ERROR frame:

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 The ERROR frame must be recorded in the log and the C6 diagnostics UI must be updated.


12. Development stage

Phase 0: Protocol and empty implementation

Goals:

1. Add shared protocol definition.
2. Add P4 C6Companion stub.
3. Add C6 companion ESP-IDF project.
4. C6 can be compiled, flashed and started.
5. P4 can run normally without C6.
6. P4 diagnostics shows C6 missing.

Acceptance:

1. P4 build passed.
2. C6 build passed.
3. Does not affect existing tlora_pager/tdeck/gat562 builds.
4. T-Display-P4 does not crash without C6 companion.

Goals:

1. P4 and C6 complete HELLO / HELLO_ACK.
2. P4 can PING, C6 returns PONG.
3. C6 reports firmware version / free heap.
4. P4 diagnostics shows 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.

Acceptance:

1. 1000 consecutive pings without CRC error.
2. P4 can re-handshake after C6 reset.
3. C6 can re-enter WAIT_P4_HELLO after P4 reset.
4. A clear error is displayed when the protocol version does not match.

Phase 2:Trail-Mate private BLE

Goals:

1. C6 turns on Trail-Mate private BLE service.
2. The mobile phone BLE debugging tool can be connected.
3. Write private characteristics to the mobile phone.
4. P4 receives BLE_UPLINK.
5. P4 returns BLE_DOWNLINK.
6. The mobile phone receives notify.

Acceptance:

1. 20 connections/disconnects without deadlock.
2. The 1KB payload can be transmitted in fragments.
3. When the notify queue is full, backpressure is returned without losing status.
4. P4 UI can display BLE connected / disconnected.

Phase 3:Meshtastic BLE facade

Goals:

1. C6 exposes Meshtastic-compatible BLE profile.
2. The mobile Meshtastic App can discover the device.
3. ToRadio bytes written by App can reach P4.
4. The FromRadio bytes generated by P4 can be notified back to the App.

Acceptance:

1. Meshtastic App can connect.
2. The basic configuration reading path is accessible.
3
4. There is no need to restart P4 or C6 after disconnection and reconnection.

Note:

If the Meshtastic App requires strict service UUID / characteristic UUID / advertisement format, it must be read from the existing Meshtastic implementation or the existing code in the project, and no guessing is allowed.

Phase 4:MeshCore BLE facade

Goals:

1. C6 exposes MeshCore companion profile.
2. MeshCore app or debugging tool can be connected.
3. MeshCore uplink/downlink raw bytes can pass P4.

Acceptance:

1. When the MeshCore BLE profile is not configured with a UUID, the compilation can pass but PROFILE_NOT_CONFIGURED is returned at runtime.
2. After configuring UUID, MeshCore raw transport is accessible.
3. Does not affect Meshtastic BLE profile.

Phase 5:ESP-NOW Team Mode

Goals:

1. C6 can send ESP-NOW discovery.
2. C6 can receive ESP-NOW discovery.
3. P4 Team Mode can handle near-field discovery through C6EspNowTransport.
4. C6 does not save team state.

Acceptance:

1. The two devices can discover each other.
2. P4 diagnostics can see peer MAC / RSSI.
3. Team Mode status changes only occur in P4.
4. After C6 reset, the Team Mode status will not be restored in C6 and must be re-issued by P4.

Phase 6: Wi-Fi recovery

Goals:

1. C6 supports Wi-Fi scan.
2. C6 supports station connect.
3. C6 reports IP.
4. P4 UI can display Wi-Fi status.

Acceptance:

1. Wi-Fi scan does not cause BLE disconnection when BLE is turned on.
2. There is a clear error code when Wi-Fi connection fails.
3. P4 downgrades correctly when Wi-Fi is unsupported.

13. Regression testing requirements

Every time you modify the C6 protocol, you must execute:

1. P4 build: t_display_p4_tft
2. P4 build: t_display_p4_amoled
3. C6 build: esp32c6 companion
4. At least one existing PlatformIO main target build
5. HostLink frame encode/decode unit test
6. CRC error injection test
7. payload too large test
8. unsupported feature test

HostLink unit test covers at least:

1. Normal frame encode/decode.
2. magic error.
3. Version error.
4. crc error.
5. payload_len exceeds the limit.
6. ACK_REQUIRED / IS_ACK.
7. channel routing.
8. fragment start/end.

14. Log specifications

C6 log tag:

C6_MAIN
C6_HOSTLINK
C6_BLE
C6_BLE_MT
C6_BLE_MC
C6_BLE_TM
C6_ESPNOW
C6_WIFI
C6_DIAG

P4 log tag:

P4_C6
P4_C6_LINK
P4_C6_BLE
P4_C6_ESPNOW
P4_C6_WIFI
P4_C6_DIAG

All HostLink errors must be printed:

seq
frame_type
channel
error_code
payload_len
free_heap if C6 side

Print in the log is prohibited:

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

15. Release policy

P4 and C6 must have independent version numbers, but require a protocol compatibility statement.

For example:

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

Maintenance compatibility range in P4 firmware:

#define TM_C6_PROTO_MIN 1
#define TM_C6_PROTO_MAX 1

Maintenance compatibility range in C6 firmware:

#define TM_C6_PROTO_MIN 1
#define TM_C6_PROTO_MAX 1

If the protocol is incompatible:

P4 must not continue to enable BLE/ESP-NOW.
P4 UI shows C6 protocol mismatch.
C6 holds WAIT_P4_HELLO or SAFE_MODE.

16. Final acceptance criteria

Acceptable goals for the first version:

1. P4 and C6 can shake hands stably.
2. P4 diagnostics can display C6 status.
3. Trail-Mate private BLE is available.
4. Meshtastic BLE facade is available or at least the raw path is accessible.
5. MeshCore BLE facade has a clear stub and will not falsely report that it is available.
6. ESP-NOW Team Mode transport can run a minimal discovery process.
7. C6 reset will not drag P4 to death.
8. C6 can reconnect after P4 reset.
9. P4 can still run LoRa/UI/GPS/map without C6 firmware.
10. C6 does not save Trail-Mate main business status.

Not as the first version acceptance target:

1. Complete Wi-Fi data plane.
2. Web server.
3. OTA.
4. File transfer.
5. Complex protocol analysis on C6.
6. Message history on C6.
7. MeshCore/Meshtastic core implementation on C6.

17. Mandatory requirements for coding AI

The following rules must be followed when implementing:

1. Do not move the core of the business to C6.
2. Do not let C6 drive LoRa directly.
3. Do not let C6 save business keys.
4. Don't guess the MeshCore UUID; make a disabled stub if there is no source.
5. Do not guess the Meshtastic UUID; it must be confirmed from the project's existing implementation or upstream protocol definition.
6. Do not make T-Display-P4's P4 firmware directly dependent on native BLE.
7. The BLE backend of T-Display-P4 must be C6Companion.
8. All wireless services of C6 must be configured after being delivered by P4.
9. P4 must degrade instead of crashing when C6 is not detected.
10. All P4-C6 data must go through HostLink frame.
11. All HostLink frames must check CRC.
12. All HostLink channels must be explicitly routed.
13. All errors must be diagnosable.
14. All new code must not break existing tdeck / tlora_pager / gat562 builds.

Summary

This solution is not to make ESP32-C6 a second Trail-Mate, but to make it a wireless edge adapter for Trail-Mate.

Final boundary:

P4:
  Trail-Mate domain core

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

HostLink:
 The only trusted data channel between P4-C6

According to this boundary implementation, Trail-Mate can support Meshtastic, MeshCore, Trail-Mate private companion, and Team Mode at the same time without splitting the business status onto two MCUs.