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

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