空调设备运行弹窗

parent 4f9fb2b3
......@@ -5,6 +5,7 @@ import { BizError } from "../util/bizError";
import { ERRORENUM } from "../config/errorEnum";
import { addData } from "../data/addData";
import { updateManyData } from "../data/updateData";
import { controlAcByEnvironment } from "./running";
/**
......@@ -80,6 +81,9 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
created_at: receivedTime,
});
// 根据设备上报的数据,联动控制空调设备
await controlAcByEnvironment(deviceId, data);
return { success: true };
}
......
......@@ -20,7 +20,7 @@ let refreshTokenPromise: Promise<string> | null = null; // 用于避免并发刷
* 飞奕云平台配置(建议从环境变量或配置文件中读取)
*/
const FEIYI_CONFIG = {
baseUrl: 'https://m.achelp.cn',
baseUrl: 'https://m.achelp.cn/open',
clientId: process.env.FEIYI_CLIENT_ID || '419636996764598272', // 我方提供的 clientId
secretKey: process.env.FEIYI_SECRET_KEY || '02fc75446b4544b3a02444a2b5f9be2b', // 我方提供的 appSecret
};
......@@ -69,6 +69,8 @@ function generateSign(params: Record<string, any>, timestamp: string, nonce: str
* @throws 如果请求失败或响应码非 00000,则抛出 BizError
*/
export async function getFeiYiToken(): Promise<string> {
const url = `${FEIYI_CONFIG.baseUrl}/oauth2/openapi/token`;
// 校验token是否有效,有效则直接返回
const now = Date.now();
if (feiYiToken && expireTime > now) {
......@@ -94,10 +96,6 @@ export async function getFeiYiToken(): Promise<string> {
sign: sign
};
const url = `${FEIYI_CONFIG.baseUrl}/open/oauth2/openapi/token`;
// 注意:原 post 方法有 bug(成功时 resolve,失败时没有 reject),此处建议使用修复后的 post 或直接使用 request 库
// 这里为了兼容,使用修复逻辑(若原 post 不可靠,可用下面的实现)
let result: any;
try {
result = await post(url, requestBody, {});
......@@ -169,9 +167,37 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
* @returns 网关设备数据,包含 rows、total 等
*/
export async function getGatewayDeviceList(page: number = 1, limit: number = 10): Promise<any> {
const path = '/open/openApi/gw/page';
const path = '/openApi/gw/page';
const body = { page, limit };
const result = await feiYiPost(path, body);
// 返回 data 部分,包含 page, limit, total, rows
return result.data;
}
/**
* 内机列表分页查询
* @param params
*/
export async function getIndoorUnitList(params: any): Promise<any> {
const path = '/openApi/indoorUnit/list';
const body = { ...params };
const result = await feiYiPost(path, body);
return result.data;
}
/**
* 空调内机控制
* @param params
* @returns
*/
export async function controlIndoorUnit(params: any): Promise<any> {
const path = '/openApi/indoorUnit/control';
if (!params.indoorUnitAddressFull || !Array.isArray(params.indoorUnitAddressFull) || params.indoorUnitAddressFull.length === 0) {
throw new BizError(ERRORENUM.参数错误, "设备编号不能为空");
}
const body = { ...params };
const result = await feiYiPost(path, body);
console.log('控制内机响应:', result);
let success = result.code === '00000'? true : false;
return { success, data: result.message };
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ import { TABLENAME } from "../config/dbEnum";
import { ERRORENUM } from "../config/errorEnum";
import { selectDataListByParam } from "../data/findData";
import { BizError } from "../util/bizError";
import { controlIndoorUnit } from "./feiyiClient";
import { getRegionList } from "./region";
/**
......@@ -548,7 +549,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
/**
* 智能管控-空调设备运行弹
* 智能管控-空调设备运行弹
* @param regionKey
* @param deviceId
*/
......@@ -578,7 +579,15 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
* 设备运行弹框 - 空调启停
* @param deviceId
*/
export async function controlAcRunning(deviceId?: string) {
export async function controlAcRunning(deviceIds?: string[], workerMode?: string) {
// 1. 通过设备信息,获取内机地址
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ device_id: { "%in%": deviceIds } },
["device_id", "region_key", "device_name", "device_ad", "control_params"]
);
// 2. 通过内机地址调用空调控制接口启停空调设备,无需校验环境温度
return await controlAcRunningApi(devices, 0, workerMode);
}
// 辅助函数:获取用电趋势(按间隔分组)
......@@ -632,7 +641,7 @@ async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Dat
}
// 基于馆内环境温度、湿度变化等,联动空调启停
async function controlAcByEnvironment(deviceId: string, data: any) {
export async function controlAcByEnvironment(deviceId: string, data: any) {
// 获取区域内环境监测设备数据
let envDevices = await selectDataListByParam(TABLENAME.设备表, {
device_id: deviceId,
......@@ -646,19 +655,59 @@ async function controlAcByEnvironment(deviceId: string, data: any) {
let acDevices = await selectDataListByParam(TABLENAME.设备表, {
region_key: regionKey,
device_type: "空调"
}, ["id", "device_name", "control_params"]);
acDevices.data.forEach((ac: any) => {
let controlParams = ac.control_params;
let maxTemp = controlParams.maxTemp;
let minTemp = controlParams.minTemp;
if (data.temperature && (data.temperature > maxTemp || data.temperature < minTemp)){
// 调用空调控制接口启停空调设备
console.log(`联动空调 ${ac.device_name}${ac.id})启停,当前温度:${data.temperature},阈值范围:${minTemp}-${maxTemp}`);
// 单个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
}
});
// 多个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
}, ["id", "device_name", "device_id", "device_ad", "control_params"]);
return await controlAcRunningApi(acDevices, data.temperature, "");
}
}
return { success: true, message: "成功" };
// 辅助:接入空调控制API,根据温度阈值启停空调设备
async function controlAcRunningApi(acDevices?: any, temperature?: number, workerMode?: string) {
let workModeMap = {};
let deviceMaxList: any = [];
let deviceMinList: any = [];
let openApiAction = "control"; // control: 控制空调模式,lock: 锁定空调模式
acDevices.data.forEach((ac: any) => {
let controlParams = ac.control_params;
let maxTemp = controlParams.maxTemp;
let minTemp = controlParams.minTemp;
if (temperature && temperature > maxTemp) {
// 调用空调控制接口启停空调设备
console.log(`联动空调 ${ac.device_name}${ac.id})启停,当前温度:${temperature},阈值范围:${minTemp}-${maxTemp}`);
// 单个接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
deviceMaxList.push(ac.device_ad);
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);
workModeMap[2] = deviceMinList;
openApiAction = "control";
} else if (!temperature && workerMode) {
// 根据workerMode控制空调设备
console.log(`根据工作模式控制空调 ${ac.device_name}${workerMode}`);
workModeMap[workerMode] = workModeMap[workerMode] ? [...workModeMap[workerMode], ac.device_ad] : [ac.device_ad];
openApiAction = "lock";
}
});
// 多个设备: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
let controlResults = { "success": true, "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.message = coldRes.data || "控制空调设备制冷失败";
}
}
if (workModeMap[2]) {
// 处理制热模式
let hotRes = await controlIndoorUnit({ "indoorUnitAddressFull": workModeMap[2], "workMode": 2, "openApiAction": openApiAction })
if (!hotRes.success) {
console.error("控制空调设备制热失败", hotRes);
controlResults.success = false;
controlResults.message = hotRes.data || "控制空调设备制热失败";
}
}
return controlResults;
}
\ No newline at end of file
......@@ -124,10 +124,16 @@ export const TablesConfig = [
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '设备自定义标识'
},
device_ad: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '设备唯一标识(由设备商提供)'
},
region_key: {
type: DataTypes.INTEGER,
type: DataTypes.BIGINT,
allowNull: false,
comment: '区域编号,关联region.id'
},
......@@ -213,16 +219,29 @@ export const TablesConfig = [
autoIncrement: true,
comment: '区域编号(regionKey)'
},
room_id: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '房间ID,关联设备商提供的房间ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '区域名称'
},
floor_id: {
type: DataTypes.STRING(100),
comment: '楼层ID,关联设备商提供的楼层ID'
},
type: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '所属楼层'
},
building_id: {
type: DataTypes.STRING(100),
comment: '楼栋ID,关联设备商提供的楼栋ID'
},
groups: {
type: DataTypes.STRING(100),
allowNull: false,
......@@ -230,7 +249,6 @@ export const TablesConfig = [
},
max_capacity: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '最大承载量'
},
sort_order: {
......
......@@ -8,6 +8,7 @@ import * as regionBiz from '../biz/region';
import * as runningBiz from '../biz/running';
import { eccReqParamater } from '../util/verificationParam';
import { checkUser } from '../middleware/user';
import { worker } from 'cluster';
export function setRouter(httpServer) {
/** 注册/更新设备信息 */
......@@ -129,10 +130,10 @@ async function getRegionList(req, res) {
* 控制空调开关
*/
async function controlAcRunning(req, res) {
let reqConf = {deviceId:'String'};
let reqConf = {deviceId:'String', workerMode:'String'};
const NotMustHaveKeys = [];
let { deviceId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await runningBiz.controlAcRunning(deviceId);
let { deviceId, workerMode } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await runningBiz.controlAcRunning(deviceId, workerMode);
res.success(result);
}
......
......@@ -6,13 +6,17 @@ import * as feiyiClientBiz from '../biz/feiyiClient';
import { eccReqParamater } from '../util/verificationParam';
export function setRouter(httpServer) {
/** 注册/更新设备信息 */
/** 网关设备列表 */
httpServer.post('/api/qdm/feiyi/gateway/list', asyncHandler(getFeiyiGatewayList));
/** 空调内机列表 */
httpServer.post('/api/qdm/feiyi/indoorUnit/list', asyncHandler(getFeiyiIndoorUnitList));
/** 空调内机控制 */
httpServer.post('/api/qdm/feiyi/indoorUnit/control', asyncHandler(getFeiyiIndoorUnitControl));
}
/**
* 注册或更新设备信息
* 网关设备列表
*/
async function getFeiyiGatewayList(req, res) {
let reqConf = {page:'Number', limit:'Number'};
......@@ -22,8 +26,35 @@ export function setRouter(httpServer) {
res.success(result);
}
/**
* 空调内机列表
* @param page 当前页码,默认 1
* @param limit 每页条数,默认 10
* @param buildingId 楼栋ID(可选)
* @param floorId 楼层ID(可选)
* @param roomId 区域ID(可选)
* @param gatewayCode 网关设备ID(可选)
*/
async function getFeiyiIndoorUnitList(req, res) {
let reqConf = {page:'Number', limit:'Number', roomId: 'String', indoorUnitAddressFull: 'String'};
const NotMustHaveKeys = [ "page", "limit", "roomId", "indoorUnitAddressFull" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.getIndoorUnitList(params);
res.success(result);
}
/**
* 空调内机控制
* @param indoorUnitAddressFull 要控制的内机地址集合
* @param workMode 1 制冷 2 制热 3 送风 4 除湿
* @param openApiAction control 控制指令 / lock 锁定指令
*/
async function getFeiyiIndoorUnitControl(req, res) {
let reqConf = { indoorUnitAddressFull: 'Array', workMode: 'Number', openApiAction: 'String' };
const NotMustHaveKeys = [ "workMode", "openApiAction" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.controlIndoorUnit(params);
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