Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
qingdaoMuseumPlatform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
node_server
qingdaoMuseumPlatform
Commits
316d5da8
Commit
316d5da8
authored
Jun 02, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
空调设备运行弹窗
parent
4f9fb2b3
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
162 additions
and
31 deletions
+162
-31
device.ts
src/biz/device.ts
+4
-0
feiyiClient.ts
src/biz/feiyiClient.ts
+33
-6
running.ts
src/biz/running.ts
+66
-16
mysqlTableConfig.ts
src/config/mysqlTableConfig.ts
+20
-2
device.ts
src/routers/device.ts
+4
-3
feiyiClient.ts
src/routers/feiyiClient.ts
+35
-4
No files found.
src/biz/device.ts
View file @
316d5da8
...
...
@@ -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
};
}
...
...
src/biz/feiyiClient.ts
View file @
316d5da8
...
...
@@ -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
/open
Api/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
src/biz/running.ts
View file @
316d5da8
...
...
@@ -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
src/config/mysqlTableConfig.ts
View file @
316d5da8
...
...
@@ -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
:
{
...
...
src/routers/device.ts
View file @
316d5da8
...
...
@@ -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
);
}
...
...
src/routers/feiyiClient.ts
View file @
316d5da8
...
...
@@ -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
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment