本帖最后由 不爱胡萝卜的仓鼠 于 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服务没有对应的特征值还是星闪没有这样的机制