Files
JBao_Temp_Humi_Fan_FW/apps/usr_app/app_linkage.c
T

306 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* @file app_linkage.c
* @brief CBR3 联动引擎实现(规格书第 10 章 + 第 12 章仲裁 + 第 8.2 闪烁)
* @author cyWu <1917507415@qq.com>
* @date 2026.09.02
* @version V3.0.0,随规格书 V3.0.0 新建(整模块替换 app_cond
*
* @note 判定跑在 usr_timer_loop(500ms,任务上下文),与定时引擎同侧。
* 每拍做两件独立的事:
* 1) 第 8.2 节闪烁:有有效配置就按「是否落在 [min,max] 闭区间」判,
* 与风扇是否在转、联动是否被手动作废、仲裁谁占着都无关。
* 2) 第 10.3 节开停:回差 + 超最高优先,只写 fan_gear,不改 mode。
* 仲裁(12 章):手动 > 定时 > 联动。定时占用(ARB_OWNER_TIMER)时
* 联动完全不插嘴;手动作废标志作废本轮直到走到「关」条件。
* ⚠ 本模块不进 20ms 中断上下文(没有 tick 接口)。
******************************************************************************/
#include <string.h>
#include "app_linkage.h"
#include "app_arbiter.h"
#include "app_disp.h"
#include "app_fan.h"
#include "app_mode.h"
#include "app_nvm.h"
#include "app_th.h"
#include "board_pin.h"
#include "jb_product.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_LKG]"
#define LOG_INFO_ENABLE
#include "debug.h"
/** 20B 配置解析结果(规格书 10.2;版本/预留只存不用) */
typedef struct {
uint16_t tank_id; /**< Byte0-1 大端,0=无效 */
int8_t tmin; /**< Byte2 ℃ */
int8_t tmax; /**< Byte3 ℃ */
uint8_t hmin; /**< Byte4 % */
uint8_t hmax; /**< Byte5 % */
} LinkageCfg_t;
/** 联动运行时(RAM,不作废标志不落 VM) */
typedef struct {
uint8_t voided; /**< 1=被手动作废,须走到「关」条件才再武装 */
} LinkageCtx_t;
static LinkageCtx_t s_lk;
/**
* @brief 解析 DP6 20B 并判定是否有效(第 10.1 / 10.2 节)
* @param c 20B 配置
* @param out 解析结果
* @return 1=有效(缸体号≠0 且 Tmin≤Tmax 且 Hmin≤Hmax
*/
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];
out->hmin = c[4];
out->hmax = c[5];
if (out->tank_id == 0) {
return 0;
}
if (out->tmin > out->tmax) {
return 0;
}
if (out->hmin > out->hmax) {
return 0;
}
return 1;
}
/**
* @brief 联动让出风扇:仅当联动正占着时才关(手动/定时占着不动)
* @return 无
*/
static void lk_stop(void)
{
if (app_arbiter_linkage_active()) {
app_arbiter_linkage_release();
app_fan_set_gear(0);
log_info("linkage stop, fan=0\n");
}
}
/**
* @brief 第 8.2 节:喂数码管告警闪烁(出 [min,max] 就闪,与被作废无关)
* @param cfg 已解析的配置
* @param valid 配置是否有效
* @return 无
*/
static void lk_feed_blink(const LinkageCfg_t *cfg, uint8_t valid)
{
/* 传感器故障 "--" 优先于闪烁(8.2):故障期不闪 */
if (!valid || !app_th_get_valid() || app_th_is_fault()) {
app_disp_set_temp_alarm(0);
app_disp_set_humi_alarm(0);
return;
}
{
int16_t tx10 = app_th_get_temp_x10();
uint8_t h = app_th_get_humi();
int16_t tmax = (int16_t)cfg->tmax * 10;
int16_t tmin = (int16_t)cfg->tmin * 10;
/* 没有等于比较:T==Tmax 不算超最高,T==Tmin 不算低于最低 */
app_disp_set_temp_alarm(((tx10 > tmax) || (tx10 < tmin)) ? 1 : 0);
app_disp_set_humi_alarm(((h > cfg->hmax) || (h < cfg->hmin)) ? 1 : 0);
}
}
/**
* @brief 初始化联动引擎
* @return 无
*/
void app_linkage_init(void)
{
#if !BOARD_LINKAGE_ENABLE
log_info("linkage engine disabled\n");
return;
#endif
memset(&s_lk, 0, sizeof(s_lk));
app_disp_set_temp_alarm(0);
app_disp_set_humi_alarm(0);
log_info("linkage init\n");
}
/**
* @brief 立刻按第 10.3 节重判一次(核心判定,幂等)
* @note 步骤:8.4 故障 → 关;8.2 喂闪烁;10.1 无效配置 → 关;
* 12 章再武装(走到关条件才清作废);仲裁;10.3 开/关/保持。
* @return 无
*/
void app_linkage_reeval(void)
{
#if !BOARD_LINKAGE_ENABLE
return;
#endif
LinkageCfg_t cfg;
uint8_t valid = lk_parse(gDevData.linkage_config, &cfg);
uint8_t over_max;
uint8_t under_min;
/* ── 8.4:采样失败 "--" 期间联动视为不成立;若联动占着 → 关掉,避免盲吹 ── */
if (app_th_is_fault()) {
app_disp_set_temp_alarm(0);
app_disp_set_humi_alarm(0);
lk_stop();
return;
}
/* ── 8.2:闪烁与开停解耦,有有效配置就判 ── */
lk_feed_blink(&cfg, valid);
/* ── 10.1:无效配置 → 不自动开;原先联动占着则关掉 ── */
if (!valid) {
lk_stop();
return;
}
if (!app_th_get_valid()) {
/* 从未采到过有效值:无可比数据,联动不成立 */
lk_stop();
return;
}
{
int16_t tx10 = app_th_get_temp_x10();
uint8_t h = app_th_get_humi();
over_max = ((tx10 > (int16_t)cfg.tmax * 10) || (h > cfg.hmax)) ? 1 : 0;
under_min = ((tx10 < (int16_t)cfg.tmin * 10) || (h < cfg.hmin)) ? 1 : 0;
}
/* ── 12 章再武装:必须先走到「关」条件(没有一侧超最高,且至少一侧 < min)
* 才清手动作废;之后重新出现超最高才允许再开 1 档 ── */
if (!over_max && under_min) {
if (s_lk.voided) {
log_info("linkage rearmed (close condition reached)\n");
}
s_lk.voided = 0;
}
if (s_lk.voided) {
return; /* 作废期间只闪不动风扇 */
}
/* ── 仲裁:模式引擎每 20ms 强制档位(通风/强力),联动不与之抢 ── */
if (app_mode_get_active() != 0) {
return;
}
if (over_max) {
/* 10.3 开:任一超最高 → 1 档,不改 mode。定时窗占用则 take 失败 */
if (!app_arbiter_linkage_take()) {
return; /* 12 章:定时窗 vs 联动 → 定时胜,窗内联动不插嘴 */
}
if (app_fan_get_gear() != BOARD_LINKAGE_OPEN_GEAR) {
app_fan_set_gear(BOARD_LINKAGE_OPEN_GEAR);
log_info("linkage open, gear=%d\n", BOARD_LINKAGE_OPEN_GEAR);
}
} else if (under_min) {
/* 10.3 关:没有一侧超最高且至少一侧 < min → 关 */
lk_stop();
}
/* 否则:两侧都在 [min,max] 闭区间(含等于)→ 保持上一态(回差) */
}
/**
* @brief 推进联动引擎(500ms,任务上下文)
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_linkage_run(uint16_t dt)
{
#if !BOARD_LINKAGE_ENABLE
(void)dt;
return;
#endif
(void)dt; /* 本模块不计时:每拍完整重判,2s 采样周期由 app_th 决定 */
app_linkage_reeval();
}
/**
* @brief DP5 linkage_delete 写 1:清配置并让出风扇
* @return 无
*/
void app_linkage_delete(void)
{
/* 引擎关掉也要落盘(与定时一致:P1 只存不跑) */
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:联动若占着风扇 → fan_gear=0 */
#endif
log_info("linkage config deleted\n");
}
/**
* @brief DP6 重写完成:存 VM + 清手动作废 + 立刻按新 min/max 重判
* @return 无
*/
void app_linkage_set_config(void)
{
/* 引擎关掉也要落盘;开停判定仅在 BOARD_LINKAGE_ENABLE=1 时跑 */
app_nvm_mark_dirty();
#if BOARD_LINKAGE_ENABLE
s_lk.voided = 0; /* 10.5:清手动作废标志 */
app_linkage_reeval(); /* 该开开、该关关 */
#endif
log_info("linkage_config stored\n");
}
/**
* @brief 手动作废本轮联动
* @return 无
*/
void app_linkage_void(void)
{
#if !BOARD_LINKAGE_ENABLE
return;
#endif
if (!s_lk.voided) {
s_lk.voided = 1;
log_info("linkage voided by manual\n");
}
}
/**
* @brief 当前配置是否有效(第 10.1 节)
* @return 1=有效
*/
uint8_t app_linkage_is_valid(void)
{
#if !BOARD_LINKAGE_ENABLE
return 0;
#endif
LinkageCfg_t cfg;
return lk_parse(gDevData.linkage_config, &cfg);
}
/**
* @brief 缸体编号(Byte0-1 大端)
* @note 只读,不做有效性判定,不受 BOARD_LINKAGE_ENABLE 影响——
* 引擎关掉时 DP6 依然由 APP 下发并存在 gDevData 里。
* @return 缸体编号,0=未配
*/
uint16_t app_linkage_get_tank_id(void)
{
return (uint16_t)(((uint16_t)gDevData.linkage_config[0] << 8) |
gDevData.linkage_config[1]);
}
/**
* @brief 联动配置版本(Byte6
* @return 配置版本
*/
uint8_t app_linkage_get_cfg_ver(void)
{
return gDevData.linkage_config[6];
}