310 lines
8.7 KiB
C
310 lines
8.7 KiB
C
/******************************************************************************
|
||
* @file app_linkage.c
|
||
* @brief CBR2 联动引擎(规格书第 11 章 + 第 13 章仲裁):只看湿度回差
|
||
* @author cyWu <1917507415@qq.com>
|
||
* @date 2026.09.09
|
||
* @version V1.0.0, 首次发布
|
||
*
|
||
* @note 开停只看湿度(11.3,uint8 %;与加热垫温度回差方向相反——过干开):
|
||
* humi < Hmin → 联动循环喷:空闲则用 DP7 参数开一轮,一轮正常
|
||
* 结束后(仲裁收口 reeval)再判再开,无额外间隔
|
||
* humi > Hmax → 联动占着则立刻停轮(req_stop,与谁开的无关)
|
||
* [Hmin,Hmax](含等于)→ 保持上一态
|
||
* 湿度源(8 章):BLE 已连接 = 服务器 DP6;断连已配对 = 扫描
|
||
* JBao-{tank};丢包保持上一态。无任何有效湿度 → 联动不改泵。
|
||
*
|
||
* 手动作废(13 章,RAM 不落 VM):PB1 停 / DP1=0 / DP7 抢权触发;
|
||
* 须先走到「关」条件(H>Hmax)再重新出现 H<Hmin 才允许再开;
|
||
* 重写 DP9 清手动作废并立刻重判(11.5)。
|
||
*
|
||
* ⚠ 判定只在 usr_timer_loop(500ms 任务上下文)与 DP6/9 写路径
|
||
* 执行,不提供 20ms 中断接口。req_* 只置字节,与 ISR 无竞争。
|
||
******************************************************************************/
|
||
|
||
#include <string.h>
|
||
|
||
#include "system/includes.h"
|
||
#include "app_linkage.h"
|
||
#include "app_spray.h"
|
||
#include "app_timer_task.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 配置解析结果(规格书 11.2;温度/版本/预留只存不用) */
|
||
typedef struct {
|
||
uint16_t tank_id; /**< Byte0-1 大端,0=无效 */
|
||
uint8_t hmin; /**< Byte4 % */
|
||
uint8_t hmax; /**< Byte5 % */
|
||
} LinkageCfg_t;
|
||
|
||
/** 联动运行时(RAM;湿度与作废标志均不落 VM) */
|
||
typedef struct {
|
||
uint8_t humi; /**< 联动用湿度 % */
|
||
uint8_t humi_valid; /**< 1=收到过任意来源的有效湿度 */
|
||
uint8_t voided; /**< 1=被手动作废,须先到「关」条件再武装 */
|
||
} LinkageCtx_t;
|
||
|
||
static LinkageCtx_t s_lk;
|
||
|
||
/**
|
||
* @brief 解析 20B 配置并判定有效性(11.1:缸体≠0 且 Hmin≤Hmax)
|
||
* @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->hmin = c[4];
|
||
out->hmax = c[5];
|
||
if (out->tank_id == 0) {
|
||
return 0;
|
||
}
|
||
if (out->hmin > out->hmax) {
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* @brief 联动让出泵:仅当联动正占着作业时才停(手动/计划占着不动)
|
||
* @return 无
|
||
*/
|
||
static void lk_stop(void)
|
||
{
|
||
if (app_spray_is_running() && (app_spray_owner() == APP_SPRAY_OWNER_LINKAGE)) {
|
||
app_spray_req_stop();
|
||
log_info("linkage stop\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 核心判定(幂等,11.3 + 13 章)
|
||
* @note 步骤:无效配置 → 联动占着则停;无有效湿度 → 不动;
|
||
* 走到「关」条件清作废(再武装);作废期间不动作;
|
||
* 计划占用中或队列待跑时不插嘴;否则按回差循环开/停。
|
||
* @return 无
|
||
*/
|
||
static void lk_reeval(void)
|
||
{
|
||
LinkageCfg_t cfg;
|
||
uint8_t valid = lk_parse(gDevData.linkage_config, &cfg);
|
||
uint8_t dry; /* H < Hmin:过干,开条件 */
|
||
uint8_t wet; /* H > Hmax:过湿,关条件 */
|
||
|
||
/* 11.1:无效配置(缸体 0 / min>max)→ 不自动开;联动占着则停掉 */
|
||
if (!valid) {
|
||
lk_stop();
|
||
return;
|
||
}
|
||
|
||
/* 11.2:尚无任何有效湿度 → 联动不改泵(保持上电关/上一态) */
|
||
if (!s_lk.humi_valid) {
|
||
return;
|
||
}
|
||
|
||
dry = (s_lk.humi < cfg.hmin) ? 1u : 0u;
|
||
wet = (s_lk.humi > cfg.hmax) ? 1u : 0u;
|
||
|
||
/* 13 章再武装:先走到「关」条件(H>Hmax)→ 清作废;
|
||
* 之后重新出现 H<Hmin 才允许再开。等于 Hmin/Hmax 都不算越界 */
|
||
if (wet) {
|
||
if (s_lk.voided) {
|
||
log_info("linkage rearmed (H>Hmax reached)\n");
|
||
}
|
||
s_lk.voided = 0;
|
||
}
|
||
if (s_lk.voided) {
|
||
return; /* 作废期间保持上一态,不动作 */
|
||
}
|
||
|
||
/* 13 章仲裁:计划轮占着(owner==PLAN)或队列待跑时联动不开不关,
|
||
* 计划收口后由 app_arbiter 调 reeval 补判 */
|
||
if (app_spray_owner() == APP_SPRAY_OWNER_PLAN) {
|
||
return;
|
||
}
|
||
if (app_timer_has_pending()) {
|
||
return;
|
||
}
|
||
|
||
if (dry) {
|
||
/* 11.3 开:H<Hmin 过干 → 空闲则用 DP7 参数开一轮(循环由收口续) */
|
||
if (app_spray_owner() == APP_SPRAY_OWNER_NONE) {
|
||
app_spray_req_start_link();
|
||
log_info("linkage open (dry, H=%u<%u)\n", s_lk.humi, cfg.hmin);
|
||
}
|
||
} else if (wet) {
|
||
/* 11.3 过湿停:走到 H>Hmax 一律停联动的轮;手动/计划占着不插嘴 */
|
||
if (app_spray_is_running() && (app_spray_owner() == APP_SPRAY_OWNER_LINKAGE)) {
|
||
app_spray_req_stop();
|
||
log_info("linkage stop (wet, H=%u>%u)\n", s_lk.humi, cfg.hmax);
|
||
}
|
||
}
|
||
/* 否则 [Hmin,Hmax](含等于):保持上一态(回差) */
|
||
}
|
||
|
||
/**
|
||
* @brief 立刻按第 11.3 节重判一次(DP9 重写 / 计划轮收口后调用)
|
||
* @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 DP8 联动删除写 1(脉冲):清 RAM+VM 20B;联动占着则停
|
||
* @note gDevData.linkage_delete 由调用方清回 0(脉冲)
|
||
* @return 无
|
||
*/
|
||
void app_linkage_delete(void)
|
||
{
|
||
memset(gDevData.linkage_config, 0, 20);
|
||
app_nvm_mark_dirty();
|
||
#if BOARD_LINKAGE_ENABLE
|
||
s_lk.voided = 0;
|
||
lk_stop(); /* 11.4:若联动占着作业 → 停 */
|
||
#endif
|
||
log_info("linkage config deleted\n");
|
||
}
|
||
|
||
/**
|
||
* @brief APP 重写 DP9(memcpy 已完成):存 VM + 清手动作废 + 立刻重判
|
||
* @return 无
|
||
*/
|
||
void app_linkage_set_config(void)
|
||
{
|
||
app_nvm_mark_dirty();
|
||
#if BOARD_LINKAGE_ENABLE
|
||
s_lk.voided = 0; /* 11.5:清手动作废标志 */
|
||
lk_reeval(); /* 立刻按新阈值重判(有有效湿度时) */
|
||
#endif
|
||
log_info("linkage_config stored\n");
|
||
}
|
||
|
||
/**
|
||
* @brief 手动作废联动轮(PB1 开/停、DP1、DP7 抢权;RAM 不写 VM)
|
||
* @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 配置是否有效(11.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 湿度(uint8 %)→ 联动湿度
|
||
* @return 无
|
||
*/
|
||
void app_linkage_set_connected_humi(uint8_t humi)
|
||
{
|
||
if (s_lk.humi_valid && (s_lk.humi == humi)) {
|
||
return;
|
||
}
|
||
s_lk.humi = humi;
|
||
s_lk.humi_valid = 1;
|
||
log_info("linkage humi from dp6: %u%%\n", (unsigned)humi);
|
||
#if BOARD_LINKAGE_ENABLE
|
||
lk_reeval(); /* 湿度边沿立刻判,不等下一个 500ms */
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 断连扫描:匹配到 JBao-{tank} 广播 → 联动湿度
|
||
* @return 无
|
||
*/
|
||
void app_linkage_set_scan_humi(uint8_t humi)
|
||
{
|
||
if (s_lk.humi_valid && (s_lk.humi == humi)) {
|
||
return;
|
||
}
|
||
s_lk.humi = humi;
|
||
s_lk.humi_valid = 1;
|
||
log_info("linkage humi from scan: %u%%\n", (unsigned)humi);
|
||
#if BOARD_LINKAGE_ENABLE
|
||
lk_reeval();
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 是否已有任何有效湿度
|
||
* @return 1=有
|
||
*/
|
||
uint8_t app_linkage_has_humi(void)
|
||
{
|
||
return s_lk.humi_valid;
|
||
}
|