【分析笔记】SiliconLabs EFR32BG22 Bluetooth Mesh SensorClient 源码分析

包子航
2023-12-01

硬件环境: 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

一、sl_btmesh_evt_node_initialized_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"

二、sl_btmesh_evt_sensor_client_descriptor_status_id

服务器节点响应事件, 服务器如果节点不存在, 则不会有此事件 

分析总结:
1. 当启动搜索传感器的时候,若有节点响应,才会触发此事件。
2. 会检查当前的节点属性是否为感兴趣的温度传感器。
3. 并且检查该节点是否在设备列表内,不在就加入到设备列表内。
4. 这设备列表很重要,只有在这列表内的节点的数据才不会被忽略。

if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
    && !mesh_address_already_exists(&registered_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(&registered_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);

三、sl_btmesh_evt_sensor_client_status_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(&registered_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(&registered_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 修饰的函数即可。

 类似资料: