homebridge-melcloud-control 4.3.0-beta.4 → 4.3.0-beta.41
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/index.js +1 -1
- package/package.json +1 -1
- package/src/constants.js +3 -1
- package/src/deviceata.js +1 -1
- package/src/deviceatw.js +1 -1
- package/src/deviceerv.js +1 -1
- package/src/melcloudata.js +126 -31
- package/src/melcloudhome.js +28 -12
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.41",
|
|
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)
|
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)
|
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)
|
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
|
|
|
@@ -67,40 +97,83 @@ class MelCloudAta extends EventEmitter {
|
|
|
67
97
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
68
98
|
deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
|
|
69
99
|
deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
|
|
70
|
-
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
100
|
+
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
|
|
71
101
|
|
|
72
102
|
//read default temps
|
|
73
103
|
const temps = await this.functions.readData(this.defaultTempsFile, true);
|
|
74
104
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
75
105
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
76
|
-
}
|
|
77
|
-
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
if (!this.connecting && !this.socketConnected) {
|
|
108
|
+
this.connecting = true;
|
|
109
|
+
|
|
110
|
+
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WsHeaders.hash}`;
|
|
111
|
+
try {
|
|
112
|
+
const socket = new WebSocket(url, { headers: devicesData.WsHeaders.headers })
|
|
113
|
+
.on('error', (error) => {
|
|
114
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
115
|
+
})
|
|
116
|
+
.on('close', () => {
|
|
117
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
118
|
+
this.reconnect();
|
|
119
|
+
})
|
|
120
|
+
.on('open', () => {
|
|
121
|
+
this.socket = socket;
|
|
122
|
+
this.socketConnected = true;
|
|
123
|
+
this.connecting = false;
|
|
124
|
+
this.emit('success', `Socket Connect Success`);
|
|
125
|
+
|
|
126
|
+
// heartbeat
|
|
127
|
+
this.heartbeat = setInterval(() => {
|
|
128
|
+
if (socket.readyState === socket.OPEN) {
|
|
129
|
+
if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
|
|
130
|
+
socket.ping();
|
|
131
|
+
}
|
|
132
|
+
}, 30000);
|
|
133
|
+
})
|
|
134
|
+
.on('pong', () => {
|
|
135
|
+
if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
|
|
136
|
+
})
|
|
137
|
+
.on('message', (message) => {
|
|
138
|
+
const parsedMessage = JSON.parse(message);
|
|
139
|
+
if (!this.logDebug) this.emit('warn', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
140
|
+
|
|
141
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
142
|
+
const unitId = messageData?.id;
|
|
143
|
+
|
|
144
|
+
switch (unitId) {
|
|
145
|
+
case this.deviceId:
|
|
146
|
+
const messageType = parsedMessage[0].messageType;
|
|
147
|
+
switch (messageType) {
|
|
148
|
+
case 'unitStateChanged':
|
|
149
|
+
const settings = Object.fromEntries(messageData.settings.map(({ name, value }) => {
|
|
150
|
+
let parsedValue = value;
|
|
151
|
+
if (value === "True") parsedValue = true;
|
|
152
|
+
else if (value === "False") parsedValue = false;
|
|
153
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
154
|
+
return [name, parsedValue];
|
|
155
|
+
}));
|
|
156
|
+
Object.assign(deviceData.Device, settings);
|
|
157
|
+
|
|
158
|
+
this.emit('deviceState', deviceData);
|
|
159
|
+
break;
|
|
160
|
+
case 'unitWiFiChanged':
|
|
161
|
+
const state = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
|
|
162
|
+
Object.assign(deviceData.Device, state);
|
|
81
163
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
164
|
+
this.emit('deviceState', deviceData);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
172
|
+
this.reconnect();
|
|
87
173
|
}
|
|
88
|
-
}
|
|
89
|
-
ws.on('open', () => {
|
|
90
|
-
if (!this.logDebug) this.emit('warn', `Connected to MelCloudHome WebSocket`);
|
|
91
|
-
this.start = false;
|
|
92
|
-
})
|
|
93
|
-
.on('message', (data) => {
|
|
94
|
-
if (!this.logDebug) this.emit('warn', `Incoming message:', ${data.toString()}`);
|
|
95
|
-
})
|
|
96
|
-
.on('close', () => {
|
|
97
|
-
if (!this.logDebug) this.emit('warn', `Connection closed`);
|
|
98
|
-
})
|
|
99
|
-
.on('error', (error) => {
|
|
100
|
-
if (!this.logDebug) this.emit('warn', `Connected error: ${error}`);
|
|
101
|
-
});
|
|
174
|
+
}
|
|
102
175
|
}
|
|
103
|
-
|
|
176
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
104
177
|
|
|
105
178
|
//device
|
|
106
179
|
const serialNumber = deviceData.SerialNumber || '4.0.0';
|
|
@@ -163,6 +236,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
163
236
|
let method = null
|
|
164
237
|
let payload = {};
|
|
165
238
|
let path = '';
|
|
239
|
+
let updateState = true;
|
|
166
240
|
switch (accountType) {
|
|
167
241
|
case "melcloud":
|
|
168
242
|
switch (flag) {
|
|
@@ -207,7 +281,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
207
281
|
headers: this.headers,
|
|
208
282
|
data: payload
|
|
209
283
|
});
|
|
210
|
-
this.updateData(deviceData);
|
|
284
|
+
this.updateData(deviceData, updateState);
|
|
211
285
|
return true;
|
|
212
286
|
case "melcloudhome":
|
|
213
287
|
switch (flag) {
|
|
@@ -281,7 +355,28 @@ class MelCloudAta extends EventEmitter {
|
|
|
281
355
|
method = 'PUT';
|
|
282
356
|
path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
|
|
283
357
|
this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
284
|
-
|
|
358
|
+
updateState = false;
|
|
359
|
+
|
|
360
|
+
const settings = [
|
|
361
|
+
{ "name": "Power", "value": deviceData.Device.Power },
|
|
362
|
+
{ "name": "SetTemperature", "value": deviceData.Device.SetTemperature },
|
|
363
|
+
{ "name": "SetFanSpeed", "value": deviceData.Device.SetFanSpeed },
|
|
364
|
+
{ "name": "OperationMode", "value": deviceData.Device.OperationMode },
|
|
365
|
+
{ "name": "VaneHorizontalDirection", "value": deviceData.Device.VaneHorizontalDirection },
|
|
366
|
+
{ "name": "VaneVerticalDirection", "value": deviceData.Device.VaneVerticalDirection }
|
|
367
|
+
]
|
|
368
|
+
const payload1 = [
|
|
369
|
+
{
|
|
370
|
+
messageType: "unitStateChanged",
|
|
371
|
+
Data: {
|
|
372
|
+
id: deviceData.DeviceID, // Twój ID jednostki
|
|
373
|
+
unitType: "ata", // tak jak incoming
|
|
374
|
+
settings: settings // czyli tablica: [{ name, value }, ...]
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
this.socket.send(JSON.stringify(payload1));
|
|
379
|
+
return;
|
|
285
380
|
}
|
|
286
381
|
|
|
287
382
|
this.headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
@@ -295,7 +390,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
295
390
|
data: payload
|
|
296
391
|
});
|
|
297
392
|
|
|
298
|
-
this.updateData(deviceData);
|
|
393
|
+
this.updateData(deviceData, updateState);
|
|
299
394
|
return true;
|
|
300
395
|
default:
|
|
301
396
|
return;
|
|
@@ -306,9 +401,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
306
401
|
}
|
|
307
402
|
}
|
|
308
403
|
|
|
309
|
-
updateData(deviceData) {
|
|
404
|
+
updateData(deviceData, updateState = true) {
|
|
310
405
|
this.locks = true;
|
|
311
|
-
this.emit('deviceState', deviceData);
|
|
406
|
+
if (updateState) this.emit('deviceState', deviceData);
|
|
312
407
|
|
|
313
408
|
setTimeout(() => {
|
|
314
409
|
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);
|