P1和P2已开发完成并验证了大部分功能都没问题
This commit is contained in:
@@ -60,6 +60,8 @@
|
||||
/* usr_le_code 库钩子:由静态库导出,供 RCSP BLE 栈回调,非应用层公开 API */
|
||||
extern void usr_le_data_recieve(u8* data,u16 len);
|
||||
extern void usr_ble_adv_init();
|
||||
/* app_th_scan 钩子:杰理扫描上报为 GAP 0xE2,packet[2] 起 adv_report_t */
|
||||
extern void app_th_scan_report_event(u8 *packet, u16 size);
|
||||
#if 1
|
||||
extern void printf_buf(u8 *buf, u32 len);
|
||||
#define log_info printf
|
||||
@@ -373,6 +375,11 @@ static void cbk_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
|
||||
case HCI_EVENT_ENCRYPTION_CHANGE:
|
||||
log_info("HCI_EVENT_ENCRYPTION_CHANGE= %d\n", packet[2]);
|
||||
break;
|
||||
|
||||
case GAP_EVENT_ADVERTISING_REPORT:
|
||||
/* 与 le_wireless_mic_client 相同:packet[2] 起为 adv_report_t */
|
||||
app_th_scan_report_event(packet, size);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -231,7 +231,8 @@ const int config_btctler_le_roles = (LE_ADV | LE_SLAVE);
|
||||
const uint64_t config_btctler_le_features = LE_ENCRYPTION;
|
||||
|
||||
#else
|
||||
const int config_btctler_le_roles = (LE_ADV | LE_SLAVE);
|
||||
/* 加热垫断连要扫风扇 JBao-* 广播:必须带 LE_SCAN,否则 ble_op_scan_enable2 无效果 */
|
||||
const int config_btctler_le_roles = (LE_ADV | LE_SLAVE | LE_SCAN);
|
||||
const uint64_t config_btctler_le_features = LE_ENCRYPTION| LE_DATA_PACKET_LENGTH_EXTENSION | LE_2M_PHY;
|
||||
#endif
|
||||
#else
|
||||
|
||||
@@ -136,11 +136,20 @@ void usr_on_key_ota_start(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 出厂重置:清业务 VM/RAM,保留 BLE 配对
|
||||
* @brief 出厂重置:清业务 VM/RAM,保留 BLE 配对(规格书 14 章)
|
||||
* @note VM 由 app_nvm_reset 全清(含 4 个 item);RAM 清加热请求、
|
||||
* 联动 20B、定时 7 组、31 天运行记录与各使能。不断开、不软复位。
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_factory_reset(void)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t *tm[7] = {
|
||||
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
|
||||
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||
gDevData.time_config7
|
||||
};
|
||||
|
||||
app_nvm_reset();
|
||||
app_relay_request(0);
|
||||
app_arbiter_init();
|
||||
@@ -148,6 +157,7 @@ void app_function_factory_reset(void)
|
||||
app_timer_init();
|
||||
app_runrec_reset();
|
||||
gDevData.heat_switch = 0;
|
||||
gDevData.factory_reset = 0;
|
||||
gDevData.linkage_delete = 0;
|
||||
gDevData.time_en = 0;
|
||||
gDevData.fault_en = 0;
|
||||
@@ -156,5 +166,8 @@ void app_function_factory_reset(void)
|
||||
gDevData.alarm_code = 0;
|
||||
memset(gDevData.linkage_config, 0, BOARD_LINKAGE_CFG_LEN);
|
||||
memset(gDevData.run_record, 0, sizeof(gDevData.run_record));
|
||||
for (i = 0; i < 7; i++) {
|
||||
memset(tm[i], 0, 8);
|
||||
}
|
||||
log_info("factory reset: business vm cleared, pairing kept\n");
|
||||
}
|
||||
|
||||
+230
-41
@@ -1,126 +1,315 @@
|
||||
/******************************************************************************
|
||||
* @file app_linkage.c
|
||||
* @brief CBR1 联动引擎骨架(P0:BOARD_LINKAGE_ENABLE=0)
|
||||
* @brief CBR1 联动引擎(规格书第 10 章 + 第 12 章仲裁):只看温度回差
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P2 填充实现(规格书第 10 章)
|
||||
* @version V1.0.1
|
||||
* @history
|
||||
* - V1.0.1, 2026.09.04, cyWu, 过热关不再限 owner==LINKAGE:VM 恢复/手动开的垫
|
||||
* 过热时也能关(10.3 + 再武装语义,用例 6/2)
|
||||
* - V1.0.0, 2026.09.04, cyWu, P2 完整实现(P0/P1 为骨架)
|
||||
*
|
||||
* @note 开停只看温度(10.3,本垫过冷开 / 过热关,与风扇相反):
|
||||
* temp_x10 < Tmin*10 → 请求开加热(T<Tmin,过冷)
|
||||
* temp_x10 > Tmax*10 → 请求关加热(T>Tmax,过热)
|
||||
* [Tmin,Tmax](含等于)→ 保持上一态(回差)
|
||||
* 温度源(8 章):BLE 已连接 = 服务器 DP6(set_connected_temp);
|
||||
* 断连已配对 = 扫描 JBao-{tank}(set_scan_temp);丢包保持上一态。
|
||||
* 无任何有效温度 → 联动不改继电器(10.2)。
|
||||
*
|
||||
* 手动作废(12 章,RAM 不落 VM):须先走到「关」条件(T>Tmax)
|
||||
* 再重新出现 T<Tmin 才允许联动再开;重写 DP8 清手动作废并立刻重判。
|
||||
*
|
||||
* ⚠ 判定只在 usr_timer_loop(500ms 任务上下文)与 DP8 写路径执行,
|
||||
* 不提供 20ms 中断接口。温度 setter 由 jb_product(DP6)与
|
||||
* app_th_scan(扫描回调)在任务上下文调用。
|
||||
******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_relay.h"
|
||||
#include "app_nvm.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[LINK]"
|
||||
#define LOG_TAG "[APP_LKG]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
static void linkage_disabled_once(void)
|
||||
{
|
||||
static uint8_t s_logged;
|
||||
/** 20B 配置解析结果(规格书 10.2;湿度/版本/预留只存不用) */
|
||||
typedef struct {
|
||||
uint16_t tank_id; /**< Byte0-1 大端,0=无效 */
|
||||
int8_t tmin; /**< Byte2 ℃ */
|
||||
int8_t tmax; /**< Byte3 ℃ */
|
||||
} LinkageCfg_t;
|
||||
|
||||
if (!s_logged) {
|
||||
s_logged = 1;
|
||||
log_info("app_linkage disabled (P2)\n");
|
||||
/** 联动运行时(RAM;温度与作废标志均不落 VM) */
|
||||
typedef struct {
|
||||
int16_t temp_x10; /**< 联动用温度(0.1℃,有符号) */
|
||||
uint8_t temp_valid; /**< 1=收到过任意来源的有效温度 */
|
||||
uint8_t voided; /**< 1=被手动作废,须先到「关」条件再武装 */
|
||||
} LinkageCtx_t;
|
||||
|
||||
static LinkageCtx_t s_lk;
|
||||
|
||||
/**
|
||||
* @brief 解析 20B 配置并判定有效性(10.1:缸体≠0 且 Tmin≤Tmax)
|
||||
* @param c 20B 配置
|
||||
* @param out 解析结果
|
||||
* @return 1=有效
|
||||
*/
|
||||
static uint8_t lk_parse(const uint8_t *c, LinkageCfg_t *out)
|
||||
{
|
||||
out->tank_id = (uint16_t)(((uint16_t)c[0] << 8) | c[1]);
|
||||
out->tmin = (int8_t)c[2];
|
||||
out->tmax = (int8_t)c[3];
|
||||
if (out->tank_id == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (out->tmin > out->tmax) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 联动让出加热:仅当联动正占着时才关(手动/定时占着不动)
|
||||
* @return 无
|
||||
*/
|
||||
static void lk_stop(void)
|
||||
{
|
||||
if (app_arbiter_linkage_active()) {
|
||||
app_arbiter_linkage_release();
|
||||
app_relay_request(0);
|
||||
log_info("linkage stop, heat=0\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化联动
|
||||
* @brief 初始化联动引擎
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_init(void)
|
||||
{
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
/* P2: 清作废标志(配置由 app_nvm_load 灌) */
|
||||
#else
|
||||
linkage_disabled_once();
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
log_info("linkage disabled\n");
|
||||
return;
|
||||
#endif
|
||||
memset(&s_lk, 0, sizeof(s_lk));
|
||||
log_info("linkage init\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文跑联动
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @brief 核心判定(幂等,10.3 + 12 章)
|
||||
* @note 步骤:无效配置 → 联动占着则关;无有效温度 → 不动;
|
||||
* 走到「关」条件清作废(再武装);作废期间不动作;
|
||||
* 仲裁定时窗占用时不插嘴;否则按回差开/关。
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_run(uint16_t dt)
|
||||
static void lk_reeval(void)
|
||||
{
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
(void)dt;
|
||||
/* P2: 有效配置 + 未作废 + 有温度 → 按 10.3 请求开/关 */
|
||||
#else
|
||||
(void)dt;
|
||||
#endif
|
||||
LinkageCfg_t cfg;
|
||||
uint8_t valid = lk_parse(gDevData.linkage_config, &cfg);
|
||||
uint8_t hot; /* T > Tmax:过热,关条件 */
|
||||
uint8_t cold; /* T < Tmin:过冷,开条件 */
|
||||
|
||||
/* 10.1:无效配置(缸体 0 / min>max)→ 不自动开;原先联动占着则关掉 */
|
||||
if (!valid) {
|
||||
lk_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 10.2:尚无任何有效温度 → 联动不改继电器(保持上电恢复值/上一态) */
|
||||
if (!s_lk.temp_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
hot = (s_lk.temp_x10 > (int16_t)cfg.tmax * 10) ? 1 : 0;
|
||||
cold = (s_lk.temp_x10 < (int16_t)cfg.tmin * 10) ? 1 : 0;
|
||||
|
||||
/* 12 章再武装:先走到「关」条件(T>Tmax)→ 清作废;
|
||||
* 之后重新出现 T<Tmin 才允许再开。等于 Tmax/Tmin 都不算越界 */
|
||||
if (hot) {
|
||||
if (s_lk.voided) {
|
||||
log_info("linkage rearmed (T>Tmax reached)\n");
|
||||
}
|
||||
s_lk.voided = 0;
|
||||
}
|
||||
if (s_lk.voided) {
|
||||
return; /* 作废期间保持上一态,不动作 */
|
||||
}
|
||||
|
||||
/* 12 章仲裁:定时窗占着(ARB_OWNER_TIMER)时联动不开不关,
|
||||
* 窗结束收口后由 app_timer 调 reeval 补判 */
|
||||
if (app_arbiter_get_owner() == ARB_OWNER_TIMER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cold) {
|
||||
/* 10.3 开:T<Tmin 过冷 → 请求开加热 */
|
||||
if (app_arbiter_linkage_take()) {
|
||||
if (!app_relay_get_request()) {
|
||||
app_relay_request(1);
|
||||
log_info("linkage open (cold), heat=1\n");
|
||||
}
|
||||
}
|
||||
} else if (hot) {
|
||||
/* 10.3 过热关:走到 T>Tmax 一律关,与谁开的无关——上电 VM 恢复的
|
||||
* 加热(owner=NONE,用例 6)或手动开的垫子在过热时都要能关;
|
||||
* 手动作废在本 tick 已先清(再武装语义,见上)。定时窗占用时前面
|
||||
* 已 return,不插嘴;删除/无效配置路径仍走 lk_stop(只关联动的) */
|
||||
if (app_arbiter_linkage_active()) {
|
||||
app_arbiter_linkage_release();
|
||||
}
|
||||
if (app_relay_get_request()) {
|
||||
app_relay_request(0);
|
||||
log_info("linkage overheat close, heat=0\n");
|
||||
}
|
||||
}
|
||||
/* 否则 [Tmin,Tmax](含等于):保持上一态(回差) */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立刻重判(APP 重写 DP8 / 定时收口)
|
||||
* @brief 立刻按第 10.3 节重判一次(DP8 重写 / 定时窗收口后调用)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_reeval(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
linkage_disabled_once();
|
||||
return;
|
||||
#endif
|
||||
lk_reeval();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DP7 联动删除(脉冲)
|
||||
* @brief 推进联动引擎(500ms,任务上下文)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_run(uint16_t dt)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#endif
|
||||
(void)dt; /* 本模块不计时:每拍完整重判 */
|
||||
lk_reeval();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DP7 联动删除写 1(脉冲):清 RAM+VM 20B;联动占着则关
|
||||
* @note gDevData.linkage_delete 由调用方清回 0(脉冲);引擎关掉也落盘
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_delete(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
linkage_disabled_once();
|
||||
memset(gDevData.linkage_config, 0, BOARD_LINKAGE_CFG_LEN);
|
||||
app_nvm_mark_dirty();
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
s_lk.voided = 0;
|
||||
lk_stop(); /* 10.4:若联动占着加热 → 关 */
|
||||
#endif
|
||||
log_info("linkage config deleted\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 重写 DP8
|
||||
* @brief APP 重写 DP8(memcpy 已完成):存 VM + 清手动作废 + 立刻重判
|
||||
* @note 引擎关掉也落盘;重判只在 BOARD_LINKAGE_ENABLE=1 时执行(10.5)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_config(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
linkage_disabled_once();
|
||||
app_nvm_mark_dirty();
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
s_lk.voided = 0; /* 10.5:清手动作废标志 */
|
||||
lk_reeval(); /* 立刻按新阈值重判(有有效温度时) */
|
||||
#endif
|
||||
log_info("linkage_config stored\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动作废本轮联动(仲裁器转调;P0 无联动,空操作)
|
||||
* @brief 手动作废本轮联动(短按/DP0 触发,仲裁器唯一转调点)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_void(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
/* P0:无联动引擎,无事可做 */
|
||||
return;
|
||||
#endif
|
||||
if (!s_lk.voided) {
|
||||
s_lk.voided = 1;
|
||||
log_info("linkage voided by manual\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置是否有效
|
||||
* @brief 配置是否有效(10.1)
|
||||
* @return 1=有效
|
||||
*/
|
||||
uint8_t app_linkage_is_valid(void)
|
||||
{
|
||||
return 0; /* P0:无有效配置 */
|
||||
LinkageCfg_t cfg;
|
||||
|
||||
return lk_parse(gDevData.linkage_config, &cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 缸体编号
|
||||
* @return tank_id
|
||||
* @brief 缸体编号(Byte0-1 大端;引擎关掉也可读,供扫描匹配用)
|
||||
* @return 缸体编号,0=未配
|
||||
*/
|
||||
uint16_t app_linkage_get_tank_id(void)
|
||||
{
|
||||
return 0;
|
||||
return (uint16_t)(((uint16_t)gDevData.linkage_config[0] << 8) |
|
||||
gDevData.linkage_config[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置版本
|
||||
* @return 版本号
|
||||
* @brief 联动配置版本(Byte6;扫描匹配用)
|
||||
* @return 配置版本
|
||||
*/
|
||||
uint8_t app_linkage_get_cfg_ver(void)
|
||||
{
|
||||
return 0;
|
||||
return gDevData.linkage_config[6];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BLE 已连接:服务器 DP6 温度(℃ float)→ 联动温度
|
||||
* @param degc 服务器下发物理量(float,scale=0.1 已还原,如 28.5)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_connected_temp(float degc)
|
||||
{
|
||||
s_lk.temp_x10 = (int16_t)(degc * 10.0f);
|
||||
s_lk.temp_valid = 1;
|
||||
log_info("linkage temp from dp6: %.1f C\n", (double)degc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 断连扫描:匹配到 JBao-{tank} 广播 → 联动温度
|
||||
* @param degc 风扇广播温度 int8(单位 ℃)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_scan_temp(int8_t degc)
|
||||
{
|
||||
int16_t x10 = (int16_t)degc * 10;
|
||||
|
||||
if (s_lk.temp_valid && (s_lk.temp_x10 == x10)) {
|
||||
return;
|
||||
}
|
||||
s_lk.temp_x10 = x10;
|
||||
s_lk.temp_valid = 1;
|
||||
log_info("linkage temp from scan: %d C\n", degc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 是否已有任何有效温度
|
||||
* @return 1=有
|
||||
*/
|
||||
uint8_t app_linkage_has_temp(void)
|
||||
{
|
||||
return s_lk.temp_valid;
|
||||
}
|
||||
|
||||
@@ -73,4 +73,24 @@ uint16_t app_linkage_get_tank_id(void);
|
||||
*/
|
||||
uint8_t app_linkage_get_cfg_ver(void);
|
||||
|
||||
/**
|
||||
* @brief BLE 已连接:服务器下发 DP6 温度(℃ float)→ 联动温度
|
||||
* @param degc 服务器下发物理量(如 28.5)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_connected_temp(float degc);
|
||||
|
||||
/**
|
||||
* @brief 断连扫描:匹配到 JBao-{tank} 广播 → 联动温度(℃ int8)
|
||||
* @param degc 风扇广播温度 int8(单位 ℃)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_scan_temp(int8_t degc);
|
||||
|
||||
/**
|
||||
* @brief 是否已有任何有效温度(连接 DP6 或扫描匹配到过)
|
||||
* @return 1=有;0=尚无温度(联动不动作)
|
||||
*/
|
||||
uint8_t app_linkage_has_temp(void);
|
||||
|
||||
#endif /* __APP_LINKAGE_H__ */
|
||||
|
||||
+334
-32
@@ -1,123 +1,425 @@
|
||||
/******************************************************************************
|
||||
* @file app_nvm.c
|
||||
* @brief CBR1 VM 持久化(P1 起):heat_switch / linkage 20B / time_en /
|
||||
* @brief CBR1 VM 持久化:heat_switch / linkage 20B / time_en /
|
||||
* time_config1~7 / run_record 31天 / fault_en / alarm_en
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P0 = 空骨架(BOARD_NVM_ENABLE=0,P1 填充)
|
||||
* @version V1.1.0
|
||||
* @history
|
||||
* - V1.1.0, 2026.09.04, cyWu, P1 完整实现(4 item 存取 + 加热请求恢复)
|
||||
* - V1.0.0, 2026.09.04, cyWu, P0 空骨架
|
||||
*
|
||||
* @note ⚠ 写 flash 只能在任务上下文(app_nvm_flush / app_nvm_save),
|
||||
* 20ms 中断上下文只允许 app_nvm_tick 打脏。
|
||||
* @note VM item 分配在 syscfg 用户自定义区 [1~49](syscfg_id.h 已占用
|
||||
* CFG_USER_PAIR_INFO=2 / TIM_FLAG=3 / VM_ALARM*=4~24 /
|
||||
* DEVICE_STA=25 / PLAN_INFO1=26 / BATTERY_LEVEL=27),
|
||||
* 本产品从 28 开始:
|
||||
* CFG_USER_CBR1_STATE =28 状态(magic+ver+heat_switch+en,10B)
|
||||
* CFG_USER_CBR1_LINKAGE =29 联动配置 linkage_config(20B,P2 写)
|
||||
* CFG_USER_CBR1_TIME =30 定时配置 time_config[7][8](56B)
|
||||
* CFG_USER_CBR1_RUNREC =31 运行记录 31×uint32 + slot0 日期(128B)
|
||||
* 拆 4 个 item:改加热只擦写 10B 小 item,不重写配置区,减少 flash
|
||||
* 磨损;首次上电(magic/ver 不符)保持 gDevData 全 0 默认。
|
||||
*
|
||||
* 落盘基准 = 加热**请求**(app_relay_get_request,规格书 13 章"上次
|
||||
* 加热请求"),不是实际吸合态;上电按它恢复请求、等过零再吸合。
|
||||
*
|
||||
* ⚠ 上下文硬约束:usr_timer_add 回调在硬件定时器中断上下文
|
||||
* (栈极小),app_nvm_tick 只能做变化检测与防抖计时,**绝对禁止
|
||||
* 调 syscfg_write**(VM 擦写 flash 栈用量大 → 栈溢出软复位)。
|
||||
* 真正写入由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
|
||||
* nvm_write_item 的比较缓冲必须 static(放栈上会撑爆中断栈)。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "app_nvm.h"
|
||||
#include "app_relay.h"
|
||||
#include "app_defer.h"
|
||||
#include "app_runrec.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
#include "system/includes.h"
|
||||
#include "syscfg_id.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_NVM]"
|
||||
#define LOG_ERROR_ENABLE
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
static void nvm_disabled_once(void)
|
||||
{
|
||||
static uint8_t s_logged;
|
||||
/* ── VM item id(用户区 1~49 内申请)── */
|
||||
#define CFG_USER_CBR1_STATE 28
|
||||
#define CFG_USER_CBR1_LINKAGE 29
|
||||
#define CFG_USER_CBR1_TIME 30
|
||||
#define CFG_USER_CBR1_RUNREC 31
|
||||
|
||||
if (!s_logged) {
|
||||
s_logged = 1;
|
||||
log_info("app_nvm disabled (P1)\n");
|
||||
#define CBR1_VM_MAGIC 0xCB01
|
||||
#define CBR1_VM_VER 1
|
||||
#define CBR1_VM_DEBOUNCE_MS 500 /* 加热/使能稳定变化防抖(规格书 13 章) */
|
||||
|
||||
/** 状态 item(10 字节) */
|
||||
typedef struct {
|
||||
uint16_t magic; /**< CBR1_VM_MAGIC */
|
||||
uint8_t ver; /**< CBR1_VM_VER */
|
||||
uint8_t heat_switch;/**< 上次加热请求(DP0;上电按此恢复) */
|
||||
uint8_t time_en; /**< DP9 定时总开关 */
|
||||
uint8_t fault_en; /**< DP19 */
|
||||
uint8_t alarm_en; /**< DP21 */
|
||||
uint8_t rsv[2]; /**< 对齐到 10B */
|
||||
} __packed Cbr1StateVm_t;
|
||||
|
||||
/** 联动配置 item(20 字节,DP8 原样落盘) */
|
||||
typedef struct {
|
||||
uint8_t linkage_config[BOARD_LINKAGE_CFG_LEN];
|
||||
} __packed Cbr1LinkageVm_t;
|
||||
|
||||
/** 定时配置 item(56 字节) */
|
||||
typedef struct {
|
||||
uint8_t time_config[7][8];
|
||||
} __packed Cbr1TimeVm_t;
|
||||
|
||||
/** 运行记录 item(124B 秒数 + 4B slot0 日期) */
|
||||
typedef struct {
|
||||
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
|
||||
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
|
||||
} __packed Cbr1RunrecVm_t;
|
||||
|
||||
#if BOARD_NVM_ENABLE
|
||||
/** NVM 运行时:防抖落盘 + 已落盘状态快照(变化检测基准) */
|
||||
typedef struct {
|
||||
uint8_t dirty; /**< 1=防抖计时中 */
|
||||
uint16_t deb_ms; /**< 防抖累计毫秒 */
|
||||
uint8_t last_req; /**< 上次已同步到 VM 的加热请求 */
|
||||
uint8_t last_time_en; /**< 上次已同步到 VM 的定时总开关 */
|
||||
} AppNvmCtx_t;
|
||||
|
||||
static AppNvmCtx_t s_nvm;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 从 gDevData 收集定时配置到 VM 结构
|
||||
* @param out 输出结构
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_collect_time(Cbr1TimeVm_t *out)
|
||||
{
|
||||
const uint8_t *src[7] = {
|
||||
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
|
||||
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||
gDevData.time_config7
|
||||
};
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
memcpy(out->time_config[i], src[i], 8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化(P1:校验 VM 有效性)
|
||||
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
|
||||
* @param item_id VM item id
|
||||
* @param buf 新数据
|
||||
* @param len 数据长度(≤128,比较缓冲大小)
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_init(void)
|
||||
static void nvm_write_item(u16 item_id, void *buf, u16 len)
|
||||
{
|
||||
/* static:比较缓冲不放栈上(省 128B 栈);本函数只在任务上下文调用,无重入 */
|
||||
static uint8_t old[128];
|
||||
|
||||
if (len > sizeof(old)) {
|
||||
log_error("nvm item %d len %d too big\n", item_id, len);
|
||||
return;
|
||||
}
|
||||
if (syscfg_read(item_id, old, len) == len) {
|
||||
if (memcmp(old, buf, len) == 0) {
|
||||
return; /* 没变化,不擦写 */
|
||||
}
|
||||
}
|
||||
if (syscfg_write(item_id, buf, len) > 0) {
|
||||
log_info("nvm item %d saved %dB\n", item_id, len);
|
||||
} else {
|
||||
log_error("nvm item %d write fail\n", item_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写状态 item(heat_switch/time_en/使能)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_state(void)
|
||||
{
|
||||
Cbr1StateVm_t st;
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
st.magic = CBR1_VM_MAGIC;
|
||||
st.ver = CBR1_VM_VER;
|
||||
st.heat_switch = app_relay_get_request();
|
||||
st.time_en = gDevData.time_en ? 1 : 0;
|
||||
st.fault_en = gDevData.fault_en;
|
||||
st.alarm_en = gDevData.alarm_en;
|
||||
nvm_write_item(CFG_USER_CBR1_STATE, &st, sizeof(st));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写联动配置 item(20B 原样落盘;P1 恒 0,P2 由 DP8 写入)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_linkage(void)
|
||||
{
|
||||
Cbr1LinkageVm_t lf;
|
||||
|
||||
memcpy(lf.linkage_config, gDevData.linkage_config, BOARD_LINKAGE_CFG_LEN);
|
||||
nvm_write_item(CFG_USER_CBR1_LINKAGE, &lf, sizeof(lf));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写定时配置 item
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_time(void)
|
||||
{
|
||||
Cbr1TimeVm_t tf;
|
||||
|
||||
nvm_collect_time(&tf);
|
||||
nvm_write_item(CFG_USER_CBR1_TIME, &tf, sizeof(tf));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写运行记录 item(31 格秒数 + slot0 日期)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_runrec(void)
|
||||
{
|
||||
Cbr1RunrecVm_t rf;
|
||||
|
||||
app_runrec_pack(rf.run_record);
|
||||
rf.slot0_date = app_runrec_get_slot0_date();
|
||||
nvm_write_item(CFG_USER_CBR1_RUNREC, &rf, sizeof(rf));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 同步"已落盘状态"快照为当前值(save/load 后调用)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_sync_snapshot(void)
|
||||
{
|
||||
#if BOARD_NVM_ENABLE
|
||||
/* P1: 校验 VM 有效性 */
|
||||
#else
|
||||
nvm_disabled_once();
|
||||
s_nvm.last_req = app_relay_get_request();
|
||||
s_nvm.last_time_en = gDevData.time_en ? 1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立即保存全部业务字段(任务上下文)
|
||||
* @brief 初始化:校验 VM 有效性并打日志(数据加载在 app_nvm_load)
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_init(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
log_info("nvm disabled\n");
|
||||
return;
|
||||
#else
|
||||
Cbr1StateVm_t st;
|
||||
int ret = syscfg_read(CFG_USER_CBR1_STATE, &st, sizeof(st));
|
||||
|
||||
if ((ret == (int)sizeof(st)) && (st.magic == CBR1_VM_MAGIC) && (st.ver == CBR1_VM_VER)) {
|
||||
log_info("nvm init, vm valid v%d (heat=%d time_en=%d)\n",
|
||||
st.ver, st.heat_switch, st.time_en);
|
||||
} else {
|
||||
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
|
||||
}
|
||||
memset(&s_nvm, 0, sizeof(s_nvm));
|
||||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立即保存全部业务字段
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_save(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
nvm_disabled_once();
|
||||
return;
|
||||
#else
|
||||
nvm_write_state();
|
||||
nvm_write_linkage();
|
||||
nvm_write_time();
|
||||
nvm_write_runrec();
|
||||
s_nvm.dirty = 0;
|
||||
s_nvm.deb_ms = 0;
|
||||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||
nvm_sync_snapshot();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立即保存运行记录 item(跨天旋转用)
|
||||
* @brief 立即保存运行记录项(跨天旋转用,任务上下文)
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_save_runrec(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
nvm_disabled_once();
|
||||
return;
|
||||
#else
|
||||
nvm_write_runrec();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载业务字段并应用(开机调用)
|
||||
* @brief 从 VM 加载并应用(开机由 usr_jb_init 调用)
|
||||
* @note 加热恢复 = 按上次**请求**恢复(等过零吸合,规格书 13 章 / 5.1);
|
||||
* 联动/定时/运行记录回填 gDevData。VM 无效(首次上电)保持全 0 默认。
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_load(void)
|
||||
{
|
||||
#if BOARD_NVM_ENABLE
|
||||
/* P1: heat_switch 恢复 + linkage/timer/run_record 回填 gDevData */
|
||||
#if !BOARD_NVM_ENABLE
|
||||
return;
|
||||
#else
|
||||
nvm_disabled_once();
|
||||
Cbr1StateVm_t st;
|
||||
Cbr1LinkageVm_t lf;
|
||||
Cbr1TimeVm_t tf;
|
||||
Cbr1RunrecVm_t rf;
|
||||
uint8_t i;
|
||||
|
||||
if ((syscfg_read(CFG_USER_CBR1_STATE, &st, sizeof(st)) != (int)sizeof(st))
|
||||
|| (st.magic != CBR1_VM_MAGIC) || (st.ver != CBR1_VM_VER)) {
|
||||
log_info("nvm load: no valid vm, defaults (heat=0)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 联动配置回填 gDevData(引擎由 BOARD_LINKAGE_ENABLE 决定是否跑) */
|
||||
if (syscfg_read(CFG_USER_CBR1_LINKAGE, &lf, sizeof(lf)) == (int)sizeof(lf)) {
|
||||
memcpy(gDevData.linkage_config, lf.linkage_config, BOARD_LINKAGE_CFG_LEN);
|
||||
}
|
||||
|
||||
/* 定时配置回填 gDevData(只存不跑时也要回填,P1 验收) */
|
||||
if (syscfg_read(CFG_USER_CBR1_TIME, &tf, sizeof(tf)) == (int)sizeof(tf)) {
|
||||
uint8_t *dst[7] = {
|
||||
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
|
||||
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||
gDevData.time_config7
|
||||
};
|
||||
for (i = 0; i < 7; i++) {
|
||||
memcpy(dst[i], tf.time_config[i], 8);
|
||||
}
|
||||
}
|
||||
|
||||
/* 运行记录回填 RAM(13.1:掉电再上电先把 31 格灌回来) */
|
||||
if (syscfg_read(CFG_USER_CBR1_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
|
||||
app_runrec_unpack(rf.run_record);
|
||||
app_runrec_set_slot0_date(rf.slot0_date);
|
||||
}
|
||||
|
||||
gDevData.time_en = st.time_en ? 1 : 0;
|
||||
gDevData.fault_en = st.fault_en;
|
||||
gDevData.alarm_en = st.alarm_en;
|
||||
|
||||
/* 上电按上次加热请求恢复(开则等过零再吸合;无过零由 app_relay 超时兜底) */
|
||||
app_relay_request(st.heat_switch ? 1 : 0);
|
||||
nvm_sync_snapshot(); /* 恢复值即为已落盘值,同步基准 */
|
||||
log_info("nvm load v%d: heat=%d time_en=%d\n",
|
||||
st.ver, st.heat_switch, st.time_en);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 业务节拍(中断上下文):变化检测 + 防抖计时,不写 flash
|
||||
* @brief 业务节拍:做加热请求/总开关变化检测 + 防抖计时,**不执行写入**
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = **硬件定时器中断上下文**
|
||||
* (异步接口,栈极小)。这里只允许做轻量比较与计时;真正的
|
||||
* syscfg_write 由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
|
||||
* 曾在此直接调 app_nvm_save 导致栈溢出软复位。
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_tick(uint16_t dt)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#else
|
||||
uint8_t req = app_relay_get_request();
|
||||
uint8_t time_en = gDevData.time_en ? 1 : 0;
|
||||
|
||||
if ((req != s_nvm.last_req) || (time_en != s_nvm.last_time_en)) {
|
||||
s_nvm.last_req = req;
|
||||
s_nvm.last_time_en = time_en;
|
||||
s_nvm.dirty = 1;
|
||||
s_nvm.deb_ms = 0; /* 变化重新计满防抖窗口 */
|
||||
}
|
||||
|
||||
if (!s_nvm.dirty) {
|
||||
return;
|
||||
}
|
||||
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
|
||||
if (s_nvm.deb_ms >= CBR1_VM_DEBOUNCE_MS) {
|
||||
s_nvm.deb_ms = 0;
|
||||
s_nvm.dirty = 0;
|
||||
app_defer_request(APP_DEFER_NVM_SAVE); /* 只置标志:真正落盘在 app_nvm_flush */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务上下文落盘:防抖满后真正写 VM(500ms loop 调用)
|
||||
* @brief 任务上下文落盘:防抖满后真正写 VM(由 usr_timer_loop 500ms 调用)
|
||||
* @note 必须且只能在**非中断上下文**调用(sys_timer 回调 / BLE 线程 /
|
||||
* app_core 任务)。VM 擦写 flash 栈用量大,禁止放进 20ms 业务节拍。
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_flush(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
nvm_disabled_once();
|
||||
return;
|
||||
#else
|
||||
if (!app_defer_take(APP_DEFER_NVM_SAVE)) {
|
||||
return;
|
||||
}
|
||||
app_nvm_save();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 标记业务字段有变化,防抖计时归零
|
||||
* @brief 标记业务字段有变化,防抖计时归零(重新计满防抖窗口才写)
|
||||
* @note 任务上下文调用(APP 下行改配置:定时/使能/故障告警使能)
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_mark_dirty(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
nvm_disabled_once();
|
||||
#if BOARD_NVM_ENABLE
|
||||
s_nvm.dirty = 1;
|
||||
s_nvm.deb_ms = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对)
|
||||
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 14 章)
|
||||
* @note 4 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||||
* BLE 配对三元组在 CFG_USER_PAIR_INFO(item 2),不受影响
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_reset(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
nvm_disabled_once();
|
||||
return;
|
||||
#else
|
||||
Cbr1StateVm_t st;
|
||||
Cbr1LinkageVm_t lf;
|
||||
Cbr1TimeVm_t tf;
|
||||
Cbr1RunrecVm_t rf;
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
memset(&lf, 0, sizeof(lf));
|
||||
memset(&tf, 0, sizeof(tf));
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
syscfg_write(CFG_USER_CBR1_STATE, &st, sizeof(st));
|
||||
syscfg_write(CFG_USER_CBR1_LINKAGE, &lf, sizeof(lf));
|
||||
syscfg_write(CFG_USER_CBR1_TIME, &tf, sizeof(tf));
|
||||
syscfg_write(CFG_USER_CBR1_RUNREC, &rf, sizeof(rf));
|
||||
s_nvm.dirty = 0;
|
||||
s_nvm.deb_ms = 0;
|
||||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||
log_info("nvm factory reset, business vm cleared (pairing kept)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ void app_nvm_save_runrec(void);
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载业务字段并应用(开机调用):
|
||||
* mode/fan_gear 恢复、联动/定时/运行记录回填 gDevData;mode 从周期头开跑
|
||||
* heat_switch 请求恢复(开则等过零吸合)、联动/定时/运行记录回填 gDevData
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_load(void);
|
||||
|
||||
@@ -97,6 +97,8 @@ void app_relay_zc_edge(void)
|
||||
return; /* 无 pending,仅记录过零(由 app_zc 管超时) */
|
||||
}
|
||||
relay_apply(s_relay.request);
|
||||
/* 7.2:下一次成功过零切变后清设备故障(RAM 写,ISR 安全) */
|
||||
gDevData.fault_code = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+214
-46
@@ -1,149 +1,317 @@
|
||||
/******************************************************************************
|
||||
* @file app_runrec.c
|
||||
* @brief CBR1 31 天运行记录骨架(P0:BOARD_RUNREC_ENABLE=0)
|
||||
* @brief CBR1 31 天运行记录(规格书 13.1 节,DP17 RO,124B)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P2 填充实现(接口见 app_runrec.h)
|
||||
* @version V1.1.0
|
||||
* @history
|
||||
* - V1.1.0, 2026.09.04, cyWu, P2 完整实现(实际吸合折秒 + 跨天旋转落盘)
|
||||
* - V1.0.0, 2026.09.04, cyWu, P0 骨架
|
||||
*
|
||||
* @note 什么叫在运行:过零后继电器**实际已吸合**(app_relay_get_actual==1),
|
||||
* pending 开还没过零不算(13.1)。日期轴:未对时只往 slot[0] 加、
|
||||
* 不旋转;对时后按本地日历左移;掉电再上电先由 app_nvm_load 灌 VM。
|
||||
*
|
||||
* ⚠ 上下文分工(勿再踩复位事故):
|
||||
* - app_runrec_tick(20ms 中断):只把在吸合的毫秒折成 pending 秒,
|
||||
* 不碰 s_rr.rec[]、不写 flash。
|
||||
* - app_runrec_rotate_if_needed(500ms 任务):收下 pending 加到
|
||||
* slot[0]、每拍灌 DP17、跨天旋转、满 600s / 跨天 / 首次对时才
|
||||
* 写 VM。拔电没有软关机钩子,接受最多丢不满 10 分钟的秒数。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "system/includes.h" /* local_irq_disable / local_irq_enable */
|
||||
#include "app_runrec.h"
|
||||
#include "app_relay.h"
|
||||
#include "app_nvm.h"
|
||||
#include "usr_rtc.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[RUNREC]"
|
||||
#define LOG_TAG "[APP_RREC]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
static void runrec_disabled_once(void)
|
||||
{
|
||||
static uint8_t s_logged;
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
/** 运行记录上下文:31 格日历 + ISR/任务两侧的折秒状态 */
|
||||
typedef struct {
|
||||
uint32_t rec[BOARD_RUN_RECORD_DAYS]; /**< slot[0]=今天 … slot[30]=前 30 天(任务侧) */
|
||||
uint32_t ymd; /**< slot[0] 对应本地日期 yyyymmdd;0=未对时 */
|
||||
uint16_t ms; /**< 不足 1 秒的毫秒零头(仅 ISR) */
|
||||
volatile uint16_t pending_sec; /**< ISR 折秒,任务收下后加到 slot[0] */
|
||||
uint16_t unsaved; /**< 距上次落盘累计的运行秒数(仅任务) */
|
||||
} AppRunrecCtx_t;
|
||||
|
||||
if (!s_logged) {
|
||||
s_logged = 1;
|
||||
log_info("app_runrec disabled (P2)\n");
|
||||
}
|
||||
static AppRunrecCtx_t s_rr;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 公历日期 → 天数序号(Howard Hinnant 的 days_from_civil)
|
||||
* @param y 年(如 2026)
|
||||
* @param m 月 1~12
|
||||
* @param d 日 1~31
|
||||
* @return 距 1970-01-01 的天数(可为负)
|
||||
*/
|
||||
static int32_t runrec_days_from_civil(int y, int m, int d)
|
||||
{
|
||||
int era;
|
||||
int yoe;
|
||||
int doy;
|
||||
int doe;
|
||||
|
||||
y -= (m <= 2) ? 1 : 0;
|
||||
era = ((y >= 0) ? y : (y - 399)) / 400;
|
||||
yoe = y - era * 400; /* [0,399] */
|
||||
doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2) / 5 + d - 1; /* [0,365] */
|
||||
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; /* [0,146096]*/
|
||||
return (int32_t)(era * 146097L + (long)doe - 719468L);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
* @brief yyyymmdd 压缩日期 → 天数序号
|
||||
* @param ymd yyyymmdd(如 20260904)
|
||||
* @return 天数序号
|
||||
*/
|
||||
static int32_t runrec_ymd_to_days(uint32_t ymd)
|
||||
{
|
||||
return runrec_days_from_civil((int)(ymd / 10000u),
|
||||
(int)((ymd / 100u) % 100u),
|
||||
(int)(ymd % 100u));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化运行记录
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_init(void)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
/* P2: 先灌 VM,未对时只加 slot0 */
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
log_info("runrec disabled\n");
|
||||
return;
|
||||
#else
|
||||
runrec_disabled_once();
|
||||
memset(&s_rr, 0, sizeof(s_rr));
|
||||
app_runrec_pack(gDevData.run_record); /* DP17 报全 0 */
|
||||
log_info("runrec init, %u days\n", (unsigned)BOARD_RUN_RECORD_DAYS);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
/**
|
||||
* @brief 20ms 节拍:实际吸合才累计
|
||||
* @brief 把 ISR 折好的秒数加到 slot[0],满 10 分钟则请求写 VM
|
||||
* @return 1=需要立刻写运行记录 item
|
||||
*/
|
||||
static uint8_t runrec_apply_pending(void)
|
||||
{
|
||||
uint16_t sec;
|
||||
uint8_t need_vm = 0;
|
||||
|
||||
local_irq_disable();
|
||||
sec = s_rr.pending_sec;
|
||||
s_rr.pending_sec = 0;
|
||||
local_irq_enable();
|
||||
|
||||
if (sec == 0) {
|
||||
return 0;
|
||||
}
|
||||
s_rr.rec[0] += sec;
|
||||
s_rr.unsaved = (uint16_t)(s_rr.unsaved + sec);
|
||||
if (s_rr.unsaved >= BOARD_RUN_RECORD_SAVE_SEC) {
|
||||
s_rr.unsaved = (uint16_t)(s_rr.unsaved % BOARD_RUN_RECORD_SAVE_SEC);
|
||||
need_vm = 1;
|
||||
}
|
||||
if (need_vm) {
|
||||
log_info("runrec %us reached, save vm\n", (unsigned)BOARD_RUN_RECORD_SAVE_SEC);
|
||||
}
|
||||
return need_vm;
|
||||
}
|
||||
#endif /* BOARD_RUNREC_ENABLE */
|
||||
|
||||
/**
|
||||
* @brief 20ms 中断上下文节拍:继电器实际吸合才折秒到 pending,不碰 s_rr.rec
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_tick(uint16_t dt)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
if (app_relay_get_actual()) {
|
||||
/* P2: s_pend_ms += dt; 满 600s → mark_dirty */
|
||||
}
|
||||
#else
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#else
|
||||
if (!app_relay_get_actual()) {
|
||||
/* 没在加热:不累加,并丢弃不足 1 秒的零头(13.1:未吸合不计入) */
|
||||
s_rr.ms = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
s_rr.ms = (uint16_t)(s_rr.ms + dt);
|
||||
while (s_rr.ms >= 1000u) {
|
||||
s_rr.ms = (uint16_t)(s_rr.ms - 1000u);
|
||||
s_rr.pending_sec++; /* 任务侧再加到 slot[0],避免与跨天旋转打架 */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文:跨天旋转 + 灌 DP17
|
||||
* @brief 500ms 任务上下文:收下 pending、灌 DP17、跨天旋转、必要时写 VM
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_rotate_if_needed(void)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
/* P2 */
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
return;
|
||||
#else
|
||||
runrec_disabled_once();
|
||||
struct sys_time t;
|
||||
uint32_t ymd;
|
||||
int32_t diff;
|
||||
int i;
|
||||
uint8_t need_vm = runrec_apply_pending();
|
||||
|
||||
if (usr_rtc_get_time(&t)) {
|
||||
ymd = (uint32_t)t.year * 10000u + (uint32_t)t.month * 100u + (uint32_t)t.day;
|
||||
|
||||
if (s_rr.ymd == 0) {
|
||||
/* 首次对时:slot[0] 已是当天累计值,只登记日期,不旋转不清零 */
|
||||
s_rr.ymd = ymd;
|
||||
need_vm = 1;
|
||||
log_info("runrec rtc synced, ymd=%lu\n", (unsigned long)ymd);
|
||||
} else if (ymd != s_rr.ymd) {
|
||||
diff = runrec_ymd_to_days(ymd) - runrec_ymd_to_days(s_rr.ymd);
|
||||
if (diff <= 0) {
|
||||
s_rr.ymd = ymd; /* 时钟回拨/异常:只纠正日期,不乱移数据 */
|
||||
} else {
|
||||
if (diff > BOARD_RUN_RECORD_DAYS) {
|
||||
diff = BOARD_RUN_RECORD_DAYS; /* 跨太多天:整表作废 */
|
||||
}
|
||||
/* 左移 N 天:old[i] → new[i+N],前面 N 格清零 */
|
||||
for (i = (int)BOARD_RUN_RECORD_DAYS - 1; i >= diff; i--) {
|
||||
s_rr.rec[i] = s_rr.rec[i - diff];
|
||||
}
|
||||
for (i = 0; i < diff; i++) {
|
||||
s_rr.rec[i] = 0;
|
||||
}
|
||||
s_rr.ymd = ymd;
|
||||
need_vm = 1;
|
||||
log_info("runrec rotate %ld day(s), ymd=%lu\n",
|
||||
(long)diff, (unsigned long)ymd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 每拍灌 DP17:读请求 / 10min 全量能拿到当前秒数;差分上报不带本 DP */
|
||||
app_runrec_pack(gDevData.run_record);
|
||||
if (need_vm) {
|
||||
app_nvm_save_runrec(); /* 满 10 分钟或跨天 / 首次对时才写 flash */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读某天秒数
|
||||
* @param day 0~30
|
||||
* @return 秒(P0 全 0)
|
||||
* @brief 取某天的运行秒数
|
||||
* @param day 0=今天 … 30=前 30 天
|
||||
* @return 秒数
|
||||
*/
|
||||
uint32_t app_runrec_get(uint8_t day)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
(void)day;
|
||||
return 0;
|
||||
#else
|
||||
(void)day;
|
||||
if (day >= BOARD_RUN_RECORD_DAYS) {
|
||||
return 0;
|
||||
}
|
||||
return s_rr.rec[day];
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打包 124B
|
||||
* @brief 打包成 DP17 的 124B 载荷(31 × uint32 大端)
|
||||
* @param out 输出缓冲
|
||||
* @note 模块关闭时仍输出全 0(13.1:关闭时报全 0),
|
||||
* 避免 app_nvm 把未初始化栈内存写进 VM 的 RUNREC item
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_pack(uint8_t *out)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
(void)out;
|
||||
uint8_t i;
|
||||
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
memset(out, 0, APP_RUNREC_PACK_LEN);
|
||||
return;
|
||||
#else
|
||||
(void)out;
|
||||
for (i = 0; i < BOARD_RUN_RECORD_DAYS; i++) {
|
||||
uint32_t v = s_rr.rec[i];
|
||||
|
||||
out[(uint8_t)(i * 4u) + 0u] = (uint8_t)(v >> 24);
|
||||
out[(uint8_t)(i * 4u) + 1u] = (uint8_t)(v >> 16);
|
||||
out[(uint8_t)(i * 4u) + 2u] = (uint8_t)(v >> 8);
|
||||
out[(uint8_t)(i * 4u) + 3u] = (uint8_t)(v & 0xFFu);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解包 124B
|
||||
* @brief 从 VM 的 124B 载荷灌回 RAM
|
||||
* @param in 输入缓冲
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_unpack(const uint8_t *in)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
uint8_t i;
|
||||
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
(void)in;
|
||||
return;
|
||||
#else
|
||||
(void)in;
|
||||
for (i = 0; i < BOARD_RUN_RECORD_DAYS; i++) {
|
||||
s_rr.rec[i] = ((uint32_t)in[(uint8_t)(i * 4u) + 0u] << 24)
|
||||
| ((uint32_t)in[(uint8_t)(i * 4u) + 1u] << 16)
|
||||
| ((uint32_t)in[(uint8_t)(i * 4u) + 2u] << 8)
|
||||
| (uint32_t)in[(uint8_t)(i * 4u) + 3u];
|
||||
}
|
||||
app_runrec_pack(gDevData.run_record); /* 开机立刻灌 DP17,不等 500ms */
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief slot[0] 日期
|
||||
* @return 日期
|
||||
* @brief slot[0] 对应的本地日期
|
||||
* @return yyyymmdd;0=未对时
|
||||
*/
|
||||
uint32_t app_runrec_get_slot0_date(void)
|
||||
{
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
return 0;
|
||||
#else
|
||||
return s_rr.ymd;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置 slot[0] 日期
|
||||
* @param ymd yyyymmdd
|
||||
* @brief 设置 slot[0] 对应日期
|
||||
* @param ymd yyyymmdd;0=未对时
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_set_slot0_date(uint32_t ymd)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
(void)ymd;
|
||||
s_rr.ymd = ymd;
|
||||
#else
|
||||
(void)ymd;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清零(出厂重置)
|
||||
* @brief 清零 31 格与日期(出厂重置)
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_reset(void)
|
||||
{
|
||||
#if BOARD_RUNREC_ENABLE
|
||||
/* P2: 全 0 + 日期清 */
|
||||
#if !BOARD_RUNREC_ENABLE
|
||||
return;
|
||||
#else
|
||||
runrec_disabled_once();
|
||||
memset(&s_rr, 0, sizeof(s_rr));
|
||||
app_runrec_pack(gDevData.run_record);
|
||||
#endif
|
||||
}
|
||||
|
||||
+280
-7
@@ -1,27 +1,252 @@
|
||||
/******************************************************************************
|
||||
* @file app_th_scan.c
|
||||
* @brief CBR1 断连温湿度扫描骨架(P0:BOARD_TH_SCAN_ENABLE=0)
|
||||
* @brief CBR1 断连温湿度扫描(规格书第 8 章):500ms 重连广播 ↔ 500ms 扫描
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P2 填充实现(规格书第 8 章)
|
||||
* @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-"+DP8 缸体号(uint16 十进制,无前导零)
|
||||
* 且厂商段 0xFFFF 首字节 == DP8 配置版本 → 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 (P2)\n");
|
||||
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/版本取自 DP8 联动配置)
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,21 +257,69 @@ static void th_scan_disabled_once(void)
|
||||
void app_th_scan_init(void)
|
||||
{
|
||||
#if BOARD_TH_SCAN_ENABLE
|
||||
/* P2: 无状态(500ms 时隙由 tick 驱动) */
|
||||
memset(&s_scan, 0, sizeof(s_scan));
|
||||
log_info("app_th_scan init\n");
|
||||
#else
|
||||
th_scan_disabled_once();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文:断连已配对时广播/扫描交替
|
||||
* @brief 500ms 任务上下文(usr_timer_loop 内):偶拍广播 / 奇拍扫描
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_tick(void)
|
||||
{
|
||||
#if BOARD_TH_SCAN_ENABLE
|
||||
/* P2: 已连接/配对/OTA → 退交替;断连已配对 → 偶见宝重连广播/奇扫描 */
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
/******************************************************************************
|
||||
* @file app_th_scan.h
|
||||
* @brief CBR1 断连温湿度扫描(第 8 章,P2):500ms 重连广播 ↔ 500ms 扫描
|
||||
* @brief CBR1 断连温湿度扫描(规格书第 8 章):500ms 重连广播 ↔ 500ms 扫描
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P0 = 骨架(BOARD_TH_SCAN_ENABLE=0)
|
||||
* @date 2026.09.07
|
||||
* @version V1.1.6
|
||||
* @history
|
||||
* - V1.1.6, 2026.09.07, cyWu, 扫描上报只留 GAP 0xE2(杰理包装后的 adv_report_t)
|
||||
* - V1.0.0, 2026.09.04, cyWu, P2 完整实现
|
||||
*
|
||||
* @note 匹配规则:名字 = "JBao-"+本机缸体号(DP8 uint16 十进制)且配置版本
|
||||
* 相等 → temp_x10 = int8℃×10;湿度只打日志不参与开停。缸体号 0 不扫。
|
||||
* 断连丢包:保持上一温度与上一加热态,禁止因丢包关加热。
|
||||
* @note 只在「断连 + 已配对 + 非配对/OTA 流程 + 联动配置有效(缸体≠0)」
|
||||
* 时进入 500ms 交替:偶拍重连广播 / 奇拍扫描。匹配规则(8.1):
|
||||
* 名字 == "JBao-"+本机 DP8 缸体号(uint16 十进制)+ 配置版本相等
|
||||
* → temp_x10 = int8℃×10;湿度只打日志不参与开停。
|
||||
* 名字/版本不对或解析失败 → 当没收到,保持上一温度与上一加热态。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_TH_SCAN_H__
|
||||
#define __APP_TH_SCAN_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化扫描模块
|
||||
* @return 无
|
||||
@@ -20,9 +27,19 @@
|
||||
void app_th_scan_init(void);
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文:断连已配对时 500ms 重连广播 / 500ms 扫描交替
|
||||
* @brief 500ms 任务上下文:满足条件时重连广播/扫描各占一拍交替
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_tick(void);
|
||||
|
||||
/**
|
||||
* @brief GAP 扫描上报(栈线程)
|
||||
* @note 杰理把扫描结果包成 GAP_EVENT_ADVERTISING_REPORT(0xE2),
|
||||
* packet[2] 起为 adv_report_t。与 wireless_mic 中央示例同一布局。
|
||||
* @param packet HCI 事件包
|
||||
* @param size 事件包长度
|
||||
* @return 无
|
||||
*/
|
||||
void app_th_scan_report_event(uint8_t *packet, uint16_t size);
|
||||
|
||||
#endif /* __APP_TH_SCAN_H__ */
|
||||
|
||||
+226
-30
@@ -1,84 +1,280 @@
|
||||
/******************************************************************************
|
||||
* @file app_timer_task.c
|
||||
* @brief CBR1 定时引擎骨架(P0:BOARD_TIMER_ENABLE=0)
|
||||
* @brief CBR1 定时引擎(第 11 章):7 组 8B 定时窗 + RTC 门闩
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.0.0,P2 填充实现(规格书第 11 章)
|
||||
* @version V1.2.1
|
||||
* @history
|
||||
* - V1.2.1, 2026.09.04, cyWu, RTC 失效/time_en=0 只停不毁单次(防开机吞单次)
|
||||
* - V1.2.0, 2026.09.04, cyWu, P2 完整引擎(窗开始开/结束关 + RTC 门闩补窗)
|
||||
* - V1.1.0, 2026.09.04, cyWu, P1 只存配置(mark_dirty)
|
||||
* - V1.0.0, 2026.09.04, cyWu, P0 骨架
|
||||
*
|
||||
* @note 运行上下文:app_timer_run 由 usr_timer_loop(500ms,任务上下文)
|
||||
* 调用——窗判断分钟级精度足够,且单次自毁要写 VM(禁止中断上下文)。
|
||||
* 字节布局(规格书 11.1):Byte0=使能 Byte1=模式(本版忽略但原样
|
||||
* 存 VM)Byte2=类型(0单次/1每天/2每周) Byte3=星期位图(Bit0=周一…
|
||||
* Bit6=周日,仅类型2) Byte4-5=开始时分 Byte6-7=结束时分。
|
||||
* 窗开始沿 → 请求开继电器;窗结束沿 → 请求关 + 立刻按 10.3 补判
|
||||
* 联动(11.1)。跨午夜以开始时刻所在星期判断(11.1)。
|
||||
* 仲裁(12 章):手动介入后本窗剩余时间不自动抢回,等下一窗开始沿
|
||||
* 或对时补窗。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_timer_task.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_relay.h"
|
||||
#include "app_nvm.h"
|
||||
#include "usr_rtc.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[TIMER]"
|
||||
#define LOG_TAG "[APP_TMR]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
static void timer_disabled_once(void)
|
||||
{
|
||||
static uint8_t s_logged;
|
||||
/** 定时引擎运行时 */
|
||||
typedef struct {
|
||||
uint8_t taken; /**< 1=本轮窗已接管(手动打断后不抢回,直到全部窗结束) */
|
||||
uint8_t in_mask; /**< bit i = 第 i 组当前在窗内(重叠 + 单次自毁用) */
|
||||
uint8_t last_rtc_ok; /**< 上次 RTC 是否有效(对时补窗) */
|
||||
} TimerState_t;
|
||||
|
||||
if (!s_logged) {
|
||||
s_logged = 1;
|
||||
log_info("app_timer_task disabled (P2)\n");
|
||||
static TimerState_t s_timer;
|
||||
|
||||
/**
|
||||
* @brief 取第 idx 组定时配置(gDevData 里是 7 个独立数组字段)
|
||||
* @param idx 0~6
|
||||
* @return 配置指针;越界返回 NULL
|
||||
*/
|
||||
static uint8_t *timer_cfg_ptr(uint8_t idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case 0: return gDevData.time_config1;
|
||||
case 1: return gDevData.time_config2;
|
||||
case 2: return gDevData.time_config3;
|
||||
case 3: return gDevData.time_config4;
|
||||
case 4: return gDevData.time_config5;
|
||||
case 5: return gDevData.time_config6;
|
||||
case 6: return gDevData.time_config7;
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算某日期是星期几
|
||||
* @param y 年
|
||||
* @param m 月 1~12
|
||||
* @param d 日
|
||||
* @return 0=周一 … 6=周日(规格书 11.1 位图 Bit0=周一)
|
||||
*/
|
||||
static uint8_t timer_calc_dow(uint16_t y, uint8_t m, uint8_t d)
|
||||
{
|
||||
uint8_t mm = m;
|
||||
uint16_t yy = y;
|
||||
uint16_t k;
|
||||
int h;
|
||||
|
||||
if (mm < 3) {
|
||||
mm += 12;
|
||||
yy--;
|
||||
}
|
||||
k = yy % 100;
|
||||
/* 蔡勒公式(格里高利历):h=0 周六 … 6 周五 */
|
||||
h = (d + 13 * (mm + 1) / 5 + k + k / 4 + (yy / 100) / 4 + 5 * (yy / 100)) % 7;
|
||||
return (uint8_t)((h + 5) % 7); /* 0=周一 … 6=周日 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 当前时刻是否在窗口内
|
||||
* @param cfg 8 字节配置
|
||||
* @param t 本地时间
|
||||
* @return 1=在窗内
|
||||
*/
|
||||
static uint8_t timer_in_window(const uint8_t *cfg, const struct sys_time *t)
|
||||
{
|
||||
uint8_t type = cfg[2];
|
||||
int16_t start = (int16_t)cfg[4] * 60 + cfg[5];
|
||||
int16_t end = (int16_t)cfg[6] * 60 + cfg[7];
|
||||
int16_t now = (int16_t)t->hour * 60 + t->min;
|
||||
|
||||
if (start == end) {
|
||||
return 0; /* 非法窗(零时长) */
|
||||
}
|
||||
|
||||
if (type == 2) {
|
||||
/* 每周:星期位图;跨午夜窗以"开始时刻所在本地星期"判断(11.1) */
|
||||
uint8_t dow = timer_calc_dow(t->year, t->month, t->day);
|
||||
uint8_t dow_bit;
|
||||
|
||||
if (start > end) {
|
||||
/* 跨午夜窗:凌晨 [0,end) 属于昨晚的窗,用昨天星期 */
|
||||
if (now < end) {
|
||||
dow = (uint8_t)((dow + 6) % 7); /* 昨天 */
|
||||
}
|
||||
}
|
||||
dow_bit = (uint8_t)(1u << dow);
|
||||
if (!(cfg[3] & dow_bit)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (start < end) {
|
||||
return ((now >= start) && (now < end)) ? 1 : 0;
|
||||
}
|
||||
return ((now >= start) || (now < end)) ? 1 : 0; /* 跨午夜 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 自然离开的窗:类型 0 单次清 Byte0 并落盘
|
||||
* @param left_mask 本拍由在窗→不在窗的组
|
||||
* @return 无
|
||||
*/
|
||||
static void timer_once_expire(uint8_t left_mask)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
uint8_t *cfg;
|
||||
|
||||
if (!(left_mask & (uint8_t)(1u << i))) {
|
||||
continue;
|
||||
}
|
||||
cfg = timer_cfg_ptr(i);
|
||||
if (cfg && cfg[0] && (cfg[2] == 0)) {
|
||||
cfg[0] = 0;
|
||||
app_nvm_mark_dirty();
|
||||
log_info("timer[%d] once done, en cleared\n", i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 全部窗结束:释放定时占用;仍占着则关加热并补判联动
|
||||
* @param force_off 1=time_en=0,即使手动打断过也关再按 10.3 补判
|
||||
* @return 无
|
||||
*/
|
||||
static void timer_window_close(uint8_t force_off)
|
||||
{
|
||||
s_timer.taken = 0;
|
||||
s_timer.in_mask = 0;
|
||||
|
||||
if (app_arbiter_get_owner() == ARB_OWNER_TIMER) {
|
||||
app_arbiter_timer_release();
|
||||
app_relay_request(0);
|
||||
log_info("timer window out, heat=0\n");
|
||||
} else {
|
||||
log_info("timer window out (manual taken)\n");
|
||||
if (force_off) {
|
||||
app_relay_request(0);
|
||||
}
|
||||
}
|
||||
app_linkage_reeval();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化定时引擎
|
||||
* @return 无
|
||||
*/
|
||||
void app_timer_init(void)
|
||||
{
|
||||
#if BOARD_TIMER_ENABLE
|
||||
/* P2: RTC 门闩 + 状态复位 */
|
||||
#else
|
||||
timer_disabled_once();
|
||||
#if !BOARD_TIMER_ENABLE
|
||||
log_info("timer engine disabled\n");
|
||||
return;
|
||||
#endif
|
||||
s_timer.taken = 0;
|
||||
s_timer.in_mask = 0;
|
||||
s_timer.last_rtc_ok = 0;
|
||||
log_info("timer init\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文跑定时引擎
|
||||
* @brief 推进定时引擎(500ms,任务上下文,由 usr_timer_loop 调用)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_timer_run(uint16_t dt)
|
||||
{
|
||||
#if BOARD_TIMER_ENABLE
|
||||
(void)dt;
|
||||
/* P2: 窗判断 → 仲裁 timer_take/release → 请求开/关 */
|
||||
#else
|
||||
#if !BOARD_TIMER_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#endif
|
||||
struct sys_time t;
|
||||
uint8_t rtc_ok = usr_rtc_get_time(&t);
|
||||
uint8_t in_now = 0;
|
||||
uint8_t i;
|
||||
|
||||
(void)dt;
|
||||
|
||||
if (rtc_ok && !s_timer.last_rtc_ok) {
|
||||
log_info("timer rtc synced, re-check window\n");
|
||||
}
|
||||
s_timer.last_rtc_ok = rtc_ok;
|
||||
|
||||
/* RTC 门闩 / time_en=0:停窗不毁单次(开机 RTC 必无效,不能把未到点的单次吞掉) */
|
||||
if (!rtc_ok || !gDevData.time_en) {
|
||||
if (s_timer.taken) {
|
||||
timer_window_close(1); /* 11.2:未对时 / time_en=0 等同所有窗结束 */
|
||||
}
|
||||
s_timer.in_mask = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
const uint8_t *cfg = timer_cfg_ptr(i);
|
||||
|
||||
if (!cfg || (cfg[0] == 0)) {
|
||||
continue;
|
||||
}
|
||||
if (timer_in_window(cfg, &t)) {
|
||||
in_now |= (uint8_t)(1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
/* 自然离开的组:单次自毁(重叠时只清离开的那组,其它组继续) */
|
||||
timer_once_expire((uint8_t)(s_timer.in_mask & (uint8_t)~in_now));
|
||||
s_timer.in_mask = in_now;
|
||||
|
||||
if (in_now) {
|
||||
if (!s_timer.taken) {
|
||||
s_timer.taken = 1;
|
||||
app_arbiter_timer_take();
|
||||
app_relay_request(1);
|
||||
log_info("timer window in, mask=0x%02x, heat=1\n", in_now);
|
||||
}
|
||||
} else if (s_timer.taken) {
|
||||
timer_window_close(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP9 定时总开关
|
||||
* @brief APP 写 DP9 定时总开关:存 gDevData + 触发 VM 持久化
|
||||
* @param en 0=关 1=开
|
||||
* @note 引擎关闭时仍保存;time_en=0 立即收口(关加热 + 10.3)
|
||||
* @return 无
|
||||
*/
|
||||
void app_timer_set_en(uint8_t en)
|
||||
{
|
||||
app_nvm_mark_dirty();
|
||||
#if BOARD_TIMER_ENABLE
|
||||
(void)en;
|
||||
/* P2: time_en=0 → 所有窗立刻结束(关加热)再补判联动 */
|
||||
#else
|
||||
(void)en;
|
||||
if (!en && s_timer.taken) {
|
||||
timer_window_close(1); /* 11.2:time_en=0 立刻结束所有窗(关加热 + 10.3) */
|
||||
}
|
||||
#endif
|
||||
log_info("time_en=%d (stored)\n", en);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP10~16 定时配置
|
||||
* @brief APP 写 DP10~16 定时配置:触发 VM 持久化(数据已在 gDevData)
|
||||
* @param idx 1~7
|
||||
* @note 引擎关闭时仍保存
|
||||
* @return 无
|
||||
*/
|
||||
void app_timer_set_config(uint8_t idx)
|
||||
{
|
||||
#if BOARD_TIMER_ENABLE
|
||||
(void)idx;
|
||||
/* P2: 配置入 RAM + mark_dirty */
|
||||
#else
|
||||
(void)idx;
|
||||
#endif
|
||||
app_nvm_mark_dirty();
|
||||
log_info("time_config[%d] stored\n", idx);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
/* ================================================================
|
||||
* jb_product.c — 用户层实现(请填补空白处)
|
||||
* 设备: 电子开关-加热垫 | 生成时间: 2026-09-04 10:30:37
|
||||
* jb_product.c — 用户层实现(薄适配:只写 DP,不写业务算法)
|
||||
* 设备: 电子开关-加热垫 | 生成: 2026-09-04(V1.0.0 → P1 接线)
|
||||
*
|
||||
* ══ 开发流程 ══
|
||||
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
|
||||
* 发送由 SOC SDK 的 ble_send_data 承担,协议层已直接调用,无需 uartWrite
|
||||
* 2. userInit() 设置 DP 默认值与外设初始化
|
||||
* 3. userHandle() 用户数据采集
|
||||
* 4. jbEventProcess() 处理 APP 下发的控制命令
|
||||
* 5. 主动上报 / OTA 的网关应答在收到 REPORTCMD_V2 后调用 jbReportAckCheck 清除等待
|
||||
* ================================================================
|
||||
*/
|
||||
* 发送由 SOC SDK 的 ble_send_data 承担,协议层已直接调用
|
||||
* 2. userInit() 设置 DP 默认值
|
||||
* 3. userHandle() 用户数据采集(本机无传感器,DP 由 app_function_poll 同步)
|
||||
* 4. jbEventProcess() APP 下行:只写 gDevData 并转调 app_*,不在这里写业务
|
||||
*
|
||||
* ⚠ 规格书第 0 章第 2 条:**业务算法一律不进本文件**。
|
||||
* 继电器 → app_relay / app_arbiter;定时存储 → app_timer_task;
|
||||
* 持久化 → app_nvm;出厂重置 → app_function。
|
||||
* ⚠ DP0 上报**实际吸合态**:本文件的赋值只表达"手动请求",app_function_poll
|
||||
* (20ms)会把它覆盖为过零后的真值,协议层差分上报读到的即实际态。
|
||||
* ================================================================ */
|
||||
|
||||
#include "jb_product.h"
|
||||
#include "app_relay.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_timer_task.h"
|
||||
#include "app_nvm.h"
|
||||
#include "app_function.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[JB_PRD]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局变量
|
||||
@@ -28,16 +43,16 @@ void userInit(void)
|
||||
{
|
||||
memset(&gDevData, 0, sizeof(jb_data_point_t));
|
||||
|
||||
gDevData.heat_switch = 0; /* DP0 加热开关 0=关 1=开 */
|
||||
gDevData.temp_control_switch = 0; /* DP1 温控开关 0=关 1=开 */
|
||||
memset(gDevData.temp_control_time, 0, 4); /* DP2 温控时间 */
|
||||
gDevData.mode = 0; /* MODE_0 (??) */
|
||||
gDevData.min_temp = 0.0f; /* DP4 最低温度 (float) */
|
||||
gDevData.max_temp = 0.0f; /* DP5 最高温度 (float) */
|
||||
gDevData.current_temp = 0.0f; /* DP6 当前温度 (float) */
|
||||
gDevData.linkage_delete = 0; /* DP7 联动删除 0=关 1=开 */
|
||||
memset(gDevData.linkage_config, 0, 20); /* DP8 联动配置 */
|
||||
gDevData.time_en = 0; /* DP9 定时使能 0=关 1=开 */
|
||||
gDevData.heat_switch = 0; /* DP0 加热开关 0=关 1=开(P1 起由 VM 恢复) */
|
||||
gDevData.temp_control_switch = 0; /* DP1 温控开关(本版写忽略) */
|
||||
memset(gDevData.temp_control_time, 0, 4); /* DP2 温控时间(本版写忽略) */
|
||||
gDevData.mode = 0; /* DP3 模式(本版写忽略) */
|
||||
gDevData.min_temp = 0.0f; /* DP4 最低温度(本版写忽略) */
|
||||
gDevData.max_temp = 0.0f; /* DP5 最高温度(本版写忽略) */
|
||||
gDevData.current_temp = 0.0f; /* DP6 当前温度(WO,只收不下发) */
|
||||
gDevData.linkage_delete = 0; /* DP7 联动删除(脉冲;P2 接) */
|
||||
memset(gDevData.linkage_config, 0, 20); /* DP8 联动配置(P2 接) */
|
||||
gDevData.time_en = 0; /* DP9 定时使能(P1 起由 VM 恢复) */
|
||||
memset(gDevData.time_config1, 0, 8); /* DP10 定时配置1 */
|
||||
memset(gDevData.time_config2, 0, 8); /* DP11 定时配置2 */
|
||||
memset(gDevData.time_config3, 0, 8); /* DP12 定时配置3 */
|
||||
@@ -45,58 +60,33 @@ void userInit(void)
|
||||
memset(gDevData.time_config5, 0, 8); /* DP14 定时配置5 */
|
||||
memset(gDevData.time_config6, 0, 8); /* DP15 定时配置6 */
|
||||
memset(gDevData.time_config7, 0, 8); /* DP16 定时配置7 */
|
||||
memset(gDevData.run_record, 0, 124); /* DP17 运行记录 */
|
||||
gDevData.heartbeat = 0; /* DP18 心跳 0=关 1=开 */
|
||||
memset(gDevData.run_record, 0, 124); /* DP17 运行记录(RO,P2 由 app_runrec 填) */
|
||||
gDevData.heartbeat = 0; /* DP18 心跳(本版忽略) */
|
||||
gDevData.fault_en = 0; /* DP19 设备故障使能 */
|
||||
gDevData.fault_code = 0; /* DP20 设备故障 */
|
||||
gDevData.fault_code = 0; /* DP20 设备故障(RO) */
|
||||
gDevData.alarm_en = 0; /* DP21 设备告警使能 */
|
||||
gDevData.alarm_code = 0; /* DP22 设备告警 */
|
||||
gDevData.factory_reset = 0; /* DP23 恢复出厂设置 0=关 1=开 */
|
||||
gDevData.alarm_code = 0; /* DP22 设备告警(RO,保持 0) */
|
||||
gDevData.factory_reset = 0; /* DP23 恢复出厂设置(WO,写 1 触发) */
|
||||
gDevData.reserva_dp1 = 0; /* DP24 预留DP1 */
|
||||
gDevData.reserva_dp2 = 0; /* DP25 预留DP2 */
|
||||
gDevData.reserva_dp3 = 0; /* DP26 预留DP3 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* userHandle — 用户数据采集
|
||||
* 协议层自动检测变化并上报(注意:此处是上报,不是下发控制)
|
||||
* userHandle — 用户数据采集(500ms 由 usr_timer_loop 调用)
|
||||
* 本机无传感器:DP0 由 app_function_poll(20ms)同步实际态;
|
||||
* DP17 运行记录 P2 由 app_runrec 在任务上下文灌;其余无可上报采集
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void userHandle(void)
|
||||
{
|
||||
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
|
||||
// gDevData.heat_switch = read_sensor(); /* DP0 加热开关 */
|
||||
// gDevData.temp_control_switch = read_sensor(); /* DP1 温控开关 */
|
||||
// memcpy(gDevData.temp_control_time, sensor_read(), 4); /* DP2 温控时间 */
|
||||
// gDevData.mode = read_sensor(); /* DP3 模式 */
|
||||
// gDevData.min_temp = 23.5f; /* DP4 最低温度 (float, scale=0.1), 直接填物理量 */
|
||||
// gDevData.max_temp = 23.5f; /* DP5 最高温度 (float, scale=0.1), 直接填物理量 */
|
||||
/* DP6 current_temp 为 WO:不下发采集、不上报;只在 jbEventProcess 收服务器写入 */
|
||||
// gDevData.linkage_delete = read_sensor(); /* DP7 联动删除 */
|
||||
// memcpy(gDevData.linkage_config, sensor_read(), 20); /* DP8 联动配置 */
|
||||
// gDevData.time_en = read_sensor(); /* DP9 定时使能 */
|
||||
// memcpy(gDevData.time_config1, sensor_read(), 8); /* DP10 定时配置1 */
|
||||
// memcpy(gDevData.time_config2, sensor_read(), 8); /* DP11 定时配置2 */
|
||||
// memcpy(gDevData.time_config3, sensor_read(), 8); /* DP12 定时配置3 */
|
||||
// memcpy(gDevData.time_config4, sensor_read(), 8); /* DP13 定时配置4 */
|
||||
// memcpy(gDevData.time_config5, sensor_read(), 8); /* DP14 定时配置5 */
|
||||
// memcpy(gDevData.time_config6, sensor_read(), 8); /* DP15 定时配置6 */
|
||||
// memcpy(gDevData.time_config7, sensor_read(), 8); /* DP16 定时配置7 */
|
||||
// memcpy(gDevData.run_record, sensor_read(), 124); /* DP17 运行记录 */
|
||||
// gDevData.fault_en = read_sensor(); /* DP19 设备故障使能 */
|
||||
// gDevData.fault_code = read_sensor(); /* DP20 设备故障 */
|
||||
// gDevData.alarm_en = read_sensor(); /* DP21 设备告警使能 */
|
||||
// gDevData.alarm_code = read_sensor(); /* DP22 设备告警 */
|
||||
// gDevData.reserva_dp1 = read_sensor(); /* DP24 预留DP1 */
|
||||
// gDevData.reserva_dp2 = read_sensor(); /* DP25 预留DP2 */
|
||||
// gDevData.reserva_dp3 = read_sensor(); /* DP26 预留DP3 */
|
||||
/* 无本机温度/湿度采集。断连扫描温度(P2)只进联动引擎,不填 DP6 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbEventProcess — 0x03 APP 控制事件处理(下行 / DP 赋值方向)
|
||||
*
|
||||
* 协议层自动解包 0x03 帧后调用本函数。
|
||||
* info->dp_ids[] = 被控制的 DP ID 列表
|
||||
* ctrl 已填入下发的值;复制到 gDevData 并执行动作
|
||||
* ⚠ 只做两件事:DP 写入 gDevData + 转调 app_* 模块。业务算法一律
|
||||
* 不进本文件(规格书第 0 章第 2 条)。
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
{
|
||||
@@ -109,197 +99,121 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
{
|
||||
switch (info->dp_ids[i])
|
||||
{
|
||||
|
||||
/* ── DP0 加热开关 (bool) ── */
|
||||
/* ── DP0 加热开关:写 = 手动抢权,请求开/关(等过零)。
|
||||
* gDevData 值会被 app_function_poll 同步为实际吸合态再上报 ── */
|
||||
case DP_ID_HEAT_SWITCH:
|
||||
if (ctrl->heat_switch)
|
||||
{
|
||||
gDevData.heat_switch = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.heat_switch = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
gDevData.heat_switch = ctrl->heat_switch ? 1 : 0;
|
||||
app_arbiter_manual_intervene(); /* 手动动作:联动/定时让位 */
|
||||
app_relay_request(gDevData.heat_switch);
|
||||
log_info("dp0 heat request=%d\n", gDevData.heat_switch);
|
||||
break;
|
||||
|
||||
/* ── DP1 温控开关 (bool) ── */
|
||||
case DP_ID_TEMP_CONTROL_SWITCH:
|
||||
if (ctrl->temp_control_switch)
|
||||
{
|
||||
gDevData.temp_control_switch = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.temp_control_switch = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
/* ── DP1~5:本版写忽略(规格书 0 章第 7 条 / 9 章)。
|
||||
* 不更新 gDevData → 不动作、不回显、不落盘 ── */
|
||||
case DP_ID_TEMP_CONTROL_SWITCH: /* DP1 温控开关 */
|
||||
case DP_ID_TEMP_CONTROL_TIME: /* DP2 温控时间 */
|
||||
case DP_ID_MODE: /* DP3 模式 */
|
||||
case DP_ID_MIN_TEMP: /* DP4 最低温度 */
|
||||
case DP_ID_MAX_TEMP: /* DP5 最高温度 */
|
||||
break;
|
||||
|
||||
/* ── DP2 温控时间 (raw) ── */
|
||||
case DP_ID_TEMP_CONTROL_TIME:
|
||||
memcpy(gDevData.temp_control_time, ctrl->temp_control_time, 4);
|
||||
// TODO: 根据 gDevData.temp_control_time 执行动作 (长度 4 字节)
|
||||
break;
|
||||
|
||||
/* ── DP3 模式 (enum) ── */
|
||||
case DP_ID_MODE:
|
||||
gDevData.mode = ctrl->mode;
|
||||
// TODO: 根据 ctrl->mode 执行动作
|
||||
break;
|
||||
|
||||
/* ── DP4 最低温度 (float, scale=0.1) ── */
|
||||
case DP_ID_MIN_TEMP:
|
||||
gDevData.min_temp = ctrl->min_temp;
|
||||
// TODO: 根据 ctrl->min_temp 执行动作 (已是浮点物理量)
|
||||
break;
|
||||
|
||||
/* ── DP5 最高温度 (float, scale=0.1) ── */
|
||||
case DP_ID_MAX_TEMP:
|
||||
gDevData.max_temp = ctrl->max_temp;
|
||||
// TODO: 根据 ctrl->max_temp 执行动作 (已是浮点物理量)
|
||||
break;
|
||||
|
||||
/* ── DP6 当前温度 (float, WO, scale=0.1) ── */
|
||||
/* ── DP6 当前温度(WO):服务器下发 → 联动用温度(10.2/8 章)。
|
||||
* 协议层不打包不上报;联动引擎关时也只存 RAM ── */
|
||||
case DP_ID_CURRENT_TEMP:
|
||||
gDevData.current_temp = ctrl->current_temp;
|
||||
/* 业务:转调 app_* 更新联动用温度;禁止在本文件写开停算法 */
|
||||
app_linkage_set_connected_temp(ctrl->current_temp);
|
||||
break;
|
||||
|
||||
/* ── DP7 联动删除 (bool) ── */
|
||||
/* ── DP7 联动删除(脉冲,10.4):写 1 清配置并让出加热,
|
||||
* 然后清回 0 再上报;写 0 = 无操作 ── */
|
||||
case DP_ID_LINKAGE_DELETE:
|
||||
if (ctrl->linkage_delete)
|
||||
{
|
||||
gDevData.linkage_delete = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.linkage_delete = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
if (ctrl->linkage_delete) {
|
||||
app_linkage_delete();
|
||||
gDevData.linkage_delete = 0; /* 脉冲:清回 0 供上报 */
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP8 联动配置 (raw) ── */
|
||||
/* ── DP8 联动配置:20B memcpy + 存 VM + 清手动作废 + 立刻重判(10.5)── */
|
||||
case DP_ID_LINKAGE_CONFIG:
|
||||
memcpy(gDevData.linkage_config, ctrl->linkage_config, 20);
|
||||
// TODO: 根据 gDevData.linkage_config 执行动作 (长度 20 字节)
|
||||
app_linkage_set_config();
|
||||
break;
|
||||
|
||||
/* ── DP9 定时使能 (bool) ── */
|
||||
/* ── DP9 定时使能:写 gDevData;引擎每 500ms 读 time_en(11.2)── */
|
||||
case DP_ID_TIME_EN:
|
||||
if (ctrl->time_en)
|
||||
{
|
||||
gDevData.time_en = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.time_en = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
gDevData.time_en = ctrl->time_en ? 1 : 0;
|
||||
app_timer_set_en(gDevData.time_en);
|
||||
break;
|
||||
|
||||
/* ── DP10 定时配置1 (raw) ── */
|
||||
/* ── DP10~16 定时配置:8B 原样存 gDevData(Byte1 模式忽略);
|
||||
* 引擎按窗口起止/星期直接读这些字段 ── */
|
||||
case DP_ID_TIME_CONFIG1:
|
||||
memcpy(gDevData.time_config1, ctrl->time_config1, 8);
|
||||
// TODO: 根据 gDevData.time_config1 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(1);
|
||||
break;
|
||||
|
||||
/* ── DP11 定时配置2 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG2:
|
||||
memcpy(gDevData.time_config2, ctrl->time_config2, 8);
|
||||
// TODO: 根据 gDevData.time_config2 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(2);
|
||||
break;
|
||||
|
||||
/* ── DP12 定时配置3 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG3:
|
||||
memcpy(gDevData.time_config3, ctrl->time_config3, 8);
|
||||
// TODO: 根据 gDevData.time_config3 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(3);
|
||||
break;
|
||||
|
||||
/* ── DP13 定时配置4 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG4:
|
||||
memcpy(gDevData.time_config4, ctrl->time_config4, 8);
|
||||
// TODO: 根据 gDevData.time_config4 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(4);
|
||||
break;
|
||||
|
||||
/* ── DP14 定时配置5 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG5:
|
||||
memcpy(gDevData.time_config5, ctrl->time_config5, 8);
|
||||
// TODO: 根据 gDevData.time_config5 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(5);
|
||||
break;
|
||||
|
||||
/* ── DP15 定时配置6 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG6:
|
||||
memcpy(gDevData.time_config6, ctrl->time_config6, 8);
|
||||
// TODO: 根据 gDevData.time_config6 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(6);
|
||||
break;
|
||||
|
||||
/* ── DP16 定时配置7 (raw) ── */
|
||||
case DP_ID_TIME_CONFIG7:
|
||||
memcpy(gDevData.time_config7, ctrl->time_config7, 8);
|
||||
// TODO: 根据 gDevData.time_config7 执行动作 (长度 8 字节)
|
||||
app_timer_set_config(7);
|
||||
break;
|
||||
|
||||
/* ── DP18 心跳 (bool) ── */
|
||||
/* ── DP17 运行记录:RO,禁止 APP 写(规格书第 15 章)── */
|
||||
case DP_ID_RUN_RECORD:
|
||||
break;
|
||||
|
||||
/* ── DP18 心跳:本版忽略 ── */
|
||||
case DP_ID_HEARTBEAT:
|
||||
if (ctrl->heartbeat)
|
||||
{
|
||||
gDevData.heartbeat = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.heartbeat = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP19 设备故障使能 (uint8) ── */
|
||||
/* ── DP19/DP21 使能:原样存(P1 不做位级过滤)── */
|
||||
case DP_ID_FAULT_EN:
|
||||
gDevData.fault_en = ctrl->fault_en;
|
||||
// TODO: 根据 ctrl->fault_en 执行动作
|
||||
app_nvm_mark_dirty(); /* 使能要持久化(13 章"若有写入") */
|
||||
break;
|
||||
|
||||
/* ── DP21 设备告警使能 (uint8) ── */
|
||||
case DP_ID_ALARM_EN:
|
||||
gDevData.alarm_en = ctrl->alarm_en;
|
||||
// TODO: 根据 ctrl->alarm_en 执行动作
|
||||
app_nvm_mark_dirty();
|
||||
break;
|
||||
|
||||
/* ── DP23 恢复出厂设置 (bool) ── */
|
||||
/* ── DP23 恢复出厂设置:清业务 VM(留配对)+ RAM 复位,
|
||||
* 不断开不软复位(规格书第 14 章)── */
|
||||
case DP_ID_FACTORY_RESET:
|
||||
if (ctrl->factory_reset)
|
||||
{
|
||||
gDevData.factory_reset = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.factory_reset = ctrl->factory_reset ? 1 : 0;
|
||||
if (gDevData.factory_reset) {
|
||||
app_function_factory_reset();
|
||||
gDevData.factory_reset = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
log_info("dp23 factory reset\n");
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP24 预留DP1 (uint32) ── */
|
||||
/* ── DP24~26 预留:存 RAM,不实现动作 ── */
|
||||
case DP_ID_RESERVA_DP1:
|
||||
gDevData.reserva_dp1 = ctrl->reserva_dp1;
|
||||
// TODO: 根据 ctrl->reserva_dp1 执行动作
|
||||
break;
|
||||
|
||||
/* ── DP25 预留DP2 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP2:
|
||||
gDevData.reserva_dp2 = ctrl->reserva_dp2;
|
||||
// TODO: 根据 ctrl->reserva_dp2 执行动作
|
||||
break;
|
||||
|
||||
/* ── DP26 预留DP3 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP3:
|
||||
gDevData.reserva_dp3 = ctrl->reserva_dp3;
|
||||
// TODO: 根据 ctrl->reserva_dp3 执行动作
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -313,14 +227,12 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
* 返回 0=接受升级(协议层应答 JB_OK + MaxDataLen)
|
||||
* 非 0=拒绝,返回值即错误码(JB_OTA_*)
|
||||
*
|
||||
* ⚠ SOC 侧 OTA 尚未实现:当前仅应答,不执行任何升级动作。 */
|
||||
* ⚠ SOC 侧协议 OTA 未实现:拒绝接收,避免 APP 继续推数据。
|
||||
* 按键 12s OTA 仍走 usr_app_ota_init。 */
|
||||
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
||||
{
|
||||
(void)fwVersion; (void)fwLength; (void)fwCrc32;
|
||||
// TODO: 检查 Flash 空间与版本,决定是否接收升级
|
||||
// 允许: 返回 JB_OK(协议层应答 JB_OK + MaxDataLen)
|
||||
// 拒绝: 返回对应 JB_OTA_* 错误码(如 JB_OTA_VER_LOW / JB_OTA_SPACE_ERR)
|
||||
return JB_OK;
|
||||
return JB_OTA_STATE_ERR;
|
||||
}
|
||||
|
||||
/* 0x32 OTA 数据(含分片序号)*/
|
||||
@@ -328,35 +240,32 @@ void jbOnOtaData(const uint8_t *data, uint16_t len,
|
||||
uint16_t shardIdx, uint16_t totalShards)
|
||||
{
|
||||
(void)data; (void)len; (void)shardIdx; (void)totalShards;
|
||||
// TODO: 写入 Flash,累计 CRC32
|
||||
// 全部数据接收完成后调用 jbSendOtaResult(JB_OK, mcuVersion)
|
||||
/* TODO(P2): 写入 Flash,累计 CRC32;全部接收后调用 jbSendOtaResult */
|
||||
}
|
||||
|
||||
/* 0x36 / 0x38 停止 OTA */
|
||||
void jbOnOtaStop(void)
|
||||
{
|
||||
// TODO: 终止 OTA,释放资源
|
||||
/* TODO(P2): 终止 OTA,释放资源 */
|
||||
}
|
||||
|
||||
/* 任意 MCU→BLE 请求的 ACK 回调(cmd 标识是哪条请求的应答,result 为结果码)
|
||||
- SOC 仅保留 OTA 主动请求应答: OTA完成 0x35 / 停止 0x39
|
||||
- 可在本回调中设置"XX 完成/失败"标志,供主循环判断"确认成功才继续" */
|
||||
/* 任意 MCU→BLE 请求的 ACK 回调 */
|
||||
void jbOnAck(uint8_t cmd, uint8_t result)
|
||||
{
|
||||
(void)cmd;
|
||||
(void)result;
|
||||
// TODO: switch(cmd){ case CMD_OTA_COMPLETE_ACK: gOtaDone = (result==JB_OK); break; case CMD_OTA_STOP_MCU_ACK: ... }
|
||||
/* TODO(P2): switch(cmd){ case CMD_OTA_COMPLETE_ACK: ... } */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 硬件抽象(按实际平台实现)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── 定时器 ── */
|
||||
/* ── 定时器:由 usr_jb_main 的 20ms 节拍按 1ms 步进调用 jbTimerIrq() ── */
|
||||
void jbTimerIrq(void)
|
||||
{
|
||||
timerMs++;
|
||||
} /* 在 1ms 中断中调用本函数 */
|
||||
}
|
||||
|
||||
uint32_t jbGetTimerCount(void)
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define PKT_HEAD_0 0x55
|
||||
#define PKT_HEAD_1 0xAA
|
||||
#define MAX_PACKAGE_LEN 256
|
||||
#define MAX_PACKAGE_LEN 512
|
||||
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
|
||||
|
||||
#define DP_COUNT 27
|
||||
|
||||
@@ -67,6 +67,9 @@ void usr_ble_adv_updata(void);
|
||||
|
||||
static int le_timer_handle = 0;
|
||||
|
||||
/* 广播门控:扫描分时期间暂停广播发送(见 app_th_scan),仅门控不断连 */
|
||||
static u8 s_adv_paused = 0;
|
||||
|
||||
/**
|
||||
* @brief BLE 是否已连接
|
||||
* @return 1=已连接,0=未连接
|
||||
@@ -216,6 +219,36 @@ u8 usr_get_pair_flag(void)
|
||||
return usr_auth_data.paired_flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 暂停/恢复广播发送(扫描分时射频仲裁,见 app_th_scan)
|
||||
* @note adv data 仍注册在协议栈,恢复时续发无需重新编码;仅门控发送,不断连接。
|
||||
* 只动广播(bt_ble_adv_enable 内部有 con_handle 保护),不动模块使能位,
|
||||
* 因此已连接时误调不会断开已有链路。
|
||||
* @param pause 1=暂停(扫描期让出射频),0=恢复
|
||||
*/
|
||||
void usr_le_set_adv_pause(u8 pause)
|
||||
{
|
||||
extern void bt_ble_adv_enable(u8 enable);
|
||||
|
||||
if (pause == s_adv_paused) {
|
||||
return;
|
||||
}
|
||||
if (pause && usr_ble_is_connected()) {
|
||||
return; /* 扫描只允许断连态;已连接保持广播 */
|
||||
}
|
||||
s_adv_paused = pause;
|
||||
bt_ble_adv_enable(pause ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询广播是否处于暂停态
|
||||
* @return 1=暂停中(扫描期),0=正常广播
|
||||
*/
|
||||
u8 usr_le_adv_is_paused(void)
|
||||
{
|
||||
return s_adv_paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 周期处理广播刷新(由 500ms 定时器调用)
|
||||
* @note 仅当 usr_adv_flag==1 时:停广播 → 写入 muti_adv_data → 再开广播
|
||||
@@ -224,6 +257,12 @@ void usr_ble_adv_updata(void)
|
||||
{
|
||||
static u16 usr_goto_slow_adv = 0;
|
||||
|
||||
if (usr_le_adv_is_paused()) {
|
||||
/* 扫描期(app_th_scan 分时):不刷广播、不切快慢,只跑协议周期 */
|
||||
usr_timer_loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (usr_var.usr_adv_flag)
|
||||
{
|
||||
ble_trans_module_enable(0);
|
||||
|
||||
@@ -43,12 +43,31 @@ void usr_clear_pair_info(void);
|
||||
*/
|
||||
uint8_t usr_get_pair_flag(void);
|
||||
|
||||
/**
|
||||
* @brief 查询 BLE 是否已连接
|
||||
* @return 1=已连接,0=未连接
|
||||
*/
|
||||
uint8_t usr_ble_is_connected(void);
|
||||
|
||||
/**
|
||||
* @brief 启动应用侧 OTA 流程
|
||||
* @note 置位 usr_goto_updata,并创建 1s 周期定时器驱动断连与 OTA 广播切换
|
||||
*/
|
||||
void usr_app_ota_init(void);
|
||||
|
||||
/**
|
||||
* @brief 暂停/恢复广播发送(扫描分时射频仲裁,见 app_th_scan)
|
||||
* @note 仅门控发送,不断开连接;恢复后按注册数据续发,无需重新编码
|
||||
* @param pause 1=暂停(扫描期让出射频),0=恢复
|
||||
*/
|
||||
void usr_le_set_adv_pause(uint8_t pause);
|
||||
|
||||
/**
|
||||
* @brief 查询广播是否处于暂停态
|
||||
* @return 1=暂停中(扫描期),0=正常广播
|
||||
*/
|
||||
uint8_t usr_le_adv_is_paused(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
#define BOARD_KEY_ENABLE 1 /**< 1=按键 PB1(低按下) */
|
||||
#define BOARD_RELAY_ENABLE 1 /**< 1=继电器 PC3(高导通) */
|
||||
#define BOARD_ZC_ENABLE 1 /**< 1=过零 PA1;=0 时开请求禁吸合、关请求强制断开 */
|
||||
#define BOARD_LINKAGE_ENABLE 0 /* 联动引擎(第 10 章);P2 开 */
|
||||
#define BOARD_TIMER_ENABLE 0 /* 定时引擎(第 11 章);P2 开 */
|
||||
#define BOARD_TH_SCAN_ENABLE 0 /* 断连扫描风扇广播(第 8 章);P2 开 */
|
||||
#define BOARD_RUNREC_ENABLE 0 /* 31 天运行记录(第 13 章);P2 开 */
|
||||
#define BOARD_NVM_ENABLE 0 /* VM 持久化(第 13 章);P1 开 */
|
||||
#define BOARD_LINKAGE_ENABLE 1 /* 联动引擎(第 10 章);P2 开 */
|
||||
#define BOARD_TIMER_ENABLE 1 /* 定时引擎(第 11 章);P2 开 */
|
||||
#define BOARD_TH_SCAN_ENABLE 1 /* 断连扫描风扇广播(第 8 章);P2 开 */
|
||||
#define BOARD_RUNREC_ENABLE 1 /* 31 天运行记录(第 13 章);P2 开 */
|
||||
#define BOARD_NVM_ENABLE 1 /* VM 持久化(第 13 章):加热请求/联动/定时/运行记录 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* GPIO */
|
||||
@@ -91,7 +91,7 @@
|
||||
#define BOARD_ZC_PULL_UP 1 /**< PA1 内部上拉(光耦 OC 输出时必须有;推挽输入也无害) */
|
||||
#define BOARD_ZC_PULL_DOWN 0 /**< PA1 内部下拉,通常 0(与上拉互斥) */
|
||||
#define BOARD_ZC_SW_POLL_1MS 0 /**< 1=额外挂真实 1ms 软件采样兜底(中断失效时滞后 ≤1ms,代价 1000Hz ISR) */
|
||||
#define BOARD_ZC_DIAG_LOG 1 /**< 1=500ms 打印一次过零沿计数(台上确认中断在跑,量产出货置 0) */
|
||||
#define BOARD_ZC_DIAG_LOG 0 /**< 1=500ms 打印一次过零沿计数(台上确认中断在跑,量产出货置 0) */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 断连扫描(P2,第 8.2 节) */
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
// #define CONFIG_LE_ROLES (LE_ADV|LE_SCAN|LE_INIT|LE_SLAVE|LE_MASTER)
|
||||
// #define CONFIG_LE_ROLES (LE_ADV|LE_SCAN)
|
||||
#define CONFIG_LE_ROLES (LE_ADV)
|
||||
#define CONFIG_LE_ROLES (LE_ADV|LE_SCAN|LE_SLAVE)
|
||||
|
||||
#include "classic/lmp_config.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user