homebridge-melcloud-control 4.3.0-beta.2 → 4.3.0-beta.20
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/package.json +1 -1
- package/src/melcloudata.js +70 -27
- package/src/melcloudhome.js +17 -0
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.20",
|
|
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/melcloudata.js
CHANGED
|
@@ -26,7 +26,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
26
26
|
//set default values
|
|
27
27
|
this.deviceData = {};
|
|
28
28
|
this.headers = {};
|
|
29
|
-
this.
|
|
29
|
+
this.hash = null;
|
|
30
|
+
this.socket = null;
|
|
31
|
+
this.socketConnected = false;
|
|
30
32
|
|
|
31
33
|
//lock flag
|
|
32
34
|
this.locks = false;
|
|
@@ -52,6 +54,15 @@ class MelCloudAta extends EventEmitter {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
cleanupSocket = () => {
|
|
58
|
+
if (this.heartbeat) {
|
|
59
|
+
clearInterval(this.heartbeat);
|
|
60
|
+
this.heartbeat = null;
|
|
61
|
+
}
|
|
62
|
+
this.socket = null;
|
|
63
|
+
this.socketConnected = false;
|
|
64
|
+
};
|
|
65
|
+
|
|
55
66
|
async checkState() {
|
|
56
67
|
try {
|
|
57
68
|
|
|
@@ -60,6 +71,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
60
71
|
if (!devicesData) return;
|
|
61
72
|
|
|
62
73
|
this.headers = devicesData.Headers;
|
|
74
|
+
this.hash = devicesData.Hash;
|
|
63
75
|
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
64
76
|
if (this.accountType === 'melcloudhome') {
|
|
65
77
|
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
@@ -69,38 +81,69 @@ class MelCloudAta extends EventEmitter {
|
|
|
69
81
|
deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
|
|
70
82
|
deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
|
|
71
83
|
|
|
84
|
+
|
|
85
|
+
if (!this.socketConnected) {
|
|
86
|
+
const socket = new WebSocket(`wss://ws.melcloudhome.com/?hash=${devicesData.Hash}`, {
|
|
87
|
+
headers: {
|
|
88
|
+
'Origin': 'https://melcloudhome.com',
|
|
89
|
+
'Pragma': 'no-cache',
|
|
90
|
+
'Cache-Control': 'no-cache'
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.on('error', (error) => {
|
|
94
|
+
if (this.logDebug) this.emit('debug', `Socket error: ${error}`);
|
|
95
|
+
socket.close();
|
|
96
|
+
})
|
|
97
|
+
.on('close', () => {
|
|
98
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
99
|
+
this.cleanupSocket();
|
|
100
|
+
})
|
|
101
|
+
.on('open', () => {
|
|
102
|
+
// connect to device success
|
|
103
|
+
this.socket = socket;
|
|
104
|
+
this.socketConnected = true;
|
|
105
|
+
this.emit('success', `Socket Connect Success`);
|
|
106
|
+
|
|
107
|
+
// start heartbeat
|
|
108
|
+
this.heartbeat = setInterval(() => {
|
|
109
|
+
if (socket.readyState === socket.OPEN) {
|
|
110
|
+
if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
|
|
111
|
+
socket.ping();
|
|
112
|
+
}
|
|
113
|
+
}, 10000);
|
|
114
|
+
})
|
|
115
|
+
.on('pong', () => {
|
|
116
|
+
if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
|
|
117
|
+
})
|
|
118
|
+
.on('message', (message) => {
|
|
119
|
+
const parsedMessage = JSON.parse(message);
|
|
120
|
+
const messageType = parsedMessage[0].messageType;
|
|
121
|
+
const messageData = parsedMessage[0].Data;
|
|
122
|
+
const unitId = messageData.id;
|
|
123
|
+
const unitType = messageData.unitType;
|
|
124
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
125
|
+
if (!this.logDebug) this.emit('warn', `Incoming message:', ${stringifyMessage}`);
|
|
126
|
+
|
|
127
|
+
switch (unitId) {
|
|
128
|
+
case this.deviceId:
|
|
129
|
+
const settings = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
|
|
130
|
+
if (!this.logDebug) this.emit('warn', `Settings:', ${settings}`);
|
|
131
|
+
Object.assign(deviceData.Device, settings);
|
|
132
|
+
|
|
133
|
+
//emit state
|
|
134
|
+
this.emit('deviceState', deviceData);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
72
140
|
//read default temps
|
|
73
141
|
const temps = await this.functions.readData(this.defaultTempsFile, true);
|
|
74
142
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
75
143
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
76
|
-
}
|
|
77
|
-
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
78
144
|
|
|
79
|
-
if (this.start) {
|
|
80
|
-
const hash = '2db32d6f-c19c-4b1f-a0dd-1915420a5152';
|
|
81
|
-
|
|
82
|
-
const ws = new WebSocket(`wss://ws.melcloudhome.com/?hash=${hash}`, {
|
|
83
|
-
headers: {
|
|
84
|
-
'Origin': 'https://melcloudhome.com',
|
|
85
|
-
'Pragma': 'no-cache',
|
|
86
|
-
'Cache-Control': 'no-cache'
|
|
87
|
-
}
|
|
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
|
-
});
|
|
102
145
|
}
|
|
103
|
-
|
|
146
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
104
147
|
|
|
105
148
|
//device
|
|
106
149
|
const serialNumber = deviceData.SerialNumber || '4.0.0';
|
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.hash = null;
|
|
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.Hash = this.hash;
|
|
211
213
|
|
|
212
214
|
await this.functions.saveData(this.devicesFile, devicesList);
|
|
213
215
|
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
@@ -276,6 +278,21 @@ class MelCloudHome extends EventEmitter {
|
|
|
276
278
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
277
279
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
278
280
|
|
|
281
|
+
// === CDP session (modern API) ===
|
|
282
|
+
const client = await page.createCDPSession();
|
|
283
|
+
await client.send('Network.enable')
|
|
284
|
+
client.on('Network.webSocketCreated', ({ url }) => {
|
|
285
|
+
try {
|
|
286
|
+
if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
|
|
287
|
+
const params = new URL(url).searchParams;
|
|
288
|
+
this.hash = params.get('hash');
|
|
289
|
+
if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${melcloudHash}`);
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
279
296
|
try {
|
|
280
297
|
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
281
298
|
} catch (error) {
|