homebridge-melcloud-control 4.4.1-beta.33 → 4.4.1-beta.34

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.
@@ -204,19 +204,22 @@
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);
207
+ // Generic remove function
208
+ function removeStaleEntities(configList, serverList, getConfigId, getServerId) {
209
+ const serverIds = new Set(serverList.map(item => String(getServerId(item))));
210
+ const removedEntities = [];
211
+
212
+ for (let i = configList.length - 1; i >= 0; i--) {
213
+ const entity = configList[i];
214
+ const entityId = String(getConfigId(entity));
215
+
216
+ if (!serverIds.has(entityId)) {
217
+ removedEntities.push(entity);
218
+ configList.splice(i, 1);
217
219
  }
218
220
  }
219
- return removedDevices;
221
+
222
+ return removedEntities;
220
223
  }
221
224
 
222
225
  function updateInfo(id, text, color) {
@@ -227,10 +230,11 @@
227
230
  }
228
231
  }
229
232
 
233
+ // Login & Sync Logic
230
234
  document.getElementById('logIn').addEventListener('click', async () => {
231
235
  homebridge.showSpinner();
232
236
 
233
- document.getElementById(`logIn`).className = "btn btn-primary";
237
+ document.getElementById('logIn').className = "btn btn-primary";
234
238
  updateInfo('info', '', 'white');
235
239
  updateInfo('info1', '', 'white');
236
240
  updateInfo('info2', '', 'white');
@@ -238,16 +242,23 @@
238
242
  try {
239
243
  const account = this.account;
240
244
  const response = await homebridge.request('/connect', account);
245
+
241
246
  if (!response.State) {
242
247
  homebridge.hideSpinner();
243
- updateInfo('info', response.Info);
248
+ updateInfo('info', response.Info, 'red');
244
249
  return;
245
250
  }
246
251
 
247
- // Initialize devices arrays
248
- const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [], scenes: [] };
252
+ // Prepare MELCloud data
253
+ const newInMelCloud = {
254
+ ata: [], ataPresets: [], ataSchedules: [],
255
+ atw: [], atwPresets: [], atwSchedules: [],
256
+ erv: [], ervPresets: [], ervSchedules: [],
257
+ scenes: []
258
+ };
259
+
249
260
  const devicesInMelCloudByType = { ata: [], atw: [], erv: [] };
250
- const scenesInMelCloud = response.Scenes ?? []
261
+ const scenesInMelCloud = response.Scenes ?? [];
251
262
 
252
263
  response.Devices.forEach(device => {
253
264
  if (device.Type === 0) devicesInMelCloudByType.ata.push(device);
@@ -259,140 +270,222 @@
259
270
  account.atwDevices = (account.atwDevices ?? []).filter(d => String(d.id) !== '0');
260
271
  account.ervDevices = (account.ervDevices ?? []).filter(d => String(d.id) !== '0');
261
272
 
262
- const removedAta = removeStaleDevices(account.ataDevices, devicesInMelCloudByType.ata);
263
- const removedAtw = removeStaleDevices(account.atwDevices, devicesInMelCloudByType.atw);
264
- const removedErv = removeStaleDevices(account.ervDevices, devicesInMelCloudByType.erv);
265
-
266
- 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
- }
273
+ // ================================
274
+ // Remove stale DEVICES
275
+ // ================================
276
+ const removedAta = removeStaleEntities(
277
+ account.ataDevices,
278
+ devicesInMelCloudByType.ata,
279
+ d => d.id,
280
+ d => d.DeviceID
281
+ );
282
+
283
+ const removedAtw = removeStaleEntities(
284
+ account.atwDevices,
285
+ devicesInMelCloudByType.atw,
286
+ d => d.id,
287
+ d => d.DeviceID
288
+ );
289
+
290
+ const removedErv = removeStaleEntities(
291
+ account.ervDevices,
292
+ devicesInMelCloudByType.erv,
293
+ d => d.id,
294
+ d => d.DeviceID
295
+ );
296
+
297
+ // Remove stale SCENES (global)
298
+ account.scenes = account.scenes ?? [];
299
+
300
+ const removedScenes = removeStaleEntities(
301
+ account.scenes,
302
+ scenesInMelCloud,
303
+ s => s.id,
304
+ s => s.Id
305
+ );
306
+
307
+ removedScenes.forEach(s =>
308
+ newInMelCloud.scenes.push({ ...s, removed: true })
309
+ );
310
+
311
+ // Handle Devices
312
+ const handleDevices = (
313
+ devicesInMelCloud,
314
+ devicesInConfig,
315
+ deviceTypeString,
316
+ newDevices,
317
+ newPresets,
318
+ newSchedules
319
+ ) => {
320
+ const configDevicesMap = new Map(
321
+ devicesInConfig.map(dev => [String(dev.id), dev])
322
+ );
323
+
324
+ devicesInMelCloud.forEach(device => {
325
+ const deviceId = String(device.DeviceID);
326
+ let deviceInConfig = configDevicesMap.get(deviceId);
327
+
328
+ // === Create missing device ===
329
+ if (!deviceInConfig) {
330
+ deviceInConfig = {
331
+ id: deviceId,
332
+ type: device.Type,
333
+ deviceTypeString,
334
+ displayType: 0,
335
+ name: device.DeviceName
336
+ };
337
+ devicesInConfig.push(deviceInConfig);
338
+ newDevices.push(deviceInConfig);
339
+ configDevicesMap.set(deviceId, deviceInConfig);
340
+ }
341
+
342
+ // PRESETS (melcloud)
343
+ if (account.type === 'melcloud') {
344
+ deviceInConfig.presets = deviceInConfig.presets ?? [];
345
+ const presetsInMelCloud = device.Presets || [];
346
+
347
+ const removedPresets = removeStaleEntities(
348
+ deviceInConfig.presets,
349
+ presetsInMelCloud,
350
+ p => p.id,
351
+ p => p.ID
352
+ );
353
+
354
+ removedPresets.forEach(p =>
355
+ newPresets.push({ ...p, removed: true })
356
+ );
357
+
358
+ const presetIds = new Set(
359
+ deviceInConfig.presets.map(p => String(p.id))
360
+ );
361
+
362
+ presetsInMelCloud.forEach((preset, index) => {
363
+ const presetId = String(preset.ID);
364
+ if (!presetIds.has(presetId)) {
365
+ const presetObj = {
366
+ id: presetId,
367
+ displayType: 0,
368
+ name: preset.NumberDescription || `Preset ${index}`,
369
+ namePrefix: false
370
+ };
371
+ deviceInConfig.presets.push(presetObj);
372
+ newPresets.push(presetObj);
373
+ }
374
+ });
375
+ }
376
+
377
+ // SCHEDULES (melcloudhome)
378
+ if (account.type === 'melcloudhome') {
379
+ deviceInConfig.schedules = deviceInConfig.schedules ?? [];
380
+ const schedulesInMelCloud = device.Schedule || [];
381
+
382
+ const removedSchedules = removeStaleEntities(
383
+ deviceInConfig.schedules,
384
+ schedulesInMelCloud,
385
+ s => s.id,
386
+ s => s.Id
387
+ );
388
+
389
+ removedSchedules.forEach(s =>
390
+ newSchedules.push({ ...s, removed: true })
391
+ );
392
+
393
+ const scheduleIds = new Set(
394
+ deviceInConfig.schedules.map(s => String(s.id))
395
+ );
396
+
397
+ schedulesInMelCloud.forEach((schedule, index) => {
398
+ const scheduleId = String(schedule.Id);
399
+ if (!scheduleIds.has(scheduleId)) {
400
+ const scheduleObj = {
401
+ id: scheduleId,
402
+ displayType: 0,
403
+ name: `Schedule ${index}`,
404
+ namePrefix: false
405
+ };
406
+ deviceInConfig.schedules.push(scheduleObj);
407
+ newSchedules.push(scheduleObj);
408
+ }
409
+ });
410
+ }
411
+ });
412
+
413
+ return devicesInConfig;
361
414
  };
362
415
 
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);
366
-
367
- const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
368
- const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
369
- 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;
372
-
373
- if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
416
+ // Execute device handlers
417
+ account.ataDevices = handleDevices(
418
+ devicesInMelCloudByType.ata,
419
+ account.ataDevices,
420
+ "Air Conditioner",
421
+ newInMelCloud.ata,
422
+ newInMelCloud.ataPresets,
423
+ newInMelCloud.ataSchedules
424
+ );
425
+
426
+ account.atwDevices = handleDevices(
427
+ devicesInMelCloudByType.atw,
428
+ account.atwDevices,
429
+ "Heat Pump",
430
+ newInMelCloud.atw,
431
+ newInMelCloud.atwPresets,
432
+ newInMelCloud.atwSchedules
433
+ );
434
+
435
+ account.ervDevices = handleDevices(
436
+ devicesInMelCloudByType.erv,
437
+ account.ervDevices,
438
+ "Energy Recovery Ventilation",
439
+ newInMelCloud.erv,
440
+ newInMelCloud.ervPresets,
441
+ newInMelCloud.ervSchedules
442
+ );
443
+
444
+ // Summary
445
+ const newDevicesCount =
446
+ newInMelCloud.ata.length +
447
+ newInMelCloud.atw.length +
448
+ newInMelCloud.erv.length;
449
+
450
+ const newPresetsCount =
451
+ newInMelCloud.ataPresets.length +
452
+ newInMelCloud.atwPresets.length +
453
+ newInMelCloud.ervPresets.length;
454
+
455
+ const newSchedulesCount =
456
+ newInMelCloud.ataSchedules.length +
457
+ newInMelCloud.atwSchedules.length +
458
+ newInMelCloud.ervSchedules.length;
459
+
460
+ const removedDevicesCount =
461
+ removedAta.length +
462
+ removedAtw.length +
463
+ removedErv.length;
464
+
465
+ if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !removedDevicesCount) {
374
466
  updateInfo('info', 'No changes detected.', 'white');
375
467
  } else {
376
468
  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');
469
+ updateInfo('info', `Found new devices: ${newDevicesCount}`, 'green');
378
470
  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');
471
+ updateInfo('info1', `Found new presets: ${newPresetsCount}`, 'green');
472
+ if (newSchedulesCount)
473
+ updateInfo('info1', `Found new schedules: ${newSchedulesCount}`, 'green');
382
474
  if (removedDevicesCount)
383
- updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
475
+ updateInfo('info2', `Removed devices: ${removedDevicesCount}`, 'orange');
384
476
  }
385
477
 
386
478
  await homebridge.updatePluginConfig(pluginConfig);
387
479
  await homebridge.savePluginConfig(pluginConfig);
480
+
388
481
  } catch (error) {
389
482
  updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
390
- document.getElementById('logIn').className = "btn btn-secondary";
391
483
  } finally {
392
484
  document.getElementById('logIn').className = "btn btn-secondary";
393
485
  homebridge.hideSpinner();
394
486
  }
395
487
  });
396
488
 
489
+
397
490
  })();
398
491
  </script>
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.33",
4
+ "version": "4.4.1-beta.34",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",