P0/P1/P2功能实现:接入业务组合根(按键/LED/过零/配网/OTA),联动、11B定时、运行记录、灯泡寿命(DP3/DP4/DP25)、CBR0 VM、512分包
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
/******************************************************************************
|
||||
* @file app_th_scan.c
|
||||
* @brief CBR0 断连温湿度扫描(规格书第 8 章):500ms 重连广播 ↔ 500ms 扫描
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.07
|
||||
* @version V1.1.6
|
||||
* @history
|
||||
* - V1.1.6, 2026.09.07, cyWu, 扫描上报只留 GAP 0xE2
|
||||
* - V1.0.0, 2026.09.04, cyWu, P2 完整实现(P0/P1 为骨架)
|
||||
*
|
||||
* @note 门控(8 章表格):已连接 / 未配对 / 配对中(usr_goto_pair) /
|
||||
* OTA 中(usr_goto_updata) / 联动配置无效(缸体 0)→ 不扫描,纯广播。
|
||||
* 进入交替后偶拍重连广播、奇拍扫描(8.2),一拍 = BOARD_ADV_SCAN_SLOT_MS。
|
||||
*
|
||||
* 匹配(8.1):名字 == "JBao-"+DP11 缸体号(uint16 十进制,无前导零)
|
||||
* 且厂商段 0xFFFF 首字节 == DP11 配置版本 → temp_x10=int8℃×10 喂联动;
|
||||
* 湿度只打日志。名字/版本/解析任何一项不符 → 当没收到,不更新温度、
|
||||
* 不关加热(保持上一态)。低开销:先滤名字,名字对了才找厂商段。
|
||||
*
|
||||
* ⚠ 上报回调跑在 BLE 栈线程;扫描只允许断连态,占用射频期间广播已
|
||||
* 暂停(usr_le_set_adv_pause),不允许「边广播边扫」。
|
||||
******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_config.h"
|
||||
#include "app_main.h" /* usr_var:配对/OTA 流程标志 */
|
||||
#include "app_th_scan.h"
|
||||
#include "app_linkage.h"
|
||||
#include "usr_le_api.h" /* usr_ble_is_connected / usr_get_pair_flag / pause */
|
||||
#include "board_pin.h"
|
||||
#include "btstack/le/ble_api.h" /* ble_op_set_scan_param / ble_op_scan_enable2 */
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[THSCAN]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/* adv AD type */
|
||||
#define TH_SCAN_AD_NAME_SHORT 0x08u /* Shortened Local Name */
|
||||
#define TH_SCAN_AD_NAME_COMPLETE 0x09u /* Complete Local Name */
|
||||
#define TH_SCAN_AD_MFR 0xFFu /* Manufacturer Specific Data */
|
||||
|
||||
/* 厂商 0xFFFF(小端)后 3B:配置版本 / 温度 int8 / 湿度(8.1) */
|
||||
#define TH_SCAN_MFR_PAYLOAD_LEN 3u
|
||||
|
||||
/* 期望名 "JBao-"(5) + uint16 十进制(<=5) + '\0' */
|
||||
#define TH_SCAN_EXPECT_MAX 11u
|
||||
|
||||
/* 扫描参数:passive;一拍连续听 BOARD_ADV_SCAN_SLOT_MS(8.2)。
|
||||
* 不去重:风扇同一地址先发回连再发 JBao-。单位 0.625ms → ms*8/5(500ms→800) */
|
||||
#define TH_SCAN_SCAN_TYPE 0u
|
||||
#define TH_SCAN_INTERVAL_UNIT (uint16_t)((BOARD_ADV_SCAN_SLOT_MS * 8u) / 5u)
|
||||
#define TH_SCAN_WINDOW_UNIT TH_SCAN_INTERVAL_UNIT
|
||||
|
||||
/** 扫描模块运行时(全部状态集中在此) */
|
||||
typedef struct {
|
||||
uint8_t active; /**< 交替运行中(满足门控后置 1) */
|
||||
uint8_t scan_on; /**< 控制器扫描已使能(上报过滤用) */
|
||||
uint32_t tick; /**< 交替拍计数:偶=重连广播,奇=扫描(8.2) */
|
||||
uint16_t tank_id; /**< 期望缸体号(配置变化时重建 expect) */
|
||||
uint8_t cfg_ver; /**< 期望配置版本 */
|
||||
uint8_t expect_len;
|
||||
char expect[TH_SCAN_EXPECT_MAX]; /**< "JBao-"+十进制缸体号 */
|
||||
} ThScanCtx_t;
|
||||
|
||||
static ThScanCtx_t s_scan;
|
||||
|
||||
static void th_scan_disabled_once(void)
|
||||
{
|
||||
static uint8_t s_logged;
|
||||
|
||||
if (!s_logged) {
|
||||
s_logged = 1;
|
||||
log_info("app_th_scan disabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 门控:只有断连已配对、非配对/OTA 流程、联动配置有效才扫描
|
||||
* @return 1=进入交替;0=纯广播(找回 APP 优先)
|
||||
*/
|
||||
static uint8_t th_scan_should_run(void)
|
||||
{
|
||||
if (usr_ble_is_connected()) {
|
||||
return 0;
|
||||
}
|
||||
if (!usr_get_pair_flag()) {
|
||||
return 0;
|
||||
}
|
||||
if (usr_var.usr_goto_pair) {
|
||||
return 0;
|
||||
}
|
||||
if (usr_var.usr_goto_updata) {
|
||||
return 0;
|
||||
}
|
||||
if (!app_linkage_is_valid()) { /* 缸体 0 / Tmin>Tmax:无联动可跑,不扫 */
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置变化时重建期望名(tank/版本取自 DP11 联动配置)
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_sync_cfg(void)
|
||||
{
|
||||
uint16_t tank = app_linkage_get_tank_id();
|
||||
uint8_t ver = app_linkage_get_cfg_ver();
|
||||
char num[6];
|
||||
uint16_t t;
|
||||
int n = 0;
|
||||
|
||||
if (s_scan.tank_id == tank && s_scan.cfg_ver == ver) {
|
||||
return;
|
||||
}
|
||||
s_scan.tank_id = tank;
|
||||
s_scan.cfg_ver = ver;
|
||||
|
||||
memcpy(s_scan.expect, "JBao-", 5);
|
||||
s_scan.expect_len = 5;
|
||||
t = tank;
|
||||
do { /* 十进制,无前导零 */
|
||||
num[n++] = (char)('0' + (t % 10u));
|
||||
t /= 10u;
|
||||
} while (t);
|
||||
while (n > 0 && s_scan.expect_len < sizeof(s_scan.expect)) {
|
||||
s_scan.expect[s_scan.expect_len++] = num[--n];
|
||||
}
|
||||
s_scan.expect[s_scan.expect_len] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 开扫描(幂等;先由调用方暂停广播,再使能扫描)
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_start(void)
|
||||
{
|
||||
ble_cmd_ret_e ret;
|
||||
|
||||
if (s_scan.scan_on) {
|
||||
return;
|
||||
}
|
||||
ble_op_set_scan_param(TH_SCAN_SCAN_TYPE, TH_SCAN_INTERVAL_UNIT, TH_SCAN_WINDOW_UNIT);
|
||||
ret = ble_op_scan_enable2(1, 0);
|
||||
if (ret != BLE_CMD_RET_SUCESS) {
|
||||
log_info("scan enable fail %d\n", (int)ret);
|
||||
return;
|
||||
}
|
||||
s_scan.scan_on = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关扫描(幂等)
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_stop(void)
|
||||
{
|
||||
if (!s_scan.scan_on) {
|
||||
return;
|
||||
}
|
||||
ble_op_scan_enable2(0, 0);
|
||||
s_scan.scan_on = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 退出交替:停扫、恢复广播,回到纯 adv 策略
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_leave(void)
|
||||
{
|
||||
th_scan_stop();
|
||||
usr_le_set_adv_pause(0);
|
||||
s_scan.active = 0;
|
||||
s_scan.tick = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 名字是否等于期望 "JBao-{tank}"
|
||||
* @param data adv 数据
|
||||
* @param dlen adv 数据长度
|
||||
* @return 1=匹配
|
||||
*/
|
||||
static uint8_t th_scan_name_ok(const uint8_t *data, int dlen)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i + 1 < dlen) {
|
||||
uint8_t alen = data[i];
|
||||
|
||||
if (alen == 0 || i + 1 + alen > dlen) {
|
||||
break;
|
||||
}
|
||||
if ((data[i + 1] == TH_SCAN_AD_NAME_COMPLETE ||
|
||||
data[i + 1] == TH_SCAN_AD_NAME_SHORT) &&
|
||||
(uint8_t)(alen - 1u) == s_scan.expect_len &&
|
||||
memcmp(&data[i + 2], s_scan.expect, s_scan.expect_len) == 0) {
|
||||
return 1;
|
||||
}
|
||||
i += 1 + alen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 名字已匹配前提下取厂商段;版本相等才更新联动温度
|
||||
* @note 版本不等 / 厂商段缺失 → 当没收到(保持上一态)
|
||||
* @param data adv 数据
|
||||
* @param dlen adv 数据长度
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_try_feed(const uint8_t *data, int dlen)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i + 1 < dlen) {
|
||||
uint8_t alen = data[i];
|
||||
|
||||
if (alen == 0 || i + 1 + alen > dlen) {
|
||||
break;
|
||||
}
|
||||
if (data[i + 1] == TH_SCAN_AD_MFR && alen - 1u >= 2u + TH_SCAN_MFR_PAYLOAD_LEN) {
|
||||
const uint8_t *pl = &data[i + 4]; /* 跳过 len/type/2B 厂商号 */
|
||||
|
||||
if (data[i + 2] == 0xFFu && data[i + 3] == 0xFFu &&
|
||||
pl[0] == s_scan.cfg_ver) {
|
||||
app_linkage_set_scan_temp((int8_t)pl[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
i += 1 + alen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 一包 adv payload 的匹配入口
|
||||
* @param data AD 结构
|
||||
* @param dlen 长度
|
||||
* @return 无
|
||||
*/
|
||||
static void th_scan_on_adv(const uint8_t *data, int dlen)
|
||||
{
|
||||
if (data == NULL || dlen <= 0) {
|
||||
return;
|
||||
}
|
||||
if (th_scan_name_ok(data, dlen)) {
|
||||
th_scan_try_feed(data, dlen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化扫描模块
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_init(void)
|
||||
{
|
||||
#if BOARD_TH_SCAN_ENABLE
|
||||
memset(&s_scan, 0, sizeof(s_scan));
|
||||
log_info("app_th_scan init\n");
|
||||
#else
|
||||
th_scan_disabled_once();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文(usr_timer_loop 内):偶拍广播 / 奇拍扫描
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_tick(void)
|
||||
{
|
||||
#if BOARD_TH_SCAN_ENABLE
|
||||
if (!th_scan_should_run()) {
|
||||
th_scan_leave();
|
||||
return;
|
||||
}
|
||||
|
||||
th_scan_sync_cfg();
|
||||
if (!s_scan.active) {
|
||||
s_scan.active = 1;
|
||||
s_scan.tick = 0; /* 8.2:进入先广播一拍,让 APP 优先找回 */
|
||||
}
|
||||
|
||||
if ((s_scan.tick & 1u) == 0u) {
|
||||
th_scan_stop(); /* 偶:重连广播 */
|
||||
usr_le_set_adv_pause(0);
|
||||
} else {
|
||||
usr_le_set_adv_pause(1); /* 奇:关广播、开扫描 ≤500ms */
|
||||
th_scan_start();
|
||||
}
|
||||
s_scan.tick++;
|
||||
#else
|
||||
th_scan_disabled_once();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GAP_EVENT_ADVERTISING_REPORT(栈线程)
|
||||
* @note 杰理布局与 wireless_mic 相同:packet[0]=0xE2 [1]=len [2]=adv_report_t
|
||||
* @param packet HCI 事件包
|
||||
* @param size 事件包长度
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_report_event(uint8_t *packet, uint16_t size)
|
||||
{
|
||||
#if BOARD_TH_SCAN_ENABLE
|
||||
adv_report_t *rpt;
|
||||
|
||||
if (!s_scan.active || !s_scan.scan_on || packet == NULL) {
|
||||
return;
|
||||
}
|
||||
if (size < 12) {
|
||||
return;
|
||||
}
|
||||
rpt = (adv_report_t *)&packet[2];
|
||||
if (rpt->length == 0 || rpt->length > 31) {
|
||||
return;
|
||||
}
|
||||
th_scan_on_adv(rpt->data, rpt->length);
|
||||
#else
|
||||
(void)packet;
|
||||
(void)size;
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user