homebridge-melcloud-control 4.3.0-beta.5 → 4.3.0-beta.51
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 +129 -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.51",
|
|
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,100 @@ 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
|
-
if (!this.logDebug) this.emit('warn', `Connection closed`);
|
|
96
|
-
})
|
|
97
|
-
.on('error', (error) => {
|
|
98
|
-
if (!this.logDebug) this.emit('warn', `Connected error: ${error}`);
|
|
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`);
|
|
101
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;
|
|
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) {
|
|
179
|
+
this.deviceData = deviceData;
|
|
180
|
+
this.emit('deviceState', deviceData);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
185
|
+
this.reconnect();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
102
188
|
}
|
|
103
189
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
104
190
|
|
|
@@ -163,6 +249,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
163
249
|
let method = null
|
|
164
250
|
let payload = {};
|
|
165
251
|
let path = '';
|
|
252
|
+
let headers = this.headers;
|
|
253
|
+
let updateState = true;
|
|
166
254
|
switch (accountType) {
|
|
167
255
|
case "melcloud":
|
|
168
256
|
switch (flag) {
|
|
@@ -204,10 +292,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
204
292
|
method: 'POST',
|
|
205
293
|
baseURL: ApiUrls.BaseURL,
|
|
206
294
|
timeout: 30000,
|
|
207
|
-
headers:
|
|
295
|
+
headers: headers,
|
|
208
296
|
data: payload
|
|
209
297
|
});
|
|
210
|
-
this.updateData(deviceData);
|
|
298
|
+
this.updateData(deviceData, updateState);
|
|
211
299
|
return true;
|
|
212
300
|
case "melcloudhome":
|
|
213
301
|
switch (flag) {
|
|
@@ -220,7 +308,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
220
308
|
};
|
|
221
309
|
method = 'POST';
|
|
222
310
|
path = ApiUrlsHome.PostProtectionFrost;
|
|
223
|
-
|
|
311
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
|
|
224
312
|
break;
|
|
225
313
|
case 'overheatprotection':
|
|
226
314
|
payload = {
|
|
@@ -231,7 +319,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
231
319
|
};
|
|
232
320
|
method = 'POST';
|
|
233
321
|
path = ApiUrlsHome.PostProtectionOverheat;
|
|
234
|
-
|
|
322
|
+
headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
|
|
235
323
|
break;
|
|
236
324
|
case 'holidaymode':
|
|
237
325
|
payload = {
|
|
@@ -242,7 +330,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
242
330
|
};
|
|
243
331
|
method = 'POST';
|
|
244
332
|
path = ApiUrlsHome.PostHolidayMode;
|
|
245
|
-
|
|
333
|
+
headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
|
|
246
334
|
break;
|
|
247
335
|
case 'schedule':
|
|
248
336
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
@@ -253,7 +341,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
253
341
|
case 'scene':
|
|
254
342
|
method = 'PUT';
|
|
255
343
|
path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
|
|
256
|
-
|
|
344
|
+
headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
|
|
257
345
|
break;
|
|
258
346
|
default:
|
|
259
347
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
@@ -280,22 +368,24 @@ class MelCloudAta extends EventEmitter {
|
|
|
280
368
|
};
|
|
281
369
|
method = 'PUT';
|
|
282
370
|
path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
|
|
283
|
-
|
|
284
|
-
|
|
371
|
+
headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
372
|
+
updateState = false;
|
|
373
|
+
break;
|
|
285
374
|
}
|
|
286
375
|
|
|
287
|
-
|
|
288
|
-
|
|
376
|
+
//sens payload
|
|
377
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
378
|
+
headers.Origin = ApiUrlsHome.Origin;
|
|
289
379
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
290
380
|
await axios(path, {
|
|
291
381
|
method: method,
|
|
292
382
|
baseURL: ApiUrlsHome.BaseURL,
|
|
293
383
|
timeout: 30000,
|
|
294
|
-
headers:
|
|
384
|
+
headers: headers,
|
|
295
385
|
data: payload
|
|
296
386
|
});
|
|
297
387
|
|
|
298
|
-
this.updateData(deviceData);
|
|
388
|
+
this.updateData(deviceData, updateState);
|
|
299
389
|
return true;
|
|
300
390
|
default:
|
|
301
391
|
return;
|
|
@@ -306,9 +396,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
306
396
|
}
|
|
307
397
|
}
|
|
308
398
|
|
|
309
|
-
updateData(deviceData) {
|
|
399
|
+
updateData(deviceData, updateState = true) {
|
|
310
400
|
this.locks = true;
|
|
311
|
-
this.emit('deviceState', deviceData);
|
|
401
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
312
402
|
|
|
313
403
|
setTimeout(() => {
|
|
314
404
|
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);
|