homebridge-melcloud-control 4.4.1-beta.8 → 4.5.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,17 @@ 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.5.0] - (19.12.2025)
26
+
27
+ ## Changes
28
+
29
+ - stability and performance improvements
30
+ - remove oldnot existed presets, schedules, scenes fom config during (Connect To Melcloud)
31
+ - bump dependencies
32
+ - config schema updated
33
+ - redme updated
34
+ - cleanup
35
+
25
36
  # [4.4.0] - (10.12.2025)
26
37
 
27
38
  ## Changes
package/README.md CHANGED
@@ -22,24 +22,25 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
22
22
  | Package | Installation | Role | Required |
23
23
  | --- | --- | --- | --- |
24
24
  | [Homebridge v2.0.0](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required |
25
- | [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge Web User Interface | Required |
26
- | [MELCloud](https://github.com/grzegorz914/homebridge-melcloud-control) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-melcloud-control/wiki) | Homebridge Plug-In | Required |
25
+ | [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge User Interface | Required |
26
+ | [MELCloud](https://github.com/grzegorz914/homebridge-melcloud-control) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-melcloud-control/wiki) | Plug-In | Required |
27
27
 
28
28
  ### About The Plugin
29
29
 
30
- * Support devices connected to MELCloud or MELCloud Home.
31
- * Support multiple accounts, buildings, floors, areas.
32
- * Support temperature display units `Celsius/Fahrenheit`.
33
- * Support assing inividual operating mode for `Heat/Cool/Auto`.
34
- * Support direct `Presets`, only MELCloud.
35
- * Support direct `Schedules`, only MELCloud Home.
36
- * Support direct `Scenes`, only MELCloud Home.
37
- * Support direct `Frost protection`, only MELCloud Home.
38
- * Support direct `Overheat Protection`, only MELCloud Home.
39
- * Support direct `Holiday Mode`.
40
- * Support direct `Functions`, using extra `Buttons`, switch it to `OFF` restore previous device state.
41
- * Support automations, shortcuts and Siri.
42
- * Support external integrations, [RESTFul](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#mqtt-integration).
30
+ * Support:
31
+ * Devices connected to MELCloud or MELCloud Home.
32
+ * Multiple accounts, buildings, floors, areas.
33
+ * Temperature display units `Celsius/Fahrenheit`.
34
+ * Assing individual operating mode for `Heat/Cool/Auto`.
35
+ * Presets, only MELCloud.
36
+ * Scenes, only MELCloud Home.
37
+ * Frost protection, only MELCloud Home.
38
+ * Overheat Protection, only MELCloud Home.
39
+ * Holiday Mode, only MELCloud Home.
40
+ * Physical lock controls `LOCK/UNLOCK`, only MELCloud.
41
+ * Functions, using extra `Buttons`, switch to `OFF` restore previous state.
42
+ * Automations, shortcuts and Siri.
43
+ * External integrations, [RESTFul](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#mqtt-integration).
43
44
  * Control devices over local network You need use ESP module and [Tasmota Control](https://github.com/grzegorz914/homebridge-tasmota-control) plugin.
44
45
 
45
46
  ### Control Mode
@@ -58,18 +59,17 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
58
59
  * Operating mode `POWER OFF/HEAT/COOL/AUTO`.
59
60
  * Temperature `HEATING/COOLING/AUTO`.
60
61
  * Temperature display unit `°F/°C`.
61
- * Assign operating mode for `HEAT/AUTO`
62
62
  * Buttons:
63
63
  * For direct device control.
64
64
  * Power `ON/OFF`.
65
65
  * Operating mode `HEAT/DRY/COOL/FAN/AUTO`.
66
- * Physical lock controls `LOCK/UNLOCK`.
66
+ * Physical lock controls `LOCK/UNLOCK`
67
67
  * Vane H mode `AUTO/1/2/3/4/5/SPLIT/SWING`.
68
68
  * Vane V mode `AUTO/1/2/3/4/5/SWING`.
69
69
  * Fan speed mode `AUTO/1/2/3/4/5`.
70
- * Presets `SET/UNSET`.
71
- * Frost protection `ON/OFF`.
72
- * Overheat protection `ON/OFF`.
70
+ * Preset `SET/UNSET`
71
+ * Frost protection `ON/OFF/MINTEMP/MAXTEMP`.
72
+ * Overheat protection `ON/OFF/MINTEMP/MAXTEMP`.
73
73
  * Holiday mode `ON/OFF`.
74
74
  * Schedules `ON/OFF`.
75
75
  * Scene `ON/OFF`.
@@ -81,7 +81,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
81
81
  * Vane H mode `AUTO/1/2/3/4/5/SPLIT/SWING`.
82
82
  * Vane V mode `AUTO/1/2/3/4/5/SWING`.
83
83
  * Fan speed mode `AUTO/1/2/3/4/5/`.
84
- * Presets `ACTIV/UNACTIV`.
84
+ * Preset `ACTIV/UNACTIV`.
85
85
  * Room temperature.
86
86
  * Outdoor temperature.
87
87
  * Frost protection.
@@ -128,8 +128,8 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
128
128
  * Power `ON/OFF`.
129
129
  * Operating mode `HEAT/COOL/CURVE/HOLIDAY/AUTO HOT WATER/ECO HOT WATER/FORCE HOT WATER`.
130
130
  * Physical lock controls `LOCK/UNLOCK`.
131
- * Presets `SET/UNSET`.
132
- * Frost protection `ON/OFF`.
131
+ * Preset `SET/UNSET`.
132
+ * Frost protection `ON/OFF/MINTEMP/MAXTEMP`.
133
133
  * Holiday mode `ON/OFF`.
134
134
  * Schedules `ON/OFF`.
135
135
  * Scene `ON/OFF`.
@@ -138,7 +138,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
138
138
  * Power `ON/OFF`.
139
139
  * Operating mode `HEAT/COOL/CURVE/HOLIDAY/AUTO HOT WATER/ECO HOT WATER/FORCE HOT WATER`.
140
140
  * Physical lock controls `LOCK/UNLOCK`.
141
- * Presets `ACTIV/UNACTIV`.
141
+ * Preset `ACTIV/UNACTIV`.
142
142
  * Outdoor temperature.
143
143
  * Zone 1 temperature.
144
144
  * Zone 2 temperature.
@@ -168,7 +168,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
168
168
  * Power `ON/OFF`.
169
169
  * Operating mode `LOSSNAY/BYPASS/AUTO/NIGHT PURGE`.
170
170
  * Fan speed mode `AUTO/1/2/3/4`.
171
- * Presets `SET/UNSET`.
171
+ * Preset `SET/UNSET`.
172
172
  * Holiday mode `ON/OFF`.
173
173
  * Schedules `ON/OFF`.
174
174
  * Scene `ON/OFF`.
@@ -210,7 +210,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
210
210
  * Target temperature is calculated as a middle value between `LO` and `HI` and the rest is calculated internally.
211
211
  * Thermostat
212
212
  * In this mode we can set only target temperature:
213
- * Target temperature issend to device and calculated internally:
213
+ * Target temperature is send to device and calculated internally:
214
214
  * Calculation method in device internally:
215
215
  * If the room temperature `<` Heating Setpoint, the unit will be set to HEAT with a setpoint of 23°C.
216
216
  * In HEAT, if the room temperature `>` Heating Setpoint `+` 1°C, the unit will be set to FAN.
@@ -204,21 +204,7 @@
204
204
  }
205
205
  });
206
206
 
207
- // Device Handling & Login Logic
208
- function removeStaleDevices(configDevices, melcloudDevices) {
209
- const melcloudIds = melcloudDevices.map(d => d.DeviceID);
210
- const removedDevices = [];
211
-
212
- for (let i = configDevices.length - 1; i >= 0; i--) {
213
- const device = configDevices[i];
214
- if (!melcloudIds.includes(device.id)) {
215
- removedDevices.push(device);
216
- configDevices.splice(i, 1);
217
- }
218
- }
219
- return removedDevices;
220
- }
221
-
207
+ // Update info on page
222
208
  function updateInfo(id, text, color) {
223
209
  const el = document.getElementById(id);
224
210
  if (el) {
@@ -227,10 +213,44 @@
227
213
  }
228
214
  }
229
215
 
216
+ // Generic remove function
217
+ function removeStaleEntities(configEntities, melcloudEntities, getConfigId, getMelcloudId) {
218
+ const serverIds = new Set((melcloudEntities ?? []).map(item => String(getMelcloudId(item))));
219
+ const removedEntities = [];
220
+
221
+ for (let i = configEntities.length - 1; i >= 0; i--) {
222
+ const entity = configEntities[i];
223
+ const entityId = String(getConfigId(entity));
224
+
225
+ if (!serverIds.has(entityId)) {
226
+ removedEntities.push(entity);
227
+ configEntities.splice(i, 1);
228
+ }
229
+ }
230
+
231
+ return removedEntities;
232
+ }
233
+
234
+ // Map UnitId → Scenes for quick lookup
235
+ function mapUnitIdToScenes(scenesInMelCloud) {
236
+ const map = new Map();
237
+
238
+ scenesInMelCloud.forEach(scene => {
239
+ const allSceneSettings = [...(scene.AtaSceneSettings ?? []), ...(scene.AtwSceneSettings ?? []), ...(scene.ErvSceneSettings ?? [])];
240
+ allSceneSettings.forEach(setting => {
241
+ const unitId = String(setting.UnitId);
242
+ if (!map.has(unitId)) map.set(unitId, []);
243
+ map.get(unitId).push(scene);
244
+ });
245
+ });
246
+
247
+ return map;
248
+ }
249
+
250
+ // Login & Sync Logic
230
251
  document.getElementById('logIn').addEventListener('click', async () => {
231
252
  homebridge.showSpinner();
232
-
233
- document.getElementById(`logIn`).className = "btn btn-primary";
253
+ document.getElementById('logIn').className = "btn btn-primary";
234
254
  updateInfo('info', '', 'white');
235
255
  updateInfo('info1', '', 'white');
236
256
  updateInfo('info2', '', 'white');
@@ -238,161 +258,154 @@
238
258
  try {
239
259
  const account = this.account;
240
260
  const response = await homebridge.request('/connect', account);
261
+
241
262
  if (!response.State) {
242
263
  homebridge.hideSpinner();
243
- updateInfo('info', response.Info);
264
+ updateInfo('info', response.Info, 'red');
244
265
  return;
245
266
  }
246
267
 
247
- // Initialize devices arrays
248
- const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [], scenes: [] };
268
+ // Prepare MELCloud data
269
+ const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], ataScenes: [], atw: [], atwPresets: [], atwSchedules: [], atwScenes: [], erv: [], ervPresets: [], ervSchedules: [], ervScenes: [] };
249
270
  const devicesInMelCloudByType = { ata: [], atw: [], erv: [] };
250
- const scenesInMelCloud = response.Scenes ?? []
271
+ const scenesInMelCloud = response.Scenes ?? [];
251
272
 
273
+ // Split devices by type
252
274
  response.Devices.forEach(device => {
253
275
  if (device.Type === 0) devicesInMelCloudByType.ata.push(device);
254
276
  if (device.Type === 1) devicesInMelCloudByType.atw.push(device);
255
277
  if (device.Type === 3) devicesInMelCloudByType.erv.push(device);
256
278
  });
257
279
 
280
+ // Clean up local config
258
281
  account.ataDevices = (account.ataDevices ?? []).filter(d => String(d.id) !== '0');
259
282
  account.atwDevices = (account.atwDevices ?? []).filter(d => String(d.id) !== '0');
260
283
  account.ervDevices = (account.ervDevices ?? []).filter(d => String(d.id) !== '0');
261
284
 
262
- const removedAta = removeStaleDevices(account.ataDevices, devicesInMelCloudByType.ata);
263
- const removedAtw = removeStaleDevices(account.atwDevices, devicesInMelCloudByType.atw);
264
- const removedErv = removeStaleDevices(account.ervDevices, devicesInMelCloudByType.erv);
285
+ const removedFromConfigAta = removeStaleEntities(account.ataDevices, devicesInMelCloudByType.ata, d => d.id, d => d.DeviceID);
286
+ const removedFromConfigAtw = removeStaleEntities(account.atwDevices, devicesInMelCloudByType.atw, d => d.id, d => d.DeviceID);
287
+ const removedFromConfigErv = removeStaleEntities(account.ervDevices, devicesInMelCloudByType.erv, d => d.id, d => d.DeviceID);
288
+ const removedFromConfig = { presets: [], schedules: [], scenes: [] };
289
+
290
+ // Map UnitId → Scenes
291
+ const unitIdToScenes = mapUnitIdToScenes(scenesInMelCloud);
265
292
 
293
+ // Generic device handler (obsługuje urządzenia, presety, harmonogramy i sceny)
266
294
  const handleDevices = (devicesInMelCloud, devicesInConfig, deviceTypeString, newDevices, newPresets, newSchedules, newScenes) => {
267
- try {
268
- const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
269
-
270
- devicesInMelCloud.forEach(device => {
271
- const deviceId = String(device.DeviceID);
272
- let deviceInConfig = configDevicesMap.get(deviceId);
273
-
274
- // === Create device if missing ===
275
- if (!deviceInConfig) {
276
- deviceInConfig = {
277
- id: deviceId,
278
- type: device.Type,
279
- deviceTypeString,
280
- displayType: 0,
281
- name: device.DeviceName
282
- };
283
- devicesInConfig.push(deviceInConfig);
284
- newDevices.push(deviceInConfig);
285
- configDevicesMap.set(deviceId, deviceInConfig);
286
- }
287
-
288
- //only for melcloud
289
- if (account.type === 'melcloud') {
290
-
291
- // === Process presets ===
292
- const presetsInMelCloud = device.Presets || [];
293
- const presetsInConfig = (deviceInConfig.presets ?? []).filter(p => String(p.id) !== '0');
294
- const presetIds = new Set(presetsInConfig.map(p => String(p.id)));
295
-
296
- presetsInMelCloud.forEach((preset, index) => {
297
- const presetId = String(preset.ID);
298
- if (!presetIds.has(presetId)) {
299
- const presetObj = {
300
- id: presetId,
301
- displayType: 0,
302
- name: preset.NumberDescription || `Preset ${index}`,
303
- namePrefix: false
304
- };
305
- presetsInConfig.push(presetObj);
306
- newPresets.push(presetObj);
307
- presetIds.add(presetId);
308
- }
309
- });
310
- }
311
-
312
- //only for melcloudhome
313
- if (account.type === 'melcloudhome') {
314
-
315
- // === Process schedules ===
316
- const schedulesInMelCloud = device.Schedule || [];
317
- const schedulesInConfig = (deviceInConfig.schedules ?? []).filter(s => String(s.id) !== '0');
318
- const scheduleIds = new Set(schedulesInConfig.map(s => String(s.id)));
319
-
320
- schedulesInMelCloud.forEach((schedule, index) => {
321
- const scheduleId = String(schedule.Id);
322
- if (!scheduleIds.has(scheduleId)) {
323
- const scheduleObj = {
324
- id: scheduleId,
325
- displayType: 0,
326
- name: `Schedule ${index}`,
327
- namePrefix: false
328
- };
329
- schedulesInConfig.push(scheduleObj);
330
- newSchedules.push(scheduleObj);
331
- scheduleIds.add(scheduleId);
332
- }
333
- });
334
-
335
- // === Process scenes ===
336
- const scenesInConfig = (deviceInConfig.scenes ?? []).filter(s => String(s.id) !== '0');
337
- const sceneIds = new Set(scenesInConfig.map(s => String(s.id)));
338
-
339
- scenesInMelCloud.forEach((scene, index) => {
340
- const sceneId = String(scene.Id);
341
- if (!sceneIds.has(sceneId)) {
342
- const sceneObj = {
343
- id: sceneId,
344
- displayType: 0,
345
- name: scene.Name || `Scene ${index}`,
346
- namePrefix: false
347
- };
348
- scenesInConfig.push(sceneObj);
349
- newScenes.push(sceneObj);
350
- sceneIds.add(sceneId);
351
- }
352
- });
353
- }
354
- });
355
-
356
- // Return filtered devicesInConfig to make sure upstream code uses it
357
- return devicesInConfig;
358
- } catch (error) {
359
- updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
360
- }
295
+ const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
296
+
297
+ devicesInMelCloud.forEach(device => {
298
+ const deviceId = String(device.DeviceID);
299
+ let deviceInConfig = configDevicesMap.get(deviceId);
300
+
301
+ if (!deviceInConfig) {
302
+ deviceInConfig = { id: deviceId, type: device.Type, deviceTypeString, displayType: 0, name: device.DeviceName };
303
+ devicesInConfig.push(deviceInConfig);
304
+ newDevices.push(deviceInConfig);
305
+ configDevicesMap.set(deviceId, deviceInConfig);
306
+ }
307
+
308
+ // PRESETS (melcloud)
309
+ if (account.type === 'melcloud') {
310
+ deviceInConfig.presets = (deviceInConfig.presets ?? []).filter(p => String(p.id) !== '0');
311
+ const presetsInMelCloud = device.Presets ?? [];
312
+ removedFromConfig.presets.push(...removeStaleEntities(deviceInConfig.presets, presetsInMelCloud, p => p.id, p => p.ID));
313
+ const presetIds = new Set(deviceInConfig.presets.map(p => String(p.id)));
314
+ presetsInMelCloud.forEach((preset, index) => {
315
+ const presetId = String(preset.ID);
316
+ if (!presetIds.has(presetId)) {
317
+ const presetObj = {
318
+ id: presetId,
319
+ displayType: 0,
320
+ name: preset.NumberDescription || `Preset ${index}`,
321
+ namePrefix: false
322
+ };
323
+ deviceInConfig.presets.push(presetObj);
324
+ newPresets.push(presetObj);
325
+ }
326
+ });
327
+ }
328
+
329
+ // SCHEDULES & SCENES (melcloudhome)
330
+ if (account.type === 'melcloudhome') {
331
+ // SCHEDULES
332
+ deviceInConfig.schedules = (deviceInConfig.schedules ?? []).filter(s => String(s.id) !== '0');
333
+ const schedulesInMelCloud = device.Schedule ?? [];
334
+ removedFromConfig.schedules.push(...removeStaleEntities(deviceInConfig.schedules, schedulesInMelCloud, s => s.id, s => s.Id));
335
+ const scheduleIds = new Set(deviceInConfig.schedules.map(s => String(s.id)));
336
+ schedulesInMelCloud.forEach((schedule, index) => {
337
+ const scheduleId = String(schedule.Id);
338
+ if (!scheduleIds.has(scheduleId)) {
339
+ const scheduleObj = {
340
+ id: scheduleId,
341
+ displayType: 0,
342
+ name: `Schedule ${index}`,
343
+ namePrefix: false
344
+ };
345
+ deviceInConfig.schedules.push(scheduleObj);
346
+ newSchedules.push(scheduleObj);
347
+ }
348
+ });
349
+
350
+ // SCENES
351
+ deviceInConfig.scenes = (deviceInConfig.scenes ?? []).filter(s => String(s.id) !== '0');
352
+ const scenesForDevice = unitIdToScenes.get(deviceId) ?? [];
353
+ removedFromConfig.scenes.push(...removeStaleEntities(deviceInConfig.scenes, scenesForDevice, s => s.id, s => s.Id));
354
+ const sceneIds = new Set(deviceInConfig.scenes.map(s => String(s.id)));
355
+ scenesForDevice.forEach((scene, index) => {
356
+ const sceneId = String(scene.Id);
357
+ if (!sceneIds.has(sceneId)) {
358
+ const sceneObj = {
359
+ id: sceneId,
360
+ displayType: 0,
361
+ name: scene.Name || `Scene ${index}`,
362
+ namePrefix: false
363
+ };
364
+ deviceInConfig.scenes.push(sceneObj);
365
+ newScenes.push(sceneObj);
366
+ }
367
+ });
368
+ }
369
+ });
370
+
371
+ return devicesInConfig;
361
372
  };
362
373
 
363
- account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.scenes);
364
- account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.scenes);
365
- account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.scenes);
374
+ // Execute device handlers
375
+ account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.ataScenes);
376
+ account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.atwScenes);
377
+ account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.ervScenes);
366
378
 
379
+ // Summary counts
367
380
  const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
368
381
  const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
369
382
  const newSchedulesCount = newInMelCloud.ataSchedules.length + newInMelCloud.atwSchedules.length + newInMelCloud.ervSchedules.length;
370
- const newScenesCount = newInMelCloud.scenes.length;
371
- const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
383
+ const newScenesCount = newInMelCloud.ataScenes.length + newInMelCloud.atwScenes.length + newInMelCloud.ervScenes.length;
384
+ const removedDevicesCount = removedFromConfigAta.length + removedFromConfigAtw.length + removedFromConfigErv.length;
385
+ const removedPresetsCount = removedFromConfig.presets.length;
386
+ const removedSchedulesCount = removedFromConfig.schedules.length;
387
+ const removedScenesCount = removedFromConfig.scenes.length;
372
388
 
373
- if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
389
+ if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount && !removedPresetsCount && !removedSchedulesCount && !removedScenesCount) {
374
390
  updateInfo('info', 'No changes detected.', 'white');
375
391
  } else {
376
- if (newDevicesCount)
377
- updateInfo('info', `Found new devices: ${newInMelCloud.ata.length ? `ATA: ${newInMelCloud.ata.length},` : ''} ${newInMelCloud.atw.length ? `ATW: ${newInMelCloud.atw.length},` : ''} ${newInMelCloud.erv.length ? `ERV: ${newInMelCloud.erv.length},` : ''}.`, 'green');
378
- if (newPresetsCount)
379
- updateInfo('info1', `Found new presets: ${newInMelCloud.ataPresets.length ? `ATA: ${newInMelCloud.ataPresets.length},` : ''} ${newInMelCloud.atwPresets.length ? `ATW: ${newInMelCloud.atwPresets.length},` : ''} ${newInMelCloud.ervPresets.length ? `ERV: ${newInMelCloud.ervPresets.length}` : ''}.`, 'green');
380
- if (newScenesCount || newSchedulesCount)
381
- updateInfo('info1', `Found new ${newSchedulesCount ? `schedules:` : ''} ${newInMelCloud.ataSchedules.length ? `ATA: ${newInMelCloud.ataSchedules.length},` : ''} ${newInMelCloud.atwSchedules.length ? `ATW: ${newInMelCloud.atwSchedules.length},` : ''} ${newInMelCloud.ervSchedules.length ? `ERV: ${newInMelCloud.ervSchedules.length},` : ''} ${newScenesCount ? `scenes: ${newScenesCount}` : ''}.`, 'green');
382
- if (removedDevicesCount)
383
- updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
392
+ if (newDevicesCount || newPresetsCount || newSchedulesCount || newScenesCount) {
393
+ updateInfo('info', `Found new ${newDevicesCount ? `devices:` : ''} ${newInMelCloud.ata.length ? `ATA: ${newInMelCloud.ata.length}` : ''}${newInMelCloud.atw.length ? `, ATW: ${newInMelCloud.atw.length}` : ''}${newInMelCloud.erv.length ? `, ERV: ${newInMelCloud.erv.length},` : ''}${newPresetsCount ? `, presets:` : ''} ${newInMelCloud.ataPresets.length ? `ATA: ${newInMelCloud.ataPresets.length}` : ''}${newInMelCloud.atwPresets.length ? `, ATW: ${newInMelCloud.atwPresets.length}` : ''}${newInMelCloud.ervPresets.length ? `, ERV: ${newInMelCloud.ervPresets.length}` : ''}${newSchedulesCount ? `, schedules:` : ''} ${newInMelCloud.ataSchedules.length ? `ATA: ${newInMelCloud.ataSchedules.length}` : ''}${newInMelCloud.atwSchedules.length ? `, ATW: ${newInMelCloud.atwSchedules.length}` : ''}${newInMelCloud.ervSchedules.length ? `, ERV: ${newInMelCloud.ervSchedules.length}` : ''}${newScenesCount ? `, scenes:` : ''} ${newInMelCloud.ataScenes.length ? `,ATA: ${newInMelCloud.ataScenes.length}` : ''}${newInMelCloud.atwScenes.length ? `, ATW: ${newInMelCloud.atwScenes.length}` : ''}${newInMelCloud.ervScenes.length ? `, ERV: ${newInMelCloud.ervScenes.length}` : ''}.`, 'green');
394
+ }
395
+ if (removedDevicesCount || removedPresetsCount || removedSchedulesCount || removedScenesCount) {
396
+ updateInfo('info1', `Removed old ${removedDevicesCount ? `devices:` : ''} ${removedFromConfigAta.length ? `ATA: ${removedFromConfigAta.length}` : ''}${removedFromConfigAtw.length ? `, ATW: ${removedFromConfigAtw.length}` : ''}${removedFromConfigErv.length ? `, ERV: ${removedFromConfigErv.length}` : ''}${removedPresetsCount ? `, presets: ${removedPresetsCount}` : ''}${removedSchedulesCount ? `, schedules: ${removedSchedulesCount}` : ''}${removedScenesCount ? `, scenes: ${removedScenesCount}` : ''}.`, 'orange');
397
+ }
384
398
  }
385
399
 
386
400
  await homebridge.updatePluginConfig(pluginConfig);
387
401
  await homebridge.savePluginConfig(pluginConfig);
402
+
388
403
  } catch (error) {
389
404
  updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
390
- document.getElementById('logIn').className = "btn btn-secondary";
391
405
  } finally {
392
406
  document.getElementById('logIn').className = "btn btn-secondary";
393
407
  homebridge.hideSpinner();
394
408
  }
395
409
  });
396
-
397
410
  })();
398
411
  </script>
package/index.js CHANGED
@@ -137,12 +137,27 @@ class MelCloudPlatform {
137
137
  }
138
138
 
139
139
  //chack device from config exist on melcloud
140
- const deviceExistInMelCloud = melcloudDevicesList.Devices.some(dev => dev.DeviceID === device.id);
141
- if (!deviceExistInMelCloud) {
140
+ const deviceInMelCloud = melcloudDevicesList.Devices.find(d => d.DeviceID === device.id);
141
+ if (!deviceInMelCloud) {
142
142
  if (logLevel.warn) log.warn(`${name}, ${deviceTypeString}, ${deviceName}, not exist on server, please login to MELCLoud from plugin UI to fix this issue.`);
143
143
  continue;
144
144
  }
145
145
 
146
+ //presets
147
+ const presetIds = (deviceInMelCloud.Presets ?? []).map(p => String(p.ID));
148
+ const presets = account.type === 'melcloud' ? (device.presets || []).filter(p => (p.displayType ?? 0) > 0 && p.id !== '0' && presetIds.includes(p.id)) : [];
149
+
150
+ //schedules
151
+ const schedulesIds = (deviceInMelCloud.Schedule ?? []).map(s => String(s.Id));
152
+ const schedules = account.type === 'melcloudhome' ? (device.schedules || []).filter(s => (s.displayType ?? 0) > 0 && s.id !== '0' && schedulesIds.includes(s.id)) : [];
153
+
154
+ //scenes
155
+ const scenesIds = (melcloudDevicesList.Scenes ?? []).map(s => String(s.Id));
156
+ const scenes = account.type === 'melcloudhome' ? (device.scenes || []).filter(s => (s.displayType ?? 0) > 0 && s.id !== '0' && scenesIds.includes(s.id)) : [];
157
+
158
+ //buttons
159
+ const buttons = (device.buttonsSensors || []).filter(b => (b.displayType ?? 0) > 0);
160
+
146
161
  // set rest ful port
147
162
  account.restFul.port = (device.id).slice(-4).replace(/^0/, '9');
148
163
 
@@ -168,15 +183,15 @@ class MelCloudPlatform {
168
183
  let configuredDevice;
169
184
  switch (deviceType) {
170
185
  case 0: //ATA
171
- configuredDevice = new DeviceAta(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
186
+ configuredDevice = new DeviceAta(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
172
187
  break;
173
188
  case 1: //ATW
174
- configuredDevice = new DeviceAtw(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
189
+ configuredDevice = new DeviceAtw(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
175
190
  break;
176
191
  case 2:
177
192
  break;
178
193
  case 3: //ERV
179
- configuredDevice = new DeviceErv(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
194
+ configuredDevice = new DeviceErv(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
180
195
  break;
181
196
  default:
182
197
  if (logLevel.warn) log.warn(`${name}, ${deviceTypeString}, ${deviceName}, unknown device: ${deviceType}.`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.4.1-beta.8",
4
+ "version": "4.5.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",
@@ -39,7 +39,7 @@
39
39
  "mqtt": "^5.14.1",
40
40
  "axios": "^1.13.2",
41
41
  "express": "^5.2.1",
42
- "puppeteer": "^24.33.0",
42
+ "puppeteer": "^24.34.0",
43
43
  "ws": "^8.18.3"
44
44
  },
45
45
  "keywords": [
package/src/constants.js CHANGED
@@ -17,7 +17,7 @@ export const ApiUrls = {
17
17
  Atw: "/Device/SetAtw",
18
18
  Erv: "/Device/SetErv",
19
19
  UpdateApplicationOptions: "/User/UpdateApplicationOptions",
20
- HolidayModeUpdate: "/HolidayMode/Update",
20
+ HolidayMode: "/HolidayMode/Update",
21
21
  EnergyCostReport: "/EnergyCost/Report",
22
22
  },
23
23
  Home: {
@@ -25,21 +25,21 @@ export const ApiUrls = {
25
25
  WebSocket: "wss://ws.melcloudhome.com/?hash=",
26
26
  Get: {
27
27
  Configuration: "/api/configuration",
28
- UserContext: "/api/user/context",
29
- UserScenes: "/api/user/scenes",
28
+ ListDevices: "/api/user/context",
29
+ Scenes: "/api/user/scenes",
30
30
  },
31
31
  Post: {
32
- Schedule: "/api/cloudschedule/deviceid", // POST {"days":[2],"time":"17:59:00","enabled":true,"id":"53c5e804-0663-47d0-85c2-2d8ccd2573de","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
33
- ProtectionFrost: "/api/protection/frost", // POST {"enabled":true,"min":13,"max":16,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
34
- ProtectionOverheat: "/api/protection/overheat", // POST {"enabled":true,"min":32,"max":35,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
35
- HolidayMode: "/api/holidaymode", // POST {"enabled":true,"startDate":"2025-11-11T17:42:24.913","endDate":"2026-06-01T09:18:00","units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
36
- Scene: "/api/scene", // POST {"id": "8e484d50-528b-434a-9acb-7d7c81f06a12", "userId": "2db32d6f-c19c-4b1f-a0dd-1915420a5152","name": "Poza domem","enabled": false,"icon": "AwayIcon","ataSceneSettings": [{"unitId": "054dd950-f6e0-4195-bea7-59d8ea0668c2","ataSettings": { "power": false, "operationMode": "heat","setFanSpeed": "auto","vaneHorizontalDirection": "auto", "vaneVerticalDirection": "auto", "setTemperature": 21,"temperatureIncrementOverride": null,"inStandbyMode": null},"previousSettings": null}],"atwSceneSettings": []}
32
+ ProtectionFrost: "/api/protection/frost", //{"enabled":true,"min":13,"max":16,"units":{"ATA":["deviceid"]}}
33
+ ProtectionOverheat: "/api/protection/overheat", //{"enabled":true,"min":32,"max":35,"units":{"ATA":["deviceid"]}}
34
+ HolidayMode: "/api/holidaymode", //{"enabled":true,"startDate":"2025-11-11T17:42:24.913","endDate":"2026-06-01T09:18:00","units":{"ATA":["deviceid"]}}
35
+ Schedule: "/api/cloudschedule/deviceid", //{"days":[2],"time":"17:59:00","enabled":true,"id":"scheduleid","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
36
+ Scene: "/api/scene", //{"id": "sceneid", "userId": "userid","name": "Poza domem","enabled": false,"icon": "AwayIcon","ataSceneSettings": [{"unitId": "deviceid","ataSettings": { "power": false, "operationMode": "heat","setFanSpeed": "auto","vaneHorizontalDirection": "auto", "vaneVerticalDirection": "auto", "setTemperature": 21,"temperatureIncrementOverride": null,"inStandbyMode": null},"previousSettings": null}],"atwSceneSettings": []}
37
37
  },
38
38
  Put: {
39
- Ata: "/api/ataunit/deviceid",
39
+ Ata: "/api/ataunit/deviceid", //{ power: true,setTemperature: 22, setFanSpeed: "auto", operationMode: "heat", vaneHorizontalDirection: "auto",vaneVerticalDirection: "auto", temperatureIncrementOverride: null, inStandbyMode: null}
40
40
  Atw: "/api/atwunit/deviceid",
41
41
  Erv: "/api/ervunit/deviceid",
42
- ScheduleEnableDisable: "/api/cloudschedule/deviceid/enabled", // PUT {"enabled":true}
42
+ ScheduleEnableDisable: "/api/cloudschedule/deviceid/enabled", // {"enabled": true}
43
43
  SceneEnableDisable: "/api/scene/sceneid/enabledisable",
44
44
  },
45
45
  Delete: {