homebridge-melcloud-control 4.3.0-beta.9 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/index.js +12 -12
- package/package.json +1 -1
- package/src/constants.js +3 -1
- package/src/deviceata.js +7 -1
- package/src/deviceatw.js +7 -1
- package/src/deviceerv.js +7 -1
- package/src/melcloudata.js +120 -54
- package/src/melcloudatw.js +15 -11
- package/src/melclouderv.js +15 -11
- package/src/melcloudhome.js +17 -5
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
22
|
|
|
23
23
|
- Do not use Homebridge UI > v5.5.0 because of break config.json
|
|
24
24
|
|
|
25
|
+
# [4.3.0] - (20.11.2025)
|
|
26
|
+
|
|
27
|
+
## Changes
|
|
28
|
+
|
|
29
|
+
- use web socket for Real Time communication with MELCloud Home
|
|
30
|
+
- cleanup
|
|
31
|
+
|
|
25
32
|
# [4.2.8] - (18.11.2025)
|
|
26
33
|
|
|
27
34
|
## Changes
|
package/index.js
CHANGED
|
@@ -85,12 +85,12 @@ class MelCloudPlatform {
|
|
|
85
85
|
.on('start', async () => {
|
|
86
86
|
try {
|
|
87
87
|
//melcloud account
|
|
88
|
-
const melCloud = account.type === 'melcloud' ? new MelCloud(account, accountFile, buildingsFile, devicesFile, true) : new MelCloudHome(account, accountFile, buildingsFile, devicesFile, true)
|
|
89
|
-
|
|
90
|
-
.on('info', (msg) =>
|
|
91
|
-
.on('debug', (msg) =>
|
|
92
|
-
.on('warn', (msg) =>
|
|
93
|
-
.on('error', (msg) =>
|
|
88
|
+
const melCloud = account.type === 'melcloud' ? new MelCloud(account, accountFile, buildingsFile, devicesFile, true) : new MelCloudHome(account, accountFile, buildingsFile, devicesFile, true)
|
|
89
|
+
.on('success', (msg) => log.success(`${accountName}, ${msg}`))
|
|
90
|
+
.on('info', (msg) => log.info(`${accountName}, ${msg}`))
|
|
91
|
+
.on('debug', (msg) => log.info(`${accountName}, debug: ${msg}`))
|
|
92
|
+
.on('warn', (msg) => log.warn(`${accountName}, ${msg}`))
|
|
93
|
+
.on('error', (msg) => log.error(`${accountName}, ${msg}`));
|
|
94
94
|
|
|
95
95
|
//connect
|
|
96
96
|
let accountInfo;
|
|
@@ -181,11 +181,11 @@ class MelCloudPlatform {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
configuredDevice.on('devInfo', (info) => logLevel.devInfo && log.info(info))
|
|
184
|
-
.on('success', (msg) =>
|
|
185
|
-
.on('info', (msg) =>
|
|
186
|
-
.on('debug', (msg) =>
|
|
187
|
-
.on('warn', (msg) =>
|
|
188
|
-
.on('error', (msg) =>
|
|
184
|
+
.on('success', (msg) => log.success(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
|
|
185
|
+
.on('info', (msg) => log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
|
|
186
|
+
.on('debug', (msg) => log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, debug: ${msg}`))
|
|
187
|
+
.on('warn', (msg) => log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
|
|
188
|
+
.on('error', (msg) => log.error(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`));
|
|
189
189
|
|
|
190
190
|
const accessory = await configuredDevice.start();
|
|
191
191
|
if (accessory) {
|
|
@@ -194,7 +194,7 @@ class MelCloudPlatform {
|
|
|
194
194
|
|
|
195
195
|
//start impulse generators\
|
|
196
196
|
await configuredDevice.startStopImpulseGenerator(true, [{ name: 'checkState', sampling: deviceRefreshInterval }]);
|
|
197
|
-
const timmers = accountType === 'melcloudhome' ? [{ name: 'connect', sampling:
|
|
197
|
+
const timmers = accountType === 'melcloudhome' ? [{ name: 'connect', sampling: 3300000 }, { name: 'checkDevicesList', sampling: 3000 }] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
|
|
198
198
|
await melCloud.impulseGenerator.state(true, timmers, false);
|
|
199
199
|
|
|
200
200
|
//stop impulse generator
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.1",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/constants.js
CHANGED
|
@@ -12,6 +12,7 @@ export const ApiUrls = {
|
|
|
12
12
|
SetAta: "/Device/SetAta",
|
|
13
13
|
SetAtw: "/Device/SetAtw",
|
|
14
14
|
SetErv: "/Device/SetErv",
|
|
15
|
+
GetRefreshUnit: "/Device/RequestRefresh?id=deviceid",
|
|
15
16
|
UpdateApplicationOptions: "/User/UpdateApplicationOptions",
|
|
16
17
|
HolidayModeUpdate: "/HolidayMode/Update",
|
|
17
18
|
EnergyCostReport: "/EnergyCost/Report",
|
|
@@ -42,7 +43,8 @@ export const ApiUrlsHome = {
|
|
|
42
43
|
PutDeviceSettings: "https://melcloudhome.com/dashboard",
|
|
43
44
|
PutScheduleEnabled: "https://melcloudhome.com/ata/deviceid/schedule",
|
|
44
45
|
},
|
|
45
|
-
Origin: "https://melcloudhome.com"
|
|
46
|
+
Origin: "https://melcloudhome.com",
|
|
47
|
+
WebSocketURL: "wss://ws.melcloudhome.com/?hash="
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
export const DeviceType = [
|
package/src/deviceata.js
CHANGED
|
@@ -1079,7 +1079,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1079
1079
|
//control
|
|
1080
1080
|
if (button.displayType > 3) {
|
|
1081
1081
|
if (this.logDebug) this.emit('debug', `Prepare button control ${name} service`);
|
|
1082
|
-
const buttonControlService = new
|
|
1082
|
+
const buttonControlService = new Service.Switch(serviceName, `buttonControlService${deviceId} ${i}`);
|
|
1083
1083
|
buttonControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1084
1084
|
buttonControlService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
|
|
1085
1085
|
buttonControlService.getCharacteristic(Characteristic.On)
|
|
@@ -1673,6 +1673,8 @@ class DeviceAta extends EventEmitter {
|
|
|
1673
1673
|
if (this.presets.length > 0) {
|
|
1674
1674
|
this.presets.forEach((preset, i) => {
|
|
1675
1675
|
const presetData = presetsOnServer.find(p => p.ID === preset.id);
|
|
1676
|
+
if (!presetData) return;
|
|
1677
|
+
|
|
1676
1678
|
const characteristicType = preset.characteristicType;
|
|
1677
1679
|
|
|
1678
1680
|
preset.state = presetData ? (presetData.Power === power
|
|
@@ -1694,6 +1696,8 @@ class DeviceAta extends EventEmitter {
|
|
|
1694
1696
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
1695
1697
|
this.schedules.forEach((schedule, i) => {
|
|
1696
1698
|
const scheduleData = schedulesOnServer.find(s => s.Id === schedule.id);
|
|
1699
|
+
if (!scheduleData) return;
|
|
1700
|
+
|
|
1697
1701
|
const characteristicType = schedule.characteristicType;
|
|
1698
1702
|
schedule.state = scheduleEnabled ? (scheduleData.Enabled ?? false) : false;
|
|
1699
1703
|
|
|
@@ -1712,6 +1716,8 @@ class DeviceAta extends EventEmitter {
|
|
|
1712
1716
|
if (this.scenes.length > 0) {
|
|
1713
1717
|
this.scenes.forEach((scene, i) => {
|
|
1714
1718
|
const sceneData = scenesOnServer.find(s => s.Id === scene.id);
|
|
1719
|
+
if (!sceneData) return;
|
|
1720
|
+
|
|
1715
1721
|
const characteristicType = scene.characteristicType;
|
|
1716
1722
|
scene.state = sceneData.Enabled;
|
|
1717
1723
|
|
package/src/deviceatw.js
CHANGED
|
@@ -1346,7 +1346,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1346
1346
|
//control
|
|
1347
1347
|
if (button.displayType > 3) {
|
|
1348
1348
|
if (this.logDebug) this.emit('debug', `Prepare button control ${name} service`);
|
|
1349
|
-
const buttonControlService = new
|
|
1349
|
+
const buttonControlService = new Service.Switch(serviceName1, `buttonControlService${deviceId} ${i}`);
|
|
1350
1350
|
buttonControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1351
1351
|
buttonControlService.setCharacteristic(Characteristic.ConfiguredName, serviceName1);
|
|
1352
1352
|
buttonControlService.getCharacteristic(Characteristic.On)
|
|
@@ -2089,6 +2089,8 @@ class DeviceAtw extends EventEmitter {
|
|
|
2089
2089
|
if (this.presets.length > 0) {
|
|
2090
2090
|
this.presets.forEach((preset, i) => {
|
|
2091
2091
|
const presetData = presetsOnServer.find(p => p.ID === preset.id);
|
|
2092
|
+
if (!presetData) return;
|
|
2093
|
+
|
|
2092
2094
|
const characteristicType = preset.characteristicType;
|
|
2093
2095
|
|
|
2094
2096
|
preset.state = presetData ? (presetData.Power === power
|
|
@@ -2116,6 +2118,8 @@ class DeviceAtw extends EventEmitter {
|
|
|
2116
2118
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
2117
2119
|
this.schedules.forEach((schedule, i) => {
|
|
2118
2120
|
const scheduleData = schedulesOnServer.find(s => s.Id === schedule.id);
|
|
2121
|
+
if (!scheduleData) return;
|
|
2122
|
+
|
|
2119
2123
|
const characteristicType = schedule.characteristicType;
|
|
2120
2124
|
schedule.state = scheduleEnabled ? (scheduleData.Enabled ?? false) : false;
|
|
2121
2125
|
|
|
@@ -2134,6 +2138,8 @@ class DeviceAtw extends EventEmitter {
|
|
|
2134
2138
|
if (this.scenes.length > 0) {
|
|
2135
2139
|
this.scenes.forEach((scene, i) => {
|
|
2136
2140
|
const sceneData = scenesOnServer.find(s => s.Id === scene.id);
|
|
2141
|
+
if (!sceneData) return;
|
|
2142
|
+
|
|
2137
2143
|
const characteristicType = scene.characteristicType;
|
|
2138
2144
|
scene.state = sceneData.Enabled;
|
|
2139
2145
|
|
package/src/deviceerv.js
CHANGED
|
@@ -1006,7 +1006,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1006
1006
|
//control
|
|
1007
1007
|
if (button.displayType > 3) {
|
|
1008
1008
|
if (this.logDebug) this.emit('debug', `Prepare button control ${name} service`);
|
|
1009
|
-
const buttonControlService = new
|
|
1009
|
+
const buttonControlService = new Service.Switch(serviceName1, `buttonControlService${deviceId} ${i}`);
|
|
1010
1010
|
buttonControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1011
1011
|
buttonControlService.setCharacteristic(Characteristic.ConfiguredName, serviceName1);
|
|
1012
1012
|
buttonControlService.getCharacteristic(Characteristic.On)
|
|
@@ -1438,6 +1438,8 @@ class DeviceErv extends EventEmitter {
|
|
|
1438
1438
|
if (this.presets.length > 0) {
|
|
1439
1439
|
this.presets.forEach((preset, i) => {
|
|
1440
1440
|
const presetData = presetsOnServer.find(p => p.ID === preset.id);
|
|
1441
|
+
if (!presetData) return;
|
|
1442
|
+
|
|
1441
1443
|
const characteristicType = preset.characteristicType;
|
|
1442
1444
|
|
|
1443
1445
|
preset.state = presetData ? (presetData.Power === power
|
|
@@ -1458,6 +1460,8 @@ class DeviceErv extends EventEmitter {
|
|
|
1458
1460
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
1459
1461
|
this.schedules.forEach((schedule, i) => {
|
|
1460
1462
|
const scheduleData = schedulesOnServer.find(s => s.Id === schedule.id);
|
|
1463
|
+
if (!scheduleData) return;
|
|
1464
|
+
|
|
1461
1465
|
const characteristicType = schedule.characteristicType;
|
|
1462
1466
|
schedule.state = scheduleEnabled ? (scheduleData.Enabled ?? false) : false;
|
|
1463
1467
|
|
|
@@ -1476,6 +1480,8 @@ class DeviceErv extends EventEmitter {
|
|
|
1476
1480
|
if (this.scenes.length > 0) {
|
|
1477
1481
|
this.scenes.forEach((scene, i) => {
|
|
1478
1482
|
const sceneData = scenesOnServer.find(s => s.Id === scene.id);
|
|
1483
|
+
if (!sceneData) return;
|
|
1484
|
+
|
|
1479
1485
|
const characteristicType = scene.characteristicType;
|
|
1480
1486
|
scene.state = sceneData.Enabled;
|
|
1481
1487
|
|
package/src/melcloudata.js
CHANGED
|
@@ -9,6 +9,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
9
9
|
constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
|
|
10
10
|
super();
|
|
11
11
|
this.accountType = account.type;
|
|
12
|
+
this.logSuccess = account.log?.success;
|
|
12
13
|
this.logWarn = account.log?.warn;
|
|
13
14
|
this.logError = account.log?.error;
|
|
14
15
|
this.logDebug = account.log?.debug;
|
|
@@ -26,8 +27,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
26
27
|
//set default values
|
|
27
28
|
this.deviceData = {};
|
|
28
29
|
this.headers = {};
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
30
|
+
this.socket = null;
|
|
31
|
+
this.connecting = false;
|
|
32
|
+
this.socketConnected = false;
|
|
33
|
+
this.heartbeat = null;
|
|
31
34
|
|
|
32
35
|
//lock flag
|
|
33
36
|
this.locks = false;
|
|
@@ -53,6 +56,20 @@ class MelCloudAta extends EventEmitter {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
cleanupSocket() {
|
|
60
|
+
if (this.heartbeat) {
|
|
61
|
+
clearInterval(this.heartbeat);
|
|
62
|
+
this.heartbeat = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.socket) {
|
|
66
|
+
try { this.socket.close(); } catch { }
|
|
67
|
+
this.socket = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.socketConnected = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
async checkState() {
|
|
57
74
|
try {
|
|
58
75
|
|
|
@@ -61,57 +78,102 @@ class MelCloudAta extends EventEmitter {
|
|
|
61
78
|
if (!devicesData) return;
|
|
62
79
|
|
|
63
80
|
this.headers = devicesData.Headers;
|
|
64
|
-
this.hash = devicesData.Hash;
|
|
65
81
|
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
82
|
+
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
83
|
+
|
|
66
84
|
if (this.accountType === 'melcloudhome') {
|
|
67
|
-
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
68
85
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
|
69
86
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
70
87
|
deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
|
|
71
88
|
deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
|
|
72
|
-
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
89
|
+
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
73
90
|
|
|
74
91
|
//read default temps
|
|
75
92
|
const temps = await this.functions.readData(this.defaultTempsFile, true);
|
|
76
93
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
77
94
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
//web cocket connection
|
|
97
|
+
if (!this.connecting && !this.socketConnected) {
|
|
98
|
+
this.connecting = true;
|
|
99
|
+
|
|
100
|
+
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
|
|
101
|
+
try {
|
|
102
|
+
const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
|
|
103
|
+
.on('error', (error) => {
|
|
104
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
105
|
+
socket.close();
|
|
106
|
+
})
|
|
107
|
+
.on('close', () => {
|
|
108
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
109
|
+
this.cleanupSocket();
|
|
110
|
+
})
|
|
111
|
+
.on('open', () => {
|
|
112
|
+
this.socket = socket;
|
|
113
|
+
this.socketConnected = true;
|
|
114
|
+
this.connecting = false;
|
|
115
|
+
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
116
|
+
|
|
117
|
+
// heartbeat
|
|
118
|
+
this.heartbeat = setInterval(() => {
|
|
119
|
+
if (socket.readyState === socket.OPEN) {
|
|
120
|
+
if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
121
|
+
socket.ping();
|
|
122
|
+
}
|
|
123
|
+
}, 30000);
|
|
124
|
+
})
|
|
125
|
+
.on('pong', () => {
|
|
126
|
+
if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
127
|
+
})
|
|
128
|
+
.on('message', (message) => {
|
|
129
|
+
const parsedMessage = JSON.parse(message);
|
|
130
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
131
|
+
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
132
|
+
if (parsedMessage.message === 'Forbidden') return;
|
|
114
133
|
|
|
134
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
135
|
+
if (!messageData) return;
|
|
136
|
+
|
|
137
|
+
let updateDeviceState = false;
|
|
138
|
+
const unitId = messageData?.id;
|
|
139
|
+
switch (unitId) {
|
|
140
|
+
case this.deviceId:
|
|
141
|
+
const messageType = parsedMessage[0].messageType;
|
|
142
|
+
switch (messageType) {
|
|
143
|
+
case 'unitStateChanged':
|
|
144
|
+
const settings = Object.fromEntries(
|
|
145
|
+
messageData.settings.map(({ name, value }) => {
|
|
146
|
+
let parsedValue = value;
|
|
147
|
+
if (value === "True") parsedValue = true;
|
|
148
|
+
else if (value === "False") parsedValue = false;
|
|
149
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
150
|
+
return [name, parsedValue];
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
Object.assign(deviceData.Device, settings);
|
|
154
|
+
updateDeviceState = true;
|
|
155
|
+
break;
|
|
156
|
+
case 'unitWifiSignalChanged':
|
|
157
|
+
Object.assign(deviceData, messageData.rssi);
|
|
158
|
+
updateDeviceState = true;
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (updateDeviceState) this.emit('deviceState', deviceData);
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
174
|
+
this.cleanupSocket();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
115
177
|
}
|
|
116
178
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
117
179
|
|
|
@@ -176,6 +238,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
176
238
|
let method = null
|
|
177
239
|
let payload = {};
|
|
178
240
|
let path = '';
|
|
241
|
+
let headers = this.headers;
|
|
242
|
+
let updateState = true;
|
|
179
243
|
switch (accountType) {
|
|
180
244
|
case "melcloud":
|
|
181
245
|
switch (flag) {
|
|
@@ -217,10 +281,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
217
281
|
method: 'POST',
|
|
218
282
|
baseURL: ApiUrls.BaseURL,
|
|
219
283
|
timeout: 30000,
|
|
220
|
-
headers:
|
|
284
|
+
headers: headers,
|
|
221
285
|
data: payload
|
|
222
286
|
});
|
|
223
|
-
this.updateData(deviceData);
|
|
287
|
+
this.updateData(deviceData, updateState);
|
|
224
288
|
return true;
|
|
225
289
|
case "melcloudhome":
|
|
226
290
|
switch (flag) {
|
|
@@ -233,7 +297,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
233
297
|
};
|
|
234
298
|
method = 'POST';
|
|
235
299
|
path = ApiUrlsHome.PostProtectionFrost;
|
|
236
|
-
|
|
300
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
|
|
237
301
|
break;
|
|
238
302
|
case 'overheatprotection':
|
|
239
303
|
payload = {
|
|
@@ -244,7 +308,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
244
308
|
};
|
|
245
309
|
method = 'POST';
|
|
246
310
|
path = ApiUrlsHome.PostProtectionOverheat;
|
|
247
|
-
|
|
311
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
|
|
248
312
|
break;
|
|
249
313
|
case 'holidaymode':
|
|
250
314
|
payload = {
|
|
@@ -255,7 +319,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
255
319
|
};
|
|
256
320
|
method = 'POST';
|
|
257
321
|
path = ApiUrlsHome.PostHolidayMode;
|
|
258
|
-
|
|
322
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
259
323
|
break;
|
|
260
324
|
case 'schedule':
|
|
261
325
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
@@ -266,7 +330,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
266
330
|
case 'scene':
|
|
267
331
|
method = 'PUT';
|
|
268
332
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
269
|
-
|
|
333
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
270
334
|
break;
|
|
271
335
|
default:
|
|
272
336
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
@@ -293,22 +357,24 @@ class MelCloudAta extends EventEmitter {
|
|
|
293
357
|
};
|
|
294
358
|
method = 'PUT';
|
|
295
359
|
path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
|
|
296
|
-
|
|
297
|
-
|
|
360
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
361
|
+
updateState = false;
|
|
362
|
+
break;
|
|
298
363
|
}
|
|
299
364
|
|
|
300
|
-
|
|
301
|
-
|
|
365
|
+
//sens payload
|
|
366
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
367
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
302
368
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
303
369
|
await axios(path, {
|
|
304
370
|
method: method,
|
|
305
371
|
baseURL: ApiUrlsHome.BaseURL,
|
|
306
372
|
timeout: 30000,
|
|
307
|
-
headers:
|
|
373
|
+
headers: headers,
|
|
308
374
|
data: payload
|
|
309
375
|
});
|
|
310
376
|
|
|
311
|
-
this.updateData(deviceData);
|
|
377
|
+
this.updateData(deviceData, updateState);
|
|
312
378
|
return true;
|
|
313
379
|
default:
|
|
314
380
|
return;
|
|
@@ -319,9 +385,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
319
385
|
}
|
|
320
386
|
}
|
|
321
387
|
|
|
322
|
-
updateData(deviceData) {
|
|
388
|
+
updateData(deviceData, updateState = true) {
|
|
323
389
|
this.locks = true;
|
|
324
|
-
this.emit('deviceState', deviceData);
|
|
390
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
325
391
|
|
|
326
392
|
setTimeout(() => {
|
|
327
393
|
this.locks = false
|
package/src/melcloudatw.js
CHANGED
|
@@ -8,6 +8,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
8
8
|
constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
|
|
9
9
|
super();
|
|
10
10
|
this.accountType = account.type;
|
|
11
|
+
this.logSuccess = account.log?.success;
|
|
11
12
|
this.logWarn = account.log?.warn;
|
|
12
13
|
this.logError = account.log?.error;
|
|
13
14
|
this.logDebug = account.log?.debug;
|
|
@@ -141,6 +142,8 @@ class MelCloudAtw extends EventEmitter {
|
|
|
141
142
|
let method = null
|
|
142
143
|
let payload = {};
|
|
143
144
|
let path = '';
|
|
145
|
+
let headers = this.headers;
|
|
146
|
+
let updateState = true;
|
|
144
147
|
switch (accountType) {
|
|
145
148
|
case "melcloud":
|
|
146
149
|
switch (flag) {
|
|
@@ -183,7 +186,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
183
186
|
method: 'POST',
|
|
184
187
|
baseURL: ApiUrls.BaseURL,
|
|
185
188
|
timeout: 30000,
|
|
186
|
-
headers:
|
|
189
|
+
headers: headers,
|
|
187
190
|
data: payload
|
|
188
191
|
});
|
|
189
192
|
this.updateData(deviceData);
|
|
@@ -199,18 +202,18 @@ class MelCloudAtw extends EventEmitter {
|
|
|
199
202
|
};
|
|
200
203
|
method = 'POST';
|
|
201
204
|
path = ApiUrlsHome.PostHolidayMode;
|
|
202
|
-
|
|
205
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
203
206
|
break;
|
|
204
207
|
case 'schedule':
|
|
205
208
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
206
209
|
method = 'PUT';
|
|
207
210
|
path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
208
|
-
|
|
211
|
+
headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
209
212
|
break;
|
|
210
213
|
case 'scene':
|
|
211
214
|
method = 'PUT';
|
|
212
215
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
213
|
-
|
|
216
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
214
217
|
break;
|
|
215
218
|
default:
|
|
216
219
|
payload = {
|
|
@@ -230,21 +233,22 @@ class MelCloudAtw extends EventEmitter {
|
|
|
230
233
|
};
|
|
231
234
|
method = 'PUT';
|
|
232
235
|
path = ApiUrlsHome.PutAtw.replace('deviceid', deviceData.DeviceID);
|
|
233
|
-
|
|
236
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
237
|
+
updateState = false;
|
|
234
238
|
break
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
|
|
238
|
-
|
|
241
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
242
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
239
243
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
240
244
|
await axios(path, {
|
|
241
245
|
method: method,
|
|
242
246
|
baseURL: ApiUrlsHome.BaseURL,
|
|
243
247
|
timeout: 30000,
|
|
244
|
-
headers:
|
|
248
|
+
headers: headers,
|
|
245
249
|
data: payload
|
|
246
250
|
});
|
|
247
|
-
this.updateData(deviceData);
|
|
251
|
+
this.updateData(deviceData, updateState);
|
|
248
252
|
return true;
|
|
249
253
|
default:
|
|
250
254
|
return;
|
|
@@ -255,9 +259,9 @@ class MelCloudAtw extends EventEmitter {
|
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
261
|
|
|
258
|
-
updateData(deviceData) {
|
|
262
|
+
updateData(deviceData, updateState = true) {
|
|
259
263
|
this.locks = true;
|
|
260
|
-
this.emit('deviceState', deviceData);
|
|
264
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
261
265
|
|
|
262
266
|
setTimeout(() => {
|
|
263
267
|
this.locks = false
|
package/src/melclouderv.js
CHANGED
|
@@ -8,6 +8,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
8
8
|
constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
|
|
9
9
|
super();
|
|
10
10
|
this.accountType = account.type;
|
|
11
|
+
this.logSuccess = account.log?.success;
|
|
11
12
|
this.logWarn = account.log?.warn;
|
|
12
13
|
this.logError = account.log?.error;
|
|
13
14
|
this.logDebug = account.log?.debug;
|
|
@@ -129,6 +130,8 @@ class MelCloudErv extends EventEmitter {
|
|
|
129
130
|
let method = null
|
|
130
131
|
let payload = {};
|
|
131
132
|
let path = '';
|
|
133
|
+
let headers = this.headers;
|
|
134
|
+
let updateState = true;
|
|
132
135
|
switch (accountType) {
|
|
133
136
|
case "melcloud":
|
|
134
137
|
switch (flag) {
|
|
@@ -186,7 +189,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
186
189
|
method: 'POST',
|
|
187
190
|
baseURL: ApiUrls.BaseURL,
|
|
188
191
|
timeout: 30000,
|
|
189
|
-
headers:
|
|
192
|
+
headers: headers,
|
|
190
193
|
data: payload
|
|
191
194
|
});
|
|
192
195
|
this.updateData(deviceData);
|
|
@@ -202,18 +205,18 @@ class MelCloudErv extends EventEmitter {
|
|
|
202
205
|
};
|
|
203
206
|
method = 'POST';
|
|
204
207
|
path = ApiUrlsHome.PostHolidayMode;
|
|
205
|
-
|
|
208
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
206
209
|
break;
|
|
207
210
|
case 'schedule':
|
|
208
211
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
209
212
|
method = 'PUT';
|
|
210
213
|
path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
211
|
-
|
|
214
|
+
headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
212
215
|
break;
|
|
213
216
|
case 'scene':
|
|
214
217
|
method = 'PUT';
|
|
215
218
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
216
|
-
|
|
219
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
217
220
|
break;
|
|
218
221
|
default:
|
|
219
222
|
if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
|
|
@@ -237,21 +240,22 @@ class MelCloudErv extends EventEmitter {
|
|
|
237
240
|
};
|
|
238
241
|
method = 'PUT';
|
|
239
242
|
path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
|
|
240
|
-
|
|
243
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
244
|
+
updateState = false;
|
|
241
245
|
break
|
|
242
246
|
}
|
|
243
247
|
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
249
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
246
250
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
247
251
|
await axios(path, {
|
|
248
252
|
method: method,
|
|
249
253
|
baseURL: ApiUrlsHome.BaseURL,
|
|
250
254
|
timeout: 30000,
|
|
251
|
-
headers:
|
|
255
|
+
headers: headers,
|
|
252
256
|
data: payload
|
|
253
257
|
});
|
|
254
|
-
this.updateData(deviceData);
|
|
258
|
+
this.updateData(deviceData, updateState);
|
|
255
259
|
return true;
|
|
256
260
|
default:
|
|
257
261
|
return;
|
|
@@ -262,9 +266,9 @@ class MelCloudErv extends EventEmitter {
|
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
updateData(deviceData) {
|
|
269
|
+
updateData(deviceData, updateState = true) {
|
|
266
270
|
this.locks = true;
|
|
267
|
-
this.emit('deviceState', deviceData);
|
|
271
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
268
272
|
|
|
269
273
|
setTimeout(() => {
|
|
270
274
|
this.locks = false
|
package/src/melcloudhome.js
CHANGED
|
@@ -16,6 +16,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
16
16
|
this.user = account.user;
|
|
17
17
|
this.passwd = account.passwd;
|
|
18
18
|
this.language = account.language;
|
|
19
|
+
this.logSuccess = account.log?.success;
|
|
19
20
|
this.logWarn = account.log?.warn;
|
|
20
21
|
this.logError = account.log?.error;
|
|
21
22
|
this.logDebug = account.log?.debug;
|
|
@@ -23,7 +24,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
23
24
|
this.buildingsFile = buildingsFile;
|
|
24
25
|
this.devicesFile = devicesFile;
|
|
25
26
|
this.headers = {};
|
|
26
|
-
this.
|
|
27
|
+
this.webSocketOptions = {};
|
|
27
28
|
|
|
28
29
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
29
30
|
.on('warn', warn => this.emit('warn', warn))
|
|
@@ -209,7 +210,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
209
210
|
devicesList.Devices = devices;
|
|
210
211
|
devicesList.Scenes = scenes;
|
|
211
212
|
devicesList.Headers = this.headers;
|
|
212
|
-
devicesList.
|
|
213
|
+
devicesList.WebSocketOptions = this.webSocketOptions;
|
|
213
214
|
|
|
214
215
|
await this.functions.saveData(this.devicesFile, devicesList);
|
|
215
216
|
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
@@ -278,15 +279,16 @@ class MelCloudHome extends EventEmitter {
|
|
|
278
279
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
279
280
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
280
281
|
|
|
281
|
-
// === CDP session
|
|
282
|
+
// === CDP session ===
|
|
283
|
+
let hash = null;
|
|
282
284
|
const client = await page.createCDPSession();
|
|
283
285
|
await client.send('Network.enable')
|
|
284
286
|
client.on('Network.webSocketCreated', ({ url }) => {
|
|
285
287
|
try {
|
|
286
288
|
if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
|
|
287
289
|
const params = new URL(url).searchParams;
|
|
288
|
-
|
|
289
|
-
if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${
|
|
290
|
+
hash = params.get('hash');
|
|
291
|
+
if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
|
|
290
292
|
}
|
|
291
293
|
} catch (err) {
|
|
292
294
|
this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
|
|
@@ -373,6 +375,16 @@ class MelCloudHome extends EventEmitter {
|
|
|
373
375
|
headers: headers
|
|
374
376
|
})
|
|
375
377
|
|
|
378
|
+
this.webSocketOptions = {
|
|
379
|
+
Hash: hash,
|
|
380
|
+
Headers: {
|
|
381
|
+
'Origin': ApiUrlsHome.BaseURL,
|
|
382
|
+
'Pragma': 'no-cache',
|
|
383
|
+
'Cache-Control': 'no-cache'
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
|
|
376
388
|
accountInfo.State = true;
|
|
377
389
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
378
390
|
await this.functions.saveData(this.accountFile, accountInfo);
|