Access Layer 访问层
章节定位
Week3 已覆盖Bearer 承载层 → Network 网络层 → Lower Transport 底层传输层 → Upper Transport 上层传输层(应用安全层)。
本章从Access Layer开始,向上衔接 Foundation Model 与业务 Model。Access 层是传输层与模型层之间的中间层:不负责加解密(Upper Transport 已解密),也不执行业务逻辑(Model 层负责),核心工作是解析 Access PDU、按地址与 Opcode 路由到正确的 Model 实例。
┌─────────────────────────────────────────────────────────┐ │ Model Layer 业务模型层(OnOff、Lightness…) │ ├─────────────────────────────────────────────────────────┤ │ Foundation Model 基础模型层(Config、Health…) │ ├─────────────────────────────────────────────────────────┤ │ Access Layer ◀──── 本章:Opcode 解析、Element/Model 路由│ ├─────────────────────────────────────────────────────────┤ │ Upper Transport AppKey/DevKey 加密、TransMIC │ ├─────────────────────────────────────────────────────────┤ │ Lower Transport 分段重组、Segment ACK │ ├─────────────────────────────────────────────────────────┤ │ Network Layer NetKey、Relay、IV Index │ ├─────────────────────────────────────────────────────────┤ │ Bearer Layer ADV / GATT │ └─────────────────────────────────────────────────────────┘Access 层一句话
Upper Transport 解密后得到明文 Access PDU;Access 层按DST 地址 + Opcode查Element → Model表,把消息交给对应 Model 的 handler 处理。
一、Access PDU 格式
Access PDU 是 Upper Transport 解密后的应用层可见明文,结构固定:
Access PDU ┌─────────────┬──────────────────────────────────┐ │ Opcode │ Parameters(可选) │ │ 1 ~ 3 字节 │ 0 ~ 多字节,由消息定义决定 │ └─────────────┴──────────────────────────────────┘| 字段 | 作用 |
|---|---|
| Opcode | 消息类型 ID;Access 层据此匹配 Model 的 handler |
| Parameters | 载荷;Set 写入 State,Get 通常为空,Status 从 State 读出 |
Opcode 编码规则
| 长度 | 取值范围 | 归属 | 进 Access 层? |
|---|---|---|---|
| 1 字节 | 0x00~0x7F | 由 Network PDU 的 CTL 位决定归属:CTL=1 → Transport Control;CTL=0 → Foundation 层 Access 消息 | CTL=1不进;CTL=0进 |
| 2 字节 | 0x8000~0xBFFF | Access 层 SIG 消息(业务 Model 全部 + Foundation 大部分),CTL=0 | 进 |
| 3 字节 | 0xC0xxxx | Access 层厂商自定义:0xC0+ Vendor Opcode + Company ID,CTL=0 | 进(查vnd_models[]) |
关键区分:区分 Control vs Access 的不是 Opcode 长度,是 Network PDU 的 CTL 位。1 字节 Opcode 既可能是 Transport Control(CTL=1,Transport 层自处理),也可能是 Foundation 层 Access 消息(CTL=0,进 Access 层查 Model handler)。Access 层会收到 1/2/3 字节 Opcode 都可能——只有 CTL=1 的 Control 消息才不进 Access。
1 字节 Opcode 的两种归属
1 字节 Opcode 空间(0x00~0x7F)被 SIG分给两类完全不同的消息,靠 CTL 位区分:
第一类:Transport Control(CTL=1)—— 不进 Access 层
源码transport.h:23:
#define TRANS_CTL_OP_ACK 0x00 #define TRANS_CTL_OP_FRIEND_POLL 0x01 #define TRANS_CTL_OP_FRIEND_UPDATE 0x02 #define TRANS_CTL_OP_FRIEND_REQ 0x03 #define TRANS_CTL_OP_FRIEND_OFFER 0x04 #define TRANS_CTL_OP_FRIEND_CLEAR 0x05 #define TRANS_CTL_OP_FRIEND_CLEAR_CFM 0x06 #define TRANS_CTL_OP_FRIEND_SUB_ADD 0x07 #define TRANS_CTL_OP_FRIEND_SUB_REM 0x08 #define TRANS_CTL_OP_FRIEND_SUB_CFM 0x09 #define TRANS_CTL_OP_HEARTBEAT 0x0a| Opcode | 名称 | 方向 | 用途 |
|---|---|---|---|
| 0x00 | Segment ACK | 接收方 → 发送方 | 分段消息确认,携带 BlockAck 位图 |
| 0x01 | Friend Poll | LPN → Friend | 低功耗节点轮询 Friend 是否有缓存消息 |
| 0x02 | Friend Update | Friend → LPN | 回复 Poll,告知是否有消息 + 当前 IV Index/Flags |
| 0x03 | Friend Request | LPN → 周边 | 广播找 Friend,携带 ReceiveWindow/QueueSize 等参数 |
| 0x04 | Friend Offer | 候选 Friend → LPN | 应答 Request,告知自身缓存能力 |
| 0x05 | Friend Clear | 新 Friend → 旧 Friend | LPN 换 Friend 时,新 Friend 通知旧 Friend 释放 |
| 0x06 | Friend Clear Confirm | 旧 Friend → 新 Friend | 确认释放,握手完成 |
| 0x07 | Friend Subscription Add | LPN → Friend | LPN 让 Friend 帮它监听新增的 Group/Virtual 地址 |
| 0x08 | Friend Subscription Remove | LPN → Friend | LPN 让 Friend 取消监听某些地址 |
| 0x09 | Friend Subscription Confirm | Friend → LPN | 确认订阅列表已更新 |
| 0x0A | Heartbeat | 节点 → 周边 | 周期心跳,携带 Active Relay/Proxy/Feature 等位图 + TTL |
第二类:Foundation 层 Access 消息(CTL=0)—— 进 Access 层
源码foundation.h:12:
#define OP_APP_KEY_ADD BT_MESH_MODEL_OP_1(0x00) #define OP_APP_KEY_UPDATE BT_MESH_MODEL_OP_1(0x01) #define OP_DEV_COMP_DATA_STATUS BT_MESH_MODEL_OP_1(0x02) #define OP_MOD_PUB_SET BT_MESH_MODEL_OP_1(0x03) #define OP_HEALTH_CURRENT_STATUS BT_MESH_MODEL_OP_1(0x04) #define OP_HEALTH_FAULT_STATUS BT_MESH_MODEL_OP_1(0x05) #define OP_HEARTBEAT_PUB_STATUS BT_MESH_MODEL_OP_1(0x06)| Opcode | 名称 | 方向 | 类型 | 用途 |
|---|---|---|---|---|
| 0x00 | Config AppKey Add | Client → Server | 命令 | 添加 AppKey(最高频 Config 命令,故给短 Opcode) |
| 0x01 | Config AppKey Update | Client → Server | 命令 | 更新 AppKey(Key Refresh 用) |
| 0x02 | Config Composition Data Status | Server → Client | Status | 回复 Composition Data Get,携带完整 Composition |
| 0x03 | Config Model Publication Set | Client → Server | 命令 | 设置 Model Publish 参数 |
| 0x04 | Health Current Status | Server → Client | Status | 上报当前 Health Fault 列表 |
| 0x05 | Health Fault Status | Server → Client | Status | 回复 Health Fault Get/Test,携带 Fault 列表 |
| 0x06 | Config Heartbeat Publication Status | Server → Client | Status | 回复 Heartbeat Pub Get/Set,携带发布配置 |
重要观察:
- 这 7 个 1 字节 Access Opcode全部属于 Foundation 层(Config + Health + Heartbeat),业务 Model(Generic/Lighting/Sensor 等)没有任何 1 字节 Opcode——业务消息全部用 2 字节(0x82xx 系列)
- Foundation 层也不是全部用 1 字节:只有这 7 个高频消息用短 Opcode,其余(Composition Data Get 0x8008、AppKey Delete 0x8000、Beacon Get 0x8009 等)仍用 2 字节
- 1 字节空间混合了命令和 Status:0x00/0x01/0x03 是命令,0x02/0x04/0x05/0x06 是 Status
三类 Opcode 归属对比(修正版)
| 维度 | 1 字节(CTL=1)Control | 1 字节(CTL=0)Foundation | 2 字节(CTL=0)SIG 业务 | 3 字节(CTL=0)Vendor |
|---|---|---|---|---|
| 归属层 | Transport 层 Control | Access 层 Foundation Model | Access 层 SIG 业务 Model | Access 层厂商 Model |
| 加密 | NetKey 加密即可,不加密 AppKey | AppKey/DevKey 加密 + TransMIC | AppKey/DevKey 加密 + TransMIC | AppKey/DevKey 加密 + TransMIC |
| 是否进 Access 层 | 不进,Transport 层自己处理 | 进,查 Config/Health Server 的op[] | 进,查业务 Model 的op[] | 进,查vnd_models[] |
| 谁定义 | Mesh Profile Spec §3.4.x | Mesh Profile Spec §5.4(Foundation) | Mesh Model Spec(各 Model 章节) | 厂商自定义(Company ID 区分) |
| 消息类型 | ACK/Friendship/Heartbeat | Config/Health/Heartbeat Pub 的高频消息 | OnOff Set、Lightness Get 等业务 | 厂商私有业务 |
| 路由方式 | Transport 层按ctl_opswitch,不经 Model handler | Access 层find_op()→ Config/Health Server handler | Access 层find_op()→ 业务 Model handler | 同左,但查vnd_models[] |
| 例子 | 0x00 ACK、0x01 Friend Poll | 0x00 AppKey Add、0x02 Comp Data Status | 0x8202 OnOff Set、0x8008 Comp Data Get | 0xC0xxxx 厂商私有 |
源码证据:CTL=1 的 Control 消息在 Transport 层直接按ctl_opswitch 分发,根本不调用bt_mesh_model_recv():
case TRANS_CTL_OP_ACK: /* ... 处理 Segment ACK ... */ break; /* ... */ switch (ctl_op) { case TRANS_CTL_OP_FRIEND_POLL: /* ... */ case TRANS_CTL_OP_FRIEND_REQ: /* ... */ case TRANS_CTL_OP_FRIEND_CLEAR: /* ... */ case TRANS_CTL_OP_FRIEND_CLEAR_CFM: /* ... */ case TRANS_CTL_OP_FRIEND_SUB_ADD: /* ... */ case TRANS_CTL_OP_