[BearPi-Pico H2821]测评 ④SLE server源码解读
本帖最后由 不爱胡萝卜的仓鼠 于 2024-8-21 00:24 编辑<p>今天我们来简单的分析一下SEL server demo的源码,看看他流程上会调用哪些东西,关键的广播数据、服务、特征值、收发数据是对应哪些函数。</p>
<p> </p>
<p>因为时间和篇幅有限,今天的解读会比较粗糙,不会讲的很细。具体细节还是建议大家自己花时间去阅读,我这个还是一个浅层的引导。下面贴出来的代码中部分是我自己加的,有的可能写的不是很好,请见谅</p>
<p> </p>
<p>首先了解一下SDK,整个SDK是跑OS的,用的是LiteOS。</p>
<p>我们这个demo的源码在如下路径</p>
<div style="text-align: center;"></div>
<p>其中“sel_uart.c”是server和client公用的,server自己的代码放在“sel_uart_cliebt”、"sle_uart_server"文件夹中</p>
<p> </p>
<p>我们再来回顾一下server demo的大致流程。模块上电->注册各种回调->开启SLE->配置adv、server等->开始adv->等待client连接->连上后触发连接cb、交换MTU等->与client通讯</p>
<p> </p>
<p>代码首先会进入“sel_uart.c”中的“sle_uart_entry”函数,代码如下<code class="language-cpp"> </code></p>
<pre>
<code class="language-cpp">/**
* @brief SEL UART入口函数(可以认为是SDK正常运行后就会调用的函数)
* @param none
* @return none
*/
static void sle_uart_entry(void)
{
osal_task *task_handle = NULL;
/* 配置时钟,MCU时钟频率配置为64M */
if (uapi_clock_control(CLOCK_CONTROL_FREQ_LEVEL_CONFIG, CLOCK_FREQ_LEVEL_HIGH) == ERRCODE_SUCC)
{
osal_printk("Clock config succ.\r\n");
}
else
{
osal_printk("Clock config fail.\r\n");
}
/* 锁定任务调度(为了避免别的task调度,干扰下面TASK的创建?) */
osal_kthread_lock();
#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER)
/* 注册sel server部分的回调函数 */
sle_dev_register_cbks();
/* 创建server demo的task */
task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_server_task, 0, "SLEUartServerTask",
SLE_UART_TASK_STACK_SIZE);
#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT)
/* 注册sel client部分的回调函数 */
sle_uart_client_sample_dev_cbk_register();
/* 创建client demo的task */
task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_client_task, 0, "SLEUartDongleTask",
SLE_UART_TASK_STACK_SIZE);
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */
if (task_handle != NULL)
{
/* 如果TASK创建成功,设置task优先级 */
osal_kthread_set_priority(task_handle, SLE_UART_TASK_PRIO);
}
else
{
osal_printk("sel uart task create fail!!!\r\n");
}
/* 解锁任务调度 */
osal_kthread_unlock();
}
</code></pre>
<p>这里会配置时钟,然后注册SEL会使用的CB,然后创建串口TASK(我们现在主要关注SLE,所以串口部分我就不讲了)</p>
<p> </p>
<p>对于server demo,我们进入sle_dev_register_cbks();看一下</p>
<pre>
<code class="language-cpp">/**
* @brief 注册SEL的回调函数
* @param none
* @return errcode_t
*/
errcode_t sle_dev_register_cbks(void)
{
errcode_t ret = 0;
sle_dev_manager_callbacks_t dev_mgr_cbks = {0};
dev_mgr_cbks.sle_power_on_cb = sle_power_on_cbk;
dev_mgr_cbks.sle_enable_cb = sle_enable_cbk;
ret = sle_dev_manager_register_callbacks(&dev_mgr_cbks);
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle_dev_register_cbks,register_callbacks fail :%x\r\n",
SLE_UART_SERVER_LOG, ret);
return ret;
}
#if (CORE_NUMS < 2)
/* SLE使能 */
enable_sle();
#endif
return ERRCODE_SLE_SUCCESS;
}</code></pre>
<p>这里会注册SLE管理的回调函数,然后使能SLE</p>
<p>“sle_dev_manager_callbacks_t”结构体里有3个回调:SLE上电回调、SLE使能回调、SLE去使能回调。具体内容如下</p>
<pre>
<code class="language-cpp">/**
* @if Eng
* @brief Struct of SLE device announce callback function.
* @else
* @brief SLE 设备公开回调函数接口定义。
* @endif
*/
typedef struct {
sle_power_on_callback sle_power_on_cb; /*!< @if Eng SLE device power on callback.
@else SLE设备上电回调函数。 @endif */
sle_enable_callback sle_enable_cb; /*!< @if Eng SLE stack enable callback.
@else SLE协议栈使能回调函数。 @endif */
sle_disable_callback sle_disable_cb; /*!< @if Eng SLE stack disable callback.
@else SLE协议栈去使能回调函数。 @endif */
} sle_dev_manager_callbacks_t;
</code></pre>
<p>我们的这个代码中只注册了前2个,因为没有关闭,所以去使能就不注册了</p>
<p> </p>
<p>设置完后,首先会进入power on的回调,这里面又回去使能一次SLE(和上面重复了,我个人觉得,在这里enable会更好,上面的可以去掉。不过重复调用貌似也没什么问题)</p>
<pre>
<code class="language-cpp">/**
* @brief SEL上电回调
* @param status:状态,具体含义未知
* @return none
*/
static void sle_power_on_cbk(uint8_t status)
{
sample_at_log_print("sle power on: %d\r\n", status);
enable_sle();
}</code></pre>
<p>当SLE enable完成后,会进入enbale cb</p>
<pre>
<code class="language-cpp">/**
* @brief SEL使能回调
* @param status:状态,具体含义未知
* @return none
*/
static void sle_enable_cbk(uint8_t status)
{
sample_at_log_print("sle enable: %d\r\n", status);
sle_enable_server_cbk();
}</code></pre>
<p>这里面会调用“sle_enable_server_cbk()”函数,去开启server</p>
<pre>
<code class="language-cpp">/**
* @brief server使能回调
* @param none
* @return none
*/
errcode_t sle_enable_server_cbk(void)
{
errcode_t ret;
/* 添加服务 */
ret = sle_uart_server_add();
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle_uart_server_init,sle_uart_server_add fail :%x\r\n", SLE_UART_SERVER_LOG, ret);
return ret;
}
/* 初始化ADV */
ret = sle_uart_server_adv_init();
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle_uart_server_init,sle_uart_server_adv_init fail :%x\r\n", SLE_UART_SERVER_LOG, ret);
return ret;
}
return ERRCODE_SLE_SUCCESS;
}</code></pre>
<p>这里面会两件事,分别是添加服务,然后是初始化ADV。这个函数运行完后,整个server就可以认为初始化完成了,处于ADV状态,等待client发现并连接了</p>
<p> </p>
<p>添加服务中主要添加的就是SSAP服务,用于数据透传,里面主要有4个步骤,注册SSAP服务,会得到一个服务ID,后面添加UUID、添加特征值时用得到。然后就是添加UUID、添加特征值,最后开启服务。代码如下</p>
<pre>
<code class="language-cpp">/**
* @brief 添加服务
* @param none
* @return none
*/
static errcode_t sle_uart_server_add(void)
{
errcode_t ret;
sle_uuid_t app_uuid = {0};
sample_at_log_print("%s sle uart add service in\r\n", SLE_UART_SERVER_LOG);
app_uuid.len = sizeof(g_sle_uuid_app_uuid);
if (memcpy_s(app_uuid.uuid, app_uuid.len, g_sle_uuid_app_uuid, sizeof(g_sle_uuid_app_uuid)) != EOK)
{
return ERRCODE_SLE_FAIL;
}
/* 注册SSAP服务,用于透传数据。注册成功会得到g_server_id,后面添加UUID啥的需要 */
ssaps_register_server(&app_uuid, &g_server_id);
/* 添加服务UUID */
if (sle_uuid_server_service_add() != ERRCODE_SLE_SUCCESS)
{
ssaps_unregister_server(g_server_id);
return ERRCODE_SLE_FAIL;
}
/* 添加特征值、特征值描述符 */
if (sle_uuid_server_property_add() != ERRCODE_SLE_SUCCESS)
{
ssaps_unregister_server(g_server_id);
return ERRCODE_SLE_FAIL;
}
sample_at_log_print("%s sle uart add service, server_id:%x, service_handle:%x, property_handle:%x\r\n",
SLE_UART_SERVER_LOG, g_server_id, g_service_handle, g_property_handle);
/* 开启SSAP服务 */
ret = ssaps_start_service(g_server_id, g_service_handle);
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle uart add service fail, ret:%x\r\n", SLE_UART_SERVER_LOG, ret);
return ERRCODE_SLE_FAIL;
}
sample_at_log_print("%s sle uart add service out\r\n", SLE_UART_SERVER_LOG);
return ERRCODE_SLE_SUCCESS;
}</code></pre>
<p> </p>
<p>初始化ADV中,有三个步骤,配置ADV参数,配置ADV内容,开启ADV,代码如下</p>
<pre>
<code class="language-cpp">/**
* @brief server端adv初始化
* @param none
* @return errcode_t
*/
errcode_t sle_uart_server_adv_init(void)
{
errcode_t ret;
/* 设置ADV参数 */
sle_set_default_announce_param();
/* 设置ADV data */
sle_set_default_announce_data();
/* 开始ADV */
ret = sle_start_announce(SLE_ADV_HANDLE_DEFAULT);
if (ret != ERRCODE_SLE_SUCCESS) {
sample_at_log_print("%s sle_uart_server_adv_init,sle_start_announce fail :%x\r\n", SLE_UART_SERVER_LOG, ret);
return ret;
}
return ERRCODE_SLE_SUCCESS;
}</code></pre>
<p> </p>
<p>sle_set_default_announce_param中主要是配置ADV的参数,例如广播间隔、广播模式、地址类型等等</p>
<pre>
<code class="language-cpp">/**
* @brief 设置adv参数
* @param none
* @return none
*/
static int sle_set_default_announce_param(void)
{
errno_t ret;
sle_announce_param_t param = {0};
uint8_t index;
unsigned char local_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
param.announce_mode = SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE;
param.announce_handle = SLE_ADV_HANDLE_DEFAULT;
param.announce_gt_role = SLE_ANNOUNCE_ROLE_T_CAN_NEGO;
param.announce_level = SLE_ANNOUNCE_LEVEL_NORMAL;
param.announce_channel_map = SLE_ADV_CHANNEL_MAP_DEFAULT;
param.announce_interval_min = SLE_ADV_INTERVAL_MIN_DEFAULT;
param.announce_interval_max = SLE_ADV_INTERVAL_MAX_DEFAULT;
param.conn_interval_min = SLE_CONN_INTV_MIN_DEFAULT;
param.conn_interval_max = SLE_CONN_INTV_MAX_DEFAULT;
param.conn_max_latency = SLE_CONN_MAX_LATENCY;
param.conn_supervision_timeout = SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT;
param.own_addr.type = 0;
ret = memcpy_s(param.own_addr.addr, SLE_ADDR_LEN, local_addr, SLE_ADDR_LEN);
if (ret != EOK)
{
sample_at_log_print("%s sle_set_default_announce_param data memcpy fail\r\n", SLE_UART_SERVER_LOG);
return 0;
}
sample_at_log_print("%s sle_uart_local addr: ", SLE_UART_SERVER_LOG);
for (index = 0; index < SLE_ADDR_LEN; index++)
{
sample_at_log_print("0x%02x ", param.own_addr.addr);
}
sample_at_log_print("\r\n");
return sle_set_announce_param(param.announce_handle, &param);
}
</code></pre>
<p> </p>
<p>sle_set_default_announce_data中会组装adv数据、scan rsp数据,并设置</p>
<pre>
<code class="language-cpp">/**
* @brief 设置adv data
* @param none
* @return none
*/
static int sle_set_default_announce_data(void)
{
errcode_t ret;
uint8_t announce_data_len = 0;
uint8_t seek_data_len = 0;
sle_announce_data_t data = {0};
uint8_t adv_handle = SLE_ADV_HANDLE_DEFAULT;
uint8_t announce_data = {0};
uint8_t seek_rsp_data = {0};
uint8_t data_index = 0;
/* 组装ADV数据 */
announce_data_len = sle_set_adv_data(announce_data);
data.announce_data = announce_data;
data.announce_data_len = announce_data_len;
sample_at_log_print("%s data.announce_data_len = %d\r\n", SLE_UART_SERVER_LOG, data.announce_data_len);
sample_at_log_print("%s data.announce_data: ", SLE_UART_SERVER_LOG);
for (data_index = 0; data_index<data.announce_data_len; data_index++) {
sample_at_log_print("0x%02x ", data.announce_data);
}
sample_at_log_print("\r\n");
/* 组装scan rsp数据 */
seek_data_len = sle_set_scan_response_data(seek_rsp_data);
data.seek_rsp_data = seek_rsp_data;
data.seek_rsp_data_len = seek_data_len;
sample_at_log_print("%s data.seek_rsp_data_len = %d\r\n", SLE_UART_SERVER_LOG, data.seek_rsp_data_len);
sample_at_log_print("%s data.seek_rsp_data: ", SLE_UART_SERVER_LOG);
for (data_index = 0; data_index<data.seek_rsp_data_len; data_index++) {
sample_at_log_print("0x%02x ", data.seek_rsp_data);
}
sample_at_log_print("\r\n");
/* 设置adv数据 */
ret = sle_set_announce_data(adv_handle, &data);
if (ret == ERRCODE_SLE_SUCCESS) {
sample_at_log_print("%s set announce data success.\r\n", SLE_UART_SERVER_LOG);
} else {
sample_at_log_print("%s set adv param fail.\r\n", SLE_UART_SERVER_LOG);
}
return ERRCODE_SLE_SUCCESS;
}
</code></pre>
<p> </p>
<p>之后我们中断关注的就是连接、断连回调,还有接收数据的回调。还有如何发数据的接口</p>
<p> </p>
<p>在SLE中连接有2个步骤,第一步是connect,第二步是pair。但是目前我还没有理解这两个有什么区别,并且为什么有一步pair?</p>
<p>connect会进入“sle_connect_state_changed_cbk”,断连也是会进去这里</p>
<pre>
<code class="language-cpp">/**
* @brief SEL连接状态改变回调
* @param conn_id:连接ID
* @param addr:mac 地址
* @param conn_state:连接状态
* @param pair_state:配对状态
* @param disc_reason:断开原因
* @return none
*/
static void sle_connect_state_changed_cbk(uint16_t conn_id, const sle_addr_t *addr,
sle_acb_state_t conn_state, sle_pair_state_t pair_state, sle_disc_reason_t disc_reason)
{
uint8_t sle_connect_state[] = "sle_dis_connect";
sample_at_log_print("%s connect state changed callback conn_id:0x%02x, conn_state:0x%x, pair_state:0x%x, \
disc_reason:0x%x\r\n", SLE_UART_SERVER_LOG,conn_id, conn_state, pair_state, disc_reason);
sample_at_log_print("%s connect state changed callback addr:%02x:**:**:**:%02x:%02x\r\n", SLE_UART_SERVER_LOG,
addr->addr, addr->addr);
if (conn_state == SLE_ACB_STATE_CONNECTED)
{
g_sle_conn_hdl = conn_id;
#ifdef CONFIG_SAMPLE_SUPPORT_LOW_LATENCY_TYPE
sle_low_latency_tx_enable();
osal_printk("%s sle_low_latency_tx_enable \r\n", SLE_UART_SERVER_LOG);
#endif
}
else if (conn_state == SLE_ACB_STATE_DISCONNECTED)
{
g_sle_conn_hdl = 0;
g_sle_pair_hdl = 0;
if (g_sle_uart_server_msg_queue != NULL)
{
g_sle_uart_server_msg_queue(sle_connect_state, sizeof(sle_connect_state));
}
}
}
</code></pre>
<p> </p>
<p>配对完成回调如下</p>
<pre>
<code class="language-cpp">/**
* @brief SEL配对完成回调
* @param conn_id:连接ID
* @param addr:mac 地址
* @param errcode_t:错误码
* @return none
*/
static void sle_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status)
{
sample_at_log_print("%s pair complete conn_id:%02x, status:%x\r\n", SLE_UART_SERVER_LOG,
conn_id, status);
sample_at_log_print("%s pair complete addr:%02x:**:**:**:%02x:%02x\r\n", SLE_UART_SERVER_LOG,
addr->addr, addr->addr);
g_sle_pair_hdl = conn_id + 1;
ssap_exchange_info_t parameter = { 0 };
parameter.mtu_size = 520;
parameter.version = 1;
ssaps_set_info(g_server_id, &parameter);
}
</code></pre>
<p> </p>
<p>接收数据回调:</p>
<p>接收数据 是一个注册函数,在如下函数中注册</p>
<pre>
<code class="language-cpp">static errcode_t sle_ssaps_register_cbks(ssaps_read_request_callback ssaps_read_callback, ssaps_write_request_callback
ssaps_write_callback)
{
errcode_t ret;
ssaps_callbacks_t ssaps_cbk = {0};
ssaps_cbk.add_service_cb = ssaps_add_service_cbk;
ssaps_cbk.add_property_cb = ssaps_add_property_cbk;
ssaps_cbk.add_descriptor_cb = ssaps_add_descriptor_cbk;
ssaps_cbk.start_service_cb = ssaps_start_service_cbk;
ssaps_cbk.delete_all_service_cb = ssaps_delete_all_service_cbk;
ssaps_cbk.mtu_changed_cb = ssaps_mtu_changed_cbk;
ssaps_cbk.read_request_cb = ssaps_read_callback;
ssaps_cbk.write_request_cb = ssaps_write_callback;
ret = ssaps_register_callbacks(&ssaps_cbk);
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle_ssaps_register_cbks,ssaps_register_callbacks fail :%x\r\n", SLE_UART_SERVER_LOG,
ret);
return ret;
}
return ERRCODE_SLE_SUCCESS;
}
</code></pre>
<p>就是ssaps_cbk.write_request_cb = ssaps_write_callback; SSAP服务如果收到数据会触发write_request_cb。那么真正工作的函数是谁呢?就要看看sle_ssaps_register_cbks被谁调用了,因为write的函数会传进来。他会被sle_uart_server_init调用,而他又会被sle_uart_server_task调用。最终真正处理收到数据的是ssaps_server_write_request_cbk,代码如下</p>
<pre>
<code class="language-cpp">static void ssaps_server_write_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_write_cb_t *write_cb_para,
errcode_t status)
{
osal_printk("%s ssaps write request callback cbk server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",
SLE_UART_SERVER_LOG, server_id, conn_id, write_cb_para->handle, status);
if ((write_cb_para->length > 0) && write_cb_para->value)
{
uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)write_cb_para->value, write_cb_para->length, 0);
}
}
</code></pre>
<p> </p>
<p> </p>
<p>发送数据回调:</p>
<p>发送函数可以从串口接收入手开始找,在sle_uart_server_task中会注册串口接收回调函数</p>
<div style="text-align: center;"></div>
<p>串口收到数据后,会给sle去透传,那么很容易就找到了发送函数:sle_uart_server_send_report_by_handle</p>
<pre>
<code class="language-cpp">static void sle_uart_server_read_int_handler(const void *buffer, uint16_t length, bool error)
{
unused(error);
if (sle_uart_client_is_connected())
{
#ifdef CONFIG_SAMPLE_SUPPORT_LOW_LATENCY_TYPE
g_buff_data_valid = 1;
g_uart_buff_len = 0;
(void)memcpy_s(g_buff, SLE_UART_SERVER_SEND_BUFF_MAX_LEN, buffer, length);
g_uart_buff_len = length;
#else
sle_uart_server_send_report_by_handle(buffer, length);
#endif
}
else
{
osal_printk("%s sle client is not connected! \r\n", SLE_UART_SERVER_LOG);
}
}
</code></pre>
<p>sle_uart_server_send_report_by_handle源码如下,真正的发送函数是ssaps_notify_indicate。这里我又有一个疑惑了,怎么notify和indicate和在一起了?</p>
<pre>
<code class="language-cpp">/* device通过handle向host发送数据:report */
errcode_t sle_uart_server_send_report_by_handle(const uint8_t *data, uint16_t len)
{
ssaps_ntf_ind_t param = {0};
uint8_t receive_buf = { 0 }; /* max receive length. */
param.handle = g_property_handle;
param.type = SSAP_PROPERTY_TYPE_VALUE;
param.value = receive_buf;
param.value_len = len;
if (memcpy_s(param.value, param.value_len, data, len) != EOK)
{
return ERRCODE_SLE_FAIL;
}
return ssaps_notify_indicate(g_server_id, g_sle_conn_hdl, &param);
}</code></pre>
<p>这个是通过handle发送数据,还有一个通过UUID发送数据的函数,就在他的上面,代码如下</p>
<pre>
<code class="language-cpp">/* device通过uuid向host发送数据:report */
errcode_t sle_uart_server_send_report_by_uuid(const uint8_t *data, uint8_t len)
{
errcode_t ret;
ssaps_ntf_ind_by_uuid_t param = {0};
param.type = SSAP_PROPERTY_TYPE_VALUE;
param.start_handle = g_service_handle;
param.end_handle = g_property_handle;
param.value_len = len;
param.value = (uint8_t *)osal_vmalloc(len);
if (param.value == NULL)
{
sample_at_log_print("%s send report new fail\r\n", SLE_UART_SERVER_LOG);
return ERRCODE_SLE_FAIL;
}
if (memcpy_s(param.value, param.value_len, data, len) != EOK)
{
sample_at_log_print("%s send input report memcpy fail\r\n", SLE_UART_SERVER_LOG);
osal_vfree(param.value);
return ERRCODE_SLE_FAIL;
}
sle_uuid_setu2(SLE_UUID_SERVER_NTF_REPORT, &param.uuid);
ret = ssaps_notify_indicate_by_uuid(g_server_id, g_sle_conn_hdl, &param);
if (ret != ERRCODE_SLE_SUCCESS)
{
sample_at_log_print("%s sle_uart_server_send_report_by_uuid,ssaps_notify_indicate_by_uuid fail :%x\r\n",
SLE_UART_SERVER_LOG, ret);
osal_vfree(param.value);
return ret;
}
osal_vfree(param.value);
return ERRCODE_SLE_SUCCESS;
}
</code></pre>
<p> </p>
<p> </p>
<p>现在server demo基本上理清楚了,关键的几个设置、回调函数都知道了。还有一些其他的函数和回调不是非常重要,可以后续自己慢慢去看。</p>
<p> </p>
<p>下一篇我们来看看client的代码。等两边的代码基本都熟悉后,我们就可以改造一下,做一下数据收发丢包和连接稳定性测试了</p>
<p>学习到了</p>
页:
[1]