homebridge-melcloud-control 4.3.0-beta.5 → 4.3.0-beta.50
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 +8 -0
- package/index.js +1 -1
- 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 +126 -39
- package/src/melcloudatw.js +14 -11
- package/src/melclouderv.js +14 -11
- package/src/melcloudhome.js +28 -12
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,14 @@ 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] - (xx.11.2025)
|
|
26
|
+
|
|
27
|
+
## Changes
|
|
28
|
+
|
|
29
|
+
- use web socket for Real Time communication with MELCloud Home
|
|
30
|
+
- readme updated
|
|
31
|
+
- cleanup
|
|
32
|
+
|
|
25
33
|
# [4.2.8] - (18.11.2025)
|
|
26
34
|
|
|
27
35
|
## Changes
|
package/index.js
CHANGED
|
@@ -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: 3600000 }, { 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.0-beta.
|
|
4
|
+
"version": "4.3.0-beta.50",
|
|
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
|
@@ -26,7 +26,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
26
26
|
//set default values
|
|
27
27
|
this.deviceData = {};
|
|
28
28
|
this.headers = {};
|
|
29
|
-
this.
|
|
29
|
+
this.socket = null;
|
|
30
|
+
this.connecting = false;
|
|
31
|
+
this.socketConnected = false;
|
|
32
|
+
this.reconnectDelay = 1000;
|
|
30
33
|
|
|
31
34
|
//lock flag
|
|
32
35
|
this.locks = false;
|
|
@@ -52,6 +55,33 @@ class MelCloudAta extends EventEmitter {
|
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
cleanupSocket() {
|
|
59
|
+
if (this.heartbeat) {
|
|
60
|
+
clearInterval(this.heartbeat);
|
|
61
|
+
this.heartbeat = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.socket) {
|
|
65
|
+
try { this.socket.close(); } catch { }
|
|
66
|
+
this.socket = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.socketConnected = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
reconnect() {
|
|
73
|
+
this.cleanupSocket();
|
|
74
|
+
|
|
75
|
+
const delay = Math.min(this.reconnectDelay, 30000); // max 30s
|
|
76
|
+
if (this.logDebug) this.emit('debug', `Reconnect in ${delay}ms`);
|
|
77
|
+
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
this.reconnectDelay *= 2;
|
|
80
|
+
this.reconnectDelay = Math.max(1000, this.reconnectDelay);
|
|
81
|
+
this.connectSocket(deviceData);
|
|
82
|
+
}, delay);
|
|
83
|
+
};
|
|
84
|
+
|
|
55
85
|
async checkState() {
|
|
56
86
|
try {
|
|
57
87
|
|
|
@@ -61,44 +91,97 @@ class MelCloudAta extends EventEmitter {
|
|
|
61
91
|
|
|
62
92
|
this.headers = devicesData.Headers;
|
|
63
93
|
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
94
|
+
|
|
64
95
|
if (this.accountType === 'melcloudhome') {
|
|
65
96
|
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
66
97
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
|
67
98
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
68
99
|
deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
|
|
69
100
|
deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
|
|
70
|
-
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
101
|
+
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
71
102
|
|
|
72
103
|
//read default temps
|
|
73
104
|
const temps = await this.functions.readData(this.defaultTempsFile, true);
|
|
74
105
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
75
106
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
76
107
|
|
|
77
|
-
if (this.
|
|
78
|
-
|
|
108
|
+
if (!this.connecting && !this.socketConnected) {
|
|
109
|
+
this.connecting = true;
|
|
79
110
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
'
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WsHeaders.hash}`;
|
|
112
|
+
try {
|
|
113
|
+
const socket = new WebSocket(url, { headers: devicesData.WsHeaders.headers })
|
|
114
|
+
.on('error', (error) => {
|
|
115
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
116
|
+
})
|
|
117
|
+
.on('close', () => {
|
|
118
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
119
|
+
this.reconnect();
|
|
120
|
+
})
|
|
121
|
+
.on('open', () => {
|
|
122
|
+
this.socket = socket;
|
|
123
|
+
this.socketConnected = true;
|
|
124
|
+
this.connecting = false;
|
|
125
|
+
this.emit('success', `Socket Connect Success`);
|
|
126
|
+
|
|
127
|
+
// heartbeat
|
|
128
|
+
this.heartbeat = setInterval(() => {
|
|
129
|
+
if (socket.readyState === socket.OPEN) {
|
|
130
|
+
if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
|
|
131
|
+
socket.ping();
|
|
132
|
+
}
|
|
133
|
+
}, 30000);
|
|
134
|
+
})
|
|
135
|
+
.on('pong', () => {
|
|
136
|
+
if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
|
|
137
|
+
})
|
|
138
|
+
.on('message', (message) => {
|
|
139
|
+
const parsedMessage = JSON.parse(message);
|
|
140
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
141
|
+
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
142
|
+
if (parsedMessage.message === 'Forbidden') return;
|
|
143
|
+
|
|
144
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
145
|
+
if (!messageData) return;
|
|
101
146
|
|
|
147
|
+
let updateDeviceState = false;
|
|
148
|
+
const unitId = messageData?.id;
|
|
149
|
+
switch (unitId) {
|
|
150
|
+
case this.deviceId:
|
|
151
|
+
const messageType = parsedMessage[0].messageType;
|
|
152
|
+
switch (messageType) {
|
|
153
|
+
case 'unitStateChanged':
|
|
154
|
+
const settings = Object.fromEntries(messageData.settings.map(({ name, value }) => {
|
|
155
|
+
let parsedValue = value;
|
|
156
|
+
if (value === "True") parsedValue = true;
|
|
157
|
+
else if (value === "False") parsedValue = false;
|
|
158
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
159
|
+
return [name, parsedValue];
|
|
160
|
+
}));
|
|
161
|
+
Object.assign(deviceData.Device, settings);
|
|
162
|
+
updateDeviceState = true;
|
|
163
|
+
break;
|
|
164
|
+
case 'unitWifiSignalChanged':
|
|
165
|
+
Object.assign(deviceData, messageData.rssi);
|
|
166
|
+
updateDeviceState = true;
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
if (this.logWarn) this.emit('warn', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
if (this.logWarn) this.emit('warn', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (updateDeviceState) this.emit('deviceState', deviceData);
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
182
|
+
this.reconnect();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
102
185
|
}
|
|
103
186
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
104
187
|
|
|
@@ -163,6 +246,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
163
246
|
let method = null
|
|
164
247
|
let payload = {};
|
|
165
248
|
let path = '';
|
|
249
|
+
let headers = this.headers;
|
|
250
|
+
let updateState = true;
|
|
166
251
|
switch (accountType) {
|
|
167
252
|
case "melcloud":
|
|
168
253
|
switch (flag) {
|
|
@@ -204,10 +289,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
204
289
|
method: 'POST',
|
|
205
290
|
baseURL: ApiUrls.BaseURL,
|
|
206
291
|
timeout: 30000,
|
|
207
|
-
headers:
|
|
292
|
+
headers: headers,
|
|
208
293
|
data: payload
|
|
209
294
|
});
|
|
210
|
-
this.updateData(deviceData);
|
|
295
|
+
this.updateData(deviceData, updateState);
|
|
211
296
|
return true;
|
|
212
297
|
case "melcloudhome":
|
|
213
298
|
switch (flag) {
|
|
@@ -220,7 +305,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
220
305
|
};
|
|
221
306
|
method = 'POST';
|
|
222
307
|
path = ApiUrlsHome.PostProtectionFrost;
|
|
223
|
-
|
|
308
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
|
|
224
309
|
break;
|
|
225
310
|
case 'overheatprotection':
|
|
226
311
|
payload = {
|
|
@@ -231,7 +316,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
231
316
|
};
|
|
232
317
|
method = 'POST';
|
|
233
318
|
path = ApiUrlsHome.PostProtectionOverheat;
|
|
234
|
-
|
|
319
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
|
|
235
320
|
break;
|
|
236
321
|
case 'holidaymode':
|
|
237
322
|
payload = {
|
|
@@ -242,7 +327,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
242
327
|
};
|
|
243
328
|
method = 'POST';
|
|
244
329
|
path = ApiUrlsHome.PostHolidayMode;
|
|
245
|
-
|
|
330
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
246
331
|
break;
|
|
247
332
|
case 'schedule':
|
|
248
333
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
@@ -253,7 +338,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
253
338
|
case 'scene':
|
|
254
339
|
method = 'PUT';
|
|
255
340
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
256
|
-
|
|
341
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
257
342
|
break;
|
|
258
343
|
default:
|
|
259
344
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
@@ -280,22 +365,24 @@ class MelCloudAta extends EventEmitter {
|
|
|
280
365
|
};
|
|
281
366
|
method = 'PUT';
|
|
282
367
|
path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
|
|
283
|
-
|
|
284
|
-
|
|
368
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
369
|
+
updateState = false;
|
|
370
|
+
break;
|
|
285
371
|
}
|
|
286
372
|
|
|
287
|
-
|
|
288
|
-
|
|
373
|
+
//sens payload
|
|
374
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
375
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
289
376
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
290
377
|
await axios(path, {
|
|
291
378
|
method: method,
|
|
292
379
|
baseURL: ApiUrlsHome.BaseURL,
|
|
293
380
|
timeout: 30000,
|
|
294
|
-
headers:
|
|
381
|
+
headers: headers,
|
|
295
382
|
data: payload
|
|
296
383
|
});
|
|
297
384
|
|
|
298
|
-
this.updateData(deviceData);
|
|
385
|
+
this.updateData(deviceData, updateState);
|
|
299
386
|
return true;
|
|
300
387
|
default:
|
|
301
388
|
return;
|
|
@@ -306,9 +393,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
306
393
|
}
|
|
307
394
|
}
|
|
308
395
|
|
|
309
|
-
updateData(deviceData) {
|
|
396
|
+
updateData(deviceData, updateState = true) {
|
|
310
397
|
this.locks = true;
|
|
311
|
-
this.emit('deviceState', deviceData);
|
|
398
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
312
399
|
|
|
313
400
|
setTimeout(() => {
|
|
314
401
|
this.locks = false
|
package/src/melcloudatw.js
CHANGED
|
@@ -141,6 +141,8 @@ class MelCloudAtw extends EventEmitter {
|
|
|
141
141
|
let method = null
|
|
142
142
|
let payload = {};
|
|
143
143
|
let path = '';
|
|
144
|
+
let headers = this.headers;
|
|
145
|
+
let updateState = true;
|
|
144
146
|
switch (accountType) {
|
|
145
147
|
case "melcloud":
|
|
146
148
|
switch (flag) {
|
|
@@ -183,7 +185,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
183
185
|
method: 'POST',
|
|
184
186
|
baseURL: ApiUrls.BaseURL,
|
|
185
187
|
timeout: 30000,
|
|
186
|
-
headers:
|
|
188
|
+
headers: headers,
|
|
187
189
|
data: payload
|
|
188
190
|
});
|
|
189
191
|
this.updateData(deviceData);
|
|
@@ -199,18 +201,18 @@ class MelCloudAtw extends EventEmitter {
|
|
|
199
201
|
};
|
|
200
202
|
method = 'POST';
|
|
201
203
|
path = ApiUrlsHome.PostHolidayMode;
|
|
202
|
-
|
|
204
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
203
205
|
break;
|
|
204
206
|
case 'schedule':
|
|
205
207
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
206
208
|
method = 'PUT';
|
|
207
209
|
path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
208
|
-
|
|
210
|
+
headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
209
211
|
break;
|
|
210
212
|
case 'scene':
|
|
211
213
|
method = 'PUT';
|
|
212
214
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
213
|
-
|
|
215
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
214
216
|
break;
|
|
215
217
|
default:
|
|
216
218
|
payload = {
|
|
@@ -230,21 +232,22 @@ class MelCloudAtw extends EventEmitter {
|
|
|
230
232
|
};
|
|
231
233
|
method = 'PUT';
|
|
232
234
|
path = ApiUrlsHome.PutAtw.replace('deviceid', deviceData.DeviceID);
|
|
233
|
-
|
|
235
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
236
|
+
updateState = false;
|
|
234
237
|
break
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
241
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
239
242
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
240
243
|
await axios(path, {
|
|
241
244
|
method: method,
|
|
242
245
|
baseURL: ApiUrlsHome.BaseURL,
|
|
243
246
|
timeout: 30000,
|
|
244
|
-
headers:
|
|
247
|
+
headers: headers,
|
|
245
248
|
data: payload
|
|
246
249
|
});
|
|
247
|
-
this.updateData(deviceData);
|
|
250
|
+
this.updateData(deviceData, updateState);
|
|
248
251
|
return true;
|
|
249
252
|
default:
|
|
250
253
|
return;
|
|
@@ -255,9 +258,9 @@ class MelCloudAtw extends EventEmitter {
|
|
|
255
258
|
}
|
|
256
259
|
}
|
|
257
260
|
|
|
258
|
-
updateData(deviceData) {
|
|
261
|
+
updateData(deviceData, updateState = true) {
|
|
259
262
|
this.locks = true;
|
|
260
|
-
this.emit('deviceState', deviceData);
|
|
263
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
261
264
|
|
|
262
265
|
setTimeout(() => {
|
|
263
266
|
this.locks = false
|
package/src/melclouderv.js
CHANGED
|
@@ -129,6 +129,8 @@ class MelCloudErv extends EventEmitter {
|
|
|
129
129
|
let method = null
|
|
130
130
|
let payload = {};
|
|
131
131
|
let path = '';
|
|
132
|
+
let headers = this.headers;
|
|
133
|
+
let updateState = true;
|
|
132
134
|
switch (accountType) {
|
|
133
135
|
case "melcloud":
|
|
134
136
|
switch (flag) {
|
|
@@ -186,7 +188,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
186
188
|
method: 'POST',
|
|
187
189
|
baseURL: ApiUrls.BaseURL,
|
|
188
190
|
timeout: 30000,
|
|
189
|
-
headers:
|
|
191
|
+
headers: headers,
|
|
190
192
|
data: payload
|
|
191
193
|
});
|
|
192
194
|
this.updateData(deviceData);
|
|
@@ -202,18 +204,18 @@ class MelCloudErv extends EventEmitter {
|
|
|
202
204
|
};
|
|
203
205
|
method = 'POST';
|
|
204
206
|
path = ApiUrlsHome.PostHolidayMode;
|
|
205
|
-
|
|
207
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
206
208
|
break;
|
|
207
209
|
case 'schedule':
|
|
208
210
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
209
211
|
method = 'PUT';
|
|
210
212
|
path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
211
|
-
|
|
213
|
+
headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
|
|
212
214
|
break;
|
|
213
215
|
case 'scene':
|
|
214
216
|
method = 'PUT';
|
|
215
217
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
216
|
-
|
|
218
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
217
219
|
break;
|
|
218
220
|
default:
|
|
219
221
|
if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
|
|
@@ -237,21 +239,22 @@ class MelCloudErv extends EventEmitter {
|
|
|
237
239
|
};
|
|
238
240
|
method = 'PUT';
|
|
239
241
|
path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
|
|
240
|
-
|
|
242
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
243
|
+
updateState = false;
|
|
241
244
|
break
|
|
242
245
|
}
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
248
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
246
249
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
247
250
|
await axios(path, {
|
|
248
251
|
method: method,
|
|
249
252
|
baseURL: ApiUrlsHome.BaseURL,
|
|
250
253
|
timeout: 30000,
|
|
251
|
-
headers:
|
|
254
|
+
headers: headers,
|
|
252
255
|
data: payload
|
|
253
256
|
});
|
|
254
|
-
this.updateData(deviceData);
|
|
257
|
+
this.updateData(deviceData, updateState);
|
|
255
258
|
return true;
|
|
256
259
|
default:
|
|
257
260
|
return;
|
|
@@ -262,9 +265,9 @@ class MelCloudErv extends EventEmitter {
|
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
267
|
|
|
265
|
-
updateData(deviceData) {
|
|
268
|
+
updateData(deviceData, updateState = true) {
|
|
266
269
|
this.locks = true;
|
|
267
|
-
this.emit('deviceState', deviceData);
|
|
270
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
268
271
|
|
|
269
272
|
setTimeout(() => {
|
|
270
273
|
this.locks = false
|
package/src/melcloudhome.js
CHANGED
|
@@ -23,6 +23,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
23
23
|
this.buildingsFile = buildingsFile;
|
|
24
24
|
this.devicesFile = devicesFile;
|
|
25
25
|
this.headers = {};
|
|
26
|
+
this.wsHeaders = {};
|
|
26
27
|
|
|
27
28
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
28
29
|
.on('warn', warn => this.emit('warn', warn))
|
|
@@ -208,6 +209,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
208
209
|
devicesList.Devices = devices;
|
|
209
210
|
devicesList.Scenes = scenes;
|
|
210
211
|
devicesList.Headers = this.headers;
|
|
212
|
+
devicesList.WsHeaders = this.wsHeaders;
|
|
211
213
|
|
|
212
214
|
await this.functions.saveData(this.devicesFile, devicesList);
|
|
213
215
|
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
@@ -273,21 +275,25 @@ class MelCloudHome extends EventEmitter {
|
|
|
273
275
|
const page = await browser.newPage();
|
|
274
276
|
page.on('error', error => this.emit('error', `Page crashed: ${error.message}`));
|
|
275
277
|
page.on('pageerror', error => this.emit('error', `Browser error: ${error.message}`));
|
|
276
|
-
// Zmienna na znaleziony hash
|
|
277
|
-
let melcloudHash = null;
|
|
278
|
-
|
|
279
|
-
// Nasłuchuj WebSocketów
|
|
280
|
-
page.on('request', req => {
|
|
281
|
-
if (req.url().startsWith('wss://ws.melcloudhome.com/?hash=')) {
|
|
282
|
-
const url = req.url();
|
|
283
|
-
const params = new URL(url).searchParams;
|
|
284
|
-
melcloudHash = params.get('hash');
|
|
285
|
-
if (!this.logDebug) this.emit('warn', `FOUND HASH: ${melcloudHash}`);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
278
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
289
279
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
290
280
|
|
|
281
|
+
// === CDP session ===
|
|
282
|
+
let hash = null;
|
|
283
|
+
const client = await page.createCDPSession();
|
|
284
|
+
await client.send('Network.enable')
|
|
285
|
+
client.on('Network.webSocketCreated', ({ url }) => {
|
|
286
|
+
try {
|
|
287
|
+
if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
|
|
288
|
+
const params = new URL(url).searchParams;
|
|
289
|
+
hash = params.get('hash');
|
|
290
|
+
if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
291
297
|
try {
|
|
292
298
|
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
293
299
|
} catch (error) {
|
|
@@ -368,6 +374,16 @@ class MelCloudHome extends EventEmitter {
|
|
|
368
374
|
headers: headers
|
|
369
375
|
})
|
|
370
376
|
|
|
377
|
+
this.wsHeaders = {
|
|
378
|
+
hash: hash,
|
|
379
|
+
headers: {
|
|
380
|
+
'Origin': ApiUrlsHome.BaseURL,
|
|
381
|
+
'Pragma': 'no-cache',
|
|
382
|
+
'Cache-Control': 'no-cache'
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
|
|
371
387
|
accountInfo.State = true;
|
|
372
388
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
373
389
|
await this.functions.saveData(this.accountFile, accountInfo);
|