环境监测和运行分析优化

parent ffe69e79
...@@ -1084,7 +1084,7 @@ ...@@ -1084,7 +1084,7 @@
if (!largeChart || largeChart.isDisposed()) return; if (!largeChart || largeChart.isDisposed()) return;
const ind = getInd(key); const ind = getInd(key);
if (!ind) return; if (!ind) return;
largeChart.setOption(createGaugeOption(ind, true), true); largeChart.setOption(createGaugeOption(ind, true));
} }
function destroySmallCharts() { function destroySmallCharts() {
...@@ -1145,7 +1145,7 @@ ...@@ -1145,7 +1145,7 @@
smallIndicators.value.forEach(ind => { smallIndicators.value.forEach(ind => {
const existing = smallChartMap[ind.key]; const existing = smallChartMap[ind.key];
if (existing && !existing.isDisposed()) { if (existing && !existing.isDisposed()) {
existing.setOption(createGaugeOption(ind, false), true); existing.setOption(createGaugeOption(ind, false));
} else { } else {
const dom = document.getElementById('gauge-' + ind.key); const dom = document.getElementById('gauge-' + ind.key);
if (dom) { if (dom) {
...@@ -1169,6 +1169,7 @@ ...@@ -1169,6 +1169,7 @@
// ======== 定时轮询 ======== // ======== 定时轮询 ========
const POLL_INTERVAL = 30 * 1000; // 30 秒刷新一次 const POLL_INTERVAL = 30 * 1000; // 30 秒刷新一次
let pollTimer = null; let pollTimer = null;
let polling = false; // 并发锁:上一次请求未完成则跳过本次
// 轻量刷新:只 setOption 更新数据,不销毁重建图表 // 轻量刷新:只 setOption 更新数据,不销毁重建图表
function refreshCharts() { function refreshCharts() {
...@@ -1179,7 +1180,7 @@ ...@@ -1179,7 +1180,7 @@
smallIndicators.value.forEach(ind => { smallIndicators.value.forEach(ind => {
const chart = smallChartMap[ind.key]; const chart = smallChartMap[ind.key];
if (chart && !chart.isDisposed()) { if (chart && !chart.isDisposed()) {
chart.setOption(createGaugeOption(ind, false), true); chart.setOption(createGaugeOption(ind, false));
} }
}); });
} }
...@@ -1187,10 +1188,15 @@ ...@@ -1187,10 +1188,15 @@
function startPolling() { function startPolling() {
stopPolling(); // 先清旧定时器,防止累积 stopPolling(); // 先清旧定时器,防止累积
pollTimer = setInterval(async () => { pollTimer = setInterval(async () => {
if (!isMounted.value) return; // 组件已卸载,跳过 if (!isMounted.value || polling) return; // 组件已卸载或上一次未完成,跳过
polling = true;
try {
await fetchEnvironmentData(); await fetchEnvironmentData();
await nextTick(); await nextTick();
refreshCharts(); refreshCharts();
} finally {
polling = false;
}
}, POLL_INTERVAL); }, POLL_INTERVAL);
} }
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<mysqlPwd>123456</mysqlPwd> <mysqlPwd>123456</mysqlPwd>
<dataBase>beermuseumplatform</dataBase> <dataBase>beermuseumplatform</dataBase>
<!-- 服务器mysql配置 --> <!-- 服务器mysql配置 -->
<!-- <mysqlHost>127.0.0.1</mysqlHost> <!-- <mysqlHost>123.207.147.179</mysqlHost>
<mysqlPort>3306</mysqlPort> <mysqlPort>3306</mysqlPort>
<mysqlUser>root</mysqlUser> <mysqlUser>root</mysqlUser>
<mysqlPwd>qaz123456</mysqlPwd> <mysqlPwd>qaz123456</mysqlPwd>
......
...@@ -96,7 +96,7 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?: ...@@ -96,7 +96,7 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
// 调用空调控制(非文创区域才允许自动控温) // 调用空调控制(非文创区域才允许自动控温)
// if (regionRow?.data && !regionRow.data.name?.includes("文创")) { // if (regionRow?.data && !regionRow.data.name?.includes("文创")) {
// 根据设备上报的数据,联动控制空调设备 // 根据设备上报的数据,联动控制空调设备
let regionName = regionRow?.data && !regionRow.data.name ? regionRow.data.name : ""; let regionName = regionRow && regionRow.data && regionRow.data.name ? regionRow.data.name : "";
await controlAcByEnvironment(deviceId, data, regionName); await controlAcByEnvironment(deviceId, data, regionName);
// } // }
......
...@@ -227,9 +227,9 @@ export async function controlIndoorUnit(params: any): Promise<any> { ...@@ -227,9 +227,9 @@ export async function controlIndoorUnit(params: any): Promise<any> {
throw new BizError(ERRORENUM.参数错误, "设备编号不能为空"); throw new BizError(ERRORENUM.参数错误, "设备编号不能为空");
} }
const body = { ...params }; const body = { ...params };
const result = await feiYiPost(path, body); const result = await feiYiPost(path, body); // {code:"", message:""}
console.log('控制内机响应:', result?.code); console.log('控制内机响应:', result?.code);
let success = result.code === '00000'? true : false; let success = result && result.code === '00000'? true : false;
return { success, data: result.message }; return { success, data: result.message };
} }
......
...@@ -107,7 +107,7 @@ export async function processDeviceData(message) { ...@@ -107,7 +107,7 @@ export async function processDeviceData(message) {
// && !regionRow.data.name?.includes("社会责任厅") // && !regionRow.data.name?.includes("社会责任厅")
// && !regionRow.data.name?.includes("新包装") // && !regionRow.data.name?.includes("新包装")
// ) { // ) {
let regionName = regionRow?.data && !regionRow.data.name? regionRow.data.name : ""; let regionName = regionRow && regionRow.data && regionRow.data.name ? regionRow.data.name : "";
await controlAcByEnvironment(devEUI, data, regionName); await controlAcByEnvironment(devEUI, data, regionName);
// } // }
} }
...@@ -153,7 +153,7 @@ export async function customerDeviceData(message) { ...@@ -153,7 +153,7 @@ export async function customerDeviceData(message) {
console.error('消息不是合法JSON,跳过:', msgStr); console.error('消息不是合法JSON,跳过:', msgStr);
return; return;
} }
console.log('收到客流数据:', data.length); console.log('收到客流数据:', msgStr);
// 提取设备信息 // 提取设备信息
const deviceInfo = data.device_info; const deviceInfo = data.device_info;
......
...@@ -812,7 +812,7 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) { ...@@ -812,7 +812,7 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
// 获取设备信息 // 获取设备信息
let devices = await selectDataListByParam( let devices = await selectDataListByParam(
TABLENAME.设备表, TABLENAME.设备表,
{ region_key: regionKey, device_id: deviceId }, { device_id: deviceId }, // region_key: regionKey,
["device_id", "device_type", "device_name", "control_params"] ["device_id", "device_type", "device_name", "control_params"]
); );
if (!devices.data.length) { if (!devices.data.length) {
...@@ -824,9 +824,13 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) { ...@@ -824,9 +824,13 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
{ device_id: deviceId, "%orderDesc%": "device_time", "%limit%": 1 }, { device_id: deviceId, "%orderDesc%": "device_time", "%limit%": 1 },
["data", "device_time"] ["data", "device_time"]
); );
let detailData = deviceData.data.length ? deviceData.data[0].data : null;
detailData.fanSpeed = detailData && detailData.fanSpeed ? acDeviceKeyMap[detailData.fanSpeed] : 2;
return { return {
deviceInfo: devices.data[0].control_params, deviceInfo: devices.data[0].control_params,
latestData: deviceData.data.length ? deviceData.data[0].data : null latestData: detailData
} }
} }
...@@ -856,8 +860,9 @@ export async function controlAcRunning(params: {}) { ...@@ -856,8 +860,9 @@ export async function controlAcRunning(params: {}) {
// 2. 接入空调控制API,更新空调设备信息 // 2. 接入空调控制API,更新空调设备信息
let acPrarms = { let acPrarms = {
"indoorUnitAddressFull": [deviceId], "indoorUnitAddressFull": [deviceId],
"workMode": acDeviceKeyMap[mode], "workMode": mode ? acDeviceKeyMap[mode] : 1, // 关机时传一个默认模式
"onOff": acDeviceKeyMap[power], // "onOff": acDeviceKeyMap[power],
"onOff": 1, // TODO:临时处理,等待前端调好就改回通过传参处理
"tempSet": Number(setTemp), "tempSet": Number(setTemp),
"fanSpeed": acDeviceKeyMap[fanSpeed], "fanSpeed": acDeviceKeyMap[fanSpeed],
"onOffLock": acDeviceKeyMap[autoControl], "onOffLock": acDeviceKeyMap[autoControl],
...@@ -865,9 +870,36 @@ export async function controlAcRunning(params: {}) { ...@@ -865,9 +870,36 @@ 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"
"openApiAction": "control" // TODO:临时处理
}; };
return await controlIndoorUnit(acPrarms); const result = await controlIndoorUnit(acPrarms);
// 手动控制日志
const now = new Date();
const operationTime = new Date();
const acLogs = [{
device_id: deviceId,
device_type: '空调',
operation_content: {
option: '手动控制',
device_id: deviceId,
time: operationTime,
mode: mode,
power: power,
setTemp: setTemp,
fanSpeed: fanSpeed,
autoControl: autoControl,
maxTemp: maxTemp,
minTemp: minTemp,
success: result.success,
message: result.message || (result.success ? '控制成功' : '控制失败'),
},
status: result.success ? 1 : 0,
created_at: now,
updated_at: now,
}];
await addData(TABLENAME.设备日志表, acLogs);
return result;
} }
// 辅助函数:获取用电趋势(按间隔分组) // 辅助函数:获取用电趋势(按间隔分组)
...@@ -910,6 +942,63 @@ async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Dat ...@@ -910,6 +942,63 @@ async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Dat
return result; return result;
} }
/**
* 获取本月累计用电(按天)
* 查询三相电表/电能监测设备的每日用电量,补全当月 1 日到今天的 x 轴
* @param regionGroup 区域分组
*/
export async function getCurrentMonthDailyEnergy(regionGroup: string) {
// 1. 查区域
let regionParam: any = {};
if (regionGroup) {
regionParam.region_group = regionGroup;
}
let regionInfo: any = await selectDataListByParam(TABLENAME.区域表, regionParam, ["id"]);
let regionKeys = regionInfo.data.map((r: any) => r.id);
// 2. 查三相电表/电能监测设备
let powerParams: any = { device_type: { "%in%": ["三相电表", "电能监测"] } };
if (regionGroup) {
powerParams.region_key = { "%in%": regionKeys };
}
let energyDevices = await selectDataListByParam(TABLENAME.设备表, powerParams, ["device_id"]);
let energyDeviceIds = energyDevices.data.map((d: any) => d.device_id);
if (energyDeviceIds.length === 0) return [];
// 3. 时间范围:本月 1 日 ~ 现在
const now = new Date();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
// 4. 查设备数据
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": energyDeviceIds },
device_time: { "%gte%": formatLocalTime(monthStart), "%lte%": formatLocalTime(now) },
"%orderAsc%": "device_time"
}, ["device_id", "data", "device_time"]);
// 5. 按天聚合用电量
const dayMap = new Map<string, number>();
for (let rec of records.data) {
let energy = rec.data?.current;
if (energy === undefined) continue;
let d = new Date(rec.device_time);
const pad = (n: number) => String(n).padStart(2, '0');
const key = `${d.getFullYear()}/${pad(d.getMonth() + 1)}/${pad(d.getDate())}`;
dayMap.set(key, (dayMap.get(key) || 0) + energy);
}
// 6. 补全本月 1 日到今天的每一天(缺失填 0)
const result: { key: string; value: string }[] = [];
const todayDate = now.getDate();
for (let i = 1; i <= todayDate; i++) {
const d = new Date(now.getFullYear(), now.getMonth(), i);
const pad = (n: number) => String(n).padStart(2, '0');
const key = `${d.getFullYear()}/${pad(d.getMonth() + 1)}/${pad(d.getDate())}`;
result.push({ key, value: String(dayMap.get(key) || 0) });
}
return result;
}
// 判断当前季节:5-9月为夏季(制冷),其余为冬季(制热) // 判断当前季节:5-9月为夏季(制冷),其余为冬季(制热)
function getSeason(): 'summer' | 'winter' { function getSeason(): 'summer' | 'winter' {
const month = new Date().getMonth() + 1; const month = new Date().getMonth() + 1;
...@@ -961,7 +1050,9 @@ export async function controlAcByEnvironment(deviceId: string, data: any, region ...@@ -961,7 +1050,9 @@ export async function controlAcByEnvironment(deviceId: string, data: any, region
let regionKey = envDevices.data[0].region_key; let regionKey = envDevices.data[0].region_key;
// ===== 新风联动(后执行,通过送风模式覆盖空调温控) ===== // ===== 新风联动(后执行,通过送风模式覆盖空调温控) =====
if (co2 != null && !regionName?.includes("原料厅") && !regionName?.includes("社会责任厅")) { if (co2 != null && !regionName?.includes("原料厅")
&& !regionName?.includes("社会责任厅")
&& !regionName?.includes("中途酒吧")) {
const fanParams = calcFanParams(co2); const fanParams = calcFanParams(co2);
if (fanParams) { if (fanParams) {
let fanDevices = await selectDataListByParam(TABLENAME.设备表, { let fanDevices = await selectDataListByParam(TABLENAME.设备表, {
......
...@@ -116,6 +116,7 @@ function buildControlParams( ...@@ -116,6 +116,7 @@ function buildControlParams(
indoorUnitAddressFull: deviceIds, indoorUnitAddressFull: deviceIds,
onOff: AC_KEY_MAP[acControl] ?? 1, onOff: AC_KEY_MAP[acControl] ?? 1,
openApiAction: "control", openApiAction: "control",
workMode: 1 // 传一个默认模式
}; };
// 关机只发送 onOff=0,不传模式/温度/风速 // 关机只发送 onOff=0,不传模式/温度/风速
......
...@@ -19,15 +19,15 @@ async function lanuch() { ...@@ -19,15 +19,15 @@ async function lanuch() {
/**创建http服务 */ /**创建http服务 */
httpServer.createServer(systemConfig.port); httpServer.createServer(systemConfig.port);
/**启动MQTT订阅服务 */ /**启动MQTT订阅服务 */
await mqttSrv.startMqttClient(); // await mqttSrv.startMqttClient();
/**启动定时任务 */ /**启动定时任务 */
startSchedule(); // startSchedule();
/**启动预警工单定时任务(10分钟) */ /**启动预警工单定时任务(10分钟) */
startAlertSchedule(); // startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */ /**启动故障状态更新定时任务(5分钟) */
startFaultStatusSchedule(); // startFaultStatusSchedule();
/**启动日程执行器 */ /**启动日程执行器 */
await initScheduleTasks(); // await initScheduleTasks();
console.log('This indicates that the server is started successfully.'); console.log('This indicates that the server is started successfully.');
......
...@@ -44,6 +44,9 @@ export function setRouter(httpServer) { ...@@ -44,6 +44,9 @@ export function setRouter(httpServer) {
httpServer.post('/api/qdm/device/envDevice', asyncHandler(getEnvironmentDevices)); httpServer.post('/api/qdm/device/envDevice', asyncHandler(getEnvironmentDevices));
/** IAQ-获取环境监测数据 */ /** IAQ-获取环境监测数据 */
httpServer.post('/api/qdm/device/environment', asyncHandler(getEnvironmentData)); httpServer.post('/api/qdm/device/environment', asyncHandler(getEnvironmentData));
/** 本月累计用电(按天) */
httpServer.post('/api/qdm/run/energy/monthly', asyncHandler(getMonthlyEnergy));
} }
/** /**
...@@ -130,7 +133,7 @@ async function getRegionList(req, res) { ...@@ -130,7 +133,7 @@ async function getRegionList(req, res) {
*/ */
async function getAcRunningPop(req, res) { async function getAcRunningPop(req, res) {
let reqConf = {regionKey:'Number', deviceId:'String'}; let reqConf = {regionKey:'Number', deviceId:'String'};
const NotMustHaveKeys = []; const NotMustHaveKeys = [ 'regionKey' ];
let { regionKey, deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { regionKey, deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await runningBiz.getAcRunningPop(regionKey, deviceId); const result = await runningBiz.getAcRunningPop(regionKey, deviceId);
res.success(result); res.success(result);
...@@ -197,6 +200,17 @@ async function getRunAnalysisEnvPop(req, res) { ...@@ -197,6 +200,17 @@ async function getRunAnalysisEnvPop(req, res) {
res.success(result); res.success(result);
} }
/**
* 本月累计用电(按天)
*/
async function getMonthlyEnergy(req, res) {
let reqConf = { regionGroup: 'String' };
const NotMustHaveKeys = ["regionGroup"];
let { regionGroup } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await runningBiz.getCurrentMonthDailyEnergy(regionGroup);
res.success(result);
}
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import * as feiyiClientBiz from '../biz/feiyiClient'; import * as feiyiClientBiz from '../biz/feiyiClient';
import { eccReqParamater } from '../util/verificationParam'; import { eccReqParamater } from '../util/verificationParam';
import { addData } from '../data/addData';
import { TABLENAME } from '../config/dbEnum';
export function setRouter(httpServer) { export function setRouter(httpServer) {
/** 网关设备列表 */ /** 网关设备列表 */
...@@ -54,6 +56,28 @@ async function getFeiyiIndoorUnitControl(req, res) { ...@@ -54,6 +56,28 @@ async function getFeiyiIndoorUnitControl(req, res) {
const NotMustHaveKeys = [ "workMode", "openApiAction" ]; const NotMustHaveKeys = [ "workMode", "openApiAction" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.controlIndoorUnit(params); const result = await feiyiClientBiz.controlIndoorUnit(params);
// 直接API控制日志
const now = new Date();
const operationTime = new Date();
const logEntries = params.indoorUnitAddressFull.map((deviceId: string) => ({
device_id: deviceId,
device_type: '空调',
operation_content: {
option: '直接API控制',
device_id: deviceId,
time: operationTime,
params: {
workMode: params.workMode,
openApiAction: params.openApiAction,
},
success: result.success,
message: result.message || (result.success ? '控制成功' : '控制失败'),
},
status: result.success ? 1 : 0,
created_at: now,
updated_at: now,
}));
await addData(TABLENAME.设备日志表, logEntries);
res.success(result); res.success(result);
} }
......
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