核心约束: 纯广播信道,无ACK确认,无重传机制。所有设计必须拥抱不确定性——用概率、冗余和最终一致性代替确定性保证。


一、数量级跃迁带来的根本性变化

指标 30节点 300节点
TDMA时隙数 ~40/superframe ~400/superframe → 超帧长达4秒+
集群数 3-6个 如果保持每簇10节点 → 30个Head
Head路由表 H² = 9~36 H² = 900 → Head间控制信道饱和
泛洪放大(TTL=2) ~10x ~100x → 广播风暴摧毁信道
发现/收敛时间 <15秒 扁平方案: >2分钟(不可接受)
单跳覆盖节点数 全部可达 物理上只能覆盖局部邻居

核心结论:

300节点必须放弃”全网泛洪”思维,转向:

  1. 多级层次结构(至少3层)
  2. 地理/拓扑分区(只和局部通信)
  3. MPR多跳中继 + RPF反向路径转发(选代表性节点转发,抑制泛洪)
  4. 拥抱不确定性 — 用概率冗余代替精确状态同步

二、整体架构 — 三级层次 Mesh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
              Level-2: Domain (域)
┌───────────────┐
┌─────│ Domain A │─────┐
│ │ Backbone Node B1│ │
│ └──┬──────┬──────┘ │
│ │ │ │
┌────▼───┐ ┌──▼──────┐ ... │
│Cluster │ │Cluster │ │
│ A1 │ │ A2 │ │
│Head:H1 │ │Head: H2 │ │
└──┬┬┬───┘ └──┬┬┬────┘ │
│││ │││ │
Level-0: Node │... │
(叶子节点) │ │
│ ┌─────▼─────┐
│ ┌─────│ Domain B │
│ │ │ Backbone B2│
│ │ └───────────┘
│ │
Level-1: Cluster │ (每个Domain ~5个Cluster)
(集群, ~10节点) │ (每个Cluster ~10节点)

3层结构: │
Domain → Cluster → Node│
~6域 → ~50集群 → 300节点

层级参数

Level 名称 数量 每单元大小 代表角色
L2 Domain ~6个 ~50节点 Backbone Node (骨干)
L1 Cluster ~50个 ~10节点 Head (集群头)
L0 Node 300个 - Leaf (叶子节点)

为什么选这个比例?

  • L0→L1: 每簇10节点 → 集群内TTL=2泛洪可控(~10x放大)
  • L1→L2: 每域5个Cluster → Backbone路由表仅5项
  • L2: 6个Domain → Domain间路由表仅6项,Backbone之间可直接广播通信

总控制开销对比

1
2
3
扁平泛洪:     O(n²) = 90,000
三层层次: O(L0×10 + L1×5 + L2×6) = 3000 + 250 + 36 ≈ 3,286
→ 减少 ~96%

三、各层级详细设计

Level-0: 叶子节点层 — 微型TDMA集群

每个Cluster内部的设计与30节点方案类似,但更紧凑。

Superframe结构 (500ms周期)

1
2
3
4
5
6
7
8
9
10
┌──────┬─────────────────────────────┬──────┐
│Sync │ Data Slots │Ctrl │
│(5ms) │ ┌───┬───┬───...┬───┬───┐ │(10ms)│
│ │ │H │N1 │N2 │N9 │Rsv│ │ │
│ │ │5ms│5ms│ │5ms│5ms │ │ │
│ │ └───┴───┴───────┴───┴───┘ │ │
│ │ 10个时隙 = 55ms │ │
└──────┴─────────────────────────────┴──────┘

剩余435ms: 节点可以休眠(省电)或监听跨集群转发

关键变化 vs 30节点方案

  1. Superframe缩短到500ms → 降低延迟
  2. 时隙压缩到5ms/节点 → 需要更好的时钟同步(±1ms)
  3. 增加休眠机制 → 叶子节点87%时间可以sleep

叶子节点状态机

1
2
3
4
5
6
7
8
9
10
┌──────────┐    ┌─────────────┐    ┌──────────┐
│ DETACHED │───▶│ DISCOVERY │───▶│ JOINING │
│ (新/离线) │ │ (监听Head) │ │(发送请求) │
└──────────┘ └─────────────┘ └────┬─────┘
▲ │
│ Head离线 │ 分配时隙
│ 主动离开 ┌─────▼─────┐
│ │ ATTACHED │
│ │ (正常工作) │
│ └────────────┘

ATTACHED节点行为:

  • 在自己的TDMA时隙发送数据/HELLO
  • 监听Head的广播(路由更新、同步信标)
  • 在非活跃时段进入低功耗模式
  • 每30秒报告一次链路质量给Head
  • 新增: 相邻节点间交换时间戳做Peer-to-Peer时钟校正

Level-1: Cluster Head层 — RPF + 概率冗余转发网络

这是300节点方案最核心的设计。Head之间不能泛洪——需要用 RPF (Reverse Path Forwarding) + 概率冗余 来抑制广播风暴,同时避免MPR精确状态同步在无ACK环境下的静默丢包问题。

问题: 50个Head如果全部互相广播,控制信道饱和。

RPF转发规则(核心改进)

不再依赖”谁选了我做MPR”的全局状态——这种状态本身通过不可靠的广播维护,丢失即导致静默丢包。

改为 反向路径转发 (RPF) + 概率冗余 混合模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
收到跨集群数据包 packet 时:

# 情况1: 我是目标 → 处理
if packet.dst_cluster in my_managed_clusters:
deliver_to_cluster(packet)
return

# 情况2: RPF检查 — 包是否从最优下一跳方向到达?
arrived_from = packet.first_hop_head_id # 包中携带的上一跳
optimal_next_hop = Forwarding_Graph.get(arrived_from)

if arrived_from == optimal_next_hop or optimal_next_hop is None:
# 包从正确方向来 → 转发(RPF通过)
rebroadcast(packet)
return

# 情况3: RPF未通过 — 概率性冗余转发
# 在链路质量差或网络负载低时,保留一定概率的冗余转发
redundancy_prob = compute_redundancy_prob()
if random() < redundancy_prob:
rebroadcast(packet)
return

# 情况4: 丢弃(重复包)
drop(packet)


def compute_redundancy_prob():
"""根据链路质量和网络负载动态调整冗余概率"""
avg_lqi = average_link_quality(Direct_Neighbors)
channel_load = estimate_channel_load() # 0.0~1.0

# 链路差时提高冗余,信道忙时降低冗余
prob = (1.0 - avg_lqi) * 0.4 + (1.0 - channel_load) * 0.2
return clamp(prob, 0.05, 0.3)

效果:

  • RPF保证”正确方向”的包一定转发 — 不依赖MPR_ASSIGNMENT的全局状态
  • 概率冗余提供容错 — 即使RPF判断有误(因为拓扑信息不完整),仍有~10-20%的概率被其他节点转发
  • 转发量从50x降到 ~8-12x,比纯MPR略高但鲁棒性大幅提升

Head间通信的时隙方案 — 粗粒度TDMA子槽

Head不使用精确TDMA(太复杂),也不使用纯CSMA(碰撞率被低估)。改用 粗粒度TDMA子槽 + ALOHA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Head通信窗口 (每个Superframe的Ctrl时段):
┌─────────────────────────────────────┐
│ Head Control Window │
│ (10ms, 每500ms一次) │
│ │
│ 分成4个子槽,每子槽2.5ms: │
│ ┌──────┬──────┬──────┬──────┐ │
│ │ Sub0 │ Sub1 │ Sub2 │ Sub3 │ │
│ │2.5ms │2.5ms │2.5ms │2.5ms │ │
│ └──────┴──────┴──────┴──────┘ │
│ │
│ 子槽分配: hash(Head_ID, epoch) % 4 │
│ 每epoch轮换一次(防固定碰撞) │
│ │
│ 子槽内发送规则 (ALOHA): │
│ - 不LBT,直接发 │
│ - 控制包RS(4,2) FEC保护 │
│ - 每个子槽最多1个活跃Head │
└─────────────────────────────────────┘

为什么用ALOHA代替CSMA?

在广播无ACK环境下,CSMA的LBT(先听后说)实际上帮不上忙:

  • LBT只能检测”现在信道忙不忙”
  • 但不能防止两个节点同时开始发送(隐藏终端问题)
  • 对于<64B的小控制包,ALOHA + RS(4,2) FEC的端到端可靠性可能优于CSMA——因为省去了LBT延迟和退避逻辑

为什么Head可以用ALOHA而叶子节点不行?

  • Head只有50个,分到4个子槽 → 每子槽~12.5个竞争者
  • 控制包小(<64B),RS(4,2)编码后96B,占用信道时间极短
  • 控制消息本身有时间分集保护(连续3轮重复发送)

Level-2: Backbone层 — Domain间路由

Backbone Node (BN) 是每个Domain的”网关”:

职责

  1. 代表整个Domain对外通信
  2. 维护Domain间路由表
  3. 聚合Cluster Head的路由信息
  4. 在Domain间转发数据

BN选举(每个Domain内)— Bully算法 + 全局Epoch

改进: 使用基于同步时钟的全局epoch,避免两个Head各自独立递增导致的分裂脑。

1
2
3
4
5
# epoch基于全网同步后的时间计算——所有节点用同一公式算出相同值
election_epoch = floor(synchronized_time_ms / ELECTION_PERIOD_MS)
# ELECTION_PERIOD_MS = 60000 (每60秒一个选举周期)

# 每个epoch只选一次,避免频繁重选

条件优先级:

  1. HAS_BACKBONE flag(有线/高速连接的设备优先)
  2. 连接到最多Cluster Head的节点
  3. Node_ID最小者(打破平局)

BN间通信

6个BN可以直接互相广播(数量少,CSMA完全够用)。

BN路由表 (极简):

Domain_ID Next_Hop_BN Hop_Count AoI_ms
(1 byte) (1 byte) (1 byte) (2 bytes)

最多5条路由项(6个Domain减自己)。新增 AoI_ms 字段记录信息年龄。

BN路由发现

每个BN每秒广播 DOMAIN_ADVERT:

BN_ID Domain_Route_Summary Election_Epoch
(1 byte) [{domain_id, hops}..] (2 bytes)

收到后更新路由表(标准距离向量)。新增: 用AoI代替LQI做路由选择——不依赖有偏的链路质量估计。


完整的数据转发路径示例

Node A (Cluster A1, Domain A) → Node Z (Cluster B3, Domain B):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─ L0: 集群内 ────────────────────────────────┐
│ A在TDMA时隙发送 DATA(dst=Z, dst_cluster=B3, │
│ dst_domain=B) │
│ Head_A1收到 → 查路由表 │
└──────────────────────────────────────────────┘

┌─ L1: RPF + 概率冗余转发 ────────────────────┐
│ Head_A1查表: B3属于Domain B │
│ Domain B的BN是B2 │
│ 包进入RPF转发链: │
│ Head_A1 → [RPF check] → Head_A3 │
│ → [RPF + prob] → BN_B2 │
│ (2-3跳,由转发表+概率冗余决定路径) │
└──────────────────────────────────────────────┘

┌─ L2: Backbone网关 ──────────────────────────┐
│ BN_B2收到 → 查Domain内路由 │
│ B3的Head是H_B3 │
│ BN_B2 → Head_B3 (RPF或直接) │
└──────────────────────────────────────────────┘

┌─ L0: 目标集群 ──────────────────────────────┐
│ Head_B3在集群内TTL=2泛洪给Z │
│ Z收到 → 交付应用层 │
└──────────────────────────────────────────────┘

总跳数: ~4-6跳(vs 扁平方案的不可行)
Path_Trail记录完整路径防环

四、路由协议 — 分层距离向量 + AoI信息年龄度量

路由信息聚合

关键设计: 每一层只关心本层的路由,不感知下层细节。

L0→L1 聚合 (Head维护)

  • Head_A1知道:
    • 集群内所有节点ID和时隙分配
    • 每个节点的单向链路质量(LQI_in)
  • Head_A1对外暴露:
    • “我能到达Cluster A1的所有节点”
    • 不暴露内部拓扑

L1→L2 聚合 (Backbone维护)

  • BN_A知道:
    • Domain A内所有Cluster的Head ID
    • Head间的RPF转发关系
  • BN_A对外暴露:
    • “我能到达Domain A的所有Cluster”

L2 全局视图 (每个BN)

  • BN知道:
    • 其他Domain的BN ID
    • Domain间跳数 + AoI(信息年龄)

路由查询(下行分解)

1
2
3
4
dst=Node_Z →
BN查: Z在哪个Domain? → Domain B →
BN_B查: Z在哪个Cluster? → Cluster B3 →
Head_B3查: Z的时隙/地址 → 交付

链路质量度量(LQI)— 无ACK环境下的单向估计

问题: 没有ACK,怎么知道链路质量好坏?而且无线链路经常不对称。

方案: 去掉对称性假设,只用单向指标 + 间接确认

LQI数据结构

每个节点维护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LQI_Entry[neighbor_id] = {
# 入向质量 — 我收到对方HELLO的频率 / 期望频率
lq_in: float, # 0.0~1.0,EWMA平滑

# 出向质量 — 从对方的TWO_HOP_ADVERT中读取的对方报告的lq_in[my_id]
# 这是一种"间接确认"——如果我在对方的邻居列表中,说明我→对方可达
reported_lq_out: float, # 0.0~1.0

# RSSI采样(辅助指标)
rssi_samples: circular_buffer[16],

# 信息年龄 — 这条LQI记录多久没更新了
aoi_ms: uint32,

# 最后更新时间
last_update_ms: uint32,
}

LQI更新逻辑(每5秒窗口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 入向质量 — 只依赖我收到的数据,不假设对称性
received_count = count_hellos_from(neighbor_id, window=5s)
expected_count = 5 * HELLO_INTERVAL_S # 期望收到多少个

lq_in_raw = received_count / expected_count
rssi_factor = clamp((rssi_avg + 90) / 60, 0, 1)

lq_in_new = lq_in_raw * rssi_factor
LQI_Entry[neighbor_id].lq_in = 0.7 * LQI_Entry[neighbor_id].lq_in + 0.3 * lq_in_new

# 出向质量 — 从对方的TWO_HOP_ADVERT中提取
收到 neighbor的 TWO_HOP_ADVERT:
if my_id in advert.neighbor_list:
reported_lqi = advert.neighbor_list[my_id].lqi
LQI_Entry[neighbor_id].reported_lq_out = reported_lqi

# 更新AoI
LQI_Entry[neighbor_id].aoi_ms = now() - LQI_Entry[neighbor_id].last_update_ms

路由选择 — AoI优先,LQI辅助

核心改进: 用信息年龄(AoI)代替纯LQI做路由决策。AoI不依赖链路质量估计——只看”这条路由信息有多新鲜”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Route_Entry[dest] = {
next_hop: Node_ID,
hop_count: int,
aoi_ms: uint32, # 信息年龄(毫秒)
generation: uint8, # 每60秒递增,防环
}

路由选择:
best_route = argmin(candidates,
key=lambda r: r.aoi_ms * WEIGHT_AOI
+ r.hop_count * WEIGHT_HOPS)

# WEIGHT_AOI = 10 (优先新鲜的路由信息)
# WEIGHT_HOPS = 1 (次要考虑跳数)

AoI的优势:

  • 不依赖链路质量估计——单向广播就能更新AoI
  • 天然处理拓扑变化——旧路由自动被淘汰(aoi_ms增大 → 优先级降低)
  • 不需要双向测量——解决了无ACK环境下LQ_out无法准确获取的问题

路由环检测 — 增强版

300节点时路由环风险大增,需要多层防护。

机制1: Path_Trail(已有)

  • 包携带已访问的Head/BN ID列表
  • 最大长度: L0(0) + L1(8) + L2(4) = 12字节

机制2: Sequence Number + Generation Counter

每个路由条目携带 generation:

1
2
3
4
5
6
7
8
9
10
Route_Entry[dest] = {
next_hop: Node_ID,
hop_count: int,
aoi_ms: uint32, # 信息年龄
generation: uint8, # 每60秒递增
}

收到路由更新时:
if incoming.generation < my_known.generation:
DROP # 过期信息,忽略

机制3: Split Horizon + Poison Reverse(适配广播)

发送路由通告时:

  • 从Head X学到的路由,不向X反向通告
  • 如果某路由失效:
    • 主动广播 INVALIDATION(dest, hop_count=255) — “到dest的路径已断”

机制4: TTL硬性限制

层级 TTL限制
L0内 TTL ≤ 2
L1间 RPF_Hops ≤ 8
L2间 BN_Hops ≤ 4

超限直接丢弃,不进入路由表。


五、时钟同步 — 三级分层 + Peer-to-Peer校正

同步层级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Stratum 0: Global Reference (GR)
└── 全网唯一的时间源(Node_ID最小的BN)
│ 广播 GLOBAL_SYNC (每秒1次,所有BN监听)
│ SYNC信标本身用RS(4,2)编码+连续3轮重复

Stratum 1: Backbone Nodes (5个BN)
├── 根据GR调整时钟
└── 每500ms广播 DOMAIN_SYNC 给本Domain的Head
│ SYNC同样FEC保护+多轮重复

Stratum 2: Cluster Heads (~50个Head)
├── 根据BN调整时钟
└── 每个Superframe开始广播 CLUSTER_SYNC
│ SYNC用RS(4,2)编码,连续3次发送
│ 节点收到任意2份即可解码——几乎不可能全部丢失

Stratum 3: Leaf Nodes (300个叶子)
├── 根据Head调整时钟(主同步源)
└── Peer-to-Peer交叉校正(辅助同步源)

同步误差累积分析

层级 单级误差 累积误差 是否满足需求
GR→BN ±0.5ms ±0.5ms BN间通信: ✓ (需±2ms)
BN→Head ±0.5ms ±1.0ms Head间CSMA: ✓ (需±2ms)
Head→Node ±0.5ms ±1.5ms TDMA 5ms时隙: ⚠ (需±1ms,临界)

改进: Guard Interval从200μs增加到 500μs → 容忍偏差从±1ms提升到±2.5ms。代价:时隙利用率从92%降到82%,但容错提升2.5倍。

Peer-to-Peer时钟校正(新增)

不仅Head→Node单向同步,相邻叶子节点也交换时间戳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 每个Superframe中,节点在自己的时隙末尾附加自己的timestamp
在TDMA时隙发送数据时:
frame.trailer.timestamp = local_timer.read() # 微秒精度

# 下一个时隙的节点收到后可以做交叉校验
收到 neighbor的数据帧:
time_diff = abs(my_local_time - frame.trailer.timestamp)

if time_diff > SYNC_THRESHOLD (800μs):
# 检测到时钟偏差,用多个邻居的平均值校正
peer_offset_sum += time_diff * direction
peer_count += 1

if peer_count >= 3:
avg_peer_offset = peer_offset_sum / peer_count
local_timer_offset += avg_peer_offset * alpha_peer
# alpha_peer = 0.05 → 每轮修正5%的偏差(比Head校正更保守)

# 类似FTSP (Flooding Time Synchronization Protocol)的思想
# 优势: 不依赖单一Head——即使错过CLUSTER_SYNC,也能从邻居获得校正

Head→Node的主同步机制

CLUSTER_SYNC 帧格式:

Magic Epoch Frame_Counter Timestamp FEC_Parity
(2 bytes) (2 bytes) (2 bytes) (4 bytes) (2 bytes)

新增: FEC_Parity — CLUSTER_SYNC本身用RS(4,2)编码。节点收到任意2份(共3轮重复)即可解码。

成员节点的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
收到 CLUSTER_SYNC 时记录本地时间 T_rx_local

phase_error = (sync.timestamp + estimated_propagation_delay) - T_rx_local

# 只调整相位,不调整频率(避免时钟漂移累积)
local_timer_offset += phase_error * alpha_phase
# alpha_phase = 0.1 → 每轮修正10%的误差

# 同时估计传播延迟(用多轮采样)
delay_estimate = exponentialMovingAverage(T_rx_local - sync.timestamp)

# 限制: offset调整量每轮不超过 ±500μs
local_timer_offset = clamp(offset, -500, 500)

同步收敛分析

初始状态: 成员时钟随机偏移,最大偏差 ±2.5ms(半个时隙)

每轮修正10%:

Round 偏差
0 ±2500μs
1 ±2250μs
2 ±2025μs
10 ±984μs ← 已达标
20 ±387μs
50 ±24μs ← 非常精确

收敛时间: 10 × 500ms = 5秒 → 完全可接受


六、发现与自组织 — 300节点的启动流程

冷启动(所有节点同时上线)

Phase 1: 静默监听 (0~5秒)

所有节点只收不发,收集环境信息。记录: 听到的Node_ID、RSSI分布、信号特征。

Phase 2: 分级宣告 (5~15秒)

Step 2a: BN候选者自举 — Bully算法 + 全局Epoch

1
2
3
4
5
6
7
8
9
10
# epoch基于同步后的时间计算——所有节点用同一公式算出相同值
election_epoch = floor(synchronized_time_ms / ELECTION_PERIOD_MS)

# Bully算法流程:
1. CAN_BE_BACKBONE的节点广播 BACKBONE_CANDIDATE(priority, my_id, election_epoch)
2. 如果收到更高优先级的CANDIDATE → 退出竞争
3. 如果在T秒内没收到更高的 → 宣布自己当选
4. 每个epoch只选一次——避免频繁重选

# 收敛: ~3秒

Step 2b: BN间建立L2路由

  • 6个BN互相发现,交换DOMAIN_ADVERT
  • 收敛: ~2秒

Step 2c: Head候选者自举(每个区域内)

  • CAN_BE_HEAD的节点广播 HEAD_CANDIDATE
  • 受限于本Domain的BN协调
  • 每域选5个Head → 全网~50个Head
  • 收敛: ~3秒

Step 2d: Head间建立RPF转发表

  • 交换TWO_HOP_NEIGHBORS
  • 构建Forwarding_Graph(基于AoI和跳数)
  • 收敛: ~2秒

Phase 3: 叶子节点加入 (15~30秒)

普通节点在Resv窗口发送 JOIN_REQUEST。Head分配时隙 → SLOT_ASSIGNMENT。批量处理: 每个Head每轮处理最多3个新成员。

Phase 4: 稳定运行 (>30秒)

  • TDMA调度生效
  • RPF转发链建立
  • 路由表收敛

总启动时间: ~30秒(vs 3节点的2秒,30节点的15秒)

热加入(网络已运行,新节点上线)

  1. 监听5秒 → 发现最近的Head
  2. Resv窗口发 JOIN_REQUEST(head_id=最近Head)
  3. Head分配时隙 → SLOT_ASSIGNMENT
  4. 下一Superframe开始工作

热加入时间: <8秒


七、可靠性 — 五层防御体系(升级版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──────────────────────────────────────────────────────┐
│ Layer A: TDMA (L0) + RPF+概率冗余 (L1/L2) │
│ → 消除碰撞,抑制泛洪 │
│ 效果: 碰撞丢包从 ~30% → <5% │
│ 改进: RPF不依赖精确MPR状态,概率冗余提供容错 │
├──────────────────────────────────────────────────────┤
│ Layer B: 自适应FEC Reed-Solomon │
│ L0内: RS(n,k)动态调整 (默认4/7, 范围3/5~4/9) │
│ L1间: k=3, n=6 (容忍3/6丢失,Head链路更可靠) │
│ L2间: k=2, n=4 (BN数量少,简单冗余即可) │
├──────────────────────────────────────────────────────┤
│ Layer C: 网络编码 (L0内) │
│ 中间节点广播XOR组合包 │
│ → 降低对特定FEC块的依赖 │
├──────────────────────────────────────────────────────┤
│ Layer D: 多路径传输 │
│ 大数据分片走不同RPF路径 │
│ → 单点故障不影响整体 │
├──────────────────────────────────────────────────────┤
│ Layer E: 时间分集 + 应用层语义冗余 │
│ 关键控制消息重复3次(不同Superframe) │
│ SYNC信标本身FEC保护 │
│ 应用层设计容忍丢包的协议(如CRDT最终一致性状态机) │
└──────────────────────────────────────────────────────┘

综合可靠性估算

场景A: 集群内通信 (L0)

  • TDMA消除碰撞 → p=0.03
  • FEC(4/7) → P_fail ≈ 0.001
  • NC增强 → P_fail ≈ 0.0003
  • 时间分集x2 → P_fail ≈ 1e-7
  • → 可靠性: 99.99999%

场景B: 跨Domain通信 (L0→L1→L2→L1→L0)

  • 每跳 p=0.03, ~5跳
  • FEC逐层应用
  • RPF + 概率冗余备份
  • → P_fail ≈ 0.004 → 可靠性: 99.6%

注意: 跨域通信可靠性较低是因为跳数多,每层都有损耗。解法: 关键数据走”慢但可靠”模式(更多FEC冗余+重复)


八、完整协议帧格式 v4

CMP-v4 (Custom Mesh Protocol v4)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──────────┬──────┬──────┬───────┐
| Magic | Ver | Type | Len │ Header (5 bytes)
| 0xCMP4 |(1b) |(1b) |(2b) │
├──────────┼──────┼──────┼───────┤
| Src_ID │ Dst │ TTL │ Seq │ Routing (5 bytes)
|(1 byte) │(1b) │(1b) │(2b) │
├──────────┼──────┴──────┴───────┤
| Epoch │ Cluster_ID │ Domain_ID│ Hierarchy (4 bytes)
│(2 bytes) │(1 byte) │(1 byte) │
├──────────┴─────────────────────┤
| Path_Trail (0-12 bytes) │ L1/L2路由防环
├────────────────────────────────┤
| Hop_FEC_Info (3 bytes) │ 逐跳FEC元数据
│ group_id(2b) + index(1b) │
├────────────────────────────────┤
| First_Hop_ID (1 byte) │ RPF检查用(新增)
├────────────────────────────────┤
| Type-Specific Payload │ Variable (0-512 bytes)
├────────────────────────────────┤
| Timestamp_Trailer (4 bytes) │ P2P同步用(新增,可选)
├────────────────────────────────┤
| MAC (4 bytes) │ Optional
└────────────────────────────────┘

固定开销: 18~30字节(不含Path_Trail和MAC)

Type codes v4 (按层级分组)

L0 - 集群内:

Code 名称 说明
0x01 HELLO 节点心跳 + 携带compact_missing_bitmap
0x02 JOIN_REQUEST 加入集群
0x03 SLOT_ASSIGNMENT TDMA时隙分配
0x04 CLUSTER_SYNC 时钟同步(FEC保护)
0x08 DATA 用户数据
0x09 FEC_BLOCK FEC块
0x0A NC_BLOCK 网络编码块

L1 - Head间:

Code 名称 说明
0x10 HEAD_ANNOUNCE Head宣告
0x11 TWO_HOP_NEIGHBORS RPF选举输入
0x13 ROUTE_ADVERT_L1 L1路由通告(含AoI)
0x14 ROUTE_INVALIDATE 路由失效通知

删除: MPR_ASSIGNMENT (0x12) — v4不再使用精确MPR状态同步

L2 - Backbone间:

Code 名称 说明
0x20 BACKBONE_CANDIDATE BN候选宣告(含election_epoch)
0x21 DOMAIN_ADVERT Domain间路由(含AoI)
0x22 GLOBAL_SYNC 全局时钟同步(FEC保护)
0x23 DOMAIN_SYNC Domain内同步(FEC保护)

跨层:

Code 名称 说明
0x30 DATA_RELAY 跨层数据转发
0x32 COLLISION_REPORT 冲突报告
0x33 CLUSTER_MERGE 集群合并/分裂

删除: NACK (0x31) — v4用盲重复+聚合式慢速反馈代替即时NACK


九、性能指标汇总

3节点 30节点 300节点(v4)
架构 扁平 两层集群 三层层次(Domain/Cluster/Node)
MAC协议 CSMA TDMA TDMA(L0) + RPF-ALOHA(L1/L2)
路由协议 TTL泛洪 受限泛洪+DV RPF中继+分层DV+AoI选路
路由表大小 2项 ~8项 L0:10 / L1:5 / L2:~5
控制节点数 0(全平等) ~4个Head ~50个Head + ~6个BN
FEC策略 k=3,n=5 k=4,n=7 自适应FEC(L0) + 分层(L1/L2)
发现时间 <2秒 <8秒 <30秒(冷启动)
热加入时间 <1秒 <3秒 <8秒
收敛时间 <1秒 <15秒 <45秒
集群内延迟 ~5ms ~30ms ~20ms
跨域延迟 N/A ~100ms ~200-500ms
单包可靠性 >95% >99.99% L0: 99.99999% / 跨域: 99.6%
控制带宽开销 ~10% ~20% ~28%(RPF冗余略高)
节点休眠率 0% 0% ~87%(叶子节点)
最大可扩展节点 ~10 ~100 ~1000(加第四层Region)

十、如果继续扩展到1000+节点

300→1000的演进路径

添加 Level-3: Region (区域)

1
2
3
4
5
6
7
Region (~200节点, ~4个Domain)
└── Super-Backbone (SBN, 每Region 1个)
└── Domain (50节点, 5个Cluster)
└── Backbone Node (BN)
└── Cluster (10节点)
└── Head
└── Leaf Nodes

4层结构: Region → Domain → Cluster → Node

SBN间数量: ~5个(1000节点)→ 可以直接广播通信

关键变化

  • SBN代替BN做跨Region路由
  • BN只关心本Domain,不感知其他Region
  • Path_Trail扩展到16字节
  • 启动时间增加到 ~60秒

十一、总结 — 三个规模的核心设计哲学差异

3节点: “大家都是邻居,广播什么都行” → 暴力泛洪 + FEC + 重复发送

30节点: “需要分组管理,避免互相干扰” → TDMA集群 + Head路由 + MPR雏形

300节点(v4): “必须分层治理,拥抱不确定性” → 三层层次 + RPF概率转发 + AoI选路 + 自适应FEC

核心原则(适用于任何规模)

  1. 广播信道不可靠 → FEC是必须的,不是可选的
  2. 无ACK无重传 → 冗余要在发送端做,不能依赖反馈
  3. 节点越多 → 层次越深,每层管理半径越小
  4. 控制面和数据面分离 → Head/BN处理路由,叶子只管收发
  5. 信息聚合 → 上层不感知下层细节,路由表保持O(常数)大小
  6. v4新增: 拥抱不确定性 → 用概率和冗余代替精确状态同步。任何依赖”全局一致视图”的机制在广播无ACK环境下都是脆弱的

十二、RPF转发与概率冗余详解(替代原MPR章节)

1.1 为什么放弃纯MPR?

v3使用OLSR风格的MPR——维护精确的二跳拓扑,只有被选中的MPR节点才转发。在无ACK环境下有致命缺陷:

  • MPR_ASSIGNMENT本身通过不可靠广播发送 → 可能丢失
  • Head Y不知道自己被X选为MPR → Y会DROP本应转发的包
  • X认为Y在MPR集中 → X以为包会被转发
  • 结果:静默丢包,发送方完全不知道

v4改为 RPF (Reverse Path Forwarding) + 概率冗余 混合模式——不依赖全局MPR状态。

1.2 RPF转发规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
收到跨集群数据包 packet 时:

# 情况1: 我是目标 → 处理
if packet.dst_cluster in my_managed_clusters:
deliver_to_cluster(packet)
return

# 情况2: RPF检查 — 包是否从最优下一跳方向到达?
arrived_from = packet.first_hop_id # 包中携带的上一跳Head ID

# 查转发表:到arrived_from的最优下一跳是谁?
optimal_next_hop = Forwarding_Graph.get(arrived_from)

if arrived_from == optimal_next_hop or optimal_next_hop is None:
# RPF通过 — 包从正确方向来 → 转发
rebroadcast(packet)
return

# 情况3: RPF未通过 — 概率性冗余转发
redundancy_prob = compute_redundancy_prob()
if random() < redundancy_prob:
rebroadcast(packet)
return

# 情况4: 丢弃(重复包)
drop(packet)


def compute_redundancy_prob():
"""根据链路质量和网络负载动态调整冗余概率"""
avg_lqi = average_link_quality(Direct_Neighbors)
channel_load = estimate_channel_load() # 0.0~1.0

# 链路差时提高冗余,信道忙时降低冗余
prob = (1.0 - avg_lqi) * 0.4 + (1.0 - channel_load) * 0.2
return clamp(prob, 0.05, 0.3)

1.3 Forwarding_Graph的构建

不再从MPR关系推导——直接用AoI和跳数构建转发表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 每个Head维护到所有可达Cluster的转发表
Forwarding_Graph[target_cluster_id] = {
next_hop_head_id: Node_ID,
hop_count: int,
aoi_ms: uint32, # 这条路由信息多久没更新了
}

构建方法:
1. 一跳邻居: 从HEAD_BEACON直接获取
for each neighbor in Direct_Neighbors:
Forwarding_Graph[neighbor.cluster_id] = {
next_hop: neighbor.id,
hop_count: 1,
aoi_ms: now() - neighbor.last_seen_ms,
}

2. 多跳邻居: 从ROUTE_ADVERT_L1获取
收到 Head X 的 ROUTE_ADVERT_L1:
for each route in advert.routes:
key = route.dest_cluster

# 如果这是新路由,或者比现有路由更优(AoI更小/跳数更少)
if key not in Forwarding_Graph or is_better(route):
Forwarding_Graph[key] = {
next_hop: X.id,
hop_count: route.hop_count + 1,
aoi_ms: route.aoi_ms + (now() - route.timestamp),
}

3. 定期清理过期路由
for each route in Forwarding_Graph:
if route.aoi_ms > ROUTE_TIMEOUT_MS (120秒):
delete route

1.4 概率冗余的效果分析

场景 纯MPR转发 RPF+概率冗余(v4)
MPR_ASSIGNMENT丢失 静默丢包(0%到达) RPF仍然工作(~95%到达)
正常情况 ~15个节点转发(30%) ~20个节点转发(40%) + 概率冗余
链路质量差(p=0.1) MPR选错→丢包 概率冗余自动提高→补偿
控制开销 MPR_ASSIGNMENT广播 无额外MPR状态同步

结论: v4的转发量比v3多~30%,但鲁棒性大幅提升——不再依赖脆弱的精确MPR状态。

1.5 可选: Gossip协议(低频场景)

对于传感器网络/物联网Mesh,数据本身是低频的,收敛时间不是瓶颈。可以考虑用Gossip/Epidemic协议代替RPF路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 每个节点周期性选择k个随机邻居交换状态
def gossip_round():
selected = random.sample(Direct_Neighbors, k=3)
for neighbor in selected:
send GOSSIP(my_state, my_id, target=neighbor.id)

收到GOSSIP时:
if I haven't seen this info before:
store it
with probability p_gossip (0.7):
forward to random neighbor

# 优势:
# - 不需要精确拓扑——随机性天然容错
# - 收敛时间O(log n),300节点约8轮(~16秒)
# - 没有单点故障——每个节点都是平等的传播者
# - 控制开销可控——每轮只和k个邻居通信

# 劣势:
# - 延迟比RPF高(随机路径 vs 最优路径)
# - 不适合低延迟场景

# 适用场景: 传感器数据采集、状态同步、配置分发

十三、TDMA实现细节

2.1 Superframe结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──────────────────────────────────────────────────────────────┐
│ Superframe (500ms) │
│ │
│ ┌─────┬─────────────────────────────────┬──────┬──────────┐ │
│ │Sync │ Data Region │ Ctrl │ Reserve │ │
│ │5ms │ ┌─────────┬─────────┐ │10ms │ 5ms │ │
│ │ │ │Head Slot│Node Slots│ │ │ │ │
│ │ │ │ 5ms │ 45ms │ │ │ │ │
│ │ │ │ │┌─┬─┬─..┬─┐ │ │ │ │
│ │ │ │ ││1│2│3 │9│ │ │ │ │
│ │ │ │ │└─┴─┴─..┴─┘ │ │ │ │
│ │ │ │ │5ms each │ │ │ │
│ └─────┴─────────────────────────────────┴──────┴──────────┘ │
│ ↑ ↑ ↑ ↑ │
│ 5ms 50ms 10ms 5ms │
│ │
│ 剩余: 500 - 5 - 50 - 10 - 5 = 430ms → 节点休眠窗口 │
└──────────────────────────────────────────────────────────────┘

各区域功能

Sync Region (5ms):

  • Head广播 CLUSTER_SYNC 信标(RS(4,2) FEC保护,连续3轮重复)
  • 包含: epoch, superframe_counter, timestamp, slot_map_update_flag
  • 所有成员监听,调整本地时钟相位

Data Region (50ms):

  • Head Slot (5ms): Head发送聚合数据/控制消息
  • Node Slots (45ms = 9个 × 5ms): 叶子节点轮流发送

Ctrl Region (10ms):

  • ALOHA模式(不是CSMA!)
  • 用于: 紧急消息、时隙请求、碰撞报告
  • 为什么用ALOHA? 见Level-1设计说明

Reserve Region (5ms):

  • 新节点加入请求
  • 时隙重新协商
  • Head离线时的选举通信

2.2 时隙内部结构(改进版)

单个5ms时隙的微秒级分解:

1
2
3
4
5
6
7
8
9
10
┌──────────┬─────────────────────┬──────────┐
│ GI │ Payload │ TA │
│ 500μs │ 4100μs │ 500μs │
│ Guard │ Data + Header │ Tail │
│ Interval │ │ Interval │
└──────────┴─────────────────────┴──────────┘

改进: GI/TA从200μs增加到500μs
→ 容忍时钟偏差从±1ms提升到±2.5ms
→ 时隙利用率从92%降到82%,但容错提升2.5倍

Guard Interval (GI, 500μs):

  • 吸收时钟偏差(±2.5ms同步误差 → 500μs GI足够应对相邻时隙泄漏)
  • 吸收前一时隙的尾部干扰
  • 节点在GI开始时开启发射器,做频率/功率稳定

Payload (4100μs):

  • 协议帧头: ~20 bytes → ~160μs @ 1Mbps WiFi
  • 最大数据载荷: 5100 bytes @ 1Mbps(实际受MTU限制2000 bytes)
  • 如果数据不足,填充到最小帧长(确保可检测)

Tail Interval (TA, 500μs):

  • 发射器关闭的缓冲时间
  • 防止信号拖尾侵入下一时隙

有效数据传输率: 4100/5000 = 82% 时隙利用率,考虑协议头开销: ~76% 净数据率

2.3 时钟同步 — TDMA正常工作的先决条件

三级主同步 + Peer-to-Peer辅助校正

Stratum 2 → Stratum 3 (Head → Members):

CLUSTER_SYNC 帧格式(含FEC保护):

Magic Epoch Frame_Counter Timestamp FEC_Parity
(2 bytes) (2 bytes) (2 bytes) (4 bytes) (2 bytes)

Timestamp: Head的硬件定时器值(微秒精度)

成员节点的处理:

1
2
3
4
5
6
7
8
9
10
收到 CLUSTER_SYNC 时记录本地时间 T_rx_local

phase_error = (sync.timestamp + estimated_propagation_delay) - T_rx_local

# 只调整相位,不调整频率(避免时钟漂移累积)
local_timer_offset += phase_error * alpha_phase
# alpha_phase = 0.1 → 每轮修正10%的误差

# 限制: offset调整量每轮不超过 ±500μs
local_timer_offset = clamp(offset, -500, 500)

Peer-to-Peer辅助校正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 每个Superframe中,节点在自己的时隙末尾附加自己的timestamp
在TDMA时隙发送数据时:
frame.trailer.timestamp = local_timer.read()

收到 neighbor的数据帧:
time_diff = abs(my_local_time - frame.trailer.timestamp)

if time_diff > SYNC_THRESHOLD (800μs):
peer_offset_sum += time_diff * direction
peer_count += 1

if peer_count >= 3:
avg_peer_offset = peer_offset_sum / peer_count
local_timer_offset += avg_peer_offset * alpha_peer
# alpha_peer = 0.05 → 比Head校正更保守

# 优势: 不依赖单一Head——即使错过CLUSTER_SYNC,也能从邻居获得校正

2.4 时隙分配算法

Head维护的 Slot_Allocator 数据结构:

1
2
3
4
5
6
7
8
Slot_Map = {
slot_0: { node_id: HEAD, duration_ms: 5, priority: HIGH, state: ACTIVE },
slot_1: { node_id: N1, duration_ms: 5, priority: NORMAL, state: ACTIVE },
slot_2: { node_id: N2, duration_ms: 5, priority: NORMAL, state: ACTIVE },
...
slot_9: { node_id: N9, duration_ms: 5, priority: LOW, state: ACTIVE },
slot_10: { node_id: None, duration_ms: 5, priority: NONE, state: FREE },
}

固定规则:

  • slot_0 永远分配给Head自己
  • slot_1~slot_9 分配给活跃成员(最多9个)
  • slot_10+ 动态扩展(如果集群超过10节点,延长Data Region)

新节点加入的时隙分配流程

Step 1: 新节点在Reserve窗口广播 SLOT_REQUEST

Node_ID Priority Data_Rate
(1 byte) (1 byte) (1 byte)

Priority: HIGH(0)/NORMAL(1)/LOW(2)
Data_Rate: 期望的吞吐量等级(0=最低, 7=最高)

Step 2: Head收到后运行分配算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 策略A: 有空闲时隙 → 直接分配
free_slot = find_first(Slot_Map, state=FREE)
if free_slot exists:
assign(free_slot, new_node)

# 策略B: 无空闲 → 抢占最低优先级节点
else:
victim = find_min(Slot_Map, key=priority)
if victim.priority < requested.priority:
kick(victim) # 发送 SLOT_REVOKED
assign(victim.slot, new_node)

# 策略C: 无法抢占 → 拒绝
else:
send SLOT_DENIED(node_id, reason=FULL)

Step 3: Head广播 SLOT_ASSIGNMENT(连续3轮重复)

Node_ID Slot_Num Effective Duration
(1 byte) (1 byte) @ Frame N (1 byte)

Effective: 从第N个Superframe开始生效(给节点准备时间)

动态时隙调整(每30秒评估一次)

Head监控每个节点的时隙利用率:

1
2
3
4
5
Utilization[node_id] = {
slots_used: int, # 最近30秒用了多少时隙
slots_allocated:int, # 分配了多少时隙
data_sent_bytes:int, # 实际发送的数据量
}

调整规则:

1
2
3
4
5
6
7
8
9
10
11
for each node in Slot_Map:
util = Utilization[node.id]
ratio = util.slots_used / util.slots_allocated

if ratio < 0.2 for 2 consecutive periods:
reduce_slot_duration(node, new_duration=3ms)

elif ratio > 0.9 for 2 consecutive periods:
donor = find_node(utilization<0.3)
if donor exists:
borrow_time(donor, borrower=node, amount=1ms)

2.5 空时隙处理与节能

空脉冲 (Empty Pulse)

当节点在分配的时隙中没有数据发送:

  • 不发完整帧
  • 只发一个20字节的 EMPTY_PULSE(含timestamp trailer用于P2P同步)
Node_ID Type Checksum Timestamp
(1 byte) 0x00 (2 bytes) (4 bytes)

Head检测到连续N个EMPTY_PULSE:

  • N=3 → 标记节点为 IDLE
  • N=10 → 暂时释放时隙(发送 SLOT_SUSPENDED

SLOT_SUSPENDED

Node_ID Resume_Condition
(1 byte) (1 byte)

Resume_Condition:

  • 0x01 = 发送SLOT_RESUME请求即可恢复
  • 0x02 = 下一个Superframe自动恢复(临时释放)

节能模式 (Power Save)

叶子节点的时间线:

1
2
3
4
5
┌─────┬─────┬─────┬───────────┬─────┬──────────────┐
│Sync │ My │ Rx │ │Ctrl │ │
│Listen│Slot│Window│ SLEEP │Listen│ SLEEP │
│5ms │5ms │10ms │ 430ms │10ms │ (optional) │
└─────┴─────┴─────┴───────────┴─────┴──────────────┘

节能等级:

模式 行为 休眠率
PSM_0 (全活跃) 监听所有时隙 0%休眠,最大吞吐量
PSM_1 (标准) 只监听Sync+MySlot+Ctrl ~86%休眠
PSM_2 (深度) Sync+MySlot,跳过Ctrl ~90%休眠(紧急消息由Head在MySlot中代理通知)

2.6 Head故障与TDMA恢复 — Bully算法选举

场景: Cluster Head离线,整个集群的TDMA调度失效。

检测机制

成员节点维护 head_alive_counter:

1
2
3
4
5
收到 CLUSTER_SYNC → head_alive_counter = 0
每个Superframe结束 → head_alive_counter += 1

if head_alive_counter >= 10: # 5秒未收到sync
trigger HEAD_FAILURE_RECOVERY

恢复流程

Phase 1: 静默 (1个Superframe)

  • 所有节点停止发送数据
  • 只监听Reserve窗口

Phase 2: Head选举 — Bully算法 + 全局Epoch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# epoch基于同步后的时间计算——同一epoch只选一次
election_epoch = floor(synchronized_time_ms / ELECTION_PERIOD_MS)

候选条件(优先级从高到低):
1. 原Head的副手(如果预选了standby)
2. 链路质量LQI_in最高的节点
3. 剩余电量最多的节点
4. Node_ID最小的节点

选举协议 (Bully算法):
Step 1: 候选者在Reserve窗口广播 HEAD_CANDIDATE(priority, my_id, election_epoch)
Step 2: 如果收到更高优先级的CANDIDATE → 退出竞争
Step 3: 如果在T秒内没收到更高的 → 宣布自己当选
Step 4: 新Head广播 HEAD_ELECTED + 初始Slot_Map(连续3轮重复)

# 防止"两个Head"冲突:
# CLUSTER_SYNC中携带 head_authority = (head_id, election_epoch)
# epoch相同的情况下,Node_ID小者胜(确定性规则)

Phase 3: 时隙重分配 (2~3个Superframes)

新Head不知道旧Head的Slot_Map,需要重建:

  1. 广播 SLOT_REENUMERATION — “所有节点在Reserve窗口报告自己”
  2. 节点依次在Reserve窗口发 NODE_PRESENT
  3. Head收集完所有节点后构建新Slot_Map
  4. 广播完整 Slot_Map(压缩格式,连续3轮重复)

Phase 4: 恢复正常TDMA操作

总恢复时间: 58秒(10~16个Superframes)

旧Head回归处理

如果原Head在选举完成后重新上线:

  • 它听到新Head的CLUSTER_SYNC
  • 意识到自己不再是Head(head_authority中的epoch更高或Node_ID更大)
  • 作为普通成员加入,发送JOIN_REQUEST
  • 被分配一个叶子节点时隙

十四、自适应FEC编解码实现细节

3.1 Reed-Solomon编码基础(适配嵌入式环境)

RS码的核心数学: 在有限域 GF(2^m) 上的多项式运算。

为什么选GF(2^8)?

  • 符号大小 = 1字节 → 与网络包边界对齐
  • 硬件友好(XOR运算代替乘法/除法)
  • 300节点Mesh中单块64~256字节,GF(2^8)足够

RS(n, k)码的参数含义

1
2
3
n = 总符号数(编码后)
k = 数据符号数(原始)
n-k = 校验符号数 = 可纠正的最大错误数

纠错能力: 最多纠正 t = (n-k)/2 个符号错误或恢复 erasure(已知位置丢失)最多 n-k 个

我们的场景是 ERASURE模式(知道哪些块丢了,不是错了)→ 可以恢复最多 n-k 个丢失的块

3.2 自适应FEC参数配置

v4改进: FEC参数动态调整

不再固定使用RS(7,4)——Head根据观测到的解码成功率动态调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Head监控每个节点的FEC解码成功率
FEC_Monitor[node_id] = {
groups_sent: int, # 发送了多少组
groups_decoded: int, # 成功解码多少组(从HELLO中的missing_bitmap推断)
success_rate: float, # EWMA平滑的成功率
}

# 每30秒评估一次
def adjust_fec_params(node_id):
monitor = FEC_Monitor[node_id]
rate = monitor.success_rate

if rate > 0.99 for 5 consecutive periods:
# 链路很好,降低冗余 → RS(5,4) (25% overhead)
return RS_PARAMS(n=5, k=4)

elif rate > 0.95 for 3 consecutive periods:
# 链路正常 → RS(7,4) (75% overhead) — 默认值
return RS_PARAMS(n=7, k=4)

elif rate < 0.85:
# 链路恶化,增加冗余 → RS(9,4) (125% overhead)
return RS_PARAMS(n=9, k=4)

else:
# 保持当前参数
return current_params[node_id]

# Head在CLUSTER_SYNC中广播当前FEC参数
# 所有成员自动跟随

三层FEC的参数配置(默认值)

层级 n k 块大小 冗余率 纠错能力 自适应范围
L0 (集群内) 7 4 64 bytes 75% 丢3/7块 n=5~9, k=4
L1 (Head间) 6 3 128 bytes 50% 丢3/6块 固定
L2 (BN间) 4 2 256 bytes 50% 丢2/4块 固定

为什么只有L0自适应? L1/L2的Head和BN是固定设备,链路质量相对稳定。L0的叶子节点可能移动、遮挡,需要动态调整。

可选: Raptor/Fountain码(替代方案)

RS码的问题:接收方必须收到特定数量的块(≥k个)。Fountain码的优势:生成无限编码符号,接收方收集任意足够数量即可。

1
2
3
4
5
6
7
8
在广播环境下特别有用:
- 发送方持续发射编码符号
- 接收方不需要"对齐"到特定的FEC组
- 天然支持网络编码(每个符号都是线性组合)

Raptor码的编码/解码复杂度O(n),和RS一样低,但更灵活。

适用场景: 链路质量波动剧烈、节点移动频繁的环境

3.3 FEC块的传输与重组协议

L0层FEC传输的完整流程

发送端 (叶子节点 Node A):

应用数据: 200 bytes

Step 1: 填充到k×block_size的整数倍

1
2
3
4
k=4, block_size=64 → 需要 256 bytes
padding = 256 - 200 = 56 bytes (零填充)

在包头中记录 original_length = 200

Step 2: RS(n,k)编码(n,k由自适应参数决定)

1
2
3
4
5
6
7
8
data_blocks = [
data[0..63], # d[0]
data[64..127], # d[1]
data[128..191], # d[2]
data[192..255], # d[3] (含padding)
]

parity_blocks = rs_encode(data_blocks, n=current_n, k=4)

Step 3: 生成FEC组,分配Group_ID

1
2
3
4
5
6
7
8
9
group_id = next_group_counter()  # 全网唯一(src_id + seq)

fec_blocks = [
{group_id, index=0, type=DATA, data=d[0]},
...
{group_id, index=k-1, type=DATA, data=d[k-1]},
{group_id, index=k, type=PARITY, data=p[0]},
...
]

Step 4: 在TDMA时隙中发送 — 盲重复策略(替代NACK)

每个FEC块 = 1个协议包。节点A的5ms时隙只能发 ~1个包 → 需要多个Superframe才能发完n个块。

v4改进: 盲重复代替即时NACK

1
2
3
4
5
6
7
8
9
# v3方案: 发送方等NACK再重传 — 但NACK本身可能丢失,违反无ACK原则
# v4方案: 不管收没收到反馈,按固定策略盲重复

Round 1: [0, 1, 2, ..., n-1] # 全量发送 (SF N~N+n-1)
Round 2: [parity_blocks优先, data中LQI最低的] # 智能重传
Round 3: [0, 1, 2, ..., n-1] # 全量重发

# Round 2的优先级由本地LQI估计决定——不需要任何反馈
# 发送方用lq_in[receiver]估计每个接收方的链路质量

FEC包头格式

Src_ID Group_ID Index Total Type Data
(1 byte) (2 bytes) (1b) (1 byte) (1b) (64 bytes)

Type: DATA(0x00) / PARITY(0x01)。解码时不需要区分——RS解码器对所有符号一视同仁。

接收端 (同集群的 Node D)

Step 1: 监听并收集FEC块

1
2
3
4
5
6
7
8
9
fec_cache[group_id] = {
blocks: {index → data}, # 已收到的块
src_id: src_id,
received_at: [timestamps],
}

收到 FEC_BLOCK 时:
if block.index not in cache.blocks:
cache.blocks[block.index] = block.data

Step 2: 检查是否可以解码

1
2
if len(cache.blocks) >= k:
decode_and_deliver(cache)

Step 3: 解码(高斯消元法)

1
2
3
4
5
6
received_indices = sorted(cache.blocks.keys())  # e.g., [0, 2, 4, 5]
received_data = [cache.blocks[i] for i in received_indices]

H = build_receive_matrix(G_n_k, received_indices)
H_inv = gaussian_eliminate_inverse(H, field=GF(2^8))
original_data = matrix_vector_multiply(H_inv, received_data)

Step 4: 去除padding,交付应用层

1
2
actual_data = original_data[:original_length]
deliver_to_application(actual_data)

Step 5: 超时清理 + 聚合式慢速反馈(替代即时NACK)

1
2
3
4
5
6
7
8
9
10
11
if now() - cache.received_at[0] > FEC_TIMEOUT (30秒):
if len(cache.blocks) < k:
# 不发送即时NACK — 改为在下一个HELLO中携带missing bitmap
missing_bitmap = build_missing_bitmap(cache.blocks, n)
add_to_next_hello(missing_bitmap)

delete cache

# HELLO本来就要周期性发送——零额外开销
# Head收集所有成员的missing bitmap → 调整下一轮的FEC策略
# 这不是实时重传请求,而是慢速反馈循环(~30秒延迟)

3.4 网络编码 (Network Coding) 增强

传统FEC的问题: 每个接收方需要独立收集足够的FEC块。中间节点只是被动转发,没有利用已收到的信息。

网络编码的核心思想: 中间节点将收到的多个包做线性组合后广播,接收方用任意k个线性无关的组合包就能解码。

编码规则

中间节点B收到了来自A的FEC块: b[2] 和 b[5]

B在自己的TDMA时隙广播:

Src_ID Group_ID Codec Coeffs Encoded_Data
(1 byte) (2 bytes) (1 byte) (k bytes) (64 bytes)

Codec: 标识这是网络编码包(vs 原始FEC块)
Coeffs: [c_0, c_1, …, c_(k-1)] ∈ GF(2^8),表示 Encoded_Data = Σ c_i × original_block[i]

示例:

1
2
3
4
B收到 b[2] 和 b[5]

encoded = b[2] XOR b[5]
B广播: NC_BLOCK(src=A, group=G, coeffs=[0,0,1,0,1,0,0], data=encoded)

接收方处理NC_BLOCK

维护接收矩阵 R (m×k,m是已收到的包数):

1
2
3
4
R = [row_1]    D = [data_1]
[row_2] [data_2]
[...] [...]
[row_m] [data_m]

解码条件: rank(R) >= k

收益: 在链路质量差的场景下,NC可以将有效接收率提升30-50%。


十五、应用层设计建议

核心观点: 应用层对丢包的容忍度比网络层优化更重要

在无ACK广播环境下,网络层能做的是”尽力而为地提高交付概率”——但真正的可靠性来自应用层的设计。

推荐的应用层模式

1. CRDT (Conflict-free Replicated Data Type)

1
2
3
4
5
6
7
8
9
10
- 每个节点维护本地状态副本
- 通过广播传播状态更新(带vector clock)
- 冲突用预定义的merge函数解决(如max, min, union, sum)
- 不需要ACK——最终会收敛到一致状态
- 适用场景: 计数器、集合、键值存储、传感器聚合

示例: 温度监控
每个节点广播 {node_id, timestamp, temperature}
Head聚合: max(temperature) over last N seconds
即使丢包,最终一致性保证结果正确

2. 事件溯源 + 序列号去重

1
2
3
4
5
- 每条消息带全局递增的sequence number
- 接收方维护last_seen_seq[sender]
- 重复包自动丢弃(seq <= last_seen)
- 缺失包由后续包的上下文推断或忽略
- 适用场景: 日志同步、配置分发、命令队列

3. 批量聚合发送

1
2
3
4
- 不逐条发消息——攒够N条或T秒后打包发送
- 大包用强FEC保护(RS(9,4))
- 比小包+弱FEC更可靠、更高效
- 适用场景: 传感器数据上报、批量配置更新

十六、架构级优化建议

多信道/频分复用(如果硬件支持)

方案A: 控制面和数据面分离到不同信道

1
2
3
4
Channel 1 (固定): 所有控制消息(HELLO, SYNC, ROUTE_ADVERT)
Channel 2-N (动态): TDMA数据时隙

优势:控制消息不和数据竞争,Head在Channel 1上稳定监听

方案B: 集群间用不同信道减少干扰

1
2
3
4
5
6
Cluster A1 → Channel 2
Cluster A2 → Channel 3
Cluster A3 → Channel 4

优势:相邻簇用不同信道 → 空间复用率提升3-5倍
要求:Head需要支持快速信道切换(<1ms)