homebridge-melcloud-control 4.3.0-beta.2 → 4.3.0-beta.21

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.21",
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/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 serviceType(serviceName, `buttonControlService${deviceId} ${i}`);
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 serviceType(serviceName1, `buttonControlService${deviceId} ${i}`);
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 serviceType(serviceName1, `buttonControlService${deviceId} ${i}`);
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)
@@ -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,68 @@ 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
+ if (!this.socketConnected) {
85
+ const socket = new WebSocket(`wss://ws.melcloudhome.com/?hash=${devicesData.Hash}`, {
86
+ headers: {
87
+ 'Origin': 'https://melcloudhome.com',
88
+ 'Pragma': 'no-cache',
89
+ 'Cache-Control': 'no-cache'
90
+ }
91
+ })
92
+ .on('error', (error) => {
93
+ if (this.logDebug) this.emit('debug', `Socket error: ${error}`);
94
+ socket.close();
95
+ })
96
+ .on('close', () => {
97
+ if (this.logDebug) this.emit('debug', `Socket closed`);
98
+ this.cleanupSocket();
99
+ })
100
+ .on('open', () => {
101
+ // connect to device success
102
+ this.socket = socket;
103
+ this.socketConnected = true;
104
+ this.emit('success', `Socket Connect Success`);
105
+
106
+ // start heartbeat
107
+ this.heartbeat = setInterval(() => {
108
+ if (socket.readyState === socket.OPEN) {
109
+ if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
110
+ socket.ping();
111
+ }
112
+ }, 30000);
113
+ })
114
+ .on('pong', () => {
115
+ if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
116
+ })
117
+ .on('message', (message) => {
118
+ const parsedMessage = JSON.parse(message);
119
+ const messageType = parsedMessage[0].messageType;
120
+ const messageData = parsedMessage[0].Data;
121
+ const unitId = messageData.id;
122
+ const unitType = messageData.unitType;
123
+ const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
124
+ if (!this.logDebug) this.emit('warn', `Incoming message:', ${stringifyMessage}`);
125
+
126
+ switch (unitId) {
127
+ case this.deviceId:
128
+ const settings = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
129
+ if (!this.logDebug) this.emit('warn', `Settings:', ${settings}`);
130
+ Object.assign(deviceData.Device, settings);
131
+
132
+ //emit state
133
+ this.emit('deviceState', deviceData);
134
+ break;
135
+ }
136
+ });
137
+ }
138
+
72
139
  //read default temps
73
140
  const temps = await this.functions.readData(this.defaultTempsFile, true);
74
141
  deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
75
142
  deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
76
- }
77
- if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
78
-
79
- if (this.start) {
80
- const hash = '2db32d6f-c19c-4b1f-a0dd-1915420a5152';
81
143
 
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
144
  }
103
-
145
+ if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
104
146
 
105
147
  //device
106
148
  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 ===
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) {