环境监测和运行分析优化

parent ffe69e79
......@@ -1084,7 +1084,7 @@
if (!largeChart || largeChart.isDisposed()) return;
const ind = getInd(key);
if (!ind) return;
largeChart.setOption(createGaugeOption(ind, true), true);
largeChart.setOption(createGaugeOption(ind, true));
}
function destroySmallCharts() {
......@@ -1145,7 +1145,7 @@
smallIndicators.value.forEach(ind => {
const existing = smallChartMap[ind.key];
if (existing && !existing.isDisposed()) {
existing.setOption(createGaugeOption(ind, false), true);
existing.setOption(createGaugeOption(ind, false));
} else {
const dom = document.getElementById('gauge-' + ind.key);
if (dom) {
......@@ -1169,6 +1169,7 @@
// ======== 定时轮询 ========
const POLL_INTERVAL = 30 * 1000; // 30 秒刷新一次
let pollTimer = null;
let polling = false; // 并发锁:上一次请求未完成则跳过本次
// 轻量刷新:只 setOption 更新数据,不销毁重建图表
function refreshCharts() {
......@@ -1179,7 +1180,7 @@
smallIndicators.value.forEach(ind => {
const chart = smallChartMap[ind.key];
if (chart && !chart.isDisposed()) {
chart.setOption(createGaugeOption(ind, false), true);
chart.setOption(createGaugeOption(ind, false));
}
});
}
......@@ -1187,10 +1188,15 @@
function startPolling() {
stopPolling(); // 先清旧定时器,防止累积
pollTimer = setInterval(async () => {
if (!isMounted.value) return; // 组件已卸载,跳过
if (!isMounted.value || polling) return; // 组件已卸载或上一次未完成,跳过
polling = true;
try {
await fetchEnvironmentData();
await nextTick();
refreshCharts();
} finally {
polling = false;
}
}, POLL_INTERVAL);
}
......
......@@ -10,7 +10,7 @@
<mysqlPwd>123456</mysqlPwd>
<dataBase>beermuseumplatform</dataBase>
<!-- 服务器mysql配置 -->
<!-- <mysqlHost>127.0.0.1</mysqlHost>
<!-- <mysqlHost>123.207.147.179</mysqlHost>
<mysqlPort>3306</mysqlPort>
<mysqlUser>root</mysqlUser>
<mysqlPwd>qaz123456</mysqlPwd>
......
......@@ -96,7 +96,7 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
// 调用空调控制(非文创区域才允许自动控温)
// 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);
// }
......
......@@ -227,9 +227,9 @@ export async function controlIndoorUnit(params: any): Promise<any> {
throw new BizError(ERRORENUM.参数错误, "设备编号不能为空");
}
const body = { ...params };
const result = await feiYiPost(path, body);
const result = await feiYiPost(path, body); // {code:"", message:""}
console.log('控制内机响应:', result?.code);
let success = result.code === '00000'? true : false;
let success = result && result.code === '00000'? true : false;
return { success, data: result.message };
}
......
......@@ -107,7 +107,7 @@ export async function processDeviceData(message) {
// && !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);
// }
}
......@@ -153,7 +153,7 @@ export async function customerDeviceData(message) {
console.error('消息不是合法JSON,跳过:', msgStr);
return;
}
console.log('收到客流数据:', data.length);
console.log('收到客流数据:', msgStr);
// 提取设备信息
const deviceInfo = data.device_info;
......
......@@ -812,7 +812,7 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
// 获取设备信息
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey, device_id: deviceId },
{ device_id: deviceId }, // region_key: regionKey,
["device_id", "device_type", "device_name", "control_params"]
);
if (!devices.data.length) {
......@@ -824,9 +824,13 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
{ device_id: deviceId, "%orderDesc%": "device_time", "%limit%": 1 },
["data", "device_time"]
);
let detailData = deviceData.data.length ? deviceData.data[0].data : null;
detailData.fanSpeed = detailData && detailData.fanSpeed ? acDeviceKeyMap[detailData.fanSpeed] : 2;
return {
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: {}) {
// 2. 接入空调控制API,更新空调设备信息
let acPrarms = {
"indoorUnitAddressFull": [deviceId],
"workMode": acDeviceKeyMap[mode],
"onOff": acDeviceKeyMap[power],
"workMode": mode ? acDeviceKeyMap[mode] : 1, // 关机时传一个默认模式
// "onOff": acDeviceKeyMap[power],
"onOff": 1, // TODO:临时处理,等待前端调好就改回通过传参处理
"tempSet": Number(setTemp),
"fanSpeed": acDeviceKeyMap[fanSpeed],
"onOffLock": acDeviceKeyMap[autoControl],
......@@ -865,9 +870,36 @@ export async function controlAcRunning(params: {}) {
"tempSetLock": acDeviceKeyMap[autoControl],
"tempSetLo": Number(minTemp),
"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
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月为夏季(制冷),其余为冬季(制热)
function getSeason(): 'summer' | 'winter' {
const month = new Date().getMonth() + 1;
......@@ -961,7 +1050,9 @@ export async function controlAcByEnvironment(deviceId: string, data: any, region
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);
if (fanParams) {
let fanDevices = await selectDataListByParam(TABLENAME.设备表, {
......
......@@ -116,6 +116,7 @@ function buildControlParams(
indoorUnitAddressFull: deviceIds,
onOff: AC_KEY_MAP[acControl] ?? 1,
openApiAction: "control",
workMode: 1 // 传一个默认模式
};
// 关机只发送 onOff=0,不传模式/温度/风速
......
......@@ -19,15 +19,15 @@ async function lanuch() {
/**创建http服务 */
httpServer.createServer(systemConfig.port);
/**启动MQTT订阅服务 */
await mqttSrv.startMqttClient();
// await mqttSrv.startMqttClient();
/**启动定时任务 */
startSchedule();
// startSchedule();
/**启动预警工单定时任务(10分钟) */
startAlertSchedule();
// startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */
startFaultStatusSchedule();
// startFaultStatusSchedule();
/**启动日程执行器 */
await initScheduleTasks();
// await initScheduleTasks();
console.log('This indicates that the server is started successfully.');
......
......@@ -44,6 +44,9 @@ export function setRouter(httpServer) {
httpServer.post('/api/qdm/device/envDevice', asyncHandler(getEnvironmentDevices));
/** IAQ-获取环境监测数据 */
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) {
*/
async function getAcRunningPop(req, res) {
let reqConf = {regionKey:'Number', deviceId:'String'};
const NotMustHaveKeys = [];
const NotMustHaveKeys = [ 'regionKey' ];
let { regionKey, deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await runningBiz.getAcRunningPop(regionKey, deviceId);
res.success(result);
......@@ -197,6 +200,17 @@ async function getRunAnalysisEnvPop(req, res) {
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 @@
import asyncHandler from 'express-async-handler';
import * as feiyiClientBiz from '../biz/feiyiClient';
import { eccReqParamater } from '../util/verificationParam';
import { addData } from '../data/addData';
import { TABLENAME } from '../config/dbEnum';
export function setRouter(httpServer) {
/** 网关设备列表 */
......@@ -54,6 +56,28 @@ async function getFeiyiIndoorUnitControl(req, res) {
const NotMustHaveKeys = [ "workMode", "openApiAction" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
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);
}
......
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