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 +2 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/constants.js +1 -0
- package/src/melcloudhome.js +120 -116
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.
|
|
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:
|
|
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.
|
|
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
package/src/melcloudhome.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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;
|