890|2

68

帖子

0

TA的资源

一粒金砂(高级)

楼主
 

[BearPi-Pico H2821]测评 ⑤SLE client源码解读 [复制链接]

本帖最后由 不爱胡萝卜的仓鼠 于 2024-8-23 23:14 编辑

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

 

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

 

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

 

client会首先进入sle_uart_client_sample_dev_cbk_register

/**
 * @brief		SEL设备回调函数注册
 * @param[in]	conn_id:连接ID
 * @param[in]	addr:mac 地址
 * @param[in]	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
}

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

 

上电回调如下,和server一样,会在里面再调用一次使能SLE的接口

/**
 * @brief		SEL上电回调
 * @param[in]	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();
}

使能完成回调如下,这里会完成2件事,sle client的各种cb的注册。然后开启scan

/**
 * @brief		SEL使能回调
 * @param[in]	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();
}

 

先看sle_uart_client_init

/**
 * @brief		SLE client 使用的各种cb注册
 * @param[in]   notification_cb:收到notify数据的回调
 * @param[in]   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
}

里面会去注册各种回调,例如开启SCAN回调、SCAN收到数据回调、停止SCAN回调;连接回调、pair回调、断连回调;交换MTU回调、发现服务回调、发现特征值回调等等。他们三个代码如下

/**
 * @brief		SLE client SCAN相关cb注册
 * @param[in]   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[in]   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[in]   notification_cb:收到notify数据的回调
 * @param[in]   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);
}

 

入参是收到notify数据的回调和收到indicate数据的回调,如果收到数据就会触发这两个回调。在demo中分别写了2个函数用于接收数据:sle_uart_notification_cb, sle_uart_indication_cb。在sle_uart.c中。他俩内容一致,就是把收到的数据通过串口打印出来。(sle_uart_indication_cb我就不放了,一样的内容,只是函数名不一样)

/**
 * @brief		SLE client收到notify回调
 * @param[in]   client_id 客户端 ID。???没明白什么意思
 * @param[in]   conn_id   连接 ID。
 * @param[in]   data      数据。
 * @param[in]   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);
}

现在各种所需的CB都注册好了,可以去调用sle_uart_start_scan开SCAN了

/**
 * @brief		开SCNA
 * @param[in]	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[0] = 1;
    param.seek_interval[0] = SLE_SEEK_INTERVAL_DEFAULT;
    param.seek_window[0] = SLE_SEEK_WINDOW_DEFAULT;
    sle_set_seek_param(¶m);
    /* 开始SCAN */
    sle_start_seek();
}

 

现在就一直处于SCAN状态,如果SCAN收到一个ADV数据,就会触发刚才注册的sle_uart_client_sample_seek_result_info_cbk中。在这个函数中会把收到的数据打印出来,然后和目标设备的做对比,如果一致,那就把MAC保存起来,然后取关SCAN(关SCAN成功后,在其对应的CB中会去发起连接)

这儿我觉得作为一个DEMO,这样写可以。如果正式项目中使用我觉得还是应该加一个flag,如果要准备去发起连接了,把flag置位,那么在调用关SCAN到真正SCAN停止期间,如果还有收到数据CB触发,也会直接不解析。

/**
 * @brief		SELSCAN收到数据回调
 * @param[in]	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[i]);
    }
    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();
    }
}

SCAN停止后,会进入sle_uart_client_sample_seek_disable_cbk,这里面就会去发起连接

/**
 * @brief		SEL SCAN停止回调
 * @param[in]	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);
    }
}

 

之后我们就等着连上,就会触发sle_uart_client_sample_connect_state_changed_cbk。然后在里面会去触发开始配对

/**
 * @brief		SLE client 连接状态改变
 * @param[in]   conn_id    连接 ID。
 * @param[in]   addr       地址。
 * @param[in]   conn_state 连接状态 { @ref sle_acb_state_t }。
 * @param[in]   pair_state 配对状态 { @ref sle_pair_state_t }。
 * @param[in]   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);
    }
}

当pair成功后,会触发sle_uart_client_sample_pair_complete_cbk

/**
 * @brief		SLE client pair完成回调
 * @param[in]   conn_id 连接 ID。
 * @param[in]   addr    地址。
 * @param[in]   status  执行结果错误码。
 * @return      none
 */
void  sle_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[0], addr->addr[4], addr->addr[5]);
    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);
    }
}

 

配对成功后请求交换SSAP信息(但是实际上貌似是去交换MTU),他完成后会触发sle_uart_client_sample_exchange_info_cbk,在里面会开始去发现SSAP服务、特征值

/**
 * @brief		SLE client MTU交互完成回调
 * @param[in]   client_id 客户端 ID。
 * @param[in]   conn_id   连接 ID。
 * @param[in]   param     交换信息。
 * @param[in]   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);
}

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

 

 

然后我们来看一下数据的收发函数,接收函数在之前已经找到了。现在还有发送,找发送函数的方式和之前的server端一样,去串口初始化里找,这里我就不展示了,直接把串口这边的处理函数贴出来

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);
}

调用ssapc_write_req就可以发送数据,按照BLE的路数,应该还有一个write with no rsp,但是我没有找到,不知道是SSAP服务没有对应的特征值还是星闪没有这样的机制

此帖出自RF/无线论坛

最新回复

我是ble和sle小白,问一下大佬,我想知道sle里,从机和主机任意一方想实现快速响应的数据发送,是通过什么方式处理呢?比方说不考虑最高吞吐量,仅仅考虑偶发的数据快速响应的情况下   详情 回复 发表于 2024-8-23 23:32
点赞 关注
 

回复
举报

221

帖子

2

TA的资源

纯净的硅(初级)

沙发
 

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

此帖出自RF/无线论坛

点评

大佬不敢当,我也是小白阶段,BLE是没有这样的机制的,BLE是双方约定某个时间在某个信道上通信。通讯的时间点是由连接间隔参数决定的。如果只是发一包数据的话,就只能在最近的一次通讯时间点才能通讯。如果你是在接  详情 回复 发表于 2024-8-26 09:46
 
 

回复

68

帖子

0

TA的资源

一粒金砂(高级)

板凳
 
walker2048 发表于 2024-8-23 23:32 我是ble和sle小白,问一下大佬,我想知道sle里,从机和主机任意一方想实现快速响应的数据发送,是通过什么 ...

大佬不敢当,我也是小白阶段,BLE是没有这样的机制的,BLE是双方约定某个时间在某个信道上通信。通讯的时间点是由连接间隔参数决定的。如果只是发一包数据的话,就只能在最近的一次通讯时间点才能通讯。如果你是在接下来的一段时间内需要快速通讯,可以请求更新连接间隔,把他调的小一点,等你业务跑完了再调大(如果不考虑功耗,只考虑延迟的话,可以一开始就把他配置的很小)。SLE的话我还不清楚,没有足够的文档可以去学习

此帖出自RF/无线论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表