homebridge-melcloud-control 4.3.16-beta.8 → 4.4.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
@@ -22,6 +22,15 @@ 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.4.0] - (10.12.2025)
26
+
27
+ ## Changes
28
+
29
+ - added possibility to set frost protection min/max temperaure (ATA, ATW)
30
+ - added possibility to set overheat protection min/max temperture (ATA)
31
+ - bump dependencies
32
+ - cleanup
33
+
25
34
  # [4.3.16] - (09.12.2025)
26
35
 
27
36
  ## Changes
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.16-beta.8",
4
+ "version": "4.4.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/deviceata.js CHANGED
@@ -697,7 +697,7 @@ class DeviceAta extends EventEmitter {
697
697
  }
698
698
 
699
699
  //frost protection
700
- if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) {
700
+ if (this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
701
701
  //control
702
702
  if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
703
703
  const frostProtectionControlService = new Service.HeaterCooler(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
@@ -793,7 +793,7 @@ class DeviceAta extends EventEmitter {
793
793
  this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`);
794
794
  this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
795
795
  .onGet(async () => {
796
- const state = this.accessory.frostProtectionEnabled;
796
+ const state = this.accessory.frostProtection.Enabled;
797
797
  return state;
798
798
  })
799
799
  accessory.addService(this.frostProtectionControlSensorService);
@@ -805,14 +805,14 @@ class DeviceAta extends EventEmitter {
805
805
  this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
806
806
  this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
807
807
  .onGet(async () => {
808
- const state = this.accessory.frostProtectionActive;
808
+ const state = this.accessory.frostProtection.Active;
809
809
  return state;
810
810
  })
811
811
  accessory.addService(this.frostProtectionSensorService);
812
812
  }
813
813
 
814
814
  //overheat protection
815
- if (this.overheatProtectionSupport && this.accessory.overheatProtectionEnabled !== null) {
815
+ if (this.overheatProtectionSupport && this.accessory.overheatProtection.Enabled !== null) {
816
816
  //control
817
817
  if (this.logDebug) this.emit('debug', `Prepare overheat protection control service`);
818
818
  const overheatProtectionControlService = new Service.HeaterCooler(`${serviceName} Overheat Protection`, `overheatProtectionControlService${deviceId}`);
@@ -908,7 +908,7 @@ class DeviceAta extends EventEmitter {
908
908
  this.overheatProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection Control`);
909
909
  this.overheatProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
910
910
  .onGet(async () => {
911
- const state = this.accessory.overheatProtectionEnabled;
911
+ const state = this.accessory.overheatProtection.Enabled;
912
912
  return state;
913
913
  })
914
914
  accessory.addService(this.overheatProtectionControlSensorService);
@@ -919,7 +919,7 @@ class DeviceAta extends EventEmitter {
919
919
  this.overheatProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection`);
920
920
  this.overheatProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
921
921
  .onGet(async () => {
922
- const state = this.accessory.overheatProtectionActive;
922
+ const state = this.accessory.overheatProtection.Active;
923
923
  return state;
924
924
  })
925
925
  accessory.addService(this.overheatProtectionSensorService);
@@ -1505,12 +1505,8 @@ class DeviceAta extends EventEmitter {
1505
1505
  const holidayModeActive = deviceData.HolidayMode?.Active ?? false;
1506
1506
 
1507
1507
  //protection
1508
- const frostProtection = deviceData.FrostProtection;
1509
- const frostProtectionEnabled = frostProtection?.Enabled;
1510
- const frostProtectionActive = frostProtection?.Active ?? false;
1511
- const overheatProtection = deviceData.OverheatProtection;
1512
- const overheatProtectionEnabled = overheatProtection?.Enabled;
1513
- const overheatProtectionActive = overheatProtection?.Active ?? false;
1508
+ const frostProtection = deviceData.FrostProtection ?? {};
1509
+ const overheatProtection = deviceData.OverheatProtection ?? {};
1514
1510
 
1515
1511
  //device control
1516
1512
  const hideVaneControls = deviceData.HideVaneControls ?? false;
@@ -1604,11 +1600,7 @@ class DeviceAta extends EventEmitter {
1604
1600
  isConnected: isConnected,
1605
1601
  isInError: isInError,
1606
1602
  frostProtection: frostProtection,
1607
- frostProtectionEnabled: frostProtectionEnabled,
1608
- frostProtectionActive: frostProtectionActive,
1609
1603
  overheatProtection: overheatProtection,
1610
- overheatProtectionEnabled: overheatProtectionEnabled,
1611
- overheatProtectionActive: overheatProtectionActive,
1612
1604
  holidayModeEnabled: holidayModeEnabled,
1613
1605
  holidayModeActive: holidayModeActive,
1614
1606
  scheduleEnabled: scheduleEnabled
@@ -1772,17 +1764,27 @@ class DeviceAta extends EventEmitter {
1772
1764
  this.errorService?.updateCharacteristic(Characteristic.ContactSensorState, isInError);
1773
1765
 
1774
1766
  //frost protection
1775
- if (this.frostProtectionSupport && frostProtectionEnabled !== null) {
1776
- this.frostProtectionControlService?.updateCharacteristic(Characteristic.On, frostProtectionEnabled);
1777
- this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionEnabled);
1778
- this.frostProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionActive);
1767
+ if (this.frostProtectionSupport && frostProtection.Enabled !== null) {
1768
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.Active, frostProtection.Enabled);
1769
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, frostProtection.Active ? 2 : 1);
1770
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.TargetHeaterCoolerState, frostProtection.Active ? 2 : 1);
1771
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CurrentTemperature, roomTemperature);
1772
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CoolingThresholdTemperature, frostProtection.Max);
1773
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.HeatingThresholdTemperature, frostProtection.Min);
1774
+ this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtection.Enabled);
1775
+ this.frostProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtection.Active);
1779
1776
  }
1780
1777
 
1781
1778
  //overheat protection
1782
- if (this.overheatProtectionSupport && overheatProtectionEnabled !== null) {
1783
- this.overheatProtectionControlService?.updateCharacteristic(Characteristic.On, overheatProtectionEnabled);
1784
- this.overheatProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, overheatProtectionEnabled);
1785
- this.overheatProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, overheatProtectionActive);
1779
+ if (this.overheatProtectionSupport && overheatProtection.Enabled !== null) {
1780
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.Active, overheatProtection.Enabled);
1781
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, overheatProtection.Active ? 2 : 1);
1782
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.TargetHeaterCoolerState, overheatProtection.Active ? 2 : 1);
1783
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.CurrentTemperature, roomTemperature);
1784
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.CoolingThresholdTemperature, overheatProtection.Max);
1785
+ this.overheatProtectionControlService?.updateCharacteristic(Characteristic.HeatingThresholdTemperature, overheatProtection.Min);
1786
+ this.overheatProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, overheatProtection.Enabled);
1787
+ this.overheatProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, overheatProtection.Active);
1786
1788
  }
1787
1789
 
1788
1790
  //holiday mode
package/src/deviceatw.js CHANGED
@@ -1054,27 +1054,95 @@ class DeviceAtw extends EventEmitter {
1054
1054
  }
1055
1055
 
1056
1056
  //frost protection
1057
- if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) {
1057
+ if (this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
1058
1058
  //control
1059
1059
  if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
1060
- this.frostProtectionControlService = new Service.Switch(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
1061
- this.frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
1062
- this.frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
1063
- this.frostProtectionControlService.getCharacteristic(Characteristic.On)
1060
+ const frostProtectionControlService = new Service.HeaterCooler(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
1061
+ frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
1062
+ frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
1063
+ frostProtectionControlService.getCharacteristic(Characteristic.Active)
1064
1064
  .onGet(async () => {
1065
- const state = this.accessory.frostProtectionEnabled;
1065
+ const state = this.accessory.frostProtection.Enabled;
1066
1066
  return state;
1067
1067
  })
1068
1068
  .onSet(async (state) => {
1069
1069
  try {
1070
- deviceData.FrostProtection.Enabled = state;
1070
+ deviceData.FrostProtection.Enabled = state ? true : false;
1071
1071
  if (this.logInfo) this.emit('info', `Frost protection: ${state ? 'Enabled' : 'Disabled'}`);
1072
1072
  await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
1073
1073
  } catch (error) {
1074
1074
  if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
1075
1075
  };
1076
1076
  });
1077
- accessory.addService(this.frostProtectionControlService);
1077
+ frostProtectionControlService.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
1078
+ .onGet(async () => {
1079
+ const value = this.accessory.frostProtection.Active ? 2 : 1;
1080
+ return value;
1081
+ })
1082
+ frostProtectionControlService.getCharacteristic(Characteristic.TargetHeaterCoolerState)
1083
+ .setProps({
1084
+ minValue: 0,
1085
+ maxValue: 0,
1086
+ validValues: [0]
1087
+ })
1088
+ .onGet(async () => {
1089
+ const value = 0
1090
+ return value;
1091
+ })
1092
+ .onSet(async (value) => {
1093
+ try {
1094
+ deviceData.FrostProtection.Enabled = true;
1095
+ if (this.logInfo) this.emit('info', `Frost protection: Enabled`);
1096
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
1097
+ } catch (error) {
1098
+ if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
1099
+ };
1100
+ });
1101
+ frostProtectionControlService.getCharacteristic(Characteristic.CurrentTemperature)
1102
+ .onGet(async () => {
1103
+ const value = this.accessory.roomTemperature;
1104
+ return value;
1105
+ });
1106
+ frostProtectionControlService.getCharacteristic(Characteristic.CoolingThresholdTemperature) //max
1107
+ .setProps({
1108
+ minValue: 6,
1109
+ maxValue: 16,
1110
+ minStep: 1
1111
+ })
1112
+ .onGet(async () => {
1113
+ const value = this.accessory.frostProtection.Max;
1114
+ return value;
1115
+ })
1116
+ .onSet(async (value) => {
1117
+ try {
1118
+ deviceData.FrostProtection.Max = value;
1119
+ if (this.logInfo) this.emit('info', `Set frost protection max. temperature: ${value}${this.accessory.temperatureUnit}`);
1120
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
1121
+ } catch (error) {
1122
+ if (this.logWarn) this.emit('warn', `Set frost protection max. temperature error: ${error}`);
1123
+ };
1124
+ });
1125
+ frostProtectionControlService.getCharacteristic(Characteristic.HeatingThresholdTemperature) //min
1126
+ .setProps({
1127
+ minValue: 4,
1128
+ maxValue: 14,
1129
+ minStep: 1
1130
+ })
1131
+ .onGet(async () => {
1132
+ const value = this.accessory.frostProtection.Min;
1133
+ return value;
1134
+ })
1135
+ .onSet(async (value) => {
1136
+ try {
1137
+ deviceData.FrostProtection.Min = value;
1138
+ if (this.logInfo) this.emit('info', `Set frost protection min. temperature: ${value}${this.accessory.temperatureUnit}`);
1139
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
1140
+ } catch (error) {
1141
+ if (this.logWarn) this.emit('warn', `Set frost protection min. temperature error: ${error}`);
1142
+ };
1143
+ });
1144
+ this.frostProtectionControlService = frostProtectionControlService;
1145
+ accessory.addService(frostProtectionControlService);
1078
1146
 
1079
1147
  if (this.logDebug) this.emit('debug', `Prepare frost protection control sensor service`);
1080
1148
  this.frostProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Frost Protection Control`, `frostProtectionControlSensorService${deviceId}`);
@@ -1082,7 +1150,7 @@ class DeviceAtw extends EventEmitter {
1082
1150
  this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`);
1083
1151
  this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
1084
1152
  .onGet(async () => {
1085
- const state = this.accessory.frostProtectionEnabled;
1153
+ const state = this.accessory.frostProtection.Enabled;
1086
1154
  return state;
1087
1155
  })
1088
1156
  accessory.addService(this.frostProtectionControlSensorService);
@@ -1094,7 +1162,7 @@ class DeviceAtw extends EventEmitter {
1094
1162
  this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
1095
1163
  this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
1096
1164
  .onGet(async () => {
1097
- const state = this.accessory.frostProtectionActive;
1165
+ const state = this.accessory.frostProtection.Active;
1098
1166
  return state;
1099
1167
  })
1100
1168
  accessory.addService(this.frostProtectionSensorService);
@@ -1609,8 +1677,7 @@ class DeviceAtw extends EventEmitter {
1609
1677
  const holidayModeActive = deviceData.HolidayMode?.Active ?? false;
1610
1678
 
1611
1679
  //protection
1612
- const frostProtectionEnabled = deviceData.FrostProtection?.Enabled;
1613
- const frostProtectionActive = deviceData.FrostProtection?.Active ?? false;
1680
+ const frostProtection = deviceData.FrostProtection ?? {};
1614
1681
 
1615
1682
  //device info
1616
1683
  const supportsStanbyMode = deviceData.Device[supportStandbyKey];
@@ -1719,8 +1786,7 @@ class DeviceAtw extends EventEmitter {
1719
1786
  temperatureUnit: TemperatureDisplayUnits[this.accountInfo.useFahrenheit ? 1 : 0],
1720
1787
  isConnected: isConnected,
1721
1788
  isInError: isInError,
1722
- frostProtectionEnabled: frostProtectionEnabled,
1723
- frostProtectionActive: frostProtectionActive,
1789
+ frostProtection: frostProtection,
1724
1790
  scheduleEnabled: scheduleEnabled,
1725
1791
  holidayModeEnabled: holidayModeEnabled,
1726
1792
  holidayModeActive: holidayModeActive,
@@ -2120,10 +2186,15 @@ class DeviceAtw extends EventEmitter {
2120
2186
  this.errorService?.updateCharacteristic(Characteristic.ContactSensorState, isInError);
2121
2187
 
2122
2188
  //frost protection
2123
- if (this.frostProtectionSupport && frostProtectionEnabled !== null) {
2124
- this.frostProtectionControlService?.updateCharacteristic(Characteristic.On, frostProtectionEnabled);
2125
- this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionEnabled);
2126
- this.frostProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionActive);
2189
+ if (this.frostProtectionSupport && frostProtection.Enabled !== null) {
2190
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.Active, frostProtection.Enabled);
2191
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, frostProtection.Active ? 2 : 1);
2192
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.TargetHeaterCoolerState, frostProtection.Active ? 2 : 1);
2193
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CurrentTemperature, roomTemperature);
2194
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.CoolingThresholdTemperature, frostProtection.Max);
2195
+ this.frostProtectionControlService?.updateCharacteristic(Characteristic.HeatingThresholdTemperature, frostProtection.Min);
2196
+ this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtection.Enabled);
2197
+ this.frostProtectionSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtection.Active);
2127
2198
  }
2128
2199
 
2129
2200
  //holiday mode
package/src/functions.js CHANGED
@@ -237,6 +237,48 @@ class Functions extends EventEmitter {
237
237
  })
238
238
  );
239
239
  }
240
+
241
+ async normalizeFrostProtection(min, max) {
242
+ // Clamp to allowed ranges
243
+ min = Math.min(Math.max(min, 4), 14);
244
+ max = Math.min(Math.max(max, 6), 16);
245
+
246
+ // Ensure difference of at least 2 degrees
247
+ if (max - min < 2) {
248
+ // Try increasing max if possible
249
+ const newMax = min + 2;
250
+ if (newMax <= 18) {
251
+ max = newMax;
252
+ } else {
253
+ // Otherwise lower min
254
+ const newMin = max - 2;
255
+ min = Math.max(newMin, 4);
256
+ }
257
+ }
258
+
259
+ return { min, max };
260
+ }
261
+
262
+ async normalizeOverheatProtection(min, max) {
263
+ // Clamp to allowed ranges
264
+ min = Math.min(Math.max(min, 31), 38);
265
+ max = Math.min(Math.max(max, 33), 40);
266
+
267
+ // Ensure difference of at least 2 degrees
268
+ if (max - min < 2) {
269
+ // Try increasing max if possible
270
+ const newMax = min + 2;
271
+ if (newMax <= 18) {
272
+ max = newMax;
273
+ } else {
274
+ // Otherwise lower min
275
+ const newMin = max - 2;
276
+ min = Math.max(newMin, 4);
277
+ }
278
+ }
279
+
280
+ return { min, max };
281
+ }
240
282
  }
241
283
 
242
284
  export default Functions
@@ -240,20 +240,22 @@ class MelCloudAta extends EventEmitter {
240
240
  case "melcloudhome":
241
241
  switch (flag) {
242
242
  case 'frostprotection':
243
+ let { frostMin, frostMax } = await this.functions.normalizeFrostProtection(deviceData.FrostProtection.Min, deviceData.FrostProtection.Max);
243
244
  payload = {
244
245
  enabled: deviceData.FrostProtection.Enabled,
245
- min: deviceData.FrostProtection.Min,
246
- max: deviceData.FrostProtection.Max,
246
+ min: frostMin,
247
+ max: frostMax,
247
248
  units: { ATA: [deviceData.DeviceID] }
248
249
  };
249
250
  method = 'POST';
250
251
  path = ApiUrlsHome.PostProtectionFrost;
251
252
  break;
252
253
  case 'overheatprotection':
254
+ let { overMin, overMax } = await this.functions.normalizeOverheatProtection(deviceData.OverheatProtection.Min, deviceData.OverheatProtection.Max);
253
255
  payload = {
254
256
  enabled: deviceData.OverheatProtection.Enabled,
255
- min: deviceData.OverheatProtection.Min,
256
- max: deviceData.OverheatProtection.Max,
257
+ min: overMin,
258
+ max: overMax,
257
259
  units: { ATA: [deviceData.DeviceID] }
258
260
  };
259
261
  method = 'POST';