硬件环境: SLTB010A(BRD4184A Rev A02 / EFR32BG22C224F512IM40)
软件环境: SimplicityStudio5/gecko_sdk_3.2.3
分析工程: Bluetooth Mesh SensorClient
恶补了 BluetoothMesh 相关知识,首次接触 SiliconLabs 芯片,搜全网,中文资料少的可怜,又一人狂啃了官方很多全英文文档,搞明白了烧录、编译、调试等基础知识,但是对软件开发方式还是云里雾里,然后着手分析源码才大致了解 SiliconLabs 的源码框架,总算是知道怎么玩这颗芯片了。
本章文章主要是分析设备已经入网之后的 SensorClinet 上电运行过程,应该是全网独一份。
main 有个 while(1), 不停的查询和从事件队列里面获取 mesh 事件,然后再根据不同的事件进行不同的处理。
void sl_btmesh_step(void)
{
sl_btmesh_msg_t evt;
uint32_t event_len = sl_btmesh_event_pending_len();
// For preventing from data loss, the event will be kept in the stack's queue
// if application cannot process it at the moment.
if ((event_len == 0) || (!sl_btmesh_can_process_event(event_len))) {
return;
}
// Pop (non-blocking) a Bluetooth stack event from event queue.
sl_status_t status = sl_btmesh_pop_event(&evt);
if(status != SL_STATUS_OK){
return;
}
sl_btmesh_process_event(&evt);
}
void sl_btmesh_process_event(sl_btmesh_msg_t *evt)
{
sl_btmesh_handle_btmesh_logging_events(evt);
sl_btmesh_handle_provisioning_decorator_event(evt);
sl_btmesh_factory_reset_on_event(evt);
sl_btmesh_handle_sensor_client_on_event(evt);
sl_btmesh_on_event(evt);
}
主要分析三个事件,在已经入网的情况下,并且有设备节点响应,就只有三个事件:
1. sl_btmesh_evt_node_initialized_id
2. sl_btmesh_evt_sensor_client_descriptor_status_id
3. sl_btmesh_evt_sensor_client_status_id
芯片上电后由蓝牙协议栈完成初始化之后发出的事件(猜测)
分析总结:
1. 如果已经入网, 则初始化传感器客户端模型: sl_btmesh_sensor_client_init();
[sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // 重要:初始化 sensor client 模型
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
mesh_sensor_client_init();
--> sl_btmesh_sensor_client_init(); // 初始化传感器客户端模型。Sensor Client 没有任何内部配置,它只激活蓝牙网格栈中的模型。
2. 如果已经入网, 则启动单次定时器, 如果未入网则发布广播让配置者知道, 等待被配置入网
[app.c]sl_btmesh_on_event(evt); // 重要: 如果已经被配置, 则启动单次定时器, 如果没有被配置则开启广播, 等待配置。
--> case sl_btmesh_evt_node_initialized_id:
--> handle_node_initialized_event(&(evt->data.evt_node_initialized));
if (evt->provisioned) // 如果已经本设备已经被配置(即已入网)
sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
else // 开启广播等待配置入网
sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);
3. 单次定时器会先清空注册的设备列表, 并记录感兴趣的温度传感器ID, 然后启动搜索指定传感器ID,再启动周期2秒的定时器请求数据
[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
registered_devices.count = 0; // 清空已注册设备的列表
memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
registering_property = property; // 将要默认要注册的温度传感器ID放入全局变量 0x004f
sl_btmesh_sensor_client_on_discovery_started(property_id:property); // 这里只输出了打印信息
--> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
// 重点: 启动探索指定传感器 0x004f, 如果有节点响应, 会触发 sl_btmesh_evt_sensor_client_descriptor_status_id 事件
sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc) {
log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
--> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
--> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// 重点: 启动周期为 2 秒的定时器定时发出获取数据的请求
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true);
4. 周期2秒的定时器,会以 2 秒的频率发出获取所有节点的指定传感器数据(这里是温度传感器 0x004f)
[app.c]app_sensor_data_timer_cb
--> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc)
log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"
源码走读:
[main.c]main() -> while(1) // 上电后协议栈会触发该事件
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_node_initialized_id:
app_log("evt:mesh_node_initialized\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 打印输出是否已经被配置过
--> case sl_btmesh_evt_node_initialized_id:
--> sl_btmesh_on_provision_init_status(provisioned, address, iv_index);
--> if (provisioned) // 如果已经本设备已经被配置(即已入网)
app_show_btmesh_node_provisioned(address, iv_index);
--> app_log("BT mesh node is provisioned (address: 0x%04x, iv_index: 0x%lx)\r\n", address, iv_index);
else
app_log("BT mesh node is unprovisioned, started unprovisioned " "beaconing...\r\n");
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // 重要:初始化 sensor client 模型
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
mesh_sensor_client_init();
--> sl_btmesh_sensor_client_init(); // 初始化传感器客户端模型。Sensor Client 没有任何内部配置,它只激活蓝牙网格栈中的模型。
--> [app.c]sl_btmesh_on_event(evt); // 重要: 如果已经被配置, 则启动单次定时器, 如果没有被配置则开启广播, 等待配置。
case sl_btmesh_evt_node_initialized_id:
--> handle_node_initialized_event(&(evt->data.evt_node_initialized));
if (evt->provisioned) // 如果已经本设备已经被配置(即已入网)
sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
else // 开启广播等待配置入网
sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);
主要是启动探索指定传感器并且启动以2秒为周期定时器来请求获取传感器数据
[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
registered_devices.count = 0; // 清空已注册设备的列表
memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
registering_property = property; // 将要默认要注册的温度传感器ID放入全局变量 0x004f
sl_btmesh_sensor_client_on_discovery_started(property_id:property); // 这里只输出了打印信息
--> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
// 重点: 启动探索指定传感器 0x004f, 如果有节点响应, 会触发 sl_btmesh_evt_sensor_client_descriptor_status_id 事件
sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc) {
log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
--> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
--> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// 重点: 启动周期为 2 秒的定时器定时发出获取数据的请求
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true);
周期为 2 秒的定时器定时获取 Server 模型中的所有传感器数据
// 周期为 2 秒的定时器定时获取 Server 模型中的所有传感器数据
[app.c]app_sensor_data_timer_cb
--> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc)
log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"
服务器节点响应事件, 服务器如果节点不存在, 则不会有此事件
分析总结:
1. 当启动搜索传感器的时候,若有节点响应,才会触发此事件。
2. 会检查当前的节点属性是否为感兴趣的温度传感器。
3. 并且检查该节点是否在设备列表内,不在就加入到设备列表内。
4. 这设备列表很重要,只有在这列表内的节点的数据才不会被忽略。
if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
&& !mesh_address_already_exists(®istered_devices, evt->server_address))
registered_devices.address_table[number_of_devices] = evt->server_address;
registered_devices.count = number_of_devices + 1;
源码走读:
[main.c]main() -> while(1) // 该事件由 sl_btmesh_sensor_client_get_descriptor() 触发
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_sensor_client_descriptor_status_id:
app_log("evt:mesh_sensor_client_descriptor_status\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 忽略:该函数未处理这个事件
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt);
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_descriptor_status(&(evt->data.evt_sensor_client_descriptor_status));
--> [sl_btmesh_sensor.c]mesh_lib_sensor_descriptors_from_buf(&descriptor, evt->descriptors.data, SIZE_OF_DESCRIPTOR); // 数据转换为传感器描述符
// 如果当前 mesh 事件的属性ID就是我们要的 registering_property:0x004f, 并且未在设备注册列表内则加入该列表
uint8_t number_of_devices = registered_devices.count;
if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
&& !mesh_address_already_exists(®istered_devices, evt->server_address))
registered_devices.address_table[number_of_devices] = evt->server_address;
registered_devices.count = number_of_devices + 1;
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_device_found(descriptor.property_id, evt->server_address);
--> app_log("BT mesh Sensor Device (address: 0x%04x, property_id: 0x%04x) is found.\r\n", address, property_id);
服务器节点状态响应事件, 服务器如果节点状态未响应或不存在, 则不会有此事件
分析总结:
1. 该事件所附带的数据包可能携带了多个节点数据, 因此需要逐一拆分
2. 过滤不在设备列表内的节点,只留下设备列表内并且是感兴趣的温度传感器数据才会处理。
源码走读:
[main.c]main() -> while(1) // 该事件由 sl_btmesh_sensor_client_get_descriptor() 触发
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_sensor_client_status_id: // 打印输出当前响应的地址是群组地址、单播地址还是虚拟地址
app_log("evt:mesh_sensor_client_status %s\r\n", (evt->data.evt_sensor_client_status.client_address & 0xC000) == 0xC000 ? "(group broadcast)" : (evt->data.evt_sensor_client_status.client_address & 0x1000) == 0 ? "(unicast)" : "(virtual)");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 忽略:该函数未处理这个事件
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // 重点: 这个函数内会提取匹配Server的传感器数据
case sl_btmesh_evt_sensor_client_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_status_id:
--> static void handle_sensor_client_status(sl_btmesh_evt_sensor_client_status_t *evt)
{
uint8_t *sensor_data = evt->sensor_data.data;
uint8_t data_len = evt->sensor_data.len;
uint8_t pos = 0;
while (pos < data_len)
{ // 遍历各个 Server 节点消息
if (data_len - pos > PROPERTY_ID_SIZE) {
mesh_device_properties_t property_id = (mesh_device_properties_t)(sensor_data[pos] + (sensor_data[pos + 1] << 8));
uint8_t property_len = sensor_data[pos + PROPERTY_ID_SIZE];
uint8_t *property_data = NULL;
// 只处理在设备注册表内的 Service 节点消息
if (mesh_address_already_exists(®istered_devices, evt->server_address)) {
sl_btmesh_sensor_client_data_status_t status;
uint16_t address;
uint8_t sensor_idx;
if (property_len && (data_len - pos > PROPERTY_HEADER_SIZE)) {
property_data = &sensor_data[pos + PROPERTY_HEADER_SIZE];
}
address = evt->server_address;
sensor_idx = mesh_get_sensor_index(®istered_devices, address);
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
switch (property_id) {
......
case PRESENT_AMBIENT_TEMPERATURE:
temperature_8_t temperature = SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN;
if (property_len == 1) {
mesh_device_property_t new_property = mesh_sensor_data_from_buf(PRESENT_AMBIENT_TEMPERATURE, property_data);
temperature = new_property.temperature_8;
if (temperature == SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN) {
status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
}
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
}
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_temperature_data(sensor_idx, address, status, temperature);
if (PRESENT_AMBIENT_TEMPERATURE != app_get_current_property()) {
return;
}
if (SL_BTMESH_SENSOR_CLIENT_DATA_VALID == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): %3d.%1d 掳C\r\n", address, INT_TEMP(temperature), FRAC_TEMP(temperature));
} else if (SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): UNKNOWN\r\n", address);
} else {
app_log("BT mesh Sensor Temperature (from 0x%04x): NOT AVAILABLE\r\n", address);
}
break;
......
}
pos += PROPERTY_HEADER_SIZE + property_len;
}
} else {
pos = data_len;
}
}
}
四、按键处理逻辑
以上即是核心的逻辑处理, 至于按键触发的逻辑,有了上面的基础,就简单很多了。
app.c 通过重定义 app_button_press.c 的按键处理回调实现拦截按键事件
[app_button_press.c ]SL_WEAK void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)button;
(void)duration;
}
[app.c]void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)duration;
if (duration == APP_BUTTON_PRESS_NONE) {
return;
}
// button pressed
if (button == BUTTON_PRESS_BUTTON_0) {
if (duration < APP_BUTTON_PRESS_DURATION_LONG) {
app_log("PB0 pressed\r\n");
sensor_client_change_current_property(); // 短按按键则调整感兴趣的传感器类型
} else {
app_log("PB0 long pressed\r\n");
update_registered_devices(); // 长按则重新启动搜索指定传感器ID
}
} else if (button == BUTTON_PRESS_BUTTON_1) {
app_log("PB1 pressed\r\n");
update_registered_devices();
}
}
[app.c]static void sensor_client_change_current_property(void)
{
switch (current_property) {
case PRESENT_AMBIENT_TEMPERATURE:
current_property = PEOPLE_COUNT;
break;
case PEOPLE_COUNT:
current_property = PRESENT_AMBIENT_LIGHT_LEVEL;
break;
case PRESENT_AMBIENT_LIGHT_LEVEL:
current_property = PRESENT_AMBIENT_TEMPERATURE;
break;
default:
app_log("Unsupported property ID change\r\n");
break;
}
}
// 参考 sl_btmesh_evt_node_initialized_id 的第三第四步骤
void update_registered_devices(void)
{
sl_status_t sc;
sl_btmesh_sensor_client_update_registered_devices(current_property);
sc = sl_simple_timer_start(&app_sensor_data_timer,SENSOR_DATA_TIMEOUT, app_sensor_data_timer_cb, NO_CALLBACK_DATA,true);
app_assert_status_f(sc, "Failed to start periodic timer\n");
}
1. 可以发现最后的结果都是在当前工程的 app_out_log.c 输出
2. 而 app_out_log.c 是通过 sl_btmesh_sensor_client.c 里面 SL_WEAK 修饰的接口来截取结果
3. 当工程中某个源文件有与 SL_WEAK 修饰的函数相同时, 编译会只选择编译该源文件的函数实现。
4. 所以要熟悉 sl_btmesh_sensor_client.c 的逻辑实现,需要拿到什么结果就重新实现 SL_WEAK 修饰的函数即可。