[BearPi-Pico H2821]测评 ⑤SLE client源码解读
<div class='showpostmsg'> 本帖最后由 不爱胡萝卜的仓鼠 于 2024-8-23 23:14 编辑<p>上一篇我们讲了server的代码。今天来看看client的代码</p>
<p> </p>
<p>client的流程是:模块上电->注册各种回调->开启SLE->配置scan->scan到目标设备后发起连接->等待client连接cb、pair完成cb->交换信息、发现服务、发现特征值->与server通讯</p>
<p> </p>
<p>我们还是从最一开始的sle_uart_entry函数看起(这儿我就不放他的代码了,上一篇有)</p>
<p> </p>
<p>client会首先进入sle_uart_client_sample_dev_cbk_register</p>
<pre>
<code class="language-cpp">/**
* @brief SEL设备回调函数注册
* @param conn_id:连接ID
* @param addr:mac 地址
* @param errcode_t:错误码
* @return none
*/
void sle_uart_client_sample_dev_cbk_register(void)
{
g_sle_dev_mgr_cbk.sle_power_on_cb = sle_uart_client_sample_sle_power_on_cbk;
g_sle_dev_mgr_cbk.sle_enable_cb = sle_uart_client_sample_sle_enable_cbk;
sle_dev_manager_register_callbacks(&g_sle_dev_mgr_cbk);
#if (CORE_NUMS < 2)
enable_sle();
#endif
}
</code></pre>
<p>和server一样的套路,注册SLES上电、使能的回调。</p>
<p> </p>
<p>上电回调如下,和server一样,会在里面再调用一次使能SLE的接口</p>
<pre>
<code class="language-cpp">/**
* @brief SEL上电回调
* @param status:状态,具体含义未知
* @return none
*/
static void sle_uart_client_sample_sle_power_on_cbk(uint8_t status)
{
osal_printk("sle power on: %d.\r\n", status);
enable_sle();
}</code></pre>
<p>使能完成回调如下,这里会完成2件事,sle client的各种cb的注册。然后开启scan</p>
<pre>
<code class="language-cpp">/**
* @brief SEL使能回调
* @param status:状态,具体含义未知
* @return none
*/
static void sle_uart_client_sample_sle_enable_cbk(uint8_t status)
{
osal_printk("sle enable: %d.\r\n", status);
/* client初始化 */
sle_uart_client_init(sle_uart_notification_cb, sle_uart_indication_cb);
/* 开启SCAN */
sle_uart_start_scan();
}</code></pre>
<p> </p>
<p>先看sle_uart_client_init</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client 使用的各种cb注册
* @param notification_cb:收到notify数据的回调
* @param indication_cb:收到indicate数据的回调
* @return none
*/
void sle_uart_client_init(ssapc_notification_callback notification_cb, ssapc_indication_callback indication_cb)
{
sle_uart_client_sample_seek_cbk_register(); /* SCAN相关的cb注册 */
sle_uart_client_sample_connect_cbk_register();/* 连接先关的cb注册 */
sle_uart_client_sample_ssapc_cbk_register(notification_cb, indication_cb);/* SSAP服务相关的cb注册 */
#ifdef CONFIG_SAMPLE_SUPPORT_LOW_LATENCY_TYPE
sle_uart_client_low_latency_recv_data_cbk_register();
#endif
}</code></pre>
<p>里面会去注册各种回调,例如开启SCAN回调、SCAN收到数据回调、停止SCAN回调;连接回调、pair回调、断连回调;交换MTU回调、发现服务回调、发现特征值回调等等。他们三个代码如下</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client SCAN相关cb注册
* @param none
* @return none
*/
static void sle_uart_client_sample_seek_cbk_register(void)
{
g_sle_uart_seek_cbk.seek_enable_cb = sle_uart_client_sample_seek_enable_cbk; /* 开启SCAN回调 */
g_sle_uart_seek_cbk.seek_result_cb = sle_uart_client_sample_seek_result_info_cbk; /* SCAN收到一包数据回调 */
g_sle_uart_seek_cbk.seek_disable_cb = sle_uart_client_sample_seek_disable_cbk;/* SCAN停止回调 */
sle_announce_seek_register_callbacks(&g_sle_uart_seek_cbk);
}
/**
* @brief SLE client 连接相关cb注册
* @param none
* @return none
*/
static void sle_uart_client_sample_connect_cbk_register(void)
{
g_sle_uart_connect_cbk.connect_state_changed_cb = sle_uart_client_sample_connect_state_changed_cbk; /* 连接状态改变回调 */
g_sle_uart_connect_cbk.pair_complete_cb =sle_uart_client_sample_pair_complete_cbk; /* pair完成回调 */
sle_connection_register_callbacks(&g_sle_uart_connect_cbk);
}
/**
* @brief SLE client ssap服务注册的回调
* @param notification_cb:收到notify数据的回调
* @param indication_cb:收到indicate数据的回调
* @return none
*/
static void sle_uart_client_sample_ssapc_cbk_register(ssapc_notification_callback notification_cb,
ssapc_notification_callback indication_cb)
{
g_sle_uart_ssapc_cbk.exchange_info_cb = sle_uart_client_sample_exchange_info_cbk; /* mtu改变的回调 */
g_sle_uart_ssapc_cbk.find_structure_cb = sle_uart_client_sample_find_structure_cbk; /* 发现服务的回调 */
g_sle_uart_ssapc_cbk.ssapc_find_property_cbk = sle_uart_client_sample_find_property_cbk; /* 发现特征值的回调 */
g_sle_uart_ssapc_cbk.find_structure_cmp_cb = sle_uart_client_sample_find_structure_cmp_cbk; /* 发现全部特征值完成的回调 */
g_sle_uart_ssapc_cbk.write_cfm_cb = sle_uart_client_sample_write_cfm_cb; /* 收到写响应的回调函数,可以认为发送成功 */
g_sle_uart_ssapc_cbk.notification_cb = notification_cb;
g_sle_uart_ssapc_cbk.indication_cb = indication_cb;
ssapc_register_callbacks(&g_sle_uart_ssapc_cbk);
}
</code></pre>
<p> </p>
<p>入参是收到notify数据的回调和收到indicate数据的回调,如果收到数据就会触发这两个回调。在demo中分别写了2个函数用于接收数据:sle_uart_notification_cb, sle_uart_indication_cb。在sle_uart.c中。他俩内容一致,就是把收到的数据通过串口打印出来。(sle_uart_indication_cb我就不放了,一样的内容,只是函数名不一样)</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client收到notify回调
* @param client_id 客户端 ID。???没明白什么意思
* @param conn_id 连接 ID。
* @param data 数据。
* @param status 执行结果错误码。
* @return none
*/
void sle_uart_notification_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data,
errcode_t status)
{
unused(client_id);
unused(conn_id);
unused(status);
/* 把收到的数据打印出来 */
osal_printk("\n sle uart recived data : %s\r\n", data->data);
uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data->data), data->data_len, 0);
}</code></pre>
<p>现在各种所需的CB都注册好了,可以去调用sle_uart_start_scan开SCAN了</p>
<pre>
<code class="language-cpp">/**
* @brief 开SCNA
* @param none
* @return none
*/
void sle_uart_start_scan(void)
{
/* 配置scan参数 */
sle_seek_param_t param = { 0 };
param.own_addr_type = 0;
param.filter_duplicates = 0;
param.seek_filter_policy = 0;
param.seek_phys = 1;
param.seek_type = 1;
param.seek_interval = SLE_SEEK_INTERVAL_DEFAULT;
param.seek_window = SLE_SEEK_WINDOW_DEFAULT;
sle_set_seek_param(&param);
/* 开始SCAN */
sle_start_seek();
}</code></pre>
<p> </p>
<p>现在就一直处于SCAN状态,如果SCAN收到一个ADV数据,就会触发刚才注册的sle_uart_client_sample_seek_result_info_cbk中。在这个函数中会把收到的数据打印出来,然后和目标设备的做对比,如果一致,那就把MAC保存起来,然后取关SCAN(关SCAN成功后,在其对应的CB中会去发起连接)</p>
<p>这儿我觉得作为一个DEMO,这样写可以。如果正式项目中使用我觉得还是应该加一个flag,如果要准备去发起连接了,把flag置位,那么在调用关SCAN到真正SCAN停止期间,如果还有收到数据CB触发,也会直接不解析。</p>
<pre>
<code class="language-cpp">/**
* @brief SELSCAN收到数据回调
* @param seek_result_data:收到数据相关的结构体变量
* @return none
*/
static void sle_uart_client_sample_seek_result_info_cbk(sle_seek_result_info_t *seek_result_data)
{
/* 指针判空保护(demo放在下面,应该放在最上面,不然打印一用就完蛋) */
if (seek_result_data == NULL)
{
osal_printk("status error\r\n");
return;
}
/* 打印SCAN到的ADV数据 */
osal_printk("%s sle uart scan data :", SLE_UART_CLIENT_LOG);
for (uint8_t i = 0; i < seek_result_data->data_length; i++)
{
osal_printk("0x%02x ", seek_result_data->data);
}
osal_printk("\r\n");
/* 收到的数据和目标设备的作对比 */
if (strstr((const char *)seek_result_data->data, SLE_UART_SERVER_NAME) != NULL)
{
/* 如果对上了,把MAC保存到g_sle_uart_remote_addr中,待会儿连接使用 */
memcpy_s(&g_sle_uart_remote_addr, sizeof(sle_addr_t), &seek_result_data->addr, sizeof(sle_addr_t));
/* 停止SCAN */
sle_stop_seek();
}
}
</code></pre>
<p>SCAN停止后,会进入sle_uart_client_sample_seek_disable_cbk,这里面就会去发起连接</p>
<pre>
<code class="language-cpp">/**
* @brief SEL SCAN停止回调
* @param status:状态
* @return none
*/
static void sle_uart_client_sample_seek_disable_cbk(errcode_t status)
{
if (status != 0)
{
osal_printk("%s sle_uart_client_sample_seek_disable_cbk,status error = %x\r\n", SLE_UART_CLIENT_LOG, status);
}
else
{
/* 删除之前配对过的设备(为了避免冲突?) */
sle_remove_paired_remote_device(&g_sle_uart_remote_addr);
/* 连接设备 */
sle_connect_remote_device(&g_sle_uart_remote_addr);
}
}</code></pre>
<p> </p>
<p>之后我们就等着连上,就会触发sle_uart_client_sample_connect_state_changed_cbk。然后在里面会去触发开始配对</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client 连接状态改变
* @param conn_id 连接 ID。
* @param addr 地址。
* @param conn_state 连接状态 { @ref sle_acb_state_t }。
* @param pair_state 配对状态 { @ref sle_pair_state_t }。
* @param disc_reason 断链原因 { @ref sle_disc_reason_t }。
* @return none
*/
static void sle_uart_client_sample_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)
{
unused(addr);
unused(pair_state);
osal_printk("%s conn state changed disc_reason:0x%x\r\n", SLE_UART_CLIENT_LOG, disc_reason);
g_sle_uart_conn_id = conn_id;
/* 已经连上 */
if (conn_state == SLE_ACB_STATE_CONNECTED)
{
osal_printk("%s SLE_ACB_STATE_CONNECTED\r\n", SLE_UART_CLIENT_LOG);
/* 配对状态是未配对 */
if (pair_state == SLE_PAIR_NONE)
{
/* 开始配对 */
sle_pair_remote_device(&g_sle_uart_remote_addr);
}
#ifdef CONFIG_SAMPLE_SUPPORT_LOW_LATENCY_TYPE
sle_uart_client_sample_set_phy_param();
osal_msleep(SLE_UART_TASK_DELAY_MS);
sle_low_latency_rx_enable();
sle_low_latency_set(get_g_sle_uart_conn_id(), true, SLE_UART_LOW_LATENCY_2K);
osal_printk("%s sle_low_latency_rx_enable \r\n", SLE_UART_CLIENT_LOG); //这句话应该在这儿,不应该放外面
#endif
}
/* 未连接(还能有未连接?怎么会有这种状态?) */
else if (conn_state == SLE_ACB_STATE_NONE)
{
osal_printk("%s SLE_ACB_STATE_NONE\r\n", SLE_UART_CLIENT_LOG);
}
/* 断连 */
else if (conn_state == SLE_ACB_STATE_DISCONNECTED)
{
osal_printk("%s SLE_ACB_STATE_DISCONNECTED\r\n", SLE_UART_CLIENT_LOG);
/* 移除配对设备 */
sle_remove_paired_remote_device(&g_sle_uart_remote_addr);
/* 再次开启SCAN */
sle_uart_start_scan();
}
/* 其他未知情况 */
else
{
osal_printk("%s status error\r\n", SLE_UART_CLIENT_LOG);
}
}
</code></pre>
<p>当pair成功后,会触发sle_uart_client_sample_pair_complete_cbk</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client pair完成回调
* @param conn_id 连接 ID。
* @param addr 地址。
* @param status执行结果错误码。
* @return none
*/
voidsle_uart_client_sample_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status)
{
osal_printk("%s pair complete conn_id:%d, addr:%02x***%02x%02x\n", SLE_UART_CLIENT_LOG, conn_id,
addr->addr, addr->addr, addr->addr);
if (status == 0)
{
ssap_exchange_info_t info = {0};
info.mtu_size = SLE_MTU_SIZE_DEFAULT;
info.version = 1;
/* 请求交换SSAP信息 */
ssapc_exchange_info_req(0, g_sle_uart_conn_id, &info);
}
}</code></pre>
<p> </p>
<p>配对成功后请求交换SSAP信息(但是实际上貌似是去交换MTU),他完成后会触发sle_uart_client_sample_exchange_info_cbk,在里面会开始去发现SSAP服务、特征值</p>
<pre>
<code class="language-cpp">/**
* @brief SLE client MTU交互完成回调
* @param client_id 客户端 ID。
* @param conn_id 连接 ID。
* @param param 交换信息。
* @param status 执行结果错误码。
* @return none
*/
static void sle_uart_client_sample_exchange_info_cbk(uint8_t client_id, uint16_t conn_id, ssap_exchange_info_t *param,
errcode_t status)
{
osal_printk("%s exchange_info_cbk,pair complete client id:%d status:%d\r\n",
SLE_UART_CLIENT_LOG, client_id, status);
osal_printk("%s exchange mtu, mtu size: %d, version: %d.\r\n", SLE_UART_CLIENT_LOG,
param->mtu_size, param->version);
ssapc_find_structure_param_t find_param = { 0 };
find_param.type = SSAP_FIND_TYPE_PROPERTY;
find_param.start_hdl = 1;
find_param.end_hdl = 0xFFFF;
/* 开始查找SSAP服务、特征值等 */
ssapc_find_structure(0, conn_id, &find_param);
}
</code></pre>
<p>当发现一个特征值就会触发sle_uart_client_sample_find_property_cbk,等全部特征值发现完毕,触发sle_uart_client_sample_find_structure_cmp_cbk。到此双方就可以进行数据的收发了</p>
<p> </p>
<p> </p>
<p>然后我们来看一下数据的收发函数,接收函数在之前已经找到了。现在还有发送,找发送函数的方式和之前的server端一样,去串口初始化里找,这里我就不展示了,直接把串口这边的处理函数贴出来</p>
<pre>
<code class="language-cpp">static void sle_uart_client_read_int_handler(const void *buffer, uint16_t length, bool error)
{
unused(error);
ssapc_write_param_t *sle_uart_send_param = get_g_sle_uart_send_param();
uint16_t g_sle_uart_conn_id = get_g_sle_uart_conn_id();
sle_uart_send_param->data_len = length;
sle_uart_send_param->data = (uint8_t *)buffer;
ssapc_write_req(0, g_sle_uart_conn_id, sle_uart_send_param);
}
</code></pre>
<p>调用ssapc_write_req就可以发送数据,按照BLE的路数,应该还有一个write with no rsp,但是我没有找到,不知道是SSAP服务没有对应的特征值还是星闪没有这样的机制</p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>我是ble和sle小白,问一下大佬,我想知道sle里,从机和主机任意一方想实现快速响应的数据发送,是通过什么方式处理呢?比方说不考虑最高吞吐量,仅仅考虑偶发的数据快速响应的情况下</p>
walker2048 发表于 2024-8-23 23:32
我是ble和sle小白,问一下大佬,我想知道sle里,从机和主机任意一方想实现快速响应的数据发送,是通过什么 ...
<p>大佬不敢当,我也是小白阶段<img height="52" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/titter.gif" width="48" />,BLE是没有这样的机制的,BLE是双方约定某个时间在某个信道上通信。通讯的时间点是由连接间隔参数决定的。如果只是发一包数据的话,就只能在最近的一次通讯时间点才能通讯。如果你是在接下来的一段时间内需要快速通讯,可以请求更新连接间隔,把他调的小一点,等你业务跑完了再调大(如果不考虑功耗,只考虑延迟的话,可以一开始就把他配置的很小)。SLE的话我还不清楚,没有足够的文档可以去学习</p>
页:
[1]