homebridge-melcloud-control 4.3.16-beta.9 → 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.9",
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/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';