565|1

37

帖子

0

TA的资源

一粒金砂(中级)

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

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

今天我们来简单的分析一下SEL server demo的源码,看看他流程上会调用哪些东西,关键的广播数据、服务、特征值、收发数据是对应哪些函数。

 

因为时间和篇幅有限,今天的解读会比较粗糙,不会讲的很细。具体细节还是建议大家自己花时间去阅读,我这个还是一个浅层的引导。下面贴出来的代码中部分是我自己加的,有的可能写的不是很好,请见谅

 

首先了解一下SDK,整个SDK是跑OS的,用的是LiteOS。

我们这个demo的源码在如下路径

202408202308345.png

其中“sel_uart.c”是server和client公用的,server自己的代码放在“sel_uart_cliebt”、"sle_uart_server"文件夹中

 

我们再来回顾一下server demo的大致流程。模块上电->注册各种回调->开启SLE->配置adv、server等->开始adv->等待client连接->连上后触发连接cb、交换MTU等->与client通讯

 

代码首先会进入“sel_uart.c”中的“sle_uart_entry”函数,代码如下

/**
 * @brief		SEL UART入口函数(可以认为是SDK正常运行后就会调用的函数)
 * @param[in]	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();
}

这里会配置时钟,然后注册SEL会使用的CB,然后创建串口TASK(我们现在主要关注SLE,所以串口部分我就不讲了)

 

对于server demo,我们进入sle_dev_register_cbks();看一下

/**
 * @brief		注册SEL的回调函数
 * @param[in]	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;
}

这里会注册SLE管理的回调函数,然后使能SLE

“sle_dev_manager_callbacks_t”结构体里有3个回调:SLE上电回调、SLE使能回调、SLE去使能回调。具体内容如下

/**
 * @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;

我们的这个代码中只注册了前2个,因为没有关闭,所以去使能就不注册了

 

设置完后,首先会进入power on的回调,这里面又回去使能一次SLE(和上面重复了,我个人觉得,在这里enable会更好,上面的可以去掉。不过重复调用貌似也没什么问题)

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

当SLE enable完成后,会进入enbale cb

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

这里面会调用“sle_enable_server_cbk()”函数,去开启server

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

这里面会两件事,分别是添加服务,然后是初始化ADV。这个函数运行完后,整个server就可以认为初始化完成了,处于ADV状态,等待client发现并连接了

 

添加服务中主要添加的就是SSAP服务,用于数据透传,里面主要有4个步骤,注册SSAP服务,会得到一个服务ID,后面添加UUID、添加特征值时用得到。然后就是添加UUID、添加特征值,最后开启服务。代码如下

/**
 * @brief		添加服务
 * @param[in]	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;
}

 

初始化ADV中,有三个步骤,配置ADV参数,配置ADV内容,开启ADV,代码如下

/**
 * @brief		server端adv初始化
 * @param[in]	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;
}

 

sle_set_default_announce_param中主要是配置ADV的参数,例如广播间隔、广播模式、地址类型等等

/**
 * @brief		设置adv参数
 * @param[in]	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[SLE_ADDR_LEN] = { 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[index]);
    }
    sample_at_log_print("\r\n");
    return sle_set_announce_param(param.announce_handle, ¶m);
}

 

sle_set_default_announce_data中会组装adv数据、scan rsp数据,并设置

/**
 * @brief		设置adv data
 * @param[in]	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[SLE_ADV_DATA_LEN_MAX] = {0};
    uint8_t seek_rsp_data[SLE_ADV_DATA_LEN_MAX] = {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[data_index]);
    }
    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[data_index]);
    }
    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;
}

 

之后我们中断关注的就是连接、断连回调,还有接收数据的回调。还有如何发数据的接口

 

在SLE中连接有2个步骤,第一步是connect,第二步是pair。但是目前我还没有理解这两个有什么区别,并且为什么有一步pair?

connect会进入“sle_connect_state_changed_cbk”,断连也是会进去这里

/**
 * @brief		SEL连接状态改变回调
 * @param[in]	conn_id:连接ID
 * @param[in]	addr:mac 地址
 * @param[in]	conn_state:连接状态
 * @param[in]	pair_state:配对状态
 * @param[in]	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[BT_INDEX_0], addr->addr[BT_INDEX_4]);
    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));
        }
    }
}

 

配对完成回调如下

/**
 * @brief		SEL配对完成回调
 * @param[in]	conn_id:连接ID
 * @param[in]	addr:mac 地址
 * @param[in]	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[BT_INDEX_0], addr->addr[BT_INDEX_4]);
    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, ¶meter);
}

 

接收数据回调:

接收数据 是一个注册函数,在如下函数中注册

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

就是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,代码如下

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

 

 

发送数据回调:

发送函数可以从串口接收入手开始找,在sle_uart_server_task中会注册串口接收回调函数

2024082100181888.png

串口收到数据后,会给sle去透传,那么很容易就找到了发送函数:sle_uart_server_send_report_by_handle

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

sle_uart_server_send_report_by_handle源码如下,真正的发送函数是ssaps_notify_indicate。这里我又有一个疑惑了,怎么notify和indicate和在一起了?

/* 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[UART_BUFF_LENGTH] = { 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, ¶m);
}

这个是通过handle发送数据,还有一个通过UUID发送数据的函数,就在他的上面,代码如下

/* 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, ¶m.uuid);
    ret = ssaps_notify_indicate_by_uuid(g_server_id, g_sle_conn_hdl, ¶m);
    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;
}

 

 

现在server demo基本上理清楚了,关键的几个设置、回调函数都知道了。还有一些其他的函数和回调不是非常重要,可以后续自己慢慢去看。

 

下一篇我们来看看client的代码。等两边的代码基本都熟悉后,我们就可以改造一下,做一下数据收发丢包和连接稳定性测试了

此帖出自RF/无线论坛

最新回复

学习到了   详情 回复 发表于 2024-9-5 16:15

回复
举报

1

帖子

0

TA的资源

一粒金砂(初级)

学习到了

此帖出自RF/无线论坛

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

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