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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.3.0-beta.2",
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",
@@ -26,7 +26,9 @@ class MelCloudAta extends EventEmitter {
26
26
  //set default values
27
27
  this.deviceData = {};
28
28
  this.headers = {};
29
- this.start = true;
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';
@@ -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) {