homebridge-melcloud-control 4.9.2-beta.7 → 4.10.0-beta.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
@@ -24,10 +24,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
24
  - For plugin < v4.6.0 use Homebridge UI <= v5.5.0
25
25
  - For plugin >= v4.6.0 use Homebridge UI >= v5.13.0
26
26
 
27
- # [4.9.2] - (xx.04.2026)
27
+ # [4.10.0] - (16.04.2026)
28
28
 
29
29
  ## Changes
30
30
 
31
+ - added web socket for real time data refresh with MELCloud Home
31
32
  - cleanup
32
33
 
33
34
  # [4.9.1] - (15.04.2026)
package/index.js CHANGED
@@ -83,7 +83,7 @@ class MelCloudPlatform {
83
83
  melCloudClass = new MelCloud(account, true);
84
84
  break;
85
85
  case 'melcloudhome':
86
- timmers = [{ name: 'checkDevicesList', sampling: 7000 }];
86
+ timmers = [{ name: 'checkDevicesList', sampling: 10000 }];
87
87
  melCloudClass = new MelCloudHome(account, true);
88
88
  break;
89
89
  default:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.9.2-beta.7",
4
+ "version": "4.10.0-beta.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
@@ -10,6 +10,7 @@ export const ApiUrls = {
10
10
  RefreshUnit: "/Device/RequestRefresh?id=deviceid",
11
11
  DeviceState: "/Device/Get?id=DID&buildingID=BID",
12
12
  TileState: "/Tile/Get2?id=DID&buildingID=BID",
13
+ Scenes: "/Scene/List"
13
14
  },
14
15
  Post: {
15
16
  ClientLogin: "/Login/ClientLogin",
@@ -21,6 +21,7 @@ class MelCloudHome extends EventEmitter {
21
21
  this.logWarn = account.log?.warn;
22
22
  this.logError = account.log?.error;
23
23
  this.logDebug = account.log?.debug;
24
+ this.pluginStart = pluginStart;
24
25
 
25
26
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
26
27
  .on('warn', warn => this.emit('warn', warn))
@@ -86,7 +87,7 @@ class MelCloudHome extends EventEmitter {
86
87
  if (this.logError) this.emit('error', `connectSocket: cannot get WS hash: ${err.message}`);
87
88
  this.connecting = false;
88
89
  this.scheduleReconnect();
89
- return false;
90
+ return;
90
91
  }
91
92
 
92
93
  const url = `${ApiUrls.Home.WebSocket}${hash}`;
@@ -423,10 +424,13 @@ class MelCloudHome extends EventEmitter {
423
424
  // attachTokenInterceptors() dodaje interceptory tylko przy pierwszym wywołaniu.
424
425
  this.ensureClient();
425
426
  this.attachTokenInterceptors();
426
- this.emit('client', this.client);
427
- const webSocket = await this.connectSocket().catch(err => {
428
- if (this.logError) this.emit('error', `Initial WebSocket connect failed: ${err.message}`);
429
- });
427
+
428
+ if (this.pluginStart) {
429
+ this.emit('client', this.client);
430
+ await this.connectSocket().catch(err => {
431
+ if (this.logError) this.emit('error', `Initial WebSocket connect failed: ${err.message}`);
432
+ });
433
+ }
430
434
  }
431
435
 
432
436
  connectInfo.State = exchangeRes;
@@ -435,117 +439,6 @@ class MelCloudHome extends EventEmitter {
435
439
  return connectInfo;
436
440
  }
437
441
 
438
- // ── Scenes & Devices ──────────────────────────────────────────────────────
439
-
440
- async checkScenesList() {
441
- try {
442
- if (this.logDebug) this.emit('debug', 'Scanning for scenes');
443
-
444
- const resp = await this.client.get(ApiUrls.Home.Get.Scenes);
445
- const scenesList = resp.data;
446
-
447
- if (this.logDebug) this.emit('debug', `Scenes: ${JSON.stringify(scenesList, null, 2)}`);
448
-
449
- return this.capitalizeKeysDeep(scenesList);
450
- } catch (error) {
451
- throw new Error(`Check scenes list error: ${error.message}`);
452
- }
453
- }
454
-
455
- async checkDevicesList() {
456
- try {
457
- const result = { State: false, Status: null, Buildings: {}, Devices: [], Scenes: [] };
458
- if (this.logDebug) this.emit('debug', 'Scanning for devices');
459
-
460
- const resp = await this.client.get(ApiUrls.Home.Get.Context);
461
- const userContext = resp.data;
462
- //if (this.logDebug) this.emit('debug', `User Context: ${JSON.stringify(userContext, null, 2)}`);
463
-
464
- const buildings = userContext.buildings ?? [];
465
- const guestBuildings = userContext.guestBuildings ?? [];
466
- const buildingsList = [...buildings, ...guestBuildings];
467
-
468
- if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
469
-
470
- if (buildingsList.length === 0) {
471
- result.Status = 'No buildings found';
472
- return result;
473
- }
474
-
475
- const capitalizeKeys = obj => Object.fromEntries(
476
- Object.entries(obj).map(([k, v]) => [k.charAt(0).toUpperCase() + k.slice(1), v])
477
- );
478
-
479
- const createDevice = (device, type) => {
480
- const settingsObject = Object.fromEntries(
481
- (device.Settings || []).map(({ name, value }) => [
482
- name.charAt(0).toUpperCase() + name.slice(1),
483
- this.functions.convertValue(value),
484
- ])
485
- );
486
-
487
- const deviceObject = {
488
- ...capitalizeKeys(device.Capabilities || {}),
489
- ...settingsObject,
490
- DeviceType: type,
491
- FirmwareAppVersion: device.ConnectedInterfaceIdentifier,
492
- IsConnected: device.IsConnected,
493
- };
494
-
495
- if (device.FrostProtection) device.FrostProtection = capitalizeKeys(device.FrostProtection);
496
- if (device.OverheatProtection) device.OverheatProtection = capitalizeKeys(device.OverheatProtection);
497
- if (device.HolidayMode) device.HolidayMode = capitalizeKeys(device.HolidayMode);
498
- if (Array.isArray(device.Schedule)) device.Schedule = device.Schedule.map(s => this.capitalizeKeysDeep(s));
499
-
500
- const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
501
-
502
- return {
503
- ...rest,
504
- Type: type,
505
- DeviceID: Id,
506
- DeviceName: GivenDisplayName,
507
- SerialNumber: Id,
508
- Device: deviceObject,
509
- };
510
- };
511
-
512
- const devices = buildingsList.flatMap(building => [
513
- ...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
514
- ...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
515
- ...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3)),
516
- ]);
517
-
518
- if (devices.length === 0) {
519
- result.Status = 'No devices found';
520
- return result;
521
- }
522
-
523
- // Sceny
524
- let scenes = [];
525
- try {
526
- scenes = await this.checkScenesList();
527
- if (this.logDebug) this.emit('debug', `Found ${scenes.length} scenes`);
528
- } catch (error) {
529
- if (this.logError) this.emit('error', `Get scenes error: ${error}`);
530
- }
531
-
532
- result.State = true;
533
- result.Status = `Found ${devices.length} devices${scenes.length > 0 ? ` and ${scenes.length} scenes` : ''}`;
534
- result.Buildings = userContext;
535
- result.Devices = devices;
536
- result.Scenes = scenes;
537
-
538
- for (const deviceData of result.Devices) {
539
- deviceData.Scenes = result.Scenes;
540
- this.emit(deviceData.DeviceID, 'request', deviceData);
541
- }
542
-
543
- return result;
544
- } catch (error) {
545
- throw new Error(`Check devices list error: ${error.message}`);
546
- }
547
- }
548
-
549
442
  // ── Connect ───────────────────────────────────────────────────────────────
550
443
 
551
444
  async connect() {
@@ -775,6 +668,117 @@ class MelCloudHome extends EventEmitter {
775
668
  throw new Error(`Connect error: ${error.message}`);
776
669
  }
777
670
  }
671
+
672
+ // ── Scenes & Devices ──────────────────────────────────────────────────────
673
+
674
+ async checkScenesList() {
675
+ try {
676
+ if (this.logDebug) this.emit('debug', 'Scanning for scenes');
677
+
678
+ const resp = await this.client.get(ApiUrls.Home.Get.Scenes);
679
+ const scenesList = resp.data;
680
+
681
+ if (this.logDebug) this.emit('debug', `Scenes: ${JSON.stringify(scenesList, null, 2)}`);
682
+
683
+ return this.capitalizeKeysDeep(scenesList);
684
+ } catch (error) {
685
+ throw new Error(`Check scenes list error: ${error.message}`);
686
+ }
687
+ }
688
+
689
+ async checkDevicesList() {
690
+ try {
691
+ const result = { State: false, Status: null, Buildings: {}, Devices: [], Scenes: [] };
692
+ if (this.logDebug) this.emit('debug', 'Scanning for devices');
693
+
694
+ const resp = await this.client.get(ApiUrls.Home.Get.Context);
695
+ const userContext = resp.data;
696
+ //if (this.logDebug) this.emit('debug', `User Context: ${JSON.stringify(userContext, null, 2)}`);
697
+
698
+ const buildings = userContext.buildings ?? [];
699
+ const guestBuildings = userContext.guestBuildings ?? [];
700
+ const buildingsList = [...buildings, ...guestBuildings];
701
+
702
+ if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
703
+
704
+ if (buildingsList.length === 0) {
705
+ result.Status = 'No buildings found';
706
+ return result;
707
+ }
708
+
709
+ const capitalizeKeys = obj => Object.fromEntries(
710
+ Object.entries(obj).map(([k, v]) => [k.charAt(0).toUpperCase() + k.slice(1), v])
711
+ );
712
+
713
+ const createDevice = (device, type) => {
714
+ const settingsObject = Object.fromEntries(
715
+ (device.Settings || []).map(({ name, value }) => [
716
+ name.charAt(0).toUpperCase() + name.slice(1),
717
+ this.functions.convertValue(value),
718
+ ])
719
+ );
720
+
721
+ const deviceObject = {
722
+ ...capitalizeKeys(device.Capabilities || {}),
723
+ ...settingsObject,
724
+ DeviceType: type,
725
+ FirmwareAppVersion: device.ConnectedInterfaceIdentifier,
726
+ IsConnected: device.IsConnected,
727
+ };
728
+
729
+ if (device.FrostProtection) device.FrostProtection = capitalizeKeys(device.FrostProtection);
730
+ if (device.OverheatProtection) device.OverheatProtection = capitalizeKeys(device.OverheatProtection);
731
+ if (device.HolidayMode) device.HolidayMode = capitalizeKeys(device.HolidayMode);
732
+ if (Array.isArray(device.Schedule)) device.Schedule = device.Schedule.map(s => this.capitalizeKeysDeep(s));
733
+
734
+ const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
735
+
736
+ return {
737
+ ...rest,
738
+ Type: type,
739
+ DeviceID: Id,
740
+ DeviceName: GivenDisplayName,
741
+ SerialNumber: Id,
742
+ Device: deviceObject,
743
+ };
744
+ };
745
+
746
+ const devices = buildingsList.flatMap(building => [
747
+ ...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
748
+ ...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
749
+ ...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3)),
750
+ ]);
751
+
752
+ if (devices.length === 0) {
753
+ result.Status = 'No devices found';
754
+ return result;
755
+ }
756
+
757
+ // Sceny
758
+ let scenes = [];
759
+ try {
760
+ scenes = await this.checkScenesList();
761
+ if (this.logDebug) this.emit('debug', `Found ${scenes.length} scenes`);
762
+ } catch (error) {
763
+ if (this.logError) this.emit('error', `Get scenes error: ${error}`);
764
+ }
765
+
766
+ result.State = true;
767
+ result.Status = `Found ${devices.length} devices${scenes.length > 0 ? ` and ${scenes.length} scenes` : ''}`;
768
+ result.Buildings = userContext;
769
+ result.Devices = devices;
770
+ result.Scenes = scenes;
771
+
772
+ for (const deviceData of result.Devices) {
773
+ deviceData.Scenes = result.Scenes;
774
+ this.emit(deviceData.DeviceID, 'request', deviceData);
775
+ }
776
+
777
+ return result;
778
+ } catch (error) {
779
+ throw new Error(`Check devices list error: ${error.message}`);
780
+ }
781
+ }
778
782
  }
779
783
 
780
784
  export default MelCloudHome;