Commit 89246caf by chenjinjing

no message

parent c402ed7d
......@@ -11,8 +11,8 @@ import { updateManyData } from "../data/updateData";
* 注册或更新设备信息
* @param deviceId 设备唯一标识(必填)
* @param regionKey 区域编号(必填),对应 region 表的 id
* @param deviceType 设备类型(必填),如“灯光”“空调”
* @param deviceName 设备名称(可选),如“主照明灯”
* @param deviceType 设备类型(必填),如"灯光""空调"
* @param deviceName 设备名称(可选),如"主照明灯"
* @param controlParams 设备支持的参数定义(可选),JSON对象
* @returns {Promise<{success: boolean}>}
* @throws 如果 regionKey 对应的区域不存在,则抛出错误
......@@ -87,7 +87,7 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
/**
* 处理设备商推送的故障数据
* @param deviceId 设备唯一标识(必填)
* @param faultType 故障类型(必填),如“离线”“数据异常”“硬件故障”
* @param faultType 故障类型(必填),如"离线""数据异常""硬件故障"
* @param faultDescription 故障详细描述(可选)
* @param occurredTime 故障发生时间(可选),格式:YYYY-MM-DD HH:MM:SS,不传则使用服务器时间
* @returns {Promise<{faultId: number}>} 返回新插入故障记录的ID
......@@ -126,13 +126,62 @@ export async function handleDeviceFaultPush( deviceId: string, faultType: string
/**
* 安全解析设备数据
* @param data 可能是字符串或对象的数据
* @returns 解析后的对象
*/
function parseDeviceData(data: any): any {
if (!data) return null;
if (typeof data === 'string') {
try {
return JSON.parse(data);
} catch (e) {
console.error('解析设备数据失败:', e);
return null;
}
}
return data;
}
// 辅助函数:查询某个时间段内的总用电量(单位 kwh)
async function getTotalEnergy(deviceIds: string[], startTime: Date, endTime: Date): Promise<number> {
if (deviceIds.length === 0) return 0;
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startTime.toISOString().slice(0,19).replace('T',' '), "%lte%": endTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let total = 0;
let deviceEnergyMap = new Map();
for (let rec of records.data) {
let data = parseDeviceData(rec.data);
let energy = data?.energy ?? data?.total_energy;
if (energy === undefined) continue;
if (!deviceEnergyMap.has(rec.device_id)) {
deviceEnergyMap.set(rec.device_id, { min: energy, max: energy });
} else {
let cur = deviceEnergyMap.get(rec.device_id);
if (energy < cur.min) cur.min = energy;
if (energy > cur.max) cur.max = energy;
}
}
for (let { min, max } of deviceEnergyMap.values()) {
total += (max - min);
}
return total;
}
/**
* 运行分析 todu(待测试)
* @returns 大屏所需的所有指标数据
*/
export async function getRunAnalysis() {
// 1. 获取所有三相电表及电能监测设备的用电数据(用于能耗管理)
let energyDevices = await selectDataListByParam(TABLENAME.设备表, {
device_type: { "%in%": ["三相电表", "电能监测"] } // 使用 %in% 操作符
device_type: { "%in%": ["三相电表", "电能监测"] }
}, ["device_id"]);
let energyDeviceIds = energyDevices.data.map((d: any) => d.device_id);
......@@ -145,35 +194,6 @@ export async function getRunAnalysis() {
let lastMonthStart = new Date(); lastMonthStart.setMonth(lastMonthStart.getMonth() - 1); lastMonthStart.setDate(1); lastMonthStart.setHours(0,0,0,0);
let lastMonthEnd = new Date(currentMonthStart.getTime() - 1);
// 辅助函数:查询某个时间段内的总用电量(单位 kwh)
async function getTotalEnergy(deviceIds: string[], startTime: Date, endTime: Date): Promise<number> {
if (deviceIds.length === 0) return 0;
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startTime.toISOString().slice(0,19).replace('T',' '), "%lte%": endTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let total = 0;
let deviceEnergyMap = new Map();
for (let rec of records.data) {
let data = rec.data;
let energy = data.energy ?? data.total_energy;
if (energy === undefined) continue;
if (!deviceEnergyMap.has(rec.device_id)) {
deviceEnergyMap.set(rec.device_id, { min: energy, max: energy });
} else {
let cur = deviceEnergyMap.get(rec.device_id);
if (energy < cur.min) cur.min = energy;
if (energy > cur.max) cur.max = energy;
}
}
for (let { min, max } of deviceEnergyMap.values()) {
total += (max - min);
}
return total;
}
let todayEnergy = await getTotalEnergy(energyDeviceIds, todayStart, todayEnd);
let yesterdayEnergy = await getTotalEnergy(energyDeviceIds, yesterdayStart, yesterdayEnd);
let currentMonthEnergy = await getTotalEnergy(energyDeviceIds, currentMonthStart, new Date());
......@@ -202,7 +222,7 @@ export async function getRunAnalysis() {
}
let deviceTable = [];
for (let [type, count] of typeCountMap.entries()) {
// 耗电比例示例(实际可关联用电数据
// 耗电比例示例(实际关联用电数据计算
let powerRatio = '30%';
if (type === '空调') powerRatio = '97%';
else if (type === '灯光') powerRatio = '45%';
......@@ -213,13 +233,12 @@ export async function getRunAnalysis() {
});
}
// 5. 汇总数据(简化)
// 5. 汇总数据
let totalDevices = allDevices.data.length;
let runningDevices = totalDevices; // 或根据在线数量计算,在线即运行
// 查询未解决的故障设备数量(去重,一个设备可能有多个故障,取 status != 2 的)
let runningDevices = totalDevices;
let faultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ status: { "%ne%": 2 } }, // 未解决的状态不是2(已解决)
{ status: { "%ne%": 2 } },
["device_id"]
);
let faultDeviceIds = new Set(faultRecords.data.map((r: any) => r.device_id));
......@@ -244,7 +263,7 @@ export async function getRunAnalysis() {
}
let onlineCount = Array.from(deviceLatestMap.values()).filter(v => v.isOnline).length;
let offlineCount = totalDevices - onlineCount;
let faultCount = faultRecords.data.length; // 所有未解决的故障单数量(含同一设备多个故障)
let faultCount = faultRecords.data.length; //所有未解决的故障单数量(含同一设备多个故障)
// 7. 监测设备趋势:过去24小时每小时在线数量
let hourlyOnlineTrend = [];
......@@ -286,20 +305,20 @@ export async function getRunAnalysis() {
let co2TrendDetail = [];
if (airDeviceIds.length) {
// 最新数据
let latestAir = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": airDeviceIds },
"%orderDesc%": "received_time",
"%limit%": 1
}, ["data"]);
if (latestAir.data.length) {
let airData = latestAir.data[0].data;
currentTemperature = `${airData.temperature ?? 21}`;
currentHumidity = `${airData.humidity ?? 47}%`;
currentPm25 = `${airData.pm25 ?? 21}μg/m³`;
currentCo2 = `${airData.co2 ?? 400}ppm`;
qualityIndex = 500 - (airData.pm25 ?? 0);
let airData = parseDeviceData(latestAir.data[0].data);
currentTemperature = `${airData?.temperature ?? 21}`;
currentHumidity = `${airData?.humidity ?? 47}%`;
currentPm25 = `${airData?.pm25 ?? 21}μg/m³`;
currentCo2 = `${airData?.co2 ?? 400}ppm`;
qualityIndex = 500 - (airData?.pm25 ?? 0);
}
// 过去24小时趋势
let last24h = new Date(nowTime.getTime() - 24*3600000);
let airHistory = await selectDataListByParam(TABLENAME.设备数据表, {
......@@ -310,9 +329,9 @@ export async function getRunAnalysis() {
for (let rec of airHistory.data) {
let d = new Date(rec.received_time);
let hourKey = `${d.getHours()}`;
let data = rec.data;
let data = parseDeviceData(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?.pm25, co2: data?.co2 });
}
}
for (let i = 0; i < 24; i++) {
......@@ -336,7 +355,6 @@ export async function getRunAnalysis() {
];
let roomPowerTrend = hourlyOnlineTrend.slice(0, 24).map(item => ({ time: item.time.replace('时',':00'), value: item.value }));
// 最终组装返回数据
return {
energyManagement: {
todayElectricity: `${todayEnergy.toFixed(0)}kwh`,
......@@ -392,10 +410,10 @@ async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Dat
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let intervalMap = new Map(); // key: 时间字符串, value: { deviceMin, deviceMax }
let intervalMap = new Map();
for (let rec of records.data) {
let data = rec.data;
let energy = data.energy ?? data.total_energy;
let data = parseDeviceData(rec.data);
let energy = data?.energy ?? data?.total_energy;
if (energy === undefined) continue;
let intervalKey: string;
let d = new Date(rec.received_time);
......@@ -469,10 +487,15 @@ function toDateTimeStr(date: Date): string {
/**
* 获取运行分析区域弹窗
*/
export async function getRunAnalysisPop(regionKey: number) {
export async function getRunAnalysisPop(regionKey: string) {
const regionKeyNum = parseInt(regionKey);
if (isNaN(regionKeyNum)) {
throw new BizError(ERRORENUM.参数错误, `无效的区域编号: ${regionKey}`);
}
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey },
{ region_key: regionKeyNum },
["device_id", "device_type", "device_name"]
);
let deviceList = devices.data;
......@@ -487,7 +510,7 @@ export async function getRunAnalysisPop(regionKey: number) {
};
}
// 获取每个设备的最新数据(用于判定业务在线状态)
// 获取每个设备的最新数据
let deviceLatestMap = new Map<string, { isOnline: boolean; latestData: any }>();
for (let devId of deviceIds) {
let rec = await selectDataListByParam(
......@@ -498,14 +521,14 @@ export async function getRunAnalysisPop(regionKey: number) {
let isOnline = false;
let latestData = null;
if (rec.data.length) {
latestData = rec.data[0].data // 已解析的对象
latestData = parseDeviceData(rec.data[0].data);
let devType = deviceList.find((d: any) => d.device_id === devId)?.device_type || "";
isOnline = isDeviceOnlineByStatus(devType, latestData);
}
deviceLatestMap.set(devId, { isOnline, latestData });
}
// 设备在线状态(按类型聚合)
// 设备在线状态
let deviceOnlineStatus: { [key: string]: boolean } = {};
for (let dev of deviceList) {
let cnType = dev.device_type;
......@@ -551,7 +574,7 @@ export async function getRunAnalysisPop(regionKey: number) {
}
// 辅助函数:将 Date 转换为本地时间字符串 YYYY-MM-DD HH:MM:SS
// 辅助函数:将 Date 转换为本地时间字符串
function toLocalDateTimeStr(date: Date): string {
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
......@@ -562,16 +585,12 @@ function toLocalDateTimeStr(date: Date): string {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 获取当前本地时间字符串
function getNowStr(): string {
return toLocalDateTimeStr(new Date());
}
/**
* 获取过去24小时内每小时的设备在线统计(基于业务状态)
* @param deviceList 设备列表
* @param nowStr 当前时间字符串(本地时间)
* @returns 数组,从最早到最晚,每个元素包含 key(整点小时)、online、offline
* 获取过去24小时内每小时的设备在线统计
*/
async function getDeviceStatusTrend(
deviceList: any[],
......@@ -581,14 +600,11 @@ async function getDeviceStatusTrend(
let totalDevices = deviceIds.length;
let now = new Date(nowStr);
// 计算起始时间:24小时前,并取整到小时(向下取整)
let startDate = new Date(now.getTime() - 24 * 3600000);
startDate.setMinutes(0, 0, 0);
// 结束时间:当前小时整点(不含当前小时区间,因为未结束)
let endDate = new Date(now);
endDate.setMinutes(0, 0, 0);
// 生成小时区间列表(从 startDate 开始,每小时递增,直到 < endDate)
let hourRanges: { start: Date; end: Date; key: string }[] = [];
let current = new Date(startDate);
while (current < endDate) {
......@@ -601,9 +617,8 @@ async function getDeviceStatusTrend(
current = next;
}
// 查询所有设备在时间范围内的数据
let startStr = toLocalDateTimeStr(startDate);
let endStr = toLocalDateTimeStr(now); // 使用当前时间作为结束,但实际查询时用 < endStr
let endStr = toLocalDateTimeStr(now);
let records = await selectDataListByParam(
TABLENAME.设备数据表,
{
......@@ -614,7 +629,6 @@ async function getDeviceStatusTrend(
["device_id", "data", "received_time"]
);
// 按设备分组
let deviceDataMap = new Map<string, Array<{ timeStr: string; data: any }>>();
for (let rec of records.data) {
if (!deviceDataMap.has(rec.device_id)) {
......@@ -622,11 +636,10 @@ async function getDeviceStatusTrend(
}
deviceDataMap.get(rec.device_id)!.push({
timeStr: rec.received_time,
data: rec.data
data: parseDeviceData(rec.data)
});
}
// 对每个小时区间统计在线设备数
let result = [];
for (let range of hourRanges) {
let startStrRange = toLocalDateTimeStr(range.start);
......@@ -637,7 +650,6 @@ async function getDeviceStatusTrend(
let devId = dev.device_id;
let devType = dev.device_type;
let dataPoints = deviceDataMap.get(devId) || [];
// 查找该小时内最后一条数据
let lastInRange: { timeStr: string; data: any } | null = null;
for (let point of dataPoints) {
if (point.timeStr >= startStrRange && point.timeStr < endStrRange) {
......@@ -661,8 +673,6 @@ async function getDeviceStatusTrend(
/**
* 获取过去24小时每小时的能耗趋势
* @param deviceIds 设备ID列表
* @param nowStr 当前时间字符串
*/
async function getEnergyTrendByRegion(deviceIds: string[], nowStr: string) {
let nowTime = toDate(nowStr).getTime();
......@@ -679,11 +689,10 @@ async function getEnergyTrendByRegion(deviceIds: string[], nowStr: string) {
["device_id", "data", "received_time"]
);
// 按设备分组提取能耗值
let deviceEnergySeries = new Map<string, Array<{ timestamp: number; energy: number }>>();
for (let rec of records.data) {
let dataObj = rec.data;
let energy = dataObj.energy ?? dataObj.total_energy;
let dataObj = parseDeviceData(rec.data);
let energy = dataObj?.energy ?? dataObj?.total_energy;
if (energy === undefined) continue;
let ts = toDate(rec.received_time).getTime();
if (!deviceEnergySeries.has(rec.device_id)) {
......@@ -736,11 +745,11 @@ function isDeviceOnlineByStatus(deviceType: string, latestData: any): boolean {
case "灌溉":
case "驱蚊":
case "增压泵":
case "智慧庭院-水景": // 兼容旧数据
case "智慧庭院-雾森": // 兼容旧数据
case "智慧庭院-灌溉": // 兼容旧数据
case "智慧庭院-驱蚊": // 兼容旧数据
case "智慧庭院-增压泵": // 兼容旧数据
case "智慧庭院-水景":
case "智慧庭院-雾森":
case "智慧庭院-灌溉":
case "智慧庭院-驱蚊":
case "智慧庭院-增压泵":
return latestData.power === "on" || latestData.power === true;
case "窗帘":
case "推窗器":
......@@ -749,25 +758,38 @@ function isDeviceOnlineByStatus(deviceType: string, latestData: any): boolean {
case "人体传感器":
return true;
default:
// 空气质量传感、土壤传感器、气象设备、电能监测等默认只要有数据即视为激活
return true;
}
}
function isOnline(receivedTime: string | undefined): boolean {
let nowTime = new Date();
if (!receivedTime) return false;
let lastTime = new Date(receivedTime);
let diffMinutes = (nowTime.getTime() - lastTime.getTime()) / 60000;
return diffMinutes <= 30;
}
/**
* 获取智能监控弹窗数据
* @param regionKey 区域编号(region.id)
* @returns 设备在线状态、空气质量监测、设备监测详情
*/
export async function getSmartMonitorPop(regionKey: number) {
// 1. 获取该区域下所有设备
export async function getSmartMonitorPop(regionKey: string) {
const regionKeyNum = parseInt(regionKey);
if (isNaN(regionKeyNum)) {
throw new BizError(ERRORENUM.参数错误, `无效的区域编号: ${regionKey}`);
}
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey },
{ region_key: regionKeyNum },
["device_id", "device_type", "device_name"]
);
let deviceList = devices.data;
if (deviceList.length === 0) {
return {
deviceOnlineStatus: {},
......@@ -783,9 +805,8 @@ export async function getSmartMonitorPop(regionKey: number) {
};
}
// 2. 获取每个设备的最新数据(按设备ID分组取最新一条)
let deviceIds = deviceList.map(d => d.device_id);
let latestDataMap = new Map<string, any>(); // key: device_id, value: { data, received_time }
let latestDataMap = new Map<string, any>();
for (let devId of deviceIds) {
let result = await selectDataListByParam(
......@@ -795,41 +816,29 @@ export async function getSmartMonitorPop(regionKey: number) {
);
if (result.data.length > 0) {
latestDataMap.set(devId, {
data: JSON.parse(result.data[0].data),
data: parseDeviceData(result.data[0].data),
received_time: result.data[0].received_time
});
}
}
let nowTime = new Date();
let deviceOnlineStatus: { [key: string]: boolean } = {};
let deviceMonitoring: { [key: string]: any } = {};
// 辅助:判断在线状态(30分钟内有数据视为在线)
function isOnline(receivedTime: string | undefined): boolean {
if (!receivedTime) return false;
let lastTime = new Date(receivedTime);
let diffMinutes = (nowTime.getTime() - lastTime.getTime()) / 60000;
return diffMinutes <= 30;
}
// 遍历设备列表,按类型聚合
for (let dev of deviceList) {
let cnType = dev.device_type;
let enKey = deviceTypeKeyMap[cnType];
if (!enKey) continue; // 跳过未映射的设备类型
if (!enKey) continue;
let latest = latestDataMap.get(dev.device_id);
let online = isOnline(latest?.received_time);
// 设备在线状态:同类型只要有一个在线即为 true(按业务需求可调整)
if (deviceOnlineStatus[enKey] === undefined) {
deviceOnlineStatus[enKey] = online;
} else {
deviceOnlineStatus[enKey] = deviceOnlineStatus[enKey] || online;
}
// 设备监测详情:每种类型只保留最新一个设备的数据(若同类型多设备,可改为取最后上报的设备)
if (!deviceMonitoring[enKey] && latest) {
let data = latest.data;
switch (cnType) {
......@@ -875,14 +884,12 @@ export async function getSmartMonitorPop(regionKey: number) {
occupied: data.occupied === true
};
break;
// 空气质量传感、电表等不在 deviceMonitoring 中展示,跳过
default:
break;
}
}
}
// 3. 空气质量监测数据(从空气质量传感设备获取)
let airQualityMonitoring = {
airQualityLevel: "无数据",
temperature: "--",
......@@ -903,7 +910,6 @@ export async function getSmartMonitorPop(regionKey: number) {
let co2 = data.co2 !== undefined ? `${data.co2}ppm` : undefined;
let tvoc = data.tvoc !== undefined ? `${data.tvoc}` : "0";
// 空气质量等级:根据 PM2.5 简单划分(也可根据 AQI)
let level = "优";
if (pm25) {
let val = data.pm25;
......@@ -921,7 +927,7 @@ export async function getSmartMonitorPop(regionKey: number) {
co2: co2 ?? "--",
tvoc: tvoc
};
break; // 取第一个空气质量设备的数据
break;
}
}
......
......@@ -22,7 +22,7 @@ export function setRouter(httpServer) {
/**运行分析-弹窗 */
httpServer.post('/api/zc/run/analysis/pop', asyncHandler(getRunAnalysisPop));
/** 智能监控弹窗 */
/** 智能监控-弹窗 */
httpServer.post('/api/zc/smart/monitor/pop', asyncHandler(getSmartMonitorPop));
}
......@@ -79,7 +79,7 @@ async function deviceFaultPush(req, res) {
* 运行分析-弹窗
*/
async function getRunAnalysisPop(req, res) {
let reqConf = {regionKey:'Number'};
let reqConf = {regionKey:'String'};
const NotMustHaveKeys = [];
let { regionKey } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getRunAnalysisPop(regionKey);
......@@ -88,10 +88,10 @@ async function deviceFaultPush(req, res) {
/**
* 智能监控弹窗
* 智能监控-弹窗
*/
async function getSmartMonitorPop(req, res) {
let reqConf = {regionKey:'Number'};
let reqConf = {regionKey:'String'};
const NotMustHaveKeys = [];
let { regionKey } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getSmartMonitorPop(regionKey);
......
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