homebridge-melcloud-control 4.3.0-beta.8 → 4.3.0

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,13 @@ 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] - (20.11.2025)
26
+
27
+ ## Changes
28
+
29
+ - use web socket for Real Time communication with MELCloud Home
30
+ - cleanup
31
+
25
32
  # [4.2.8] - (18.11.2025)
26
33
 
27
34
  ## Changes
package/index.js CHANGED
@@ -85,12 +85,12 @@ class MelCloudPlatform {
85
85
  .on('start', async () => {
86
86
  try {
87
87
  //melcloud account
88
- const melCloud = account.type === 'melcloud' ? new MelCloud(account, accountFile, buildingsFile, devicesFile, true) : new MelCloudHome(account, accountFile, buildingsFile, devicesFile, true);
89
- melCloud.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
90
- .on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
91
- .on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
92
- .on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${msg}`))
93
- .on('error', (msg) => logLevel.error && log.error(`${accountName}, ${msg}`));
88
+ const melCloud = account.type === 'melcloud' ? new MelCloud(account, accountFile, buildingsFile, devicesFile, true) : new MelCloudHome(account, accountFile, buildingsFile, devicesFile, true)
89
+ .on('success', (msg) => log.success(`${accountName}, ${msg}`))
90
+ .on('info', (msg) => log.info(`${accountName}, ${msg}`))
91
+ .on('debug', (msg) => log.info(`${accountName}, debug: ${msg}`))
92
+ .on('warn', (msg) => log.warn(`${accountName}, ${msg}`))
93
+ .on('error', (msg) => log.error(`${accountName}, ${msg}`));
94
94
 
95
95
  //connect
96
96
  let accountInfo;
@@ -181,11 +181,11 @@ class MelCloudPlatform {
181
181
  }
182
182
 
183
183
  configuredDevice.on('devInfo', (info) => logLevel.devInfo && log.info(info))
184
- .on('success', (msg) => logLevel.success && log.success(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
185
- .on('info', (msg) => logLevel.info && log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
186
- .on('debug', (msg) => logLevel.debug && log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, debug: ${msg}`))
187
- .on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
188
- .on('error', (msg) => logLevel.error && log.error(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`));
184
+ .on('success', (msg) => log.success(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
185
+ .on('info', (msg) => log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
186
+ .on('debug', (msg) => log.info(`${accountName}, ${deviceTypeString}, ${deviceName}, debug: ${msg}`))
187
+ .on('warn', (msg) => log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`))
188
+ .on('error', (msg) => log.error(`${accountName}, ${deviceTypeString}, ${deviceName}, ${msg}`));
189
189
 
190
190
  const accessory = await configuredDevice.start();
191
191
  if (accessory) {
@@ -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: 3300000 }, { 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.8",
4
+ "version": "4.3.0",
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
 
@@ -9,6 +9,7 @@ class MelCloudAta extends EventEmitter {
9
9
  constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
10
10
  super();
11
11
  this.accountType = account.type;
12
+ this.logSuccess = account.log?.success;
12
13
  this.logWarn = account.log?.warn;
13
14
  this.logError = account.log?.error;
14
15
  this.logDebug = account.log?.debug;
@@ -26,8 +27,10 @@ class MelCloudAta extends EventEmitter {
26
27
  //set default values
27
28
  this.deviceData = {};
28
29
  this.headers = {};
29
- this.hash = null;
30
- this.start = true;
30
+ this.socket = null;
31
+ this.connecting = false;
32
+ this.socketConnected = false;
33
+ this.heartbeat = null;
31
34
 
32
35
  //lock flag
33
36
  this.locks = false;
@@ -53,6 +56,20 @@ class MelCloudAta extends EventEmitter {
53
56
  }
54
57
  }
55
58
 
59
+ cleanupSocket() {
60
+ if (this.heartbeat) {
61
+ clearInterval(this.heartbeat);
62
+ this.heartbeat = null;
63
+ }
64
+
65
+ if (this.socket) {
66
+ try { this.socket.close(); } catch { }
67
+ this.socket = null;
68
+ }
69
+
70
+ this.socketConnected = false;
71
+ }
72
+
56
73
  async checkState() {
57
74
  try {
58
75
 
@@ -61,44 +78,101 @@ class MelCloudAta extends EventEmitter {
61
78
  if (!devicesData) return;
62
79
 
63
80
  this.headers = devicesData.Headers;
64
- this.hash = devicesData.Hash;
65
81
  const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
82
+ deviceData.Scenes = devicesData.Scenes ?? [];
83
+
66
84
  if (this.accountType === 'melcloudhome') {
67
- deviceData.Scenes = devicesData.Scenes ?? [];
68
85
  deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
69
86
  deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
70
87
  deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
71
88
  deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
72
- deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
89
+ deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
73
90
 
74
91
  //read default temps
75
92
  const temps = await this.functions.readData(this.defaultTempsFile, true);
76
93
  deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
77
94
  deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
78
95
 
79
- if (this.start) {
80
- const ws = new WebSocket(`wss://ws.melcloudhome.com/?hash=${this.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
- }
96
+ if (!this.connecting && !this.socketConnected) {
97
+ this.connecting = true;
101
98
 
99
+ const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
100
+ try {
101
+ const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
102
+ .on('error', (error) => {
103
+ if (this.logError) this.emit('error', `Socket error: ${error}`);
104
+ socket.close();
105
+ })
106
+ .on('close', () => {
107
+ if (this.logDebug) this.emit('debug', `Socket closed`);
108
+ this.cleanupSocket();
109
+ })
110
+ .on('open', () => {
111
+ this.socket = socket;
112
+ this.socketConnected = true;
113
+ this.connecting = false;
114
+ if (this.logSuccess) this.emit('success', `Socket Connect Success`);
115
+
116
+ // heartbeat
117
+ this.heartbeat = setInterval(() => {
118
+ if (socket.readyState === socket.OPEN) {
119
+ if (!this.logDebug) this.emit('debug', `Socket send heartbeat`);
120
+ socket.ping();
121
+ }
122
+ }, 30000);
123
+ })
124
+ .on('pong', () => {
125
+ if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
126
+ })
127
+ .on('message', (message) => {
128
+ const parsedMessage = JSON.parse(message);
129
+ const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
130
+ if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
131
+ if (parsedMessage.message === 'Forbidden') return;
132
+
133
+ const messageData = parsedMessage?.[0]?.Data;
134
+ if (!messageData) return;
135
+
136
+ let updateDeviceState = false;
137
+ const unitId = messageData?.id;
138
+ switch (unitId) {
139
+ case this.deviceId:
140
+ const messageType = parsedMessage[0].messageType;
141
+ switch (messageType) {
142
+ case 'unitStateChanged':
143
+ const settings = Object.fromEntries(
144
+ messageData.settings.map(({ name, value }) => {
145
+ let parsedValue = value;
146
+ if (value === "True") parsedValue = true;
147
+ else if (value === "False") parsedValue = false;
148
+ else if (!isNaN(value) && value !== "") parsedValue = Number(value);
149
+ return [name, parsedValue];
150
+ })
151
+ );
152
+ Object.assign(deviceData.Device, settings);
153
+ updateDeviceState = true;
154
+ break;
155
+ case 'unitWifiSignalChanged':
156
+ Object.assign(deviceData, messageData.rssi);
157
+ updateDeviceState = true;
158
+ break;
159
+ default:
160
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
161
+ return;
162
+ }
163
+ break;
164
+ default:
165
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
166
+ return;
167
+ }
168
+
169
+ if (updateDeviceState) this.emit('deviceState', deviceData);
170
+ });
171
+ } catch (error) {
172
+ if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
173
+ this.cleanupSocket();
174
+ }
175
+ }
102
176
  }
103
177
  if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
104
178
 
@@ -163,6 +237,8 @@ class MelCloudAta extends EventEmitter {
163
237
  let method = null
164
238
  let payload = {};
165
239
  let path = '';
240
+ let headers = this.headers;
241
+ let updateState = true;
166
242
  switch (accountType) {
167
243
  case "melcloud":
168
244
  switch (flag) {
@@ -204,10 +280,10 @@ class MelCloudAta extends EventEmitter {
204
280
  method: 'POST',
205
281
  baseURL: ApiUrls.BaseURL,
206
282
  timeout: 30000,
207
- headers: this.headers,
283
+ headers: headers,
208
284
  data: payload
209
285
  });
210
- this.updateData(deviceData);
286
+ this.updateData(deviceData, updateState);
211
287
  return true;
212
288
  case "melcloudhome":
213
289
  switch (flag) {
@@ -220,7 +296,7 @@ class MelCloudAta extends EventEmitter {
220
296
  };
221
297
  method = 'POST';
222
298
  path = ApiUrlsHome.PostProtectionFrost;
223
- this.headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
299
+ headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
224
300
  break;
225
301
  case 'overheatprotection':
226
302
  payload = {
@@ -231,7 +307,7 @@ class MelCloudAta extends EventEmitter {
231
307
  };
232
308
  method = 'POST';
233
309
  path = ApiUrlsHome.PostProtectionOverheat;
234
- this.headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
310
+ headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
235
311
  break;
236
312
  case 'holidaymode':
237
313
  payload = {
@@ -242,7 +318,7 @@ class MelCloudAta extends EventEmitter {
242
318
  };
243
319
  method = 'POST';
244
320
  path = ApiUrlsHome.PostHolidayMode;
245
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
321
+ headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
246
322
  break;
247
323
  case 'schedule':
248
324
  payload = { enabled: deviceData.ScheduleEnabled };
@@ -253,7 +329,7 @@ class MelCloudAta extends EventEmitter {
253
329
  case 'scene':
254
330
  method = 'PUT';
255
331
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
256
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
332
+ headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
257
333
  break;
258
334
  default:
259
335
  if (displayType === 1 && deviceData.Device.OperationMode === 8) {
@@ -280,22 +356,24 @@ class MelCloudAta extends EventEmitter {
280
356
  };
281
357
  method = 'PUT';
282
358
  path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
283
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
284
- break
359
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
360
+ updateState = false;
361
+ break;
285
362
  }
286
363
 
287
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
288
- this.headers.Origin = ApiUrlsHome.Origin;
364
+ //sens payload
365
+ headers['Content-Type'] = 'application/json; charset=utf-8';
366
+ headers.Origin = ApiUrlsHome.Origin;
289
367
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
290
368
  await axios(path, {
291
369
  method: method,
292
370
  baseURL: ApiUrlsHome.BaseURL,
293
371
  timeout: 30000,
294
- headers: this.headers,
372
+ headers: headers,
295
373
  data: payload
296
374
  });
297
375
 
298
- this.updateData(deviceData);
376
+ this.updateData(deviceData, updateState);
299
377
  return true;
300
378
  default:
301
379
  return;
@@ -306,9 +384,9 @@ class MelCloudAta extends EventEmitter {
306
384
  }
307
385
  }
308
386
 
309
- updateData(deviceData) {
387
+ updateData(deviceData, updateState = true) {
310
388
  this.locks = true;
311
- this.emit('deviceState', deviceData);
389
+ if (updateState) this.emit('deviceState', deviceData);
312
390
 
313
391
  setTimeout(() => {
314
392
  this.locks = false
@@ -8,6 +8,7 @@ class MelCloudAtw extends EventEmitter {
8
8
  constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
9
9
  super();
10
10
  this.accountType = account.type;
11
+ this.logSuccess = account.log?.success;
11
12
  this.logWarn = account.log?.warn;
12
13
  this.logError = account.log?.error;
13
14
  this.logDebug = account.log?.debug;
@@ -141,6 +142,8 @@ class MelCloudAtw extends EventEmitter {
141
142
  let method = null
142
143
  let payload = {};
143
144
  let path = '';
145
+ let headers = this.headers;
146
+ let updateState = true;
144
147
  switch (accountType) {
145
148
  case "melcloud":
146
149
  switch (flag) {
@@ -183,7 +186,7 @@ class MelCloudAtw extends EventEmitter {
183
186
  method: 'POST',
184
187
  baseURL: ApiUrls.BaseURL,
185
188
  timeout: 30000,
186
- headers: this.headers,
189
+ headers: headers,
187
190
  data: payload
188
191
  });
189
192
  this.updateData(deviceData);
@@ -199,18 +202,18 @@ class MelCloudAtw extends EventEmitter {
199
202
  };
200
203
  method = 'POST';
201
204
  path = ApiUrlsHome.PostHolidayMode;
202
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
205
+ headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
203
206
  break;
204
207
  case 'schedule':
205
208
  payload = { enabled: deviceData.ScheduleEnabled };
206
209
  method = 'PUT';
207
210
  path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
208
- this.headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
211
+ headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
209
212
  break;
210
213
  case 'scene':
211
214
  method = 'PUT';
212
215
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
213
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
216
+ headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
214
217
  break;
215
218
  default:
216
219
  payload = {
@@ -230,21 +233,22 @@ class MelCloudAtw extends EventEmitter {
230
233
  };
231
234
  method = 'PUT';
232
235
  path = ApiUrlsHome.PutAtw.replace('deviceid', deviceData.DeviceID);
233
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
236
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
237
+ updateState = false;
234
238
  break
235
239
  }
236
240
 
237
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
238
- this.headers.Origin = ApiUrlsHome.Origin;
241
+ headers['Content-Type'] = 'application/json; charset=utf-8';
242
+ headers.Origin = ApiUrlsHome.Origin;
239
243
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
240
244
  await axios(path, {
241
245
  method: method,
242
246
  baseURL: ApiUrlsHome.BaseURL,
243
247
  timeout: 30000,
244
- headers: this.headers,
248
+ headers: headers,
245
249
  data: payload
246
250
  });
247
- this.updateData(deviceData);
251
+ this.updateData(deviceData, updateState);
248
252
  return true;
249
253
  default:
250
254
  return;
@@ -255,9 +259,9 @@ class MelCloudAtw extends EventEmitter {
255
259
  }
256
260
  }
257
261
 
258
- updateData(deviceData) {
262
+ updateData(deviceData, updateState = true) {
259
263
  this.locks = true;
260
- this.emit('deviceState', deviceData);
264
+ if (updateState) this.emit('deviceState', deviceData);
261
265
 
262
266
  setTimeout(() => {
263
267
  this.locks = false
@@ -8,6 +8,7 @@ class MelCloudErv extends EventEmitter {
8
8
  constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
9
9
  super();
10
10
  this.accountType = account.type;
11
+ this.logSuccess = account.log?.success;
11
12
  this.logWarn = account.log?.warn;
12
13
  this.logError = account.log?.error;
13
14
  this.logDebug = account.log?.debug;
@@ -129,6 +130,8 @@ class MelCloudErv extends EventEmitter {
129
130
  let method = null
130
131
  let payload = {};
131
132
  let path = '';
133
+ let headers = this.headers;
134
+ let updateState = true;
132
135
  switch (accountType) {
133
136
  case "melcloud":
134
137
  switch (flag) {
@@ -186,7 +189,7 @@ class MelCloudErv extends EventEmitter {
186
189
  method: 'POST',
187
190
  baseURL: ApiUrls.BaseURL,
188
191
  timeout: 30000,
189
- headers: this.headers,
192
+ headers: headers,
190
193
  data: payload
191
194
  });
192
195
  this.updateData(deviceData);
@@ -202,18 +205,18 @@ class MelCloudErv extends EventEmitter {
202
205
  };
203
206
  method = 'POST';
204
207
  path = ApiUrlsHome.PostHolidayMode;
205
- this.headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
208
+ headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
206
209
  break;
207
210
  case 'schedule':
208
211
  payload = { enabled: deviceData.ScheduleEnabled };
209
212
  method = 'PUT';
210
213
  path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
211
- this.headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
214
+ headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
212
215
  break;
213
216
  case 'scene':
214
217
  method = 'PUT';
215
218
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
216
- this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
219
+ headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
217
220
  break;
218
221
  default:
219
222
  if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
@@ -237,21 +240,22 @@ class MelCloudErv extends EventEmitter {
237
240
  };
238
241
  method = 'PUT';
239
242
  path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
240
- this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
243
+ headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
244
+ updateState = false;
241
245
  break
242
246
  }
243
247
 
244
- this.headers['Content-Type'] = 'application/json; charset=utf-8';
245
- this.headers.Origin = ApiUrlsHome.Origin;
248
+ headers['Content-Type'] = 'application/json; charset=utf-8';
249
+ headers.Origin = ApiUrlsHome.Origin;
246
250
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
247
251
  await axios(path, {
248
252
  method: method,
249
253
  baseURL: ApiUrlsHome.BaseURL,
250
254
  timeout: 30000,
251
- headers: this.headers,
255
+ headers: headers,
252
256
  data: payload
253
257
  });
254
- this.updateData(deviceData);
258
+ this.updateData(deviceData, updateState);
255
259
  return true;
256
260
  default:
257
261
  return;
@@ -262,9 +266,9 @@ class MelCloudErv extends EventEmitter {
262
266
  }
263
267
  }
264
268
 
265
- updateData(deviceData) {
269
+ updateData(deviceData, updateState = true) {
266
270
  this.locks = true;
267
- this.emit('deviceState', deviceData);
271
+ if (updateState) this.emit('deviceState', deviceData);
268
272
 
269
273
  setTimeout(() => {
270
274
  this.locks = false
@@ -16,6 +16,7 @@ class MelCloudHome extends EventEmitter {
16
16
  this.user = account.user;
17
17
  this.passwd = account.passwd;
18
18
  this.language = account.language;
19
+ this.logSuccess = account.log?.success;
19
20
  this.logWarn = account.log?.warn;
20
21
  this.logError = account.log?.error;
21
22
  this.logDebug = account.log?.debug;
@@ -23,7 +24,7 @@ class MelCloudHome extends EventEmitter {
23
24
  this.buildingsFile = buildingsFile;
24
25
  this.devicesFile = devicesFile;
25
26
  this.headers = {};
26
- this.hash = null;
27
+ this.webSocketOptions = {};
27
28
 
28
29
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
29
30
  .on('warn', warn => this.emit('warn', warn))
@@ -209,7 +210,7 @@ class MelCloudHome extends EventEmitter {
209
210
  devicesList.Devices = devices;
210
211
  devicesList.Scenes = scenes;
211
212
  devicesList.Headers = this.headers;
212
- devicesList.Hash = this.hash;
213
+ devicesList.WebSocketOptions = this.webSocketOptions;
213
214
 
214
215
  await this.functions.saveData(this.devicesFile, devicesList);
215
216
  if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
@@ -278,15 +279,16 @@ class MelCloudHome extends EventEmitter {
278
279
  page.setDefaultTimeout(GLOBAL_TIMEOUT);
279
280
  page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
280
281
 
281
- // === CDP session (modern API) ===
282
+ // === CDP session ===
283
+ let hash = null;
282
284
  const client = await page.createCDPSession();
283
285
  await client.send('Network.enable')
284
286
  client.on('Network.webSocketCreated', ({ url }) => {
285
287
  try {
286
288
  if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
287
289
  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
+ hash = params.get('hash');
291
+ if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
290
292
  }
291
293
  } catch (err) {
292
294
  this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
@@ -373,6 +375,16 @@ class MelCloudHome extends EventEmitter {
373
375
  headers: headers
374
376
  })
375
377
 
378
+ this.webSocketOptions = {
379
+ Hash: hash,
380
+ Headers: {
381
+ 'Origin': ApiUrlsHome.BaseURL,
382
+ 'Pragma': 'no-cache',
383
+ 'Cache-Control': 'no-cache'
384
+ }
385
+ };
386
+
387
+
376
388
  accountInfo.State = true;
377
389
  accountInfo.Info = 'Connect to MELCloud Home Success';
378
390
  await this.functions.saveData(this.accountFile, accountInfo);