空调设备运行弹窗

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