Files
trail-mate/docs/team/team_chat_protocol.md
T
vicliu d320b6511d Unify PlatformIO and ESP-IDF around a shared UI/runtime shellRefactor/repo structure (#11)
* Unify PlatformIO and ESP-IDF around a shared UI/runtime shell
2026-03-12 01:17:46 +08:00

6.6 KiB
Raw Blame History

# Team Chat 协议方案(方案 Cv0.1)

本文件描述“队伍聊天”协议的最小可实施方案,用于承载结构化消息: Text / Location / Command,并与 Team 安全域绑定,支持地图与态势联动。


1. 目标与范围

  • 目标:让队伍聊天成为“可解析、可执行”的结构化消息流。
  • 覆盖媒体类型:
    • Text:普通文本。
    • Location:位置分享,可在 Chat/GPS 地图渲染。
    • Command:指令类消息,可触发队伍行动提示与地图标注。
  • 安全:仅队伍成员可解密,不依赖 Meshtastic 普通聊天频道。

前置条件(v0.2 调整):

  • 建队/入队不再通过 LoRa 或 NFC
  • 组队只在 5 米内的近距离场景发生,使用 ESP‑NOW 完成建队与密钥分发
  • LoRa 仅用于队内日常通信(Team Chat/Position/Track),不参与组队流程。

非目标:

  • 不替换现有的普通聊天/广播消息。
  • 不实现复杂可靠传输(v0.1 先做 best-effort)。

2. 端口与加密

  • 新增端口:
    • TEAM_CHAT_APP = 303(在 modules/core_team/include/team/protocol/team_portnum.h 中定义)
  • 加密方式:
    • 复用 TeamEncrypted envelopeteam_wire.h)。
    • team_psk 派生 team_chat key。
    • 建议在 TeamService::setKeysFromPsk() 中新增:
      • deriveKey(psk, "team_chat", keys.chat_key)

这样可实现“队伍聊天协议层隔离”,非队伍成员无法解密。


3. Payload 结构(v0.1

3.1 通用头部

使用简洁 TLV 或固定头 + 变长 payload。建议固定头:

struct TeamChatHeader {
  uint8_t  version;    // =1
  uint8_t  type;       // 1=Text 2=Location 3=Command
  uint16_t flags;      // 预留
  uint32_t msg_id;     // 本地生成,防重放/去重
  uint32_t ts;         // 发送时间(unix
  uint32_t from;       // 发送者 node_id
};

3.2 Text

struct TeamChatText {
  // UTF-8 text bytes
  bytes text;
};

3.3 Location

struct TeamChatLocation {
  int32_t lat_e7;
  int32_t lon_e7;
  int16_t alt_m;       // optional, 0=unknown
  uint16_t acc_m;      // optional, 0=unknown
  uint32_t ts;         // optional, 0=use header.ts
  uint8_t  source;     // 位置语义图标,见下方映射
  bytes    label;      // optional short label
};

source 映射(与当前固件实现一致):

  • 0None / 普通位置(无语义图标)
  • 1AreaCleared
  • 2BaseCamp
  • 3GoodFind
  • 4Rally
  • 5Sos

3.4 Command

v0.1 仅做最小指令集,不做撤销/过期语义。

enum TeamCommandType : uint8_t {
  RallyTo = 1,   // 集结到目标点
  MoveTo  = 2,   // 前往目标点
  Hold    = 3    // 原地待命
};

struct TeamChatCommand {
  uint8_t  cmd_type;
  int32_t  lat_e7;      // 可选:用于 Rally/Move
  int32_t  lon_e7;
  uint16_t radius_m;    // 可选:集合半径
  uint8_t  priority;    // 0=normal 1=high
  bytes    note;        // 可选:简短备注
};

4. 发送与接收流程

4.1 发送

  • UI 产生 Text/Location/Command
  • 编码为 TeamChatHeader + payload
  • 通过 TeamService::sendTeamChat()
    • 使用 keys.chat_key 加密封装为 TeamEncrypted
    • 通过 TEAM_CHAT_APP 发送(mesh_.sendAppData(...))。

4.2 接收

  • TeamService::processIncoming() 新增 TEAM_CHAT_APP 分支:
    • 解密 TeamEncrypted
    • 解析 TeamChatHeader
    • 触发 TeamChatEvent(新增 EventBus 类型)。
  • UI 接收到 TeamChatEvent
    • 追加到 team_ui_chatlog(新增类型字段)。
    • 若是 Location/Command,同步更新地图/GPS 页标注。

5. 与现有代码的改动点

最小改动清单(v0.1):

  1. 协议与端口

    • modules/core_team/include/team/protocol/team_portnum.h 新增 TEAM_CHAT_APP = 303
    • 新增 team_chat.h/.cpp(编码/解码)
  2. 密钥派生

    • TeamKeys 增加 chat_key
    • TeamService::setKeysFromPsk() 中派生 "team_chat"
  3. TeamService

    • 新增 sendTeamChat(...)
    • processIncoming() 处理 TEAM_CHAT_APP
  4. 事件总线

    • sys/event_bus.h 新增 TeamChatEvent
  5. UI / 存储

    • team_ui_chatlog_append() 扩展为结构化消息(type + payload
    • Contacts/Team 聊天页改用 TeamChat 数据源渲染卡片

6. 兼容性与过渡

  • 保留现有普通聊天:
    • Primary/Secondary 继续用 TEXT_MESSAGE_APP
  • Team Chat 作为独立通道:
    • 不影响普通聊天历史与通知
    • 可以逐步替换 Team 页的会话来源
  • Location.source 兼容建议(PC/上位机):
    • 若收到未知 source(不在 0..5),按普通 Location 渲染,不得丢弃整条消息
    • 继续使用坐标与时间字段,label 存在时可用于展示
    • 建议保留原始 source 值,便于前向兼容

7. v0.1 已确定事项

  • ACK/送达:不需要(尽力而为)。
  • Command 撤销/过期:不支持。
  • 地图交互:收到消息只弹系统通知;弹窗由 Chat 会话中选中地图标注后触发,显示该位置瓦片地图的裁剪图。
  • 组队模式 GPS:从 posring.log 渲染队员位置。

8. 版本策略

  • TeamChatHeader.version = 1
  • 后续扩展通过 flags 或新 type 兼容

附录 A:ESP‑NOW 建队与入队(v0.2

目标:减少步骤与不稳定环节,近距离(≤5m)快速建队。

A1. 角色与约束

  • 场景:所有人同一房间/范围 ≤5m。
  • 介质:ESPNOW2.4GHz),不再使用 LoRa/NFC。
  • 结果:成员收到 team_psk + team_id + key_id + epoch,之后用 LoRa 收发 Team 协议。

A2. 简化流程

Leader

  1. Create Team → 打开 “Pairing (ESPNOW)” 窗口(默认 120s)。
  2. 接收成员 Join 请求(可“Autoaccept”或手动 Accept)。
  3. 发送 KeyDistESPNOW)给新成员。

Member

  1. Join Team → Nearby TeamsESPNOW 扫描)选择目标。
  2. 发送 Join 请求并等待 KeyDist。
  3. 收到 KeyDist 后进入已加入状态。

A3. ESPNOW 消息(建议)

PAIR_BEACON { team_id_short, team_id, epoch, key_id, leader_id, expires_at }
PAIR_JOIN   { team_id, member_id, member_pub, nonce }
PAIR_ACCEPT { team_id, member_id, ok, nonce }
PAIR_KEY    { team_id, key_id, epoch, team_psk_encrypted, nonce }

说明:

  • team_psk_encrypted 可用临时会话密钥(由 member_pub + leader 私钥派生)加密。
  • nonce 用于关联请求与防重放。
  • 所有 ESPNOW 包都只在 Pairing 窗口内处理。

A4. 与 Team Chat 的关系

  • ESP‑NOW 仅负责“建队/入队/密钥分发”。
  • 完成后使用 LoRa 的 Team 协议(TEAM_CHAT / TEAM_POS / TEAM_TRACK / …)。