homebridge-melcloud-control 4.3.2-beta.0 → 4.3.2-beta.10
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/README.md +3 -0
- package/config.schema.json +10 -0
- package/package.json +1 -1
- package/src/deviceata.js +12 -11
- package/src/deviceatw.js +95 -33
- package/src/deviceerv.js +13 -12
- package/src/functions.js +8 -0
- package/src/melcloudata.js +105 -94
- package/src/melcloudhome.js +1 -5
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
91
91
|
* Shedule active.
|
|
92
92
|
* Scene control.
|
|
93
93
|
* In standby.
|
|
94
|
+
* Is connected.
|
|
94
95
|
* Error.
|
|
95
96
|
* Heat Pump:
|
|
96
97
|
* Heater Cooler:
|
|
@@ -148,6 +149,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
148
149
|
* Shedule active.
|
|
149
150
|
* Scene control.
|
|
150
151
|
* In standby.
|
|
152
|
+
* Is connected.
|
|
151
153
|
* Error.
|
|
152
154
|
* Energy Recovery Ventilation Lossnay:
|
|
153
155
|
* Heater Cooler:
|
|
@@ -186,6 +188,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
186
188
|
* Shedule active.
|
|
187
189
|
* Scene control.
|
|
188
190
|
* In standby.
|
|
191
|
+
* Is connected.
|
|
189
192
|
* Error.
|
|
190
193
|
|
|
191
194
|
### HOME app current device mode display
|
package/config.schema.json
CHANGED
|
@@ -1287,6 +1287,15 @@
|
|
|
1287
1287
|
"functionBody": "return model.accounts[arrayIndices[0]].type === 'melcloudhome';"
|
|
1288
1288
|
}
|
|
1289
1289
|
},
|
|
1290
|
+
"frostProtectionSupport": {
|
|
1291
|
+
"title": "Frost Protection Support",
|
|
1292
|
+
"type": "boolean",
|
|
1293
|
+
"default": false,
|
|
1294
|
+
"description": "This enable extra frost protection control and sensors to use with automations in HomeKit app.",
|
|
1295
|
+
"condition": {
|
|
1296
|
+
"functionBody": "return model.accounts[arrayIndices[0]].type === 'melcloudhome';"
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1290
1299
|
"refreshInterval": {
|
|
1291
1300
|
"title": "Refresh Interval",
|
|
1292
1301
|
"type": "integer",
|
|
@@ -2769,6 +2778,7 @@
|
|
|
2769
2778
|
"items": [
|
|
2770
2779
|
"accounts[].atwDevices[].hideZone",
|
|
2771
2780
|
"accounts[].atwDevices[].name",
|
|
2781
|
+
"accounts[].atwDevices[].frostProtectionSupport",
|
|
2772
2782
|
"accounts[].atwDevices[].holidayModeSupport",
|
|
2773
2783
|
"accounts[].atwDevices[].refreshInterval"
|
|
2774
2784
|
],
|
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.2-beta.
|
|
4
|
+
"version": "4.3.2-beta.10",
|
|
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/deviceata.js
CHANGED
|
@@ -649,7 +649,7 @@ class DeviceAta extends EventEmitter {
|
|
|
649
649
|
return state;
|
|
650
650
|
})
|
|
651
651
|
accessory.addService(this.roomTemperatureSensorService);
|
|
652
|
-
}
|
|
652
|
+
}
|
|
653
653
|
|
|
654
654
|
if (this.temperatureOutdoorSensor && supportsOutdoorTemperature && this.accessory.outdoorTemperature !== null) {
|
|
655
655
|
if (this.logDebug) this.emit('debug', `Prepare outdoor temperature sensor service`);
|
|
@@ -662,7 +662,7 @@ class DeviceAta extends EventEmitter {
|
|
|
662
662
|
return state;
|
|
663
663
|
})
|
|
664
664
|
accessory.addService(this.outdoorTemperatureSensorService);
|
|
665
|
-
}
|
|
665
|
+
}
|
|
666
666
|
|
|
667
667
|
//in standby sensor
|
|
668
668
|
if (this.inStandbySensor && this.accessory.inStandbyMode !== null) {
|
|
@@ -922,7 +922,7 @@ class DeviceAta extends EventEmitter {
|
|
|
922
922
|
accessory.addService(presetControlSensorService);
|
|
923
923
|
}
|
|
924
924
|
});
|
|
925
|
-
}
|
|
925
|
+
}
|
|
926
926
|
|
|
927
927
|
//schedules services
|
|
928
928
|
if (this.schedules.length > 0 && this.accessory.scheduleEnabled !== null) {
|
|
@@ -996,7 +996,7 @@ class DeviceAta extends EventEmitter {
|
|
|
996
996
|
accessory.addService(scheduleSensorService);
|
|
997
997
|
}
|
|
998
998
|
});
|
|
999
|
-
}
|
|
999
|
+
}
|
|
1000
1000
|
|
|
1001
1001
|
//scenes
|
|
1002
1002
|
if (this.scenes.length > 0) {
|
|
@@ -1055,7 +1055,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1055
1055
|
accessory.addService(sceneControlSensorService);
|
|
1056
1056
|
}
|
|
1057
1057
|
});
|
|
1058
|
-
}
|
|
1058
|
+
}
|
|
1059
1059
|
|
|
1060
1060
|
//buttons services
|
|
1061
1061
|
if (this.buttons.length > 0) {
|
|
@@ -1318,7 +1318,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1318
1318
|
accessory.addService(buttonControlSensorService);
|
|
1319
1319
|
}
|
|
1320
1320
|
});
|
|
1321
|
-
}
|
|
1321
|
+
}
|
|
1322
1322
|
|
|
1323
1323
|
return accessory;
|
|
1324
1324
|
} catch (error) {
|
|
@@ -1690,7 +1690,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1690
1690
|
//sensor
|
|
1691
1691
|
if (preset.displayType < 7) this.presetControlSensorServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
|
|
1692
1692
|
});
|
|
1693
|
-
}
|
|
1693
|
+
}
|
|
1694
1694
|
|
|
1695
1695
|
///schedules
|
|
1696
1696
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
@@ -1710,7 +1710,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1710
1710
|
//sensor
|
|
1711
1711
|
if (schedule.displayType < 7) this.scheduleSensorServices?.[i]?.updateCharacteristic(characteristicType, schedule.state);
|
|
1712
1712
|
});
|
|
1713
|
-
}
|
|
1713
|
+
}
|
|
1714
1714
|
|
|
1715
1715
|
//scenes
|
|
1716
1716
|
if (this.scenes.length > 0) {
|
|
@@ -1727,7 +1727,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1727
1727
|
//sensor
|
|
1728
1728
|
if (scene.displayType < 7) this.sceneControlSensorServices?.[i]?.updateCharacteristic(characteristicType, scene.state);
|
|
1729
1729
|
});
|
|
1730
|
-
}
|
|
1730
|
+
}
|
|
1731
1731
|
|
|
1732
1732
|
//buttons
|
|
1733
1733
|
if (this.buttons.length > 0) {
|
|
@@ -1851,7 +1851,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1851
1851
|
//sensor
|
|
1852
1852
|
if (button.displayType < 7) this.buttonControlSensorServices?.[i]?.updateCharacteristic(characteristicType, button.state);
|
|
1853
1853
|
});
|
|
1854
|
-
}
|
|
1854
|
+
}
|
|
1855
1855
|
|
|
1856
1856
|
//log current state
|
|
1857
1857
|
if (this.logInfo) {
|
|
@@ -1868,7 +1868,8 @@ class DeviceAta extends EventEmitter {
|
|
|
1868
1868
|
if (supportsSwingFunction) this.emit('info', `Air direction: ${AirConditioner.AirDirectionMapEnumToString[obj.currentSwingMode]}`);
|
|
1869
1869
|
this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
|
|
1870
1870
|
this.emit('info', `Lock physical controls: ${obj.lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
|
|
1871
|
-
|
|
1871
|
+
if (this.accountType === 'melcloudhome') this.emit('info', `Signal strength: ${deviceData.Rssi}dBm`);
|
|
1872
|
+
}
|
|
1872
1873
|
})
|
|
1873
1874
|
.on('success', (success) => this.emit('success', success))
|
|
1874
1875
|
.on('info', (info) => this.emit('info', info))
|
package/src/deviceatw.js
CHANGED
|
@@ -44,6 +44,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
44
44
|
this.inStandbySensor = device.inStandbySensor || false;
|
|
45
45
|
this.connectSensor = device.connectSensor || false;
|
|
46
46
|
this.errorSensor = device.errorSensor || false;
|
|
47
|
+
this.frostProtectionSupport = device.frostProtectionSupport || false;
|
|
47
48
|
this.holidayModeSupport = device.holidayModeSupport || false;
|
|
48
49
|
this.presets = this.accountType === 'melcloud' ? (device.presets || []).filter(preset => (preset.displayType ?? 0) > 0 && preset.id !== '0') : [];
|
|
49
50
|
this.schedules = this.accountType === 'melcloudhome' ? (device.schedules || []).filter(schedule => (schedule.displayType ?? 0) > 0 && schedule.id !== '0') : [];
|
|
@@ -851,7 +852,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
851
852
|
break;
|
|
852
853
|
};
|
|
853
854
|
});
|
|
854
|
-
}
|
|
855
|
+
}
|
|
855
856
|
|
|
856
857
|
//sensor services
|
|
857
858
|
if (zonesSensorsCount > 0) {
|
|
@@ -1022,7 +1023,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1022
1023
|
break;
|
|
1023
1024
|
};
|
|
1024
1025
|
});
|
|
1025
|
-
}
|
|
1026
|
+
}
|
|
1026
1027
|
|
|
1027
1028
|
//in standby sensor
|
|
1028
1029
|
if (this.inStandbySensor && this.accessory.inStandbyMode !== null) {
|
|
@@ -1066,6 +1067,53 @@ class DeviceAtw extends EventEmitter {
|
|
|
1066
1067
|
accessory.addService(this.errorService);
|
|
1067
1068
|
}
|
|
1068
1069
|
|
|
1070
|
+
//frost protection
|
|
1071
|
+
if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) {
|
|
1072
|
+
//control
|
|
1073
|
+
if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
|
|
1074
|
+
this.frostProtectionControlService = new Service.Switch(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
|
|
1075
|
+
this.frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1076
|
+
this.frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
|
|
1077
|
+
this.frostProtectionControlService.getCharacteristic(Characteristic.On)
|
|
1078
|
+
.onGet(async () => {
|
|
1079
|
+
const state = this.accessory.frostProtectionEnabled;
|
|
1080
|
+
return state;
|
|
1081
|
+
})
|
|
1082
|
+
.onSet(async (state) => {
|
|
1083
|
+
try {
|
|
1084
|
+
deviceData.FrostProtection.Enabled = state;
|
|
1085
|
+
if (this.logInfo) this.emit('info', `Frost protection: ${state ? 'Enabled' : 'Disabled'}`);
|
|
1086
|
+
await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
|
|
1089
|
+
};
|
|
1090
|
+
});
|
|
1091
|
+
accessory.addService(this.frostProtectionControlService);
|
|
1092
|
+
|
|
1093
|
+
if (this.logDebug) this.emit('debug', `Prepare frost protection control sensor service`);
|
|
1094
|
+
this.frostProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Frost Protection Control`, `frostProtectionControlSensorService${deviceId}`);
|
|
1095
|
+
this.frostProtectionControlSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1096
|
+
this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`);
|
|
1097
|
+
this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
|
|
1098
|
+
.onGet(async () => {
|
|
1099
|
+
const state = this.accessory.frostProtectionEnabled;
|
|
1100
|
+
return state;
|
|
1101
|
+
})
|
|
1102
|
+
accessory.addService(this.frostProtectionControlSensorService);
|
|
1103
|
+
|
|
1104
|
+
//sensor
|
|
1105
|
+
if (this.logDebug) this.emit('debug', `Prepare frost protection service`);
|
|
1106
|
+
this.frostProtectionSensorService = new Service.ContactSensor(`${serviceName} Frost Protection`, `frostProtectionSensorService${deviceId}`);
|
|
1107
|
+
this.frostProtectionSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
1108
|
+
this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
|
|
1109
|
+
this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
|
|
1110
|
+
.onGet(async () => {
|
|
1111
|
+
const state = this.accessory.frostProtectionActive;
|
|
1112
|
+
return state;
|
|
1113
|
+
})
|
|
1114
|
+
accessory.addService(this.frostProtectionSensorService);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1069
1117
|
//holiday mode
|
|
1070
1118
|
if (this.holidayModeSupport && this.accessory.holidayModeEnabled !== null) {
|
|
1071
1119
|
//control
|
|
@@ -1189,7 +1237,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1189
1237
|
accessory.addService(presetControlSensorService);
|
|
1190
1238
|
}
|
|
1191
1239
|
});
|
|
1192
|
-
}
|
|
1240
|
+
}
|
|
1193
1241
|
|
|
1194
1242
|
//schedules services
|
|
1195
1243
|
if (this.schedules.length > 0 && this.accessory.scheduleEnabled !== null) {
|
|
@@ -1263,7 +1311,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1263
1311
|
accessory.addService(scheduleSensorService);
|
|
1264
1312
|
}
|
|
1265
1313
|
});
|
|
1266
|
-
}
|
|
1314
|
+
}
|
|
1267
1315
|
|
|
1268
1316
|
//scenes
|
|
1269
1317
|
if (this.scenes.length > 0) {
|
|
@@ -1322,7 +1370,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1322
1370
|
accessory.addService(sceneControlSensorService);
|
|
1323
1371
|
}
|
|
1324
1372
|
});
|
|
1325
|
-
}
|
|
1373
|
+
}
|
|
1326
1374
|
|
|
1327
1375
|
//buttons services
|
|
1328
1376
|
if (this.buttons.length > 0) {
|
|
@@ -1517,7 +1565,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
1517
1565
|
accessory.addService(buttonControlSensorService);
|
|
1518
1566
|
}
|
|
1519
1567
|
});
|
|
1520
|
-
}
|
|
1568
|
+
}
|
|
1521
1569
|
|
|
1522
1570
|
return accessory;
|
|
1523
1571
|
} catch (error) {
|
|
@@ -1574,6 +1622,10 @@ class DeviceAtw extends EventEmitter {
|
|
|
1574
1622
|
const holidayModeEnabled = accountTypeMelcloud ? holidayMode : deviceData.HolidayMode?.Enabled;
|
|
1575
1623
|
const holidayModeActive = deviceData.HolidayMode?.Active ?? false;
|
|
1576
1624
|
|
|
1625
|
+
//protection
|
|
1626
|
+
const frostProtectionEnabled = deviceData.FrostProtection?.Enabled;
|
|
1627
|
+
const frostProtectionActive = deviceData.FrostProtection?.Active ?? false;
|
|
1628
|
+
|
|
1577
1629
|
//device info
|
|
1578
1630
|
const supportsStanbyMode = deviceData.Device[supportStandbyKey];
|
|
1579
1631
|
const supportsHeatPump = ![1, 2, 3, 4, 5, 6, 7, 15].includes(this.hideZone);
|
|
@@ -1681,6 +1733,8 @@ class DeviceAtw extends EventEmitter {
|
|
|
1681
1733
|
temperatureUnit: TemperatureDisplayUnits[this.accountInfo.useFahrenheit ? 1 : 0],
|
|
1682
1734
|
isConnected: isConnected,
|
|
1683
1735
|
isInError: isInError,
|
|
1736
|
+
frostProtectionEnabled: frostProtectionEnabled,
|
|
1737
|
+
frostProtectionActive: frostProtectionActive,
|
|
1684
1738
|
scheduleEnabled: scheduleEnabled,
|
|
1685
1739
|
holidayModeEnabled: holidayModeEnabled,
|
|
1686
1740
|
holidayModeActive: holidayModeActive,
|
|
@@ -1947,39 +2001,40 @@ class DeviceAtw extends EventEmitter {
|
|
|
1947
2001
|
let operationModeText = '';
|
|
1948
2002
|
switch (i) {
|
|
1949
2003
|
case caseHeatPump: //Heat Pump - HEAT, COOL, OFF
|
|
1950
|
-
this.emit('info',
|
|
1951
|
-
this.emit('info',
|
|
1952
|
-
this.emit('info',
|
|
1953
|
-
this.emit('info',
|
|
1954
|
-
this.emit('info',
|
|
2004
|
+
this.emit('info', `Power: ${power ? 'On' : 'Off'}`)
|
|
2005
|
+
this.emit('info', `Operation mode: ${HeatPump.SystemMapEnumToString[unitStatus]}`);
|
|
2006
|
+
this.emit('info', `Outdoor temperature: ${roomTemperature}${obj.temperatureUnit}`);
|
|
2007
|
+
this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
|
|
2008
|
+
this.emit('info', `Lock physical controls: ${lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
|
|
2009
|
+
if (this.accountType === 'melcloudhome') this.emit('info', `Signal strength: ${deviceData.Rssi}dBm`);
|
|
1955
2010
|
break;
|
|
1956
2011
|
case caseZone1: //Zone 1 - HEAT THERMOSTAT, HEAT FLOW, HEAT CURVE, COOL THERMOSTAT, COOL FLOW, FLOOR DRY UP
|
|
1957
2012
|
operationModeText = idleZone1 ? HeatPump.ZoneOperationMapEnumToString[6] : HeatPump.ZoneOperationMapEnumToString[operationModeZone1];
|
|
1958
|
-
this.emit('info',
|
|
1959
|
-
this.emit('info',
|
|
1960
|
-
this.emit('info',
|
|
1961
|
-
this.emit('info',
|
|
1962
|
-
this.emit('info',
|
|
2013
|
+
this.emit('info', `Operation mode: ${operationModeText}`);
|
|
2014
|
+
this.emit('info', `Temperature: ${roomTemperature}${obj.temperatureUnit}`);
|
|
2015
|
+
this.emit('info', `Target temperature: ${setTemperature}${obj.temperatureUnit}`)
|
|
2016
|
+
this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
|
|
2017
|
+
this.emit('info', `Lock physical controls: ${lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
|
|
1963
2018
|
break;
|
|
1964
2019
|
case caseHotWater: //Hot Water - AUTO, HEAT NOW
|
|
1965
2020
|
operationModeText = operationMode === 1 ? HeatPump.ForceDhwMapEnumToString[1] : HeatPump.ForceDhwMapEnumToString[forcedHotWaterMode ? 1 : 0];
|
|
1966
|
-
this.emit('info',
|
|
1967
|
-
this.emit('info',
|
|
1968
|
-
this.emit('info',
|
|
1969
|
-
this.emit('info',
|
|
1970
|
-
this.emit('info',
|
|
2021
|
+
this.emit('info', `Operation mode: ${operationModeText}`);
|
|
2022
|
+
this.emit('info', `Temperature: ${roomTemperature}${obj.temperatureUnit}`);
|
|
2023
|
+
this.emit('info', `Target temperature: ${setTemperature}${obj.temperatureUnit}`)
|
|
2024
|
+
this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
|
|
2025
|
+
this.emit('info', `Lock physical controls: ${lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
|
|
1971
2026
|
break;
|
|
1972
2027
|
case caseZone2: //Zone 2 - HEAT THERMOSTAT, HEAT FLOW, HEAT CURVE, COOL THERMOSTAT, COOL FLOW, FLOOR DRY UP
|
|
1973
2028
|
operationModeText = idleZone2 ? HeatPump.ZoneOperationMapEnumToString[6] : HeatPump.ZoneOperationMapEnumToString[operationModeZone2];
|
|
1974
|
-
this.emit('info',
|
|
1975
|
-
this.emit('info',
|
|
1976
|
-
this.emit('info',
|
|
1977
|
-
this.emit('info',
|
|
1978
|
-
this.emit('info',
|
|
2029
|
+
this.emit('info', `Operation mode: ${operationModeText}`);
|
|
2030
|
+
this.emit('info', `Temperature: ${roomTemperature}${obj.temperatureUnit}`);
|
|
2031
|
+
this.emit('info', `Target temperature: ${setTemperature}${obj.temperatureUnit}`)
|
|
2032
|
+
this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
|
|
2033
|
+
this.emit('info', `Lock physical controls: ${lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
|
|
1979
2034
|
break;
|
|
1980
2035
|
};
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
1983
2038
|
|
|
1984
2039
|
//update sensors characteristics
|
|
1985
2040
|
for (let i = 0; i < zonesSensorsCount; i++) {
|
|
@@ -2064,7 +2119,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
2064
2119
|
break;
|
|
2065
2120
|
};
|
|
2066
2121
|
};
|
|
2067
|
-
}
|
|
2122
|
+
}
|
|
2068
2123
|
this.accessory = obj;
|
|
2069
2124
|
|
|
2070
2125
|
//update services
|
|
@@ -2078,6 +2133,13 @@ class DeviceAtw extends EventEmitter {
|
|
|
2078
2133
|
this.connectService?.updateCharacteristic(Characteristic.ContactSensorState, isConnected);
|
|
2079
2134
|
this.errorService?.updateCharacteristic(Characteristic.ContactSensorState, isInError);
|
|
2080
2135
|
|
|
2136
|
+
//frost protection
|
|
2137
|
+
if (this.frostProtectionSupport && frostProtectionEnabled !== null) {
|
|
2138
|
+
this.frostProtectionControlService?.updateCharacteristic(Characteristic.On, frostProtectionEnabled);
|
|
2139
|
+
this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionEnabled);
|
|
2140
|
+
this.frostProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionActive);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2081
2143
|
//holiday mode
|
|
2082
2144
|
if (this.holidayModeSupport && holidayModeEnabled !== null) {
|
|
2083
2145
|
this.holidayModeControlService?.updateCharacteristic(Characteristic.On, holidayModeEnabled);
|
|
@@ -2112,7 +2174,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
2112
2174
|
//sensor
|
|
2113
2175
|
if (preset.displayType < 7) this.presetControlSensorServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
|
|
2114
2176
|
});
|
|
2115
|
-
}
|
|
2177
|
+
}
|
|
2116
2178
|
|
|
2117
2179
|
///schedules
|
|
2118
2180
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
@@ -2132,7 +2194,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
2132
2194
|
//sensor
|
|
2133
2195
|
if (schedule.displayType < 7) this.scheduleSensorServices?.[i]?.updateCharacteristic(characteristicType, schedule.state);
|
|
2134
2196
|
});
|
|
2135
|
-
}
|
|
2197
|
+
}
|
|
2136
2198
|
|
|
2137
2199
|
//scenes
|
|
2138
2200
|
if (this.scenes.length > 0) {
|
|
@@ -2149,7 +2211,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
2149
2211
|
//sensor
|
|
2150
2212
|
if (scene.displayType < 7) this.sceneControlSensorServices?.[i]?.updateCharacteristic(characteristicType, scene.state);
|
|
2151
2213
|
});
|
|
2152
|
-
}
|
|
2214
|
+
}
|
|
2153
2215
|
|
|
2154
2216
|
//buttons
|
|
2155
2217
|
if (this.buttons.length > 0) {
|
|
@@ -2237,7 +2299,7 @@ class DeviceAtw extends EventEmitter {
|
|
|
2237
2299
|
//sensor
|
|
2238
2300
|
if (button.displayType < 7) this.buttonControlSensorServices?.[i]?.updateCharacteristic(characteristicType, button.state);
|
|
2239
2301
|
});
|
|
2240
|
-
}
|
|
2302
|
+
}
|
|
2241
2303
|
})
|
|
2242
2304
|
.on('success', (success) => this.emit('success', success))
|
|
2243
2305
|
.on('info', (info) => this.emit('info', info))
|
package/src/deviceerv.js
CHANGED
|
@@ -586,7 +586,7 @@ class DeviceErv extends EventEmitter {
|
|
|
586
586
|
return state;
|
|
587
587
|
})
|
|
588
588
|
accessory.addService(this.roomTemperatureSensorService);
|
|
589
|
-
}
|
|
589
|
+
}
|
|
590
590
|
|
|
591
591
|
//temperature sensor service supply
|
|
592
592
|
if (this.temperatureSupplySensor && supportsSupplyTemperature && this.accessory.supplyTemperature !== null) {
|
|
@@ -600,7 +600,7 @@ class DeviceErv extends EventEmitter {
|
|
|
600
600
|
return state;
|
|
601
601
|
})
|
|
602
602
|
accessory.addService(this.supplyTemperatureSensorService);
|
|
603
|
-
}
|
|
603
|
+
}
|
|
604
604
|
|
|
605
605
|
//temperature sensor service outdoor
|
|
606
606
|
if (this.temperatureOutdoorSensor && supportsOutdoorTemperature && this.accessory.outdoorTemperature !== null) {
|
|
@@ -614,7 +614,7 @@ class DeviceErv extends EventEmitter {
|
|
|
614
614
|
return state;
|
|
615
615
|
})
|
|
616
616
|
accessory.addService(this.outdoorTemperatureSensorService);
|
|
617
|
-
}
|
|
617
|
+
}
|
|
618
618
|
|
|
619
619
|
//core maintenance
|
|
620
620
|
if (this.accessory.coreMaintenanceRequired !== null) {
|
|
@@ -849,7 +849,7 @@ class DeviceErv extends EventEmitter {
|
|
|
849
849
|
accessory.addService(presetControlSensorService);
|
|
850
850
|
}
|
|
851
851
|
});
|
|
852
|
-
}
|
|
852
|
+
}
|
|
853
853
|
|
|
854
854
|
//schedules services
|
|
855
855
|
if (this.schedules.length > 0 && this.accessory.scheduleEnabled !== null) {
|
|
@@ -923,7 +923,7 @@ class DeviceErv extends EventEmitter {
|
|
|
923
923
|
accessory.addService(scheduleSensorService);
|
|
924
924
|
}
|
|
925
925
|
});
|
|
926
|
-
}
|
|
926
|
+
}
|
|
927
927
|
|
|
928
928
|
//scenes
|
|
929
929
|
if (this.scenes.length > 0) {
|
|
@@ -982,7 +982,7 @@ class DeviceErv extends EventEmitter {
|
|
|
982
982
|
accessory.addService(sceneControlSensorService);
|
|
983
983
|
}
|
|
984
984
|
});
|
|
985
|
-
}
|
|
985
|
+
}
|
|
986
986
|
|
|
987
987
|
//buttons services
|
|
988
988
|
if (this.buttons.length > 0) {
|
|
@@ -1118,7 +1118,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1118
1118
|
accessory.addService(buttonControlSensorService);
|
|
1119
1119
|
}
|
|
1120
1120
|
});
|
|
1121
|
-
}
|
|
1121
|
+
}
|
|
1122
1122
|
|
|
1123
1123
|
return accessory;
|
|
1124
1124
|
} catch (error) {
|
|
@@ -1454,7 +1454,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1454
1454
|
//sensor
|
|
1455
1455
|
if (preset.displayType < 7) this.presetControlSensorServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
|
|
1456
1456
|
});
|
|
1457
|
-
}
|
|
1457
|
+
}
|
|
1458
1458
|
|
|
1459
1459
|
///schedules
|
|
1460
1460
|
if (this.schedules.length > 0 && scheduleEnabled !== null) {
|
|
@@ -1474,7 +1474,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1474
1474
|
//sensor
|
|
1475
1475
|
if (schedule.displayType < 7) this.scheduleSensorServices?.[i]?.updateCharacteristic(characteristicType, schedule.state);
|
|
1476
1476
|
});
|
|
1477
|
-
}
|
|
1477
|
+
}
|
|
1478
1478
|
|
|
1479
1479
|
//scenes
|
|
1480
1480
|
if (this.scenes.length > 0) {
|
|
@@ -1491,7 +1491,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1491
1491
|
//sensor
|
|
1492
1492
|
if (scene.displayType < 7) this.sceneControlSensorServices?.[i]?.updateCharacteristic(characteristicType, scene.state);
|
|
1493
1493
|
});
|
|
1494
|
-
}
|
|
1494
|
+
}
|
|
1495
1495
|
|
|
1496
1496
|
//buttons
|
|
1497
1497
|
if (this.buttons.length > 0) {
|
|
@@ -1552,7 +1552,7 @@ class DeviceErv extends EventEmitter {
|
|
|
1552
1552
|
//sensor
|
|
1553
1553
|
if (button.displayType < 7) this.buttonControlSensorServices?.[i]?.updateCharacteristic(characteristicType, button.state);
|
|
1554
1554
|
});
|
|
1555
|
-
}
|
|
1555
|
+
}
|
|
1556
1556
|
|
|
1557
1557
|
//log current state
|
|
1558
1558
|
if (this.logInfo) {
|
|
@@ -1571,7 +1571,8 @@ class DeviceErv extends EventEmitter {
|
|
|
1571
1571
|
if (supportsCO2Sensor) this.emit('info', `CO2 level: ${roomCO2Level} ppm`);
|
|
1572
1572
|
if (supportsPM25Sensor) this.emit('info', `PM2.5 air quality: ${Ventilation.PM25AirQualityMapEnumToString[pM25AirQuality]}`);
|
|
1573
1573
|
if (supportsPM25Sensor) this.emit('info', `PM2.5 level: ${pM25Level} µg/m`);
|
|
1574
|
-
|
|
1574
|
+
if (this.accountType === 'melcloudhome') this.emit('info', `Signal strength: ${deviceData.Rssi}dBm`);
|
|
1575
|
+
}
|
|
1575
1576
|
})
|
|
1576
1577
|
.on('success', (success) => this.emit('success', success))
|
|
1577
1578
|
.on('info', (info) => this.emit('info', info))
|
package/src/functions.js
CHANGED
|
@@ -160,5 +160,13 @@ class Functions extends EventEmitter {
|
|
|
160
160
|
return v !== undefined && v !== null && !(typeof v === 'number' && Number.isNaN(v));
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
convertValue(value) {
|
|
164
|
+
let parsedValue = value;
|
|
165
|
+
if (value === "True") parsedValue = true;
|
|
166
|
+
else if (value === "False") parsedValue = false;
|
|
167
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
168
|
+
return parsedValue;
|
|
169
|
+
}
|
|
170
|
+
|
|
163
171
|
}
|
|
164
172
|
export default Functions
|
package/src/melcloudata.js
CHANGED
|
@@ -70,17 +70,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
70
70
|
this.socketConnected = false;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async
|
|
73
|
+
async updateState(deviceData) {
|
|
74
74
|
try {
|
|
75
|
-
|
|
76
|
-
//read device info from file
|
|
77
|
-
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
78
|
-
if (!devicesData) return;
|
|
79
|
-
|
|
80
|
-
this.headers = devicesData.Headers;
|
|
81
|
-
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
82
|
-
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
83
|
-
|
|
84
75
|
if (this.accountType === 'melcloudhome') {
|
|
85
76
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
|
86
77
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
@@ -93,87 +84,6 @@ class MelCloudAta extends EventEmitter {
|
|
|
93
84
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
94
85
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
95
86
|
|
|
96
|
-
//web cocket connection
|
|
97
|
-
if (!this.connecting && !this.socketConnected) {
|
|
98
|
-
this.connecting = true;
|
|
99
|
-
|
|
100
|
-
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
|
|
101
|
-
try {
|
|
102
|
-
const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
|
|
103
|
-
.on('error', (error) => {
|
|
104
|
-
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
105
|
-
socket.close();
|
|
106
|
-
})
|
|
107
|
-
.on('close', () => {
|
|
108
|
-
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
109
|
-
this.cleanupSocket();
|
|
110
|
-
})
|
|
111
|
-
.on('open', () => {
|
|
112
|
-
this.socket = socket;
|
|
113
|
-
this.socketConnected = true;
|
|
114
|
-
this.connecting = false;
|
|
115
|
-
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
116
|
-
|
|
117
|
-
// heartbeat
|
|
118
|
-
this.heartbeat = setInterval(() => {
|
|
119
|
-
if (socket.readyState === socket.OPEN) {
|
|
120
|
-
if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
121
|
-
socket.ping();
|
|
122
|
-
}
|
|
123
|
-
}, 30000);
|
|
124
|
-
})
|
|
125
|
-
.on('pong', () => {
|
|
126
|
-
if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
127
|
-
})
|
|
128
|
-
.on('message', (message) => {
|
|
129
|
-
const parsedMessage = JSON.parse(message);
|
|
130
|
-
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
131
|
-
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
132
|
-
if (parsedMessage.message === 'Forbidden') return;
|
|
133
|
-
|
|
134
|
-
const messageData = parsedMessage?.[0]?.Data;
|
|
135
|
-
if (!messageData) return;
|
|
136
|
-
|
|
137
|
-
let updateDeviceState = false;
|
|
138
|
-
const unitId = messageData?.id;
|
|
139
|
-
switch (unitId) {
|
|
140
|
-
case this.deviceId:
|
|
141
|
-
const messageType = parsedMessage[0].messageType;
|
|
142
|
-
switch (messageType) {
|
|
143
|
-
case 'unitStateChanged':
|
|
144
|
-
const settings = Object.fromEntries(
|
|
145
|
-
messageData.settings.map(({ name, value }) => {
|
|
146
|
-
let parsedValue = value;
|
|
147
|
-
if (value === "True") parsedValue = true;
|
|
148
|
-
else if (value === "False") parsedValue = false;
|
|
149
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
150
|
-
return [name, parsedValue];
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
Object.assign(deviceData.Device, settings);
|
|
154
|
-
updateDeviceState = true;
|
|
155
|
-
break;
|
|
156
|
-
case 'unitWifiSignalChanged':
|
|
157
|
-
Object.assign(deviceData, messageData.rssi);
|
|
158
|
-
updateDeviceState = true;
|
|
159
|
-
break;
|
|
160
|
-
default:
|
|
161
|
-
if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
break;
|
|
165
|
-
default:
|
|
166
|
-
if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (updateDeviceState) this.emit('deviceState', deviceData);
|
|
171
|
-
});
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
174
|
-
this.cleanupSocket();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
87
|
}
|
|
178
88
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
179
89
|
|
|
@@ -233,6 +143,107 @@ class MelCloudAta extends EventEmitter {
|
|
|
233
143
|
};
|
|
234
144
|
};
|
|
235
145
|
|
|
146
|
+
async checkState() {
|
|
147
|
+
try {
|
|
148
|
+
|
|
149
|
+
//read device info from file
|
|
150
|
+
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
151
|
+
if (!devicesData) return;
|
|
152
|
+
|
|
153
|
+
this.headers = devicesData.Headers;
|
|
154
|
+
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
155
|
+
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
156
|
+
|
|
157
|
+
//web cocket connection
|
|
158
|
+
if (this.accountType === 'melcloudhome' && !this.connecting && !this.socketConnected) {
|
|
159
|
+
this.connecting = true;
|
|
160
|
+
|
|
161
|
+
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
|
|
162
|
+
try {
|
|
163
|
+
const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
|
|
164
|
+
.on('error', (error) => {
|
|
165
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
166
|
+
socket.close();
|
|
167
|
+
})
|
|
168
|
+
.on('close', () => {
|
|
169
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
170
|
+
this.cleanupSocket();
|
|
171
|
+
})
|
|
172
|
+
.on('open', () => {
|
|
173
|
+
this.socket = socket;
|
|
174
|
+
this.socketConnected = true;
|
|
175
|
+
this.connecting = false;
|
|
176
|
+
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
177
|
+
|
|
178
|
+
// heartbeat
|
|
179
|
+
this.heartbeat = setInterval(() => {
|
|
180
|
+
if (socket.readyState === socket.OPEN) {
|
|
181
|
+
if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
182
|
+
socket.ping();
|
|
183
|
+
}
|
|
184
|
+
}, 30000);
|
|
185
|
+
})
|
|
186
|
+
.on('pong', () => {
|
|
187
|
+
if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
188
|
+
})
|
|
189
|
+
.on('message', async (message) => {
|
|
190
|
+
const parsedMessage = JSON.parse(message);
|
|
191
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
192
|
+
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
193
|
+
if (parsedMessage.message === 'Forbidden') return;
|
|
194
|
+
|
|
195
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
196
|
+
if (!messageData) return;
|
|
197
|
+
|
|
198
|
+
let updateDeviceState = false;
|
|
199
|
+
const unitId = messageData?.id;
|
|
200
|
+
switch (unitId) {
|
|
201
|
+
case this.deviceId:
|
|
202
|
+
const messageType = parsedMessage[0].messageType;
|
|
203
|
+
switch (messageType) {
|
|
204
|
+
case 'unitStateChanged':
|
|
205
|
+
const settings = Object.fromEntries(
|
|
206
|
+
messageData.settings.map(({ name, value }) => {
|
|
207
|
+
let parsedValue = this.functions.convertValue(value);
|
|
208
|
+
return [name, parsedValue];
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
Object.assign(deviceData.Device, settings);
|
|
212
|
+
updateDeviceState = true;
|
|
213
|
+
break;
|
|
214
|
+
case 'unitWifiSignalChanged':
|
|
215
|
+
deviceData.Rssi = messageData.rssi;
|
|
216
|
+
updateDeviceState = true;
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
if (!this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
default:
|
|
224
|
+
if (!this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
//update state
|
|
229
|
+
if (updateDeviceState) await this.updateState(deviceData);
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
233
|
+
this.cleanupSocket();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
237
|
+
|
|
238
|
+
//update state
|
|
239
|
+
await this.updateState(deviceData);
|
|
240
|
+
|
|
241
|
+
return true;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new Error(`Check state error: ${error.message}`);
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
236
247
|
async send(accountType, displayType, deviceData, flag, flagData) {
|
|
237
248
|
try {
|
|
238
249
|
let method = null
|
|
@@ -374,7 +385,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
374
385
|
data: payload
|
|
375
386
|
});
|
|
376
387
|
|
|
377
|
-
this.updateData(deviceData, updateState);
|
|
388
|
+
await this.updateData(deviceData, updateState);
|
|
378
389
|
return true;
|
|
379
390
|
default:
|
|
380
391
|
return;
|
|
@@ -385,9 +396,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
385
396
|
}
|
|
386
397
|
}
|
|
387
398
|
|
|
388
|
-
updateData(deviceData, updateState = true) {
|
|
399
|
+
async updateData(deviceData, updateState = true) {
|
|
389
400
|
this.locks = true;
|
|
390
|
-
if (updateState) this.
|
|
401
|
+
if (updateState) await this.updateState(deviceData);
|
|
391
402
|
|
|
392
403
|
setTimeout(() => {
|
|
393
404
|
this.locks = false
|
package/src/melcloudhome.js
CHANGED
|
@@ -145,11 +145,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
145
145
|
|
|
146
146
|
const settingsObject = Object.fromEntries(
|
|
147
147
|
settingsArray.map(({ name, value }) => {
|
|
148
|
-
let parsedValue = value;
|
|
149
|
-
if (value === "True") parsedValue = true;
|
|
150
|
-
else if (value === "False") parsedValue = false;
|
|
151
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
152
|
-
|
|
148
|
+
let parsedValue = this.functions.convertValue(value);
|
|
153
149
|
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
154
150
|
return [key, parsedValue];
|
|
155
151
|
})
|