不爱胡萝卜的仓鼠 发表于 2024-8-23 23:14

[BearPi-Pico H2821]测评 ⑤SLE client源码解读

<div class='showpostmsg'> 本帖最后由 不爱胡萝卜的仓鼠 于 2024-8-23 23:14 编辑

<p>上一篇我们讲了server的代码。今天来看看client的代码</p>

<p>&nbsp;</p>

<p>client的流程是:模块上电-&gt;注册各种回调-&gt;开启SLE-&gt;配置scan-&gt;scan到目标设备后发起连接-&gt;等待client连接cb、pair完成cb-&gt;交换信息、发现服务、发现特征值-&gt;与server通讯</p>

<p>&nbsp;</p>

<p>我们还是从最一开始的sle_uart_entry函数看起(这儿我就不放他的代码了,上一篇有)</p>

<p>&nbsp;</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(&amp;g_sle_dev_mgr_cbk);
#if (CORE_NUMS &lt; 2)
    enable_sle();
#endif
}
</code></pre>

<p>和server一样的套路,注册SLES上电、使能的回调。</p>

<p>&nbsp;</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>&nbsp;</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(&amp;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(&amp;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(&amp;g_sle_uart_ssapc_cbk);
}
</code></pre>

<p>&nbsp;</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-&gt;data);
    uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data-&gt;data), data-&gt;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(&amp;param);
    /* 开始SCAN */
    sle_start_seek();
}</code></pre>

<p>&nbsp;</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 &lt; seek_result_data-&gt;data_length; i++)
    {
      osal_printk("0x%02x ", seek_result_data-&gt;data);
    }
    osal_printk("\r\n");

    /* 收到的数据和目标设备的作对比 */
    if (strstr((const char *)seek_result_data-&gt;data, SLE_UART_SERVER_NAME) != NULL)
    {
      /* 如果对上了,把MAC保存到g_sle_uart_remote_addr中,待会儿连接使用 */
      memcpy_s(&amp;g_sle_uart_remote_addr, sizeof(sle_addr_t), &amp;seek_result_data-&gt;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(&amp;g_sle_uart_remote_addr);
      /* 连接设备 */
      sle_connect_remote_device(&amp;g_sle_uart_remote_addr);
    }
}</code></pre>

<p>&nbsp;</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(&amp;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(&amp;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-&gt;addr, addr-&gt;addr, addr-&gt;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, &amp;info);
    }
}</code></pre>

<p>&nbsp;</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-&gt;mtu_size, param-&gt;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, &amp;find_param);
}
</code></pre>

<p>当发现一个特征值就会触发sle_uart_client_sample_find_property_cbk,等全部特征值发现完毕,触发sle_uart_client_sample_find_structure_cmp_cbk。到此双方就可以进行数据的收发了</p>

<p>&nbsp;</p>

<p>&nbsp;</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-&gt;data_len = length;
    sle_uart_send_param-&gt;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>

walker2048 发表于 2024-8-23 23:32

<p>我是ble和sle小白,问一下大佬,我想知道sle里,从机和主机任意一方想实现快速响应的数据发送,是通过什么方式处理呢?比方说不考虑最高吞吐量,仅仅考虑偶发的数据快速响应的情况下</p>

不爱胡萝卜的仓鼠 发表于 2024-8-26 09:46

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]
查看完整版本: [BearPi-Pico H2821]测评 ⑤SLE client源码解读