星闪开发之Server-Client 指令交互控制红灯亮灭案例解析(SLE_LED详解)

news2025/6/6 9:41:51

系列文章目录

星闪开发之Server-Client 指令交互控制红灯亮灭的全流程解析(SLE_LED详解)


在这里插入图片描述

文章目录

  • 系列文章目录
  • 前言
  • 一、项目地址
  • 二、客户端
    • 1.SLE_LED_Client\inc\SLE_LED_Client.h
    • 2.SLE_LED_Client\src\SLE_LED_Client.c
      • 头文件与依赖管理
      • 宏定义与全局变量
      • LED 控制功能
      • 回调函数(扫描使能,开始,停止,结果)
      • 设备扫描与连接流程
      • 服务发现与通信协议
      • 任务启动与系统集成
      • 客户端总结
  • 三、服务端
    • 1.SLE_LED_Server\inc\SLE_LED_Server.h
    • 2.SLE_LED_Server\inc\SLE_LED_Server_adv.h
      • 广播数据结构设计
      • 广播信道配置
      • 广播数据类型枚举
      • 广播初始化接口
    • 3.SLE_LED_Server\src\SLE_LED_Server.c
      • LED控制任务
      • 服务器初始化流程
      • 服务与属性创建
      • 客户端交互处理
      • 通知发送机制
      • 连接状态管理
    • 4.SLE_LED_Server\src\SLE_LED_Server_adv.c
      • 广播数据配置
      • 扫描响应数据配置
      • 设备地址与名称设置
      • 广播参数初始化
      • 回调函数与广播控制
  • 四、从代码库拿的代码跑不通(解决)
  • 总结


前言

之前有粉丝想要计蒙写一篇关于sle_led样例的详细解析文章,于是有了这篇文章,如果拿到代码库中的项目跑不通请直接跳转至第四部分。


一、项目地址

基于海思WS63项目库中的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, &param);  发送写请求
    if (ssapc_write_req(client_id, conn_id, &param) != 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(&param);
    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); // 启动应用

客户端总结

核心工作流程:

  1. 系统启动:创建 SLE LED 客户端任务,等待协议栈初始化
  2. 注册回调:设置扫描、连接、SSAP 协议的各类回调函数
  3. 设备扫描:按预设参数搜索设备,匹配到目标地址后停止
  4. 建立连接:与目标设备配对,获取连接 ID
  5. 服务发现:通过 SSAP协议发现远程服务,获取操作句柄
  6. 命令交互:接收远程通知(如 “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标识服务和属性

核心模块划分

  1. SLE协议栈接口:通过sle_common.hsle_ssap_server.h等头文件与SLE协议栈交互
  2. 任务管理:使用CMSIS-RTOS v2进行多任务管理,创建LED控制任务和服务器主任务
  3. LED控制逻辑:定义多种LED操作类型,并实现周期性控制逻辑
  4. 服务与属性管理:创建SLE服务和属性,处理读写请求
  5. 连接管理:处理设备连接状态变化和配对完成事件

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, &param) != 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_addrsle_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, &param);
}
  • 广播模式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的星闪互联案例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day25-计算机网络-3

1. DNS解析流程 windows host文件是否配置域名对应的ip查询本地DNS缓存是否有这个域名对应的ip询问本地DNS&#xff08;网卡配置的&#xff09;是否知晓域名对应的ip本地DNS访问根域名解析服务器&#xff0c;但是根DNS只有顶级域名的记录&#xff0c;根告诉我们.cn顶级域名的D…

RunnablePassthrough介绍和透传参数实战

导读&#xff1a;在构建复杂的LangChain应用时&#xff0c;你是否遇到过需要在处理链中既保留原始输入又动态扩展上下文的场景&#xff1f;RunnablePassthrough正是为解决这类数据流处理问题而设计的核心组件。 本文通过深入剖析RunnablePassthrough的工作机制和实际应用&#…

JavaSec-XSS

反射型XSS 简介 XSS(跨站脚本攻击)利用浏览器对服务器内容的信任&#xff0c;攻击者通过在网页中注入恶意脚本&#xff0c;使这些脚本在用户的浏览器上执行&#xff0c;从而实现攻击。常见的XSS攻击危害包括窃取用户会话信息、篡改网页内容、将用户重定向到恶意网站&#xff0c…

echarts在uniapp中使用安卓真机运行时无法显示的问题

文章目录 一、实现效果二、话不多说&#xff0c;上源码 前言&#xff1a;我们在uniapp中开发的时候&#xff0c;开发的时候很正常&#xff0c;echarts的图形在h5页面上也是很正常的&#xff0c;但是当我们打包成安卓app或者使用安卓真机运行的时候&#xff0c;图形根本就没有渲…

STM32----IAP远程升级

一、概述&#xff1a; IAP&#xff0c;全称是“In-Application Programming”&#xff0c;中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口&#xff08;如USART&#xff0c;IIC&#xff0c;CAN&#xff0c;USB&#xff0c;以太网接口甚至是无线射频通道&#…

C++优选算法 904. 水果成篮

文章目录 1.题目描述2.算法思路 3.完整代码容器做法数组做法 1.题目描述 看到这种题目&#xff0c;总觉得自己在做阅读理解&#xff0c;晕了&#xff0c;题目要求我们在一个数组里分别找出两种数字&#xff0c;并统计这两种数字分别出现一共是多少。 2.算法思路 采用哈希表滑…

Python6.5打卡(day37)

DAY 37 早停策略和模型权重的保存 知识点回顾&#xff1a; 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a;对信贷数据集训练后保存权重&#xf…

大中型水闸安全监测管理系统建设方案

一、背景介绍 我国现已建成流量5m/s及以上的水闸共计100321座。其中&#xff0c;大型水闸923座&#xff0c;中型水闸6,697座。按功能类型划分&#xff0c;分洪闸8193座&#xff0c;排&#xff08;退&#xff09;水闸17808座&#xff0c;挡潮闸4955座&#xff0c;引水闸13796座&…

风控研发大数据学习路线

在如今信息爆炸时代&#xff0c;风控系统离不开大数据技术的支撑&#xff0c;大数据技术可以帮助风控系统跑的更快&#xff0c;算的更准。因此&#xff0c;风控技术研发需要掌握大数据相关技术。然而大数据技术栈内容庞大丰富&#xff0c;风控研发同学很可能会面临以下这些痛点…

【设计模式】门面/外观模式

MySQL &#xff0c;MyTomcat 的启动 现在有 MySQL &#xff0c;MyTomcat 类&#xff0c;需要依次启动。 public class Application {public static void main(String[] args) {MySQL mySQL new MySQL();mySQL.initDate();mySQL.checkLog();mySQL.unlock();mySQL.listenPort(…

spring的webclient与vertx的webclient的比较

Spring WebClient 和 Vert.x WebClient 都是基于响应式编程模型的非阻塞 HTTP 客户端&#xff0c;但在设计理念、生态整合和适用场景上存在显著差异。以下是两者的核心比较&#xff1a; &#x1f504; 1. 技术背景与架构 • Spring WebClient ◦ 生态定位&#xff1a;属于 Sp…

贪心算法应用:埃及分数问题详解

贪心算法与埃及分数问题详解 埃及分数&#xff08;Egyptian Fractions&#xff09;问题是数论中的经典问题&#xff0c;要求将一个真分数表示为互不相同的单位分数之和。本文将用2万字全面解析贪心算法在埃及分数问题中的应用&#xff0c;涵盖数学原理、算法设计、Java实现、优…

高效集成AI能力:使用开放API打造问答系统,不用训练模型,也能做出懂知识的AI

本文为分享体验感受&#xff0c;非广告。 一、蓝耘平台核心功能与优势 丰富的模型资源库 蓝耘平台提供涵盖自然语言处理、计算机视觉、多模态交互等领域的预训练模型&#xff0c;支持用户直接调用或微调&#xff0c;无需从零开始训练&#xff0c;显著缩短开发周期。 高性能…

Qt 仪表盘源码分享

Qt 仪表盘源码分享 一、效果展示二、优点三、源码分享四、使用方法 一、效果展示 二、优点 直观性 数据以图表或数字形式展示&#xff0c;一目了然。用户可以快速获取关键信息&#xff0c;无需深入阅读大量文字。 实时性 仪表盘通常支持实时更新&#xff0c;确保数据的时效性。…

Python数据可视化科技图表绘制系列教程(四)

目录 带基线的棒棒糖图1 带基线的棒棒糖图2 带标记的棒棒糖图 哑铃图1 哑铃图2 包点图1 包点图2 雷达图1 雷达图2 交互式雷达图 【声明】&#xff1a;未经版权人书面许可&#xff0c;任何单位或个人不得以任何形式复制、发行、出租、改编、汇编、传播、展示或利用本博…

深入理解系统:UML类图

UML类图 类图&#xff08;class diagram&#xff09; 描述系统中的对象类型&#xff0c;以及存在于它们之间的各种静态关系。 正向工程&#xff08;forward engineering&#xff09;在编写代码之前画UML图。 逆向工程&#xff08;reverse engineering&#xff09;从已有代码建…

软件工程的定义与发展历程

文章目录 一、软件工程的定义二、软件工程的发展历程1. 前软件工程时期(1940s-1960s)2. 软件工程诞生(1968)3. 结构化方法时期(1970s)4. 面向对象时期(1980s)5. 现代软件工程(1990s-至今) 三、软件工程的发展趋势 一、软件工程的定义 软件工程是应用系统化、规范化、可量化的方…

第十三节:第五部分:集合框架:集合嵌套

集合嵌套案例分析 代码&#xff1a; package com.itheima.day27_Collection_nesting;import java.util.*;/*目标:理解集合的嵌套。 江苏省 "南京市","扬州市","苏州市","无锡市","常州市" 湖北省 "武汉市","…

Java设计模式之观察者模式详解

一、观察者模式简介 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了对象之间的一对多依赖关系。当一个对象&#xff08;主题&#xff09;的状态发生改变时&#xff0c;所有依赖于它的对象&#xff08;观察者&#xff09;都会自…