通过环境监测控制空调并存日志

parent 37602267
......@@ -390,6 +390,9 @@ async function integrateAcDevices() {
continue;
}
let deviceType = '空调';
let deviceName = '空调内机';
// 查找对应的 region_key
let regionKey = 0;
if (item.roomId) {
......@@ -398,6 +401,11 @@ async function integrateAcDevices() {
const region = await regionModel.findOne({ where: { room_id: item.roomId } });
if (region) {
regionKey = region.id;
// 如果区域地址包含新风,则空调视为新风机
if (region.name && region.name.includes('新风')) {
deviceType = '新风';
deviceName = '新风机';
}
}
}
}
......@@ -407,8 +415,8 @@ async function integrateAcDevices() {
device_id: deviceId,
device_ad: item.indoorUnitId || '',
region_key: regionKey,
device_type: '空调',
device_name: `空调内机-${deviceId}`,
device_type: deviceType,
device_name: `${deviceName}-${deviceId}`,
control_params: AC_CONTROL_PARAMS,
});
insertCount++;
......@@ -715,14 +723,14 @@ async function integrateMeterDeviceData() {
* 数据集成-九合一环境设备数据集成
*/
export async function airDeviceData() {
// TODO: 暂无对应接口
// 暂无对应接口 -> MQTT
}
/**
* 数据集成-客流监测设备数据集成
*/
export async function customerDeviceData() {
// TODO: 暂无对应接口
// 暂无对应接口 -> MQTT
}
/**
......
import { TABLENAME } from "../config/dbEnum";
import { addData } from "../data/addData";
import { selectOneDataByParam } from "../data/findData";
import { controlAcByEnvironment } from "./running";
/**
* 处理接收到的单条设备数据,并存入数据库
* 处理接收到的环境设备数据,并存入数据库
*
* 消息示例:
* {
......@@ -87,6 +88,9 @@ export async function processDeviceData(message) {
);
console.log(`数据入库成功: 设备 ${devEUI}`);
// 调用空调控制
await controlAcByEnvironment(devEUI, data);
} catch (error) {
console.error('数据处理失败:', error);
......
......@@ -2,6 +2,7 @@ import { fail } from "assert";
import { TABLENAME } from "../config/dbEnum";
import { ERRORENUM } from "../config/errorEnum";
import { selectOneDataByParam, selectDataListByParam } from "../data/findData";
import { addData } from "../data/addData";
import { BizError } from "../util/bizError";
import { controlIndoorUnit } from "./feiyiClient";
import { getRegionList } from "./region";
......@@ -29,6 +30,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 根据区域类型和名称筛选设备ID列表(如果提供了区域信息)
let regionParam: any = { groups: regionGroup };
if (regionType) {
// regionParam.type = regionTypeKeyMap[regionType];
regionParam.type = regionType;
}
let regionInfo: any = await selectDataListByParam(TABLENAME.区域表, regionParam, ["id"]);
......@@ -100,6 +102,38 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let lastYearStart = new Date(nowTime.getFullYear() - 1, nowTime.getMonth(), 1);
let monthlyEnergyYear = await getEnergyTrend(energyDeviceIds, lastYearStart, nowTime, 'month');
// 补全 x 轴坐标的工具函数
function padTrend(trend: any[], allKeys: string[]): any[] {
const map = new Map(trend.map((item: any) => [item.key, item]));
return allKeys.map(key => map.get(key) || { key, value: "0" });
}
// 过去7天 x 轴补全
const last7DaysKeys: string[] = [];
for (let i = 6; i >= 0; i--) {
const d = new Date(nowTime);
d.setDate(d.getDate() - i);
last7DaysKeys.push(`${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`);
}
dailyEnergy7 = padTrend(dailyEnergy7, last7DaysKeys);
// 过去30天 x 轴补全
const last30DaysKeys: string[] = [];
for (let i = 29; i >= 0; i--) {
const d = new Date(nowTime);
d.setDate(d.getDate() - i);
last30DaysKeys.push(`${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`);
}
dailyEnergy30 = padTrend(dailyEnergy30, last30DaysKeys);
// 过去一年(按月)x 轴补全
const lastYearKeys: string[] = [];
for (let i = 11; i >= 0; i--) {
const d = new Date(nowTime.getFullYear(), nowTime.getMonth() - i, 1);
lastYearKeys.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`);
}
monthlyEnergyYear = padTrend(monthlyEnergyYear, lastYearKeys);
// 1.4. 能耗监控数据和能耗监控趋势图数据
resultAny.energyManagement = {
todayElectricity: todayEnergy.toFixed(0),
......@@ -120,6 +154,10 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
lastYear: monthlyEnergyYear
};
// 1.5. 过去24时运行能耗
let last24hRunningEnergy = hourlyEnergy.reduce((sum: number, item: any) => sum + Number(item.value), 0);
// 4.1 环境监测九合一
let airParams: any = {device_type: "环境监测" };
if (regionGroup) {
......@@ -129,7 +167,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let airDeviceIds = airQualityDevices.data.map(d => d.device_id);
let qualityIndex = 300;
// 今日环境质量
// 今日环境质量:{co2:660,hcho:0.01,humidity:70.5,light_level:0,pir:1,pm10:33,pm2_5:31,pressure:1011.7,temperature:21.6,tvoc:1}
let todayEnvironmental = '优秀';
let currentCo2 = 0; // 当前二氧化氮
......@@ -142,6 +180,19 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let pm25Trend = [];
let co2TrendDetail = [];
let currentHcho = 0; // 当前甲醛浓度
let hchoTrend = [];
let currentLightLevel = 0; // 当前光照强度
let lightLevelTrend = [];
let currentPir = ''; // 当前红外感应
let pirTrend = [];
let currentPm10 = 0; // 当前PM10
let pm10Trend = [];
let currentPressure = 0; // 当前大气压强
let pressureTrend = [];
let currentTvoc = 0; // 当前有机物浓度
let tvocTrend = [];
if (airDeviceIds.length) {
// 最新数据
let latestAir = await selectDataListByParam(TABLENAME.设备数据表, {
......@@ -153,9 +204,15 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let airData = latestAir.data[0].data;
currentTemperature = `${airData.temperature ?? 0}`;
currentHumidity = `${airData.humidity ?? 0}%`;
currentPm25 = airData.pm25;
currentPm25 = airData.pm2_5??0;
currentCo2 = airData.co2 ?? 0;
qualityIndex = 500 - (airData.pm25 ?? 0);
qualityIndex = 500 - (airData.pm2_5 ?? 0);
currentHcho = airData.hcho??0;
currentLightLevel = airData.light_level??0;
currentPm10 = airData.pm10??0;
currentPressure = airData.pressure??0;
currentTvoc = airData.tvoc??0;
}
// 过去24小时趋势
let last24h = new Date(nowTime.getTime() - 24*3600000);
......@@ -169,7 +226,8 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let hourKey = `${d.getHours()}`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { temp: data.temperature, hum: data.humidity, pm: data.pm25, co2: data.co2 });
hourlyData.set(hourKey, { temp: data.temperature, hum: data.humidity, pm: data.pm2_5, co2: data.co2
, hcho: data.hcho, lightLevel: data.light_level, pm10: data.pm10, pressure: data.pressure, tvoc: data.tvoc });
}
}
for (let i = 0; i < 24; i++) {
......@@ -181,6 +239,12 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let co2Val = val?.co2 ?? 0;
co2TrendDetail.push({ time: `${i}:00`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `${i}时`, value: co2Val.toString() });
hchoTrend.push({ time: `${i}:00`, value: val?.hcho?.toString() ?? '0' });
lightLevelTrend.push({ time: `${i}:00`, value: val?.lightLevel?.toString() ?? '0' });
pm10Trend.push({ time: `${i}:00`, value: val?.pm10?.toString() ?? '0' });
pressureTrend.push({ time: `${i}:00`, value: val?.pressure?.toString() ?? '0' });
tvocTrend.push({ time: `${i}:00`, value: val?.tvoc?.toString() ?? '0' });
}
}
......@@ -208,15 +272,26 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
humidityTrend: humidityTrend,
pm25Trend: pm25Trend,
co2Trend: co2Trend,
qualityIndex: qualityIndex
qualityIndex: qualityIndex,
currentHcho: currentHcho,
currentLightLevel: currentLightLevel,
currentPm10: currentPm10,
currentPressure: currentPressure,
currentTvoc: currentTvoc,
hchoTrend: hchoTrend,
lightLevelTrend: lightLevelTrend,
pm10Trend: pm10Trend,
pressureTrend: pressureTrend,
tvocTrend: tvocTrend
};
// 5.1 预警监控和预警工单处理进度分析
let alertStatus = {
totalAlerts: 48,
resolved: 20,
responded: 24,
unresolved: 4
totalAlerts: 3,
resolved: 1,
responded: 1,
unresolved: 1
};
let alertWorkOrders = [
{ riskContent: "空调压缩机异常", alertTime: "26/02/01", status: "已解决" },
......@@ -229,13 +304,13 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
{ riskContent: "湿度超标", alertTime: "26/02/02", alertAddress: "A馆1F走廊", device_id: "2-4-0-0", faultCode: "13", status: "未处理" }
];
let alertWorkLevel = {
"一级预警": 10,
"二级预警": 20,
"三级预警": 18
"一级预警": 1,
"二级预警": 1,
"三级预警": 1
};
let alertWorkType = [{ count: 25, name: "设备故障", proportion: '52%' },
{ count: 15, name: "环境异常", proportion: '30%' },
{ count: 8, name: "安全事件", proportion: '18%' }];
let alertWorkType = [{ count: 1, name: "设备故障", proportion: '50%' },
{ count: 2, name: "环境异常", proportion: '50%' },
{ count: 0, name: "安全事件", proportion: '0%' }];
// 5.2. 预警监控数据
resultAny.alertManagement = {
alertStatus: alertStatus,
......@@ -288,12 +363,13 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 统计区域类型的客流量
for (let rec of customerDeviceDatas.data) {
let data = rec.data;
if (data.presence === '进') {
totalRegionCustomerNum += 1;
// if (data.presence === '进') {
if (data.current_total) {
totalRegionCustomerNum += data.current_total;
if (!regionCustomerMap.has(regionName)) {
regionCustomerMap.set(regionName, 1);
regionCustomerMap.set(regionName, data.current_total);
} else {
regionCustomerMap.set(regionName, totalNum + 1);
regionCustomerMap.set(regionName, totalNum + data.current_total);
}
}
}
......@@ -325,7 +401,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 获取过去24小时客流量数据
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
device_time: { "%gte%": formatLocalTime(last24hStart), "%lte%": formatLocalTime(nowTime) },
device_time: { "%gte%": formatLocalTime(todayStart), "%lte%": formatLocalTime(todayEnd) },
"%orderAsc%": "device_time"
}, ["device_id", "data", "device_time"]);
let hourlyCustomerMap = new Map();
......@@ -333,7 +409,8 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let d = new Date(rec.device_time);
let hourKey = `${d.getHours()}时`;
let data = rec.data;
let customerNum = data.presence === '进' ? 1 : 0;
// let customerNum = data.presence === '进' ? 1 : 0;
let customerNum = data.current_total;
if (!hourlyCustomerMap.has(hourKey)) {
hourlyCustomerMap.set(hourKey, customerNum);
} else {
......@@ -356,7 +433,8 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}, ["device_id", "data", "device_time"]);
for (let rec of yesterdayDatas.data) {
let data = rec.data;
let customerNum = data.presence === '进' ? 1 : 0;
// let customerNum = data.presence === '进' ? 1 : 0;
let customerNum = data.current_total;
yesterdayCustomer += customerNum;
}
}
......@@ -488,7 +566,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let hourKey = `${d.getHours()}时`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { airSupply: data.airSupply });
hourlyData.set(hourKey, { airSupply: airSupplyKeyMap[data.fanSpeed] });
}
}
// 3.2.2 填充24小时新风监控数据,没有在线数据的小时段则设为零,并计算每小时的新风送风量
......@@ -514,9 +592,10 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 运行监控总数据(一天)
resultAny.mainRunningManagement = {
totalCustomerNumber: 10000,
totalDeviceOnlineRate: 99.8,
totalElectricity: 360,
totalCustomerNumber: totalRegionCustomerNum,
totalDeviceOnlineRate: (acOnlineCount/acCount*100).toFixed(1),
// totalElectricity: last24hRunningEnergy.toFixed(0).slice(0, 5),
totalElectricity: last24hRunningEnergy.toFixed(0),
};
} else {
// 设备监控总数据
......@@ -528,6 +607,22 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let deviceCount = deviceIds.length;
let deviceOfflineCount = 0;
let deviceOnlineCount = deviceCount - deviceOfflineCount;
// 设备监测情况:在线/离线(根据最后数据时间是否超过30分钟)
let deviceLatestMap = new Map();
for (let devId of deviceIds) {
let rec = await selectDataListByParam(TABLENAME.设备数据表,
{ device_id: devId, "%orderDesc%": "device_time", "%limit%": 1 },
["device_time"]);
if (rec.data.length) {
let lastTime = new Date(rec.data[0].device_time);
let diffMinutes = (nowTime.getTime() - lastTime.getTime()) / 60000;
deviceLatestMap.set(devId, { lastTime, isOnline: diffMinutes <= 30 });
} else {
deviceLatestMap.set(devId, { lastTime: null, isOnline: false });
}
}
deviceOnlineCount = Array.from(deviceLatestMap.values()).filter(v => v.isOnline).length;
deviceOfflineCount = deviceCount - deviceOnlineCount;
let deviceFaultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ status: { "%ne%": 2 }, device_id: { "%in%": deviceIds } },
......@@ -740,6 +835,7 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
device_type: "环境监测"
}, ["id", "device_name", "region_key"]);
let controlRes:any = {};
// 若是环境设备,则解析环境设备数据,判断是否需要联动空调设备
if (envDevices.data.length) {
// 通过设备所属区域查询当前区域中的空调设备,获取空调设备标准参数中预设的温度、湿度阈值
......@@ -748,7 +844,51 @@ export async function controlAcByEnvironment(deviceId: string, data: any) {
region_key: regionKey,
device_type: "空调"
}, ["id", "device_name", "device_id", "device_ad", "control_params"]);
return await controlAcRunningApi(acDevices, data.temperature, "");
controlRes = await controlAcRunningApi(acDevices, data.temperature, "");
}
// 加入日志
const now = new Date();
const operationTime = now.toISOString().replace('T', ' ').slice(0, 19);
if (controlRes && controlRes.success) {
console.log(`调用空调控制成功: 设备 ${deviceId}`);
if (controlRes.devices) {
const logEntries = controlRes.devices.map((acDeviceId: string) => ({
device_id: acDeviceId,
device_type: '空调',
operation_content: {
option: '自动联动控制',
env_device_id: deviceId,
time: operationTime,
success: true,
message: controlRes.message || '控制成功',
},
status: 1,
created_at: now,
updated_at: now,
}));
await addData(TABLENAME.设备日志表, logEntries);
}
} else if (controlRes && !controlRes.success) {
console.log(`调用空调控制失败: 设备 ${deviceId} 模式 ${controlRes.message}`);
if (controlRes.devices) {
const logEntries = controlRes.devices.map((acDeviceId: string) => ({
device_id: acDeviceId,
device_type: '空调',
operation_content: {
option: '自动联动控制',
env_device_id: deviceId,
time: operationTime,
success: false,
message: controlRes.message || '控制失败',
},
status: 0,
created_at: now,
updated_at: now,
}));
await addData(TABLENAME.设备日志表, logEntries);
}
} else {
console.log(`未调用空调控制: 设备 ${deviceId}`);
}
}
......@@ -766,24 +906,25 @@ async function controlAcRunningApi(acDevices?: any, temperature?: number, worker
// 调用空调控制接口启停空调设备
console.log(`联动空调 ${ac.device_name}${ac.id})启停,当前温度:${temperature},阈值范围:${minTemp}-${maxTemp}`);
// 单个接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
deviceMaxList.push(ac.device_ad);
deviceMaxList.push(ac.device_id);
workModeMap[1] = deviceMaxList;
openApiAction = "control";
} else if (temperature && temperature < minTemp) {
console.log(`联动空调 ${ac.device_name}${ac.id})启停,当前温度:${temperature},阈值范围:${minTemp}-${maxTemp}`);
deviceMinList.push(ac.device_ad);
deviceMinList.push(ac.device_id);
workModeMap[2] = deviceMinList;
openApiAction = "control";
}
});
// 多个设备: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
let controlResults = { "success": true, "message": "成功" };
let controlResults:any = { "success": false, "message": "失败" };
if (workModeMap[1]) {
// 处理制冷模式
let coldRes = await controlIndoorUnit({ "indoorUnitAddressFull": workModeMap[1], "workMode": 1, "openApiAction": openApiAction })
if (!coldRes.success) {
console.error("控制空调设备制冷失败", coldRes);
controlResults.success = false;
controlResults.devices = workModeMap[1];
controlResults.message = coldRes.data || "控制空调设备制冷失败";
}
}
......@@ -793,6 +934,7 @@ async function controlAcRunningApi(acDevices?: any, temperature?: number, worker
if (!hotRes.success) {
console.error("控制空调设备制热失败", hotRes);
controlResults.success = false;
controlResults.devices = workModeMap[2];
controlResults.message = hotRes.data || "控制空调设备制热失败";
}
}
......@@ -833,4 +975,30 @@ let acDeviceKeyMap: { [key: string]: number } = {
"弱": 4,
"解除": 0,
"生效": 1
}
\ No newline at end of file
}
/**
* 区域楼层到Key的映射
*/
let regionTypeKeyMap: { [key: string]: string } = {
"1F": "一层",
"2F": "一层",
"3F": "一层"
}
/**
* 风速等级到送风量的映射
*/
let airSupplyKeyMap: { [key: string]: number } = {
"自动": 900,
"弱": 876,
"强": 1068,
"超强": 1272
}
......@@ -7,7 +7,8 @@ export enum TABLENAME {
设备数据表 = 'device_data',
区域表 = 'region',
设备故障表 = 'device_fault',
用户信息表 = 'user_info'
用户信息表 = 'user_info',
设备日志表 = 'device_logs'
};
/**
......
......@@ -278,5 +278,53 @@ export const TablesConfig = [
{ type: "hasMany", target: "device", foreignKey: "region_key" }
]
},
// 设备日志表
{
tableNameCn: '设备日志表',
tableName: 'device_logs',
schema: {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
comment: '自增主键'
},
device_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备标识'
},
device_type: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备类型,如“空调”“出风口”'
},
operation_content: {
type: DataTypes.JSON,
allowNull: true,
comment: '操作内容{"option":"on","time":"2026-06-26 12:00:00"}'
},
status: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '操作状态,0否1是'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '更新时间'
}
},
association: [
]
},
];
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