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 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: 3000000 }, { name: 'checkDevicesList', sampling: 3000 }] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
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.5",
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 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)
@@ -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 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)
@@ -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 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)
@@ -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
 
@@ -26,7 +26,10 @@ class MelCloudAta extends EventEmitter {
26
26
  //set default values
27
27
  this.deviceData = {};
28
28
  this.headers = {};
29
- this.start = true;
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.start) {
78
- const hash = '2db32d6f-c19c-4b1f-a0dd-1915420a5152';
108
+ if (!this.connecting && !this.socketConnected) {
109
+ this.connecting = true;
79
110
 
80
- const ws = new WebSocket(`wss://ws.melcloudhome.com/?hash=${hash}`, {
81
- headers: {
82
- 'Origin': 'https://melcloudhome.com',
83
- 'Pragma': 'no-cache',
84
- 'Cache-Control': 'no-cache'
85
- }
86
- });
87
- ws.on('open', () => {
88
- if (!this.logDebug) this.emit('warn', `Connected to MelCloudHome WebSocket`);
89
- this.start = false;
90
- })
91
- .on('message', (data) => {
92
- if (!this.logDebug) this.emit('warn', `Incoming message:', ${data.toString()}`);
93
- })
94
- .on('close', () => {
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: this.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
- this.headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
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
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
284
- break
371
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
372
+ updateState = false;
373
+ break;
285
374
  }
286
375
 
287
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
288
- this.headers.Origin = ApiUrlsHome.Origin;
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: this.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
@@ -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: this.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
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
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
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
235
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
236
+ updateState = false;
234
237
  break
235
238
  }
236
239
 
237
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
238
- this.headers.Origin = ApiUrlsHome.Origin;
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: this.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
@@ -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: this.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
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
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
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
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
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
242
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
243
+ updateState = false;
241
244
  break
242
245
  }
243
246
 
244
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
245
- this.headers.Origin = ApiUrlsHome.Origin;
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: this.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
@@ -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);