系列文章目录
星闪开发之Server-Client 指令交互控制红灯亮灭的全流程解析(SLE_LED详解)
前言
之前有粉丝想要计蒙写一篇关于sle_led样例的详细解析文章,于是有了这篇文章,如果拿到代码库中的项目跑不通请直接跳转至第四部分。
一、项目地址
二、客户端
客户端中共两个文件。分别是SLE_LED_Client.h,SLE_LED_Client.c
1.SLE_LED_Client\inc\SLE_LED_Client.h
/**
* @defgroup
* @ingroup
* @{
*/
// LED_MODULE LED控制模块
#ifndef SLE_LED_CLIENT_H //头文件保护宏开始,防止重复包含6。SLE_LED_CLIENT_H是自定义的宏名称,通常与文件名对应。
#define SLE_LED_CLIENT_H //定义上述宏,标记该头文件已被包含
#endif //头文件保护宏结束标记
这段代码是一个 LED 控制模块的头文件框架,采用了标准的头文件保护机制来防止重复包含。
头文件保护宏的作用:头文件保护宏(也称为 Include Guard)是 C/C++ 中防止头文件被重复包含的标准技术。当多个源文件包含同一个头文件时,可能会导致函数或变量的重复定义错误。使用保护宏可以确保头文件内容只被编译一次。
2.SLE_LED_Client\src\SLE_LED_Client.c
实现了一个基于 SLE协议的 LED 客户端控制系统,主要功能是通过 SLE 协议与远程设备通信并控制本地 LED 灯。整个系统采用模块化设计,包含设备扫描、连接管理、服务发现和 LED 控制等核心模块。
代码特点:
安全机制:使用memcpy_s等安全函数防止缓冲区溢出
资源管理:动态分配内存(osal_vmalloc)并及时释放(osal_vfree)
硬件抽象:通过pinctrl.h和gpio.h封装底层硬件操作
协议栈集成:深度集成 SLE 协议栈,处理扫描、连接、服务发现全流程
调试支持:通过PRINT宏输出详细日志,便于问题定位
头文件与依赖管理
#include "securec.h" // 安全函数库(如memcpy_s)
#include "sle_device_discovery.h" // SLE设备发现相关API
#include "sle_connection_manager.h"// SLE连接管理
#include "sle_ssap_client.h" // SSAP(服务发现协议)客户端
#include "../inc/SLE_LED_Client.h" // 自定义LED客户端头文件
#include "soc_osal.h" // 操作系统抽象层(如线程、锁)
#include "app_init.h" // 应用初始化
#include "common_def.h" // 通用定义(如错误码)
#include "debug_print.h" // 调试打印
#include "pinctrl.h" // 引脚控制
#include "gpio.h" // GPIO操作
依赖多个底层库,涵盖安全操作、协议栈、硬件控制和系统接口,自定义头文件SLE_LED_Client.h用于声明 LED 控制相关接口。
宏定义与全局变量
//宏定义
#define SLE_MTU_SIZE_DEFAULT 300 // 默认MTU(最大传输单元)大小
#define SLE_SEEK_INTERVAL_DEFAULT 100 // 扫描间隔(单位可能是ms)
#define SLE_SEEK_WINDOW_DEFAULT 100 // 扫描窗口
#define UUID_16BIT_LEN 2 // 16位UUID长度
#define UUID_128BIT_LEN 16 // 128位UUID长度
//全局变量
static sle_announce_seek_callbacks_t g_seek_cbk = {0};//扫描回调函数组
static sle_connection_callbacks_t g_connect_cbk = {0};//连接回调函数组
static ssapc_callbacks_t g_ssapc_cbk = {0};//SSAP回调函数组
static sle_addr_t g_remote_addr = {0};//远程设备地址
宏定义配置了通信参数(MTU、扫描间隔等),全局变量存储协议回调函数和连接状态,便于跨函数访问。
LED 控制功能
//LED控制函数
static void example_turn_onoff_led(pin_t pin, gpio_level_t level)
{
uapi_pin_set_mode(pin, HAL_PIO_FUNC_GPIO);// 配置引脚为GPIO模式
uapi_gpio_set_dir(pin, GPIO_DIRECTION_OUTPUT);// 设置为输出模式
uapi_gpio_set_val(pin, level);// 设置电平(高/低)
}
//LED通知回调
static void example_led_notification_cbk(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data)
{
// 解析数据并控制LED(如"RLED_ON"控制红色LED)
// 支持"RLED_ON", "RLED_OFF", "YLED_ON", "YLED_OFF", "GLED_ON", "GLED_OFF"命令
if (data->data_len == strlen("RLED_ON") && data->data[0] == 'R' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'N') {
example_turn_onoff_led(GPIO_07, GPIO_LEVEL_HIGH);
}
if (data->data_len == strlen("RLED_OFF") && data->data[0] == 'R' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'F' &&
data->data[7] == 'F') {
example_turn_onoff_led(GPIO_07, GPIO_LEVEL_LOW);
}
if (data->data_len == strlen("YLED_ON") && data->data[0] == 'Y' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'N') {
example_turn_onoff_led(GPIO_10, GPIO_LEVEL_HIGH);
}
if (data->data_len == strlen("YLED_OFF") && data->data[0] == 'Y' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'F' &&
data->data[7] == 'F') {
example_turn_onoff_led(GPIO_10, GPIO_LEVEL_LOW);
}
if (data->data_len == strlen("GLED_ON") && data->data[0] == 'G' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'N') {
example_turn_onoff_led(GPIO_11, GPIO_LEVEL_HIGH);
}
if (data->data_len == strlen("GLED_OFF") && data->data[0] == 'G' && data->data[1] == 'L' && data->data[2] == 'E' &&
data->data[3] == 'D' && data->data[4] == '_' && data->data[5] == 'O' && data->data[6] == 'F' &&
data->data[7] == 'F') {
example_turn_onoff_led(GPIO_11, GPIO_LEVEL_LOW);
}
//将LED状态写回服务器
/* 将本端(Cient)的LED等状态, 通过写请求发送给对端(Server) */
ssapc_write_param_t param = {0};
param.handle = g_find_service_result.start_hdl;
param.type = SSAP_PROPERTY_TYPE_VALUE;
param.data_len = data->data_len;
param.data = osal_vmalloc(param.data_len);
if (param.data == NULL) {
PRINT("[SLE Client] write req mem fail\r\n");
return;
}
if (memcpy_s(param.data, param.data_len, data, data->data_len) != EOK) {
PRINT("[SLE Client] write req memcpy fail\r\n");
osal_vfree(param.data);
return;
}
// ssapc_write_req(client_id, conn_id, ¶m); 发送写请求
if (ssapc_write_req(client_id, conn_id, ¶m) != ERRCODE_SUCC) {
PRINT("[SLE Client] write req fail\r\n");
osal_vfree(param.data);
return;
}
osal_vfree(param.data);
return;
}
通过 GPIO 接口直接控制硬件引脚,实现 LED 开关。
支持三种颜色 LED(红、黄、绿),分别对应 GPIO_07、GPIO_10、GPIO_11。
接收到远程通知后,不仅控制本地 LED,还会将状态回传给服务器。
回调函数(扫描使能,开始,停止,结果)
//扫描使能回调
static void example_sle_enable_cbk(errcode_t status)
{
if (status == ERRCODE_SUCC) {
example_sle_start_scan();
}
}
//扫描开始回调
static void example_sle_seek_enable_cbk(errcode_t status)
{
if (status == ERRCODE_SUCC) {
return;
}
}
//扫描停止回调
static void example_sle_seek_disable_cbk(errcode_t status)
{
if (status == ERRCODE_SUCC) {
sle_connect_remote_device(&g_remote_addr);// 连接设备
}
}
//定义了一个静态全局数组 g_sle_expected_addr,用于存储预期的SLE设备地址
static uint8_t g_sle_expected_addr[SLE_ADDR_LEN] = {0x04, 0x01, 0x06, 0x08, 0x06, 0x03};
// static uint8_t g_sle_expected_addr[SLE_ADDR_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
//扫描结果回调,找到特定设备后停止扫描
//触发扫描的调用流程--扫描结果处理 如果找到目标设备(地址匹配),则停止扫描。
static void example_sle_seek_result_info_cbk(sle_seek_result_info_t *seek_result_data)
{
if (seek_result_data == NULL) {
//调试输出
PRINT("[SLE Client] seek result seek_result_data is NULL\r\n");
return;
}
if (memcmp((void *)seek_result_data->addr.addr, (void *)g_sle_expected_addr, SLE_ADDR_LEN) == 0) {
PRINT("[SLE Client] seek result find expected addr:%02x***%02x%02x\r\n", seek_result_data->addr.addr[0],
seek_result_data->addr.addr[4], seek_result_data->addr.addr[5]);
(void)memcpy_s(&g_remote_addr, sizeof(sle_addr_t), &seek_result_data->addr, sizeof(sle_addr_t));
sle_stop_seek();// 停止扫描
}
}
设备扫描与连接流程
// 扫描参数配置与启动
static void example_sle_start_scan(void) {
sle_seek_param_t param;
param.seek_interval[0] = SLE_SEEK_INTERVAL_DEFAULT;
param.seek_window[0] = SLE_SEEK_WINDOW_DEFAULT;
sle_set_seek_param(¶m);
sle_start_seek(); // 启动扫描
}
// 扫描结果处理(匹配目标设备地址)
static void example_sle_seek_result_info_cbk(...) {
if (memcmp(发现地址, 预期地址, SLE_ADDR_LEN) == 0) {
memcpy_s(保存远程地址, 发现地址);
sle_stop_seek(); // 找到目标后停止扫描
}
}
// 连接状态处理
static void example_sle_connect_state_changed_cbk(...) {
if (conn_state == 已连接) {
if (未配对) sle_pair_remote_device(发起配对);
g_conn_id = conn_id; // 保存连接ID
}
}
扫描阶段:按预设参数(间隔、窗口)搜索设备,匹配到预设地址g_sle_expected_addr后停止。
连接阶段:建立连接后自动发起配对,并保存连接 ID 用于后续通信。
服务发现与通信协议
// SSAP协议交换信息回调
static void example_sle_exchange_info_cbk(...) {
if (状态成功) {
ssapc_find_structure_param_t find_param;
find_param.type = SSAP_FIND_TYPE_PRIMARY_SERVICE;
ssapc_find_structure(查找主服务);
}
}
// 服务发现结果处理
static void example_sle_find_structure_cbk(...) {
if (状态成功) {
g_find_service_result.start_hdl = service->start_hdl;
memcpy_s(保存服务UUID, service->uuid);
}
}
// 处理远程通知(核心LED控制入口)
static void example_sle_notification_cbk(...) {
example_led_notification_cbk(client_id, conn_id, data); // 调用LED控制函数
}
通过 SSAP 协议发现远程服务,获取服务句柄和 UUID
注册通知回调example_sle_notification_cbk,当收到远程命令时触发 LED 控制
任务启动与系统集成
// 主任务函数
static int example_sle_led_client_task(...) {
osal_msleep(5000); // 等待SLE初始化
// 注册各类回调函数
example_sle_seek_cbk_register();
example_sle_connect_cbk_register();
example_sle_ssapc_cbk_register();
// 启用SLE功能
if (enable_sle() != 成功) return -1;
return 0;
}
// 任务创建入口
static void example_sle_led_client_entry(void) {
osal_task *task_handle = osal_kthread_create(
example_sle_led_client_task,
"SLELedClientTask",
SLE_LED_CLI_STACK_SIZE
);
if (task_handle) {
osal_kthread_set_priority(task_handle, SLE_LED_CLI_TASK_PRIO);
osal_kfree(task_handle);
}
}
app_run(example_sle_led_client_entry); // 启动应用
客户端总结
核心工作流程:
- 系统启动:创建 SLE LED 客户端任务,等待协议栈初始化
- 注册回调:设置扫描、连接、SSAP 协议的各类回调函数
- 设备扫描:按预设参数搜索设备,匹配到目标地址后停止
- 建立连接:与目标设备配对,获取连接 ID
- 服务发现:通过 SSAP协议发现远程服务,获取操作句柄
- 命令交互:接收远程通知(如 “RLED_ON”),控制本地 LED 并回传状态
三、服务端
1.SLE_LED_Server\inc\SLE_LED_Server.h
SLE协议栈的 LED 服务器头文件框架,主要定义了服务相关的 UUID 和基础结构,使用标准的头文件保护宏防止重复包含。
//使用 #ifndef、#define 和 #endif 防止头文件被重复包含,避免编译错误
/**
* @defgroup
* @ingroup
* @{
*/
#ifndef SLE_LED_SERVER_H
#define SLE_LED_SERVER_H
//引入了 sle_ssap_server.h,可能是SLE协议栈中与SSAP(服务发现协议)服务器相关的定义。
#include "sle_ssap_server.h"
//定义了三个UUID(通用唯一标识符),用于标识服务器中的服务、通知报告和属性。
/* Service UUID */
#define SLE_UUID_SERVER_SERVICE 0xABCD //标识LED服务器提供的服务。客户端通过此UUID发现服务器。
/* Notify Repoert UUID */
#define SLE_UUID_SERVER_NTF_REPORT 0x1122 //标识服务器发送的通知或报告。客户端可以订阅此UUID以接收状态更新(如LED开关状态)。
/* Property UUID */
#define SLE_UUID_SERVER_PROPERTY 0x3344 //标识服务器中的属性。客户端可以读取或写入此UUID对应的属性(如控制LED开关)。
#endif
//服务发现
//客户端通过 SLE_UUID_SERVER_SERVICE 发现服务器提供的LED控制服务。
//通知订阅
//客户端订阅 SLE_UUID_SERVER_NTF_REPORT,接收服务器推送的LED状态变更通知。
//属性操作
//客户端通过 SLE_UUID_SERVER_PROPERTY 读取或设置LED状态(如发送"RLED_ON"命令)。
服务 UUID (0xABCD):标识 LED 服务器提供的整体服务,客户端通过此 UUID 发现服务器
通知报告 UUID (0x1122):用于服务器主动推送通知(如 LED 状态变化)
属性 UUID (0x3344):用于客户端读写 LED 控制属性(如开关状态)
2.SLE_LED_Server\inc\SLE_LED_Server_adv.h
定义了 SLE 协议中 LED 服务器的广播功能,包含数据结构、信道配置和初始化接口。
广播数据结构设计
//广播通用数据结构
struct sle_adv_common_value {
uint8_t length; // 数据长度
uint8_t type; // 数据类型
uint8_t value; // 数据值
};
// 用途:表示广播数据的基本结构,包含长度、类型和值。
// 字段解析:
// length:广播数据的长度。
// type:广播数据的类型(如设备名称、服务UUID等)。
// value:广播数据的具体内容。
广播信道配置
//广播信道映射枚举
typedef enum {
SLE_ADV_CHANNEL_MAP_77 = 0x01, // 信道77
SLE_ADV_CHANNEL_MAP_78 = 0x02, // 信道78
SLE_ADV_CHANNEL_MAP_79 = 0x04, // 信道79
SLE_ADV_CHANNEL_MAP_DEFAULT = 0x07 // 默认信道(77、78、79)
} sle_adv_channel_map;
// 用途:定义广播使用的信道映射。
// 字段解析:
// 每个枚举值对应一个信道(77、78、79)或默认信道组合。
广播数据类型枚举
//广播数据类型枚举
typedef enum {
SLE_ADV_DATA_TYPE_DISCOVERY_LEVEL = 0x01, /*!< 发现等级 */
SLE_ADV_DATA_TYPE_ACCESS_MODE = 0x02, /*!< 接入层能力 */
SLE_ADV_DATA_TYPE_SERVICE_DATA_16BIT_UUID = 0x03, /*!< 标准服务数据信息 */
SLE_ADV_DATA_TYPE_SERVICE_DATA_128BIT_UUID = 0x04, /*!< 自定义服务数据信息 */
SLE_ADV_DATA_TYPE_COMPLETE_LIST_OF_16BIT_SERVICE_UUIDS = 0x05, /*!< 完整标准服务标识列表 */
SLE_ADV_DATA_TYPE_COMPLETE_LIST_OF_128BIT_SERVICE_UUIDS = 0x06, /*!< 完整自定义服务标识列表 */
SLE_ADV_DATA_TYPE_INCOMPLETE_LIST_OF_16BIT_SERVICE_UUIDS = 0x07, /*!< 部分标准服务标识列表 */
SLE_ADV_DATA_TYPE_INCOMPLETE_LIST_OF_128BIT_SERVICE_UUIDS = 0x08, /*!< 部分自定义服务标识列表 */
SLE_ADV_DATA_TYPE_SERVICE_STRUCTURE_HASH_VALUE = 0x09, /*!< 服务结构散列值 */
SLE_ADV_DATA_TYPE_SHORTENED_LOCAL_NAME = 0x0A, /*!< 设备缩写本地名称 */
SLE_ADV_DATA_TYPE_COMPLETE_LOCAL_NAME = 0x0B, /*!< 设备完整本地名称 */
SLE_ADV_DATA_TYPE_TX_POWER_LEVEL = 0x0C, /*!< 广播发送功率 */
SLE_ADV_DATA_TYPE_SLB_COMMUNICATION_DOMAIN = 0x0D, /*!< SLB通信域域名 */
SLE_ADV_DATA_TYPE_SLB_MEDIA_ACCESS_LAYER_ID = 0x0E, /*!< SLB媒体接入层标识 */
SLE_ADV_DATA_TYPE_EXTENDED = 0xFE, /*!< 数据类型扩展 */
SLE_ADV_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF /*!< 厂商自定义信息 */
} sle_adv_data_type;
// 用途:定义广播数据的类型,涵盖标准服务、设备名称、功率等级等。
// 字段解析:
// 包括发现等级、服务UUID、设备名称、功率等级等广播数据类型。
// 0xFF 保留给厂商自定义数据。
广播初始化接口
//广播初始化函数
errcode_t example_sle_server_adv_init(void);
3.SLE_LED_Server\src\SLE_LED_Server.c
实现了一个基于 SLE协议的 LED 服务器应用,主要用于控制不同类型 LED 的开关状态。该应用支持设备发现、连接建立、服务发布以及 LED 状态的远程控制,适用于物联网设备中的 LED 管理场景。
关键数据结构
- 枚举类型:
example_control_led_type_t
定义LED控制操作(开关主板LED、灯板RGB灯等) - 全局变量:存储服务句柄、连接ID、属性句柄等关键信息
- UUID定义:使用16位UUID标识服务和属性
核心模块划分
- SLE协议栈接口:通过
sle_common.h
、sle_ssap_server.h
等头文件与SLE协议栈交互 - 任务管理:使用CMSIS-RTOS v2进行多任务管理,创建LED控制任务和服务器主任务
- LED控制逻辑:定义多种LED操作类型,并实现周期性控制逻辑
- 服务与属性管理:创建SLE服务和属性,处理读写请求
- 连接管理:处理设备连接状态变化和配对完成事件
LED控制任务
static int example_led_control_task(const char *arg)
{
example_control_led_type_t last_led_operation = EXAMPLE_CONTORL_LED_LEDBOARD_GLED_OFF;
PRINT("[SLE Server] start led control task\r\n");
while (1)
{
(void)osal_msleep(500);
if (last_led_operation == EXAMPLE_CONTORL_LED_LEDBOARD_GLED_OFF) {
// 发送打开红色LED命令
uint8_t write_req_data[] = {'R', 'L', 'E', 'D', '_', 'O', 'N'};
example_sle_server_send_notify_by_handle(write_req_data, sizeof(write_req_data));
last_led_operation = EXAMPLE_CONTORL_LED_LEDBOARD_RLED_ON;
}
// 其他LED状态切换逻辑(省略部分代码)
}
return 0;
}
- 周期性控制:每500ms切换一次LED状态,按红→黄→绿的顺序循环控制
- 命令发送:通过
example_sle_server_send_notify_by_handle
发送通知到客户端 - 状态管理:使用枚举变量
last_led_operation
跟踪上一次操作状态
服务器初始化流程
static int example_sle_led_server_task(const char *arg)
{
(void)osal_msleep(5000); // 等待SLE初始化
// 使能SLE协议栈
if (enable_sle() != ERRCODE_SUCC) return -1;
// 注册连接管理回调
if (example_sle_conn_register_cbks() != ERRCODE_SUCC) return -1;
// 注册SSAP服务器回调
if (example_sle_ssaps_register_cbks() != ERRCODE_SUCC) return -1;
// 添加服务和属性
if (example_sle_server_add() != ERRCODE_SUCC) return -1;
// 启动设备广播
if (example_sle_server_adv_init() != ERRCODE_SUCC) return -1;
return 0;
}
- 初始化顺序:延时等待→使能SLE→注册回调→创建服务→启动广播
- 关键函数:
enable_sle()
:初始化SLE协议栈example_sle_server_add()
:创建服务和属性example_sle_server_adv_init()
:启动设备发现广播
服务与属性创建
static errcode_t example_sle_server_add(void)
{
// 注册服务器
ssaps_register_server(&app_uuid, &g_server_id);
// 添加服务
if (example_sle_server_service_add() != ERRCODE_SUCC) return ERRCODE_FAIL;
// 添加属性
if (example_sle_server_property_add() != ERRCODE_SUCC) return ERRCODE_FAIL;
// 启动服务
return ssaps_start_service(g_server_id, g_service_handle);
}
- UUID设置:使用16位UUID标识服务(
SLE_UUID_SERVER_SERVICE
)和属性(SLE_UUID_SERVER_PROPERTY
) - 属性权限:设置为可读可写(
SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE
) - 描述符添加:为属性添加描述符,定义通知操作权限
客户端交互处理
static void example_ssaps_write_request_cbk(uint8_t server_id, uint16_t conn_id,
ssaps_req_write_cb_t *write_cb_para, errcode_t status)
{
if (status == ERRCODE_SUCC) {
example_print_led_state(write_cb_para);
}
}
static void example_print_led_state(ssaps_req_write_cb_t *write_cb_para)
{
// 解析"LED_ON"和"LED_OFF"命令并打印状态
if (write_cb_para->length == strlen("LED_ON") &&
memcmp(write_cb_para->value, "LED_ON", 6) == 0) {
PRINT("[SLE Server] client main board led is on.\r\n");
}
// 其他命令解析逻辑(省略)
}
- 写请求处理:接收客户端发送的LED控制命令
- 命令解析:通过字符串匹配识别"LED_ON"、"LED_OFF"等命令
- 状态反馈:通过
PRINT
函数输出LED状态到调试日志
通知发送机制
static errcode_t example_sle_server_send_notify_by_handle(const uint8_t *data, uint8_t len)
{
ssaps_ntf_ind_t param = {0};
param.handle = g_property_handle;
param.value = osal_vmalloc(len);
param.value_len = len;
// 内存拷贝
if (memcpy_s(param.value, len, data, len) != EOK) return ERRCODE_MEMCPY;
// 发送通知
if (ssaps_notify_indicate(g_server_id, g_conn_id, ¶m) != ERRCODE_SUCC) {
osal_vfree(param.value);
return ERRCODE_FAIL;
}
osal_vfree(param.value);
return ERRCODE_SUCC;
}
- 通知结构:使用
ssaps_ntf_ind_t
结构封装通知数据 - 内存管理:动态分配内存存储通知数据,发送后释放
- 协议接口:通过
ssaps_notify_indicate
函数发送通知到客户端
连接状态管理
static void example_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)
{
PRINT("[SLE Server] connect state changed conn_id:0x%02x, conn_state:0x%x\r\n",
conn_id, conn_state);
g_conn_id = conn_id;
}
static void example_sle_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status)
{
PRINT("[SLE Server] pair complete conn_id:0x%02x, status:0x%x\r\n", conn_id, status);
if (status == ERRCODE_SUCC) {
example_led_control_entry(); // 配对成功后启动LED控制任务
}
}
- 状态回调:监听连接状态变化和配对完成事件
- 任务启动:配对成功后启动LED控制任务
- 连接ID管理:使用全局变量
g_conn_id
存储当前连接ID
4.SLE_LED_Server\src\SLE_LED_Server_adv.c
实现了SLE的广播功能,主要用于设备发现和连接建立。通过配置广播参数、设置广播数据和扫描响应数据,使服务器能够在网络中被其他设备发现,并支持连接请求。代码包含地址设置、名称配置、广播参数初始化及回调函数注册等完整流程。
广播数据配置
// 设置设备名称到广播数据
static uint16_t example_sle_set_adv_local_name(uint8_t *adv_data, uint16_t max_len) {
uint8_t local_name_len = strlen((char *)sle_local_name);
adv_data[0] = local_name_len + 1; // 长度字段(含类型字节)
adv_data[1] = SLE_ADV_DATA_TYPE_COMPLETE_LOCAL_NAME; // 数据类型
memcpy_s(&adv_data[2], max_len - 2, sle_local_name, local_name_len);
return local_name_len + 2;
}
// 配置广播数据(发现等级、接入模式)
static uint16_t example_sle_set_adv_data(uint8_t *adv_data) {
// 发现等级配置
struct sle_adv_common_value adv_disc_level = {
.length = sizeof(struct sle_adv_common_value) - 1,
.type = SLE_ADV_DATA_TYPE_DISCOVERY_LEVEL,
.value = SLE_ANNOUNCE_LEVEL_NORMAL
};
memcpy_s(adv_data, SLE_ADV_DATA_LEN_MAX, &adv_disc_level, sizeof(adv_disc_level));
// 接入模式配置
struct sle_adv_common_value adv_access_mode = {
.length = sizeof(struct sle_adv_common_value) - 1,
.type = SLE_ADV_DATA_TYPE_ACCESS_MODE,
.value = 0
};
memcpy_s(&adv_data[sizeof(adv_disc_level)], SLE_ADV_DATA_LEN_MAX - sizeof(adv_disc_level),
&adv_access_mode, sizeof(adv_access_mode));
return sizeof(adv_disc_level) + sizeof(adv_access_mode);
}
- 数据结构:使用
struct sle_adv_common_value
定义广播数据项,包含长度、类型和值 - 名称设置:遵循广播数据格式(长度+类型+内容),名称为
"SLE_LED_SERVER"
- 发现等级:设置为
SLE_ANNOUNCE_LEVEL_NORMAL
(普通发现等级) - 接入模式:值为0(具体含义由协议定义)
扫描响应数据配置
// 配置扫描响应数据(功率等级、设备名称)
static uint16_t example_sle_set_scan_response_data(uint8_t *scan_rsp_data) {
uint16_t idx = 0;
// 发送功率配置
struct sle_adv_common_value tx_power_level = {
.length = sizeof(struct sle_adv_common_value) - 1,
.type = SLE_ADV_DATA_TYPE_TX_POWER_LEVEL,
.value = SLE_ADV_TX_POWER // 功率值10
};
memcpy_s(scan_rsp_data, SLE_ADV_DATA_LEN_MAX, &tx_power_level, sizeof(tx_power_level));
idx += sizeof(tx_power_level);
// 设备名称配置(复用广播数据中的名称设置函数)
idx += example_sle_set_adv_local_name(&scan_rsp_data[idx], SLE_ADV_DATA_LEN_MAX - idx);
return idx;
}
- 扫描响应作用:当设备被扫描时,返回包含功率和名称的响应数据
- 功率等级:设置为
SLE_ADV_TX_POWER=10
,影响信号覆盖范围 - 数据复用:扫描响应中的名称设置复用广播数据的名称配置函数
设备地址与名称设置
// 本地地址设置(固定地址:0x04, 0x01, 0x06, 0x08, 0x06, 0x03)
static void example_sle_set_addr(void) {
uint8_t g_sle_local_addr[SLE_ADDR_LEN] = {0x04, 0x01, 0x06, 0x08, 0x06, 0x03};
sle_addr_t sle_addr = {0};
sle_addr.type = 0;
memcpy_s(sle_addr.addr, SLE_ADDR_LEN, g_sle_local_addr, SLE_ADDR_LEN);
sle_set_local_addr(&sle_addr);
}
// 本地名称设置("sle_led_server")
static void example_sle_set_name(void) {
uint8_t g_local_device_name[] = {'s', 'l', 'e', '_', 'l', 'e', 'd', '_', 's', 'e', 'r', 'v', 'e', 'r'};
sle_set_local_name(g_local_device_name, sizeof(g_local_device_name));
}
- 地址格式:6字节固定地址,可能为厂商自定义格式
- 名称规范:使用小写字母和下划线,符合设备命名惯例
- 协议接口:通过
sle_set_local_addr
和sle_set_local_name
设置基础信息
广播参数初始化
// 设置广播参数(间隔、超时等)
static errcode_t example_sle_set_default_announce_param(void) {
sle_announce_param_t param = {
.announce_mode = SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE, // 可连接可扫描模式
.announce_handle = SLE_ADV_HANDLE_DEFAULT, // 广播句柄
.announce_gt_role = SLE_ANNOUNCE_ROLE_T_CAN_NEGO, // 角色可协商
.announce_level = SLE_ANNOUNCE_LEVEL_NORMAL, // 普通广播等级
.announce_channel_map = SLE_ADV_CHANNEL_MAP_DEFAULT, // 默认信道映射
.announce_interval_min = SLE_ADV_INTERVAL_MIN_DEFAULT, // 最小广播间隔25ms
.announce_interval_max = SLE_ADV_INTERVAL_MAX_DEFAULT, // 最大广播间隔25ms
.conn_interval_min = SLE_CONN_INTV_MIN_DEFAULT, // 最小连接间隔12.5ms
.conn_interval_max = SLE_CONN_INTV_MAX_DEFAULT, // 最大连接间隔12.5ms
.conn_max_latency = SLE_CONN_MAX_LATENCY, // 最大连接延迟4990ms
.conn_supervision_timeout = SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT // 超时时间5000ms
};
memcpy_s(param.own_addr.addr, SLE_ADDR_LEN, g_sle_local_addr, SLE_ADDR_LEN);
return sle_set_announce_param(param.announce_handle, ¶m);
}
- 广播模式:
SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE
表示支持连接和扫描 - 间隔配置:广播间隔固定为25ms,连接间隔固定为12.5ms
- 超时机制:连接监控超时5000ms,超过则断开连接
- 信道映射:使用默认信道,适应标准网络环境
回调函数与广播控制
// 广播状态回调函数
void example_sle_announce_enable_cbk(uint32_t announce_id, errcode_t status) {
PRINT("[SLE Adv] 广播启用,ID:%02x,状态:%02x\r\n", announce_id, status);
}
void example_sle_announce_disable_cbk(uint32_t announce_id, errcode_t status) {
PRINT("[SLE Adv] 广播禁用,ID:%02x,状态:%02x\r\n", announce_id, status);
}
// 注册回调函数
void example_sle_announce_register_cbks(void) {
sle_announce_seek_callbacks_t seek_cbks = {
.announce_enable_cb = example_sle_announce_enable_cbk,
.announce_disable_cb = example_sle_announce_disable_cbk,
.announce_terminal_cb = example_sle_announce_terminal_cbk,
.sle_enable_cb = example_sle_enable_cbk
};
sle_announce_seek_register_callbacks(&seek_cbks);
}
// 广播初始化主函数
errcode_t example_sle_server_adv_init(void) {
example_sle_announce_register_cbks(); // 注册回调
example_sle_set_default_announce_param(); // 设置广播参数
example_sle_set_default_announce_data(); // 设置广播数据
example_sle_set_addr(); // 设置设备地址
example_sle_set_name(); // 设置设备名称
sle_start_announce(SLE_ADV_HANDLE_DEFAULT); // 启动广播
return ERRCODE_SUCC;
}
- 回调机制:监听广播启用、禁用、终止和SLE启用事件
- 初始化流程:注册回调→设置参数→配置数据→地址名称→启动广播
- 广播控制:通过
sle_start_announce
启动广播,使用默认句柄1
四、从代码库拿的代码跑不通(解决)
将SLE_LED_Server\src\SLE_LED_Server.c文件中的example_sle_server_property_add方法内部
descriptor.permissions = SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE;
descriptor.value = osal_vmalloc(sizeof(ntf_value));
修改为:
descriptor.permissions = SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE;
descriptor.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_WRITE;
descriptor.type = SSAP_DESCRIPTOR_USER_DESCRIPTION;
descriptor.value = ntf_value;
descriptor.value_len = sizeof(ntf_value);
修改后的方法如下:
static errcode_t example_sle_server_property_add(void)
{
errcode_t ret = ERRCODE_FAIL;
ssaps_property_info_t property = {0};
ssaps_desc_info_t descriptor = {0};
uint8_t ntf_value[] = {0x01, 0x0};
property.permissions = SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE;
example_sle_uuid_setu2(SLE_UUID_SERVER_PROPERTY, &property.uuid);
property.value = osal_vmalloc(sizeof(g_sle_property_value));
if (property.value == NULL) {
PRINT("[SLE Server] sle property mem fail\r\n");
return ERRCODE_MALLOC;
}
if (memcpy_s(property.value, sizeof(g_sle_property_value), g_sle_property_value, sizeof(g_sle_property_value)) !=
EOK) {
osal_vfree(property.value);
PRINT("[SLE Server] sle property mem cpy fail\r\n");
return ERRCODE_MEMCPY;
}
ret = ssaps_add_property_sync(g_server_id, g_service_handle, &property, &g_property_handle);
if (ret != ERRCODE_SUCC) {
PRINT("[SLE Server] sle uuid add property fail, ret:0x%x\r\n", ret);
osal_vfree(property.value);
return ERRCODE_FAIL;
}
PRINT("[SLE Server] sle uuid add property property_handle: %u\r\n", g_property_handle);
//仓库的代码
// descriptor.permissions = SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE;
// descriptor.value = osal_vmalloc(sizeof(ntf_value));
//新的代码
descriptor.permissions = SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE;
descriptor.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_WRITE;
descriptor.type = SSAP_DESCRIPTOR_USER_DESCRIPTION;
descriptor.value = ntf_value;
descriptor.value_len = sizeof(ntf_value);
if (descriptor.value == NULL) {
PRINT("[SLE Server] sle descriptor mem fail\r\n");
osal_vfree(property.value);
return ERRCODE_MALLOC;
}
if (memcpy_s(descriptor.value, sizeof(ntf_value), ntf_value, sizeof(ntf_value)) != EOK) {
PRINT("[SLE Server] sle descriptor mem cpy fail\r\n");
osal_vfree(property.value);
osal_vfree(descriptor.value);
return ERRCODE_MEMCPY;
}
ret = ssaps_add_descriptor_sync(g_server_id, g_service_handle, g_property_handle, &descriptor);
if (ret != ERRCODE_SUCC) {
PRINT("[SLE Server] sle uuid add descriptor fail, ret:0x%x\r\n", ret);
osal_vfree(property.value);
osal_vfree(descriptor.value);
return ERRCODE_FAIL;
}
osal_vfree(property.value);
osal_vfree(descriptor.value);
return ERRCODE_SUCC;
}
差异主要体现在内存管理方式、描述符字段配置上。
总结
个人分析,仅供参考。如有疑问请留言或者联系计蒙。下一篇文章计划–写一个关于OLED的星闪互联案例。