【蓝牙精品系列文档】【04_blemesh model】03_业务模型层

发布时间:2026/7/15 20:37:48
【蓝牙精品系列文档】【04_blemesh model】03_业务模型层 Model Layer 业务模型层章节定位Model Layer 是 Mesh 协议栈最顶层的应用层,承载具体业务语义:开关、调光、色温、传感器、场景等。┌─────────────────────────────────────────────────────────┐ │ Model Layer ◀──── 本章:Generic / Lighting / Vendor │ ├─────────────────────────────────────────────────────────┤ │ Foundation Model Layer Config、Health(网管) │ ├─────────────────────────────────────────────────────────┤ │ Access Layer Opcode 路由、Publish/Subscribe │ └─────────────────────────────────────────────────────────┘两类 Model类型Model ID 长度定义方示例SIG Model16 位Bluetooth SIG Mesh Model SpecGeneric OnOff、Light LightnessVendor Model32 位= Company ID(16) + 厂商自定(16)厂商(Company ID 由 SIG 分配)私有场景、产测指令SIG Model ID 区间划分(16 位,有规律):区间用途代表 ID(Nimble 宏)0x0000~0x0005Foundation(网管必装)CFG_SRV=0x0000、HEALTH_SRV=0x0002、REMOTE_PROV_SRV=0x00040x1000~0x1015Generic(通用业务)GEN_ONOFF_SRV=0x1000、GEN_LEVEL_SRV=0x1002、GEN_POWER_ONOFF_SRV=0x10060x1100~0x1102SensorSENSOR_SRV=0x11000x1200~0x1208Time / Scene / SchedulerTIME_SRV=0x1200、SCENE_SRV=0x1203、SCHEDULER_SRV=0x12060x1300~0x1311Lighting(照明专用)LIGHT_LIGHTNESS_SRV=0x1300、LIGHT_CTL_SRV=0x1303、LIGHT_HSL_SRV=0x1307记忆口诀:0x0000 网管 / 0x1000 通用 / 0x1100 传感 / 0x1200 时空 / 0x1300 照明。每个 Model 通常成对定义 Server / Setup Server / Client,ID 连续递增:Light Lightness: 0x1300 Server / 0x1301 Setup Server / 0x1302 Client Light CTL: 0x1303 Server / 0x1304 Setup Server / 0x1305 Client / 0x1306 Temperature ServerSetup Server负责"只配一次"的默认值/范围 State(Default、Range),与主 Server 共享 State 但分开 Opcode,避免主 Server 的 Set 被滥用改默认值。Vendor Model ID 编码(32 位):┌──────────────────┬──────────────────┐ │ Company ID 16B │ Vendor Model 16B │ │ SIG 分配给厂商 │ 厂商自分配 │ └──────────────────┴──────────────────┘Company ID 示例:海思0x01AC、Nordic0x0059、Telink0x0212Vendor Model 与 SIG Model挂在同一 Element 下,但走vnd_models[]数组Vendor Opcode 用 3 字节编码:(0xC0~0xFF) 16 | Company ID需同一厂商的 Client/Server 成对才能互操作;跨厂商一般不认两类 Model 对照:维度SIG ModelVendor ModelID 长度16 位32 位(CID + 厂商 16 位)分配方Bluetooth SIG厂商(CID 由 SIG 分配)互操作跨厂商通用需同厂商配对Composition Data 中16-bit 字段32-bit 字段Opcode 长度1 或 2 字节(0x00–0xBFFF)3 字节(0xC0xxxx,含 Company ID)Nimble 注册宏BT_MESH_MODEL(id, op, pub, data)BT_MESH_MODEL_VND(company, id, op, pub, data)存放数组elem-models[]elem-vnd_models[]典型场景标准灯控、传感器、场景私有联动、产测、云平台私有属性前置阅读:Access 层路由与 Publish/Subscribe →01_访问层.mdConfig 绑 Key、设订阅 →02_基础模型层.md一个 Element 挂多 Model 的路由影响 →01_访问层.md「2. Model 表」一、Model 的基本形态1.1 Server / Client类型State 表行为典型部署Server有,维护状态响应 Get/Set,回复 Status灯、传感器、执行器Client通常无发送 Get/Set,不保存状态开关、手机 App、网关墙壁开关 (Client) 灯 (Server) │── OnOff Set Unack ────────│ 写 State: OnOff=1 │ │ (Unack 不回 Status) │── OnOff Get ──────────────│ 读 State │── OnOff Status ───────────│1.2 Model 扩展(extends)extends 是 SIG 在 Mesh Model Spec 里官方定义的机制,不是厂商自由组合。它解决一个问题:让一个复杂 Model(如 Light Lightness)复用基础 Model(如 Generic OnOff)的能力,而不必让设备同时实例化一堆基础 Model。Light Lightness Server (0x1300) ├── extends Generic Power OnOff Server │ └── extends Generic OnOff Server ← 开关 State ├── extends Generic Level Server ← Level 机制 └── Light Lightness Setup Server ← 默认/范围配置扩展的含义:收到Generic OnOff Set时,Light Lightness Server同样能处理,并联动亮度 State。1.2.1 规范层:继承关系是 SIG 硬绑定的Mesh Model Spec 在每个 Model 的章节里会明确写一句话:“This model extends XXX model”。这条声明是规范条款,不是可选注释。规范要求的义务:子 Model 必须能处理被它 extends 的父 Model 的所有消息(Opcode 集合是父 Model Opcode 的超集)。所以 Light Lightness Server 收到Generic OnOff Set(0x8202)必须能响应,并联动自己的 Lightness Actual State。反向不成立:Generic OnOff Server 不处理 Light Lightness 的 Opcode。extends 是单向继承。extends 是"能力继承",不要求父 Model 实例真实存在。照明设备通常不实例化Generic OnOff Server,而是让 Light Lightness Server 直接"接管" OnOff 的 Opcode(见 1.2.5)。1.2.2 实现层:Nimble 怎么把绑定落到代码Nimble(以及海思 SDK 同源 API)提供运行时 API把两个 Model 实例绑定起来:intbt_mesh_model_extend(structbt_mesh_model*extending_mod,structbt_mesh_model*base_mod);源码在nimble/host/mesh/src/access.c:837。它做的事很简单——把两个 Model 用model-next指针串成循环链表:int bt_mesh_model_extend(struct bt_mesh_model *extending_mod, struct bt_mesh_model *base_mod) { struct bt_mesh_model *a = extending_mod; struct bt_mesh_model *b = base_mod; struct bt_mesh_model *a_next = a-next; struct bt_mesh_model *b_next = b-next; struct bt_mesh_model *it; base_mod-flags |= BT_MESH_MOD_EXTENDED; // ... 去重检查 ... /* Merge lists */ if (a_next) { b-next = a_next; } else { b-next = a; } if (b_next) { a-next = b_next; } else { a-next = b; } return 0; }access.h的 API 注释说得很清楚:All models in an extension list share one subscription list per element. The access layer will utilize the combined subscription list of all models in an extension list and element, giving the models extended subscription list capacity.1.2.3 extends 共享/继承的 5 类内容(只有 1 类自动)把规范要求和 Nimble 实现合起来看,extends 关系会带来5 类共享,只有 1 类是协议栈自动做的,其余都要厂商代码或 Provisioner 配合。这是面试加分点。#共享内容谁来做自动?说明1Subscription List(Group/Virtual 订阅)协议栈 Access 层✅ 自动bt_mesh_model_extend()串链表 +bt_mesh_model_extensions_walk()遍历,扩展链上所有 Model 共享一份订阅能力2Opcode handler(消息处理能力)厂商固件❌ 手动子 Model 必须在自己的op[]里注册父 Model 的全部 Opcode,并实现联动 handler(规范义务)3State(状态数据)厂商固件❌ 手动子 Model 维护自己的 State 表,但必须保证与父 Model State 语义一致(如 OnOff=1 ↔ Lightness Actual = 上次值,OnOff=0 ↔ Lightness = 0)4AppKey 绑定(model-keys[])Provisioner 下发❌ 手动(配置层)规范要求扩展链上所有 Model必须绑定同一组 AppKey,否则 Access 层bt_mesh_model_has_key()逐 Model 校验会丢消息5Publish Address / Publish PeriodProvisioner 下发❌ 手动(配置层)规范不强制共享;产品里通常让最具体的子 Model负责发更丰富的 Status自动共享的只有 Subscription——这是协议栈唯一"调一次 API 就白送"的东西。源码证据在access.c:813的bt_mesh_model_extensions_walk(),它被bt_mesh_model_find_group()调用:uint16_t *bt_mesh_model_find_group(struct bt_mesh_model **mod, uint16_t addr) { struct find_group_visitor_ctx ctx = { .mod = *mod, .entry = NULL, .addr = addr, }; bt_mesh_model_extensions_walk(*mod, find_group_mod_visitor, ctx); *mod = ctx.mod; return ctx.entry; }好处:扩展链上 N 个 Model,每个 Model 自己的groups[]数组容量有限(Nimble 默认CONFIG_BT_MESH_MODEL_GROUP_COUNT),但共享后等效订阅容量 = 各 Model 容量之和——这是 SIG 设计 extends 的核心动机之一,让复杂 Model(如 Light HSL,要 extends 7~8 个 Generic Model)不必挤在一个 Model 的订阅表里。规范原文(Mesh Model Spec 1.1):All models in an extension list share one subscription list per element. The access layer will utilize the combined subscription list of all models in an extension list and element, giving the models extended subscription list capacity.译文:一个扩展列表(extension list)中的所有模型,在每个 element 上共享同一个订阅列表。访问层会使用该扩展列表中所有模型在每个 element 上的订阅列表的合集,从而为这些模型提供扩展后的订阅列表容量。1.2.3.1 调光灯实例:A、B、C 三个互相扩展的 Model用一盏调光灯的 element 来落地"扩展链共享订阅列表"。这个 element 上挂三个 SIG Model:代号ModelModel ID职责AGeneric OnOff Server0x1000开 / 关BLight Lightness Server0x1300亮度调节CLight CTL Server0x1303色温 + 亮度调节扩展关系(SIG 规范硬绑定,单向继承):Light CTL Server (C) └─ extends ─► Light Lightness Server (B) └─ extends ─► Generic OnOff Server (A)为什么要扩展:这三者本来就是功能嵌套——调色温时需要"亮度"作为输出载体(CTL Server 自己不持有亮度 State,绑定引用 Lightness Server 的Light Lightness Actual),调亮度时开/关状态也要联动(Lightness 变 0 时 OnOff 自动变关)。通过 extends 声明,Mesh 让它们在同 element 上共享订阅列表、统一派发语义,避免一条消息触发三次、避免状态分裂。CTL Server 真正持有的 State 只有CTL Temperature和CTL Delta UV。规范里的CTL Lightness是一个绑定 State(bound state),不独立存储,它的值 = Light Lightness Server 的Light Lightness Actual。规范原文:“The CTL Lightness state is bound to the Light Lightness Actual state… any change to either state causes a corresponding change in the other.” 这正是 CTL Server 必须 extends Light Lightness Server 的根本原因——借对方的 Lightness Actual 当自己的 CTL Lightness。订阅列表合并的实际效果(假设单 Model 订阅上限 10 个组地址):A (Generic OnOff Server) 订阅:[客厅组, 走廊组] B (Light Lightness Server) 订阅:[客厅组, 卧室组, 全屋组] C (Light CTL Server) 订阅:[客厅组, 餐厅组, 氛围灯组, 影院组]合并后取并集:扩展链共享订阅列表 = {客厅组, 走廊组, 卧室组, 全屋组, 餐厅组, 氛围灯组, 影院组} ← 7 个槽一条发给"客厅组"的消息 → A、B、C 都收到并各自联动一条发给"影院组"的消息 → A 自己没订阅,但因为 C 订阅了且 C 在扩展链里,整条链都激活,A、B 也能感知这盏灯只占了 7 个订阅槽,而不是各自独立存 2+3+4=9 个槽——这就是"extended subscription list capacity"1.2.3.2 对照组:没扩展关系的 Model 不共享同一个 element 上挂三个没有 extends 关系的 Model:A = Generic OnOff Serv