Files

316 lines
9.3 KiB
C
Raw Permalink 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 CBR0 联动引擎(规格书第 10 章 + 第 12 章仲裁):只看温度回差
* @author cyWu <1917507415@qq.com>
* @date 2026.09.04
* @version V1.0.1
* @history
* - V1.0.1, 2026.09.04, cyWu, 过热关不再限 owner==LINKAGEVM 恢复/手动开的垫
* 过热时也能关(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 已连接 = 服务器 DP6set_connected_temp);
* 断连已配对 = 扫描 JBao-{tank}set_scan_temp);丢包保持上一态。
* 无任何有效温度 → 联动不改继电器(10.2)。
*
* 手动作废(12 章,RAM 不落 VM):须先走到「关」条件(T>Tmax)
* 再重新出现 T<Tmin 才允许联动再开;重写 DP11 清手动作废并立刻重判。
*
* ⚠ 判定只在 usr_timer_loop500ms 任务上下文)与 DP11 写路径执行,
* 不提供 20ms 中断接口。温度 setter 由 jb_productDP6)与
* 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 "[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 ℃ */
} LinkageCfg_t;
/** 联动运行时(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 初始化联动引擎
* @return 无
*/
void app_linkage_init(void)
{
#if !BOARD_LINKAGE_ENABLE
log_info("linkage disabled\n");
return;
#endif
memset(&s_lk, 0, sizeof(s_lk));
log_info("linkage init\n");
}
/**
* @brief 核心判定(幂等,10.3 + 12 章)
* @note 步骤:无效配置 → 联动占着则关;无有效温度 → 不动;
* 走到「关」条件清作废(再武装);作废期间不动作;
* 仲裁定时窗占用时不插嘴;否则按回差开/关。
* @return 无
*/
static void lk_reeval(void)
{
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 立刻按第 10.3 节重判一次(DP11 重写 / 定时窗收口后调用)
* @return 无
*/
void app_linkage_reeval(void)
{
#if !BOARD_LINKAGE_ENABLE
return;
#endif
lk_reeval();
}
/**
* @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)
{
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 重写 DP11memcpy 已完成):存 VM + 清手动作废 + 立刻重判
* @note 引擎关掉也落盘;重判只在 BOARD_LINKAGE_ENABLE=1 时执行(10.5
* @return 无
*/
void app_linkage_set_config(void)
{
app_nvm_mark_dirty();
#if BOARD_LINKAGE_ENABLE
s_lk.voided = 0; /* 10.5:清手动作废标志 */
lk_reeval(); /* 立刻按新阈值重判(有有效温度时) */
#endif
log_info("linkage_config stored\n");
}
/**
* @brief 手动作废本轮联动(短按/DP0 触发,仲裁器唯一转调点)
* @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)
{
LinkageCfg_t cfg;
return lk_parse(gDevData.linkage_config, &cfg);
}
/**
* @brief 缸体编号(Byte0-1 大端;引擎关掉也可读,供扫描匹配用)
* @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];
}
/**
* @brief BLE 已连接:服务器 DP6 温度(℃ float)→ 联动温度
* @param degc 服务器下发物理量(floatscale=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;
}