空调温度上下限和生效解除逻辑优化

parent b8c4706b
......@@ -10,3 +10,10 @@
*.logs
*.zip
/dist
# 衍生/临时文件
/tmp
# 参考文档
/res/青岛啤酒博物馆空调智能化实施方案20260506.pptx
/res/飞奕云平台接口文档V1.3-计费.pdf
......@@ -592,8 +592,8 @@ async function integrateAcDeviceData() {
fanSpeed: FAN_SPEED_MAP[item.fanSpeed] || '自动',
setTemp: item.tempSet || 0,
roomTemp: item.roomTemp || 0,
maxTemp: item.tempSetHi || 0,
minTemp: item.tempSetLo || 0,
maxTemp: item.tempSetHi || 28, // 飞奕无数据时默认上限28℃
minTemp: item.tempSetLo || 16, // 飞奕无数据时默认下限16℃
autoControl: '生效',
};
toInsert.push({
......
......@@ -9,6 +9,12 @@ import { getRegionList } from "./region";
import { startSchedule, stopSchedule } from "../config/schedule";
/**
* 联动去重缓存:记录每个区域上次下发的控制状态,避免重复调用第三方接口
* key: regionKey, value: 上次下发的控制参数
*/
const envControlCache: Map<string, { tempSet?: number; workMode?: number; fanSpeed?: number; onOff?: number; maxTemp?: number; minTemp?: number; co2Action?: string }> = new Map();
/**
* 将 Date 对象格式化为本地时间字符串 'YYYY-MM-DD HH:mm:ss'
* 替代 toISOString() 避免 UTC 时区偏移问题
*/
......@@ -19,8 +25,8 @@ function formatLocalTime(date: Date): string {
/**
* 智能管控-运行分析
* @param regionGroup 区域组(可选),如“A馆”,如果提供则返回该类型下所有区域的分析数据
* @param regionType 区域类型(可选),如“1F”,如果提供则返回该区域的分析数据
* @param regionGroup 区域组(可选),如"A馆",如果提供则返回该类型下所有区域的分析数据
* @param regionType 区域类型(可选),如"1F",如果提供则返回该区域的分析数据
* @returns 大屏所需的所有指标数据
*/
export async function getRunAnalysis(regionGroup: string, regionType: string) {
......@@ -749,8 +755,8 @@ export async function controlAcRunning(params: {}) {
let mode = params["mode"];
let power = params["power"];
let setTemp = params["setTemp"];
let maxTemp = params["maxTemp"];
let minTemp = params["minTemp"];
let maxTemp = params["maxTemp"] || 28; // 默认锁定温度上限
let minTemp = params["minTemp"] || 16; // 默认锁定温度下限
let autoControl = params["autoControl"];
let fanSpeed = params["fanSpeed"];
......@@ -775,7 +781,7 @@ export async function controlAcRunning(params: {}) {
"tempSetLock": acDeviceKeyMap[autoControl],
"tempSetLo": Number(minTemp),
"tempSetHi": Number(maxTemp),
"openApiAction": autoControl === "解除" ? "control" : "lock"
"openApiAction": autoControl === "生效" ? "control" : "lock"
};
return await controlIndoorUnit(acPrarms);
}
......@@ -827,17 +833,21 @@ function getSeason(): 'summer' | 'winter' {
}
// 根据IAQ温度计算空调设定温度
function calcAcTempSet(temperature: number, season: 'summer' | 'winter'): number {
/**
* 根据文档 Slide 39 三段式温度段,计算联动控制参数
* 返回:目标温度、风速、开关状态
*/
function calcAcTempSet(temperature: number, season: 'summer' | 'winter'): { tempSet: number; fanSpeed: number; onOff: number } {
if (season === 'summer') {
// 夏季制冷:温度越高,设定越低
if (temperature > 26) return 22;
if (temperature >= 22) return 24;
return 26;
if (temperature > 26) return { tempSet: 22, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制冷
if (temperature >= 22) return { tempSet: 24, fanSpeed: 4, onOff: Math.round(temperature) === 24 ? 0 : 1 }; // 舒适区:四舍五入24℃待机
return { tempSet: 26, fanSpeed: 4, onOff: 1 }; // 已达需求:低风维持
} else {
// 冬季制热:温度越低,设定越高
if (temperature > 21) return 18;
if (temperature >= 18) return 20;
return 21;
if (temperature < 18) return { tempSet: 21, fanSpeed: 1, onOff: 1 }; // 未达需求:高风制热
if (temperature <= 21) return { tempSet: 20, fanSpeed: 4, onOff: Math.round(temperature) === 20 ? 0 : 1 }; // 舒适区:四舍五入20℃待机
return { tempSet: 18, fanSpeed: 4, onOff: 1 }; // 已达需求:低风维持
}
}
......@@ -874,11 +884,37 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
}, ["id", "device_name", "device_id", "device_ad", "control_params"]);
if (acDevices.data.length > 0) {
const tempSet = calcAcTempSet(temperature, season);
const { tempSet, fanSpeed, onOff } = calcAcTempSet(temperature, season);
const workMode = season === 'summer' ? 1 : 2; // 1制冷 2制热
const modeName = season === 'summer' ? '制冷' : '制热';
let controlRes = await controlAcRunningApi(acDevices, workMode, tempSet);
// 取空调设备数据信息中的温度上下限
let maxTemp: number | undefined;
let minTemp: number | undefined;
const acDeviceIds = acDevices.data.map((d: any) => d.device_id);
if (acDeviceIds.length) {
const acDataRes = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": acDeviceIds },
"%orderDesc%": "device_time",
"%limit%": 1
}, ["data"]);
if (acDataRes.data.length) {
const acData = acDataRes.data[0].data;
maxTemp = acData.maxTemp || 28;
minTemp = acData.minTemp || 16;
}
}
// 去重:若本次计算的控制参数与上次相同,跳过调用
const cacheKey = `ac_${regionKey}`;
const lastState = envControlCache.get(cacheKey);
if (lastState && lastState.tempSet === tempSet && lastState.workMode === workMode && lastState.fanSpeed === fanSpeed && lastState.onOff === onOff && lastState.maxTemp === maxTemp && lastState.minTemp === minTemp) {
console.log(`空调联动跳过(控制参数未变): ${modeName}模式, 设定${tempSet}℃, 风速${fanSpeed === 1 ? '高' : '低'}风, ${onOff ? '开机' : '待机'}, 区域 ${regionKey}`);
} else {
let controlRes = await controlAcRunningApi(acDevices, workMode, tempSet, fanSpeed, onOff, maxTemp, minTemp);
// 更新缓存
envControlCache.set(cacheKey, { tempSet, workMode, fanSpeed, onOff, maxTemp, minTemp });
// 空调日志
const acLogs = acDevices.data.map((ac: any) => ({
......@@ -908,6 +944,7 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
}
}
}
}
// ===== 新风联动 =====
if (co2 != null) {
......@@ -919,8 +956,17 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
}, ["id", "device_name", "device_id", "device_ad"]);
if (fanDevices.data.length > 0) {
// 去重:若本次新风动作与上次相同,跳过调用
const fanCacheKey = `fan_${regionKey}`;
const lastFanState = envControlCache.get(fanCacheKey);
if (lastFanState && lastFanState.co2Action === fanParams.action) {
console.log(`新风联动跳过(控制参数未变): CO2 ${co2}ppm, 动作 ${fanParams.action}, 区域 ${regionKey}`);
} else {
let fanControlRes = await controlFanRunningApi(fanDevices, fanParams);
// 更新缓存
envControlCache.set(fanCacheKey, { co2Action: fanParams.action });
const fanLogs = fanDevices.data.map((fan: any) => ({
device_id: fan.device_id,
device_type: '新风',
......@@ -948,10 +994,11 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
}
}
}
}
}
// 辅助:接入空调控制API,根据温度设定值启停空调设备
async function controlAcRunningApi(acDevices?: any, workMode?: number, tempSet?: number) {
async function controlAcRunningApi(acDevices?: any, workMode?: number, tempSet?: number, fanSpeed?: number, onOff?: number, maxTemp?: number, minTemp?: number) {
let deviceList: any = [];
acDevices.data.forEach((ac: any) => {
deviceList.push(ac.device_id);
......@@ -963,14 +1010,21 @@ async function controlAcRunningApi(acDevices?: any, workMode?: number, tempSet?:
let params: any = {
"indoorUnitAddressFull": deviceList,
"workMode": workMode,
"onOff": 1, // 联动时自动开机
"fanSpeed": 2, // 默认中风
"onOff": onOff ?? 1, // 联动时按温差动态判断,默认开机兜底
"fanSpeed": fanSpeed ?? 2, // 联动时按温差动态计算,默认中风兜底
"openApiAction": "control",
};
// 如果传入了设定温度,一并下发
if (tempSet != null) {
params.tempSet = tempSet;
}
// 下发温度锁定上下限(取自设备数据信息)
if (maxTemp != null) {
params.tempSetHi = maxTemp;
}
if (minTemp != null) {
params.tempSetLo = minTemp;
}
let res = await controlIndoorUnit(params);
if (res.success) {
......@@ -1039,7 +1093,7 @@ export async function stopScheduleTask() {
* 1 制冷 2 制热 3 送风 4 除湿
* 0 关-off 1 开-on
* 1 高风-超强 2 中风-强 4 低风-弱
* 0 不锁定-解除 1 锁定-生效
* 0 不锁定-生效 1 锁定-解除
*/
let acDeviceKeyMap: { [key: string]: number } = {
"制冷": 1,
......@@ -1051,8 +1105,8 @@ let acDeviceKeyMap: { [key: string]: number } = {
"超强": 1,
"强": 2,
"弱": 4,
"解除": 0,
"生效": 1
"解除": 1,
"生效": 0
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment