homebridge-melcloud-control 4.1.2-beta.2 → 4.1.2-beta.21

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.1.2] - (10.11.2025)
26
+
27
+ ## Changes
28
+
29
+ - added presets/schedules support for MELCLoud Home ATA devices
30
+ - config schema updated
31
+ - readme updated
32
+ - cleanup
33
+
25
34
  ## [4.1.1] - (10.11.2025)
26
35
 
27
36
  ## Changes
@@ -239,13 +239,11 @@
239
239
  },
240
240
  "type": {
241
241
  "title": "Type",
242
- "type": "integer",
243
- "default": 0
242
+ "type": "integer"
244
243
  },
245
244
  "typeString": {
246
245
  "title": "Type",
247
- "type": "string",
248
- "default": "Air Conditioner"
246
+ "type": "string"
249
247
  },
250
248
  "displayType": {
251
249
  "title": "Display Type",
@@ -224,8 +224,6 @@
224
224
 
225
225
  document.getElementById('logIn').addEventListener('click', async () => {
226
226
  document.getElementById(`logIn`).className = "btn btn-primary";
227
-
228
- updateInfo('info', 'Connecting...', 'yellow');
229
227
  homebridge.showSpinner();
230
228
 
231
229
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.1.2-beta.2",
4
+ "version": "4.1.2-beta.21",
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
@@ -21,8 +21,12 @@ export const ApiUrlsHome = {
21
21
  BaseURL: "https://melcloudhome.com",
22
22
  GetUserContext: "/api/user/context",
23
23
  SetAta: "/api/ataunit/deviceid",
24
+ GetAtaSchedule: "/ata/deviceid/schedule",
24
25
  SetAtw: "/api/atwunit/deviceid",
26
+ GetAtwSchedule: "/atw/deviceid/schedule",
25
27
  SetErv: "/api/ervunit/deviceid",
28
+ GetErvSchedule: "/erv/deviceid/schedule",
29
+ SetSchedule: "/api/cloudschedule/deviceid/enabled",
26
30
  };
27
31
 
28
32
  export const DeviceType = [
package/src/deviceata.js CHANGED
@@ -683,8 +683,9 @@ class DeviceAta extends EventEmitter {
683
683
  if (this.presets.length > 0) {
684
684
  if (this.logDebug) this.emit('debug', `Prepare presets services`);
685
685
  this.presetsServices = [];
686
+ const presetsIdKey = this.accountType === 'melcloud' ? 'ID' : 'Id';
686
687
  this.presets.forEach((preset, i) => {
687
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
688
+ const presetData = presetsOnServer.find(p => p[presetsIdKey] === preset.id);
688
689
 
689
690
  //get preset name
690
691
  const name = preset.name;
@@ -705,8 +706,6 @@ class DeviceAta extends EventEmitter {
705
706
  })
706
707
  .onSet(async (state) => {
707
708
  try {
708
- const setTempKey = this.accountType === 'melcloud' ? 'SetTemperature' : 'SetPoint';
709
- const fanKey = this.accountType === 'melcloud' ? 'FanSpeed' : 'SetFanSpeed';
710
709
  switch (state) {
711
710
  case true:
712
711
  preset.previousSettings = deviceData.Device;
@@ -715,7 +714,7 @@ class DeviceAta extends EventEmitter {
715
714
  deviceData.Device.SetTemperature = presetData.SetTemperature;
716
715
  deviceData.Device.VaneHorizontalDirection = presetData.VaneHorizontalDirection;
717
716
  deviceData.Device.VaneVerticalDirection = presetData.VaneVerticalDirection;
718
- deviceData.Device[fanKey] = presetData[fanKey];
717
+ deviceData.Device.SetFanSpeed = presetData.SetFanSpeed;
719
718
  break;
720
719
  case false:
721
720
  deviceData.Device.Power = preset.previousSettings.Power;
@@ -723,7 +722,7 @@ class DeviceAta extends EventEmitter {
723
722
  deviceData.Device.SetTemperature = preset.previousSettings.SetTemperature;
724
723
  deviceData.Device.VaneHorizontalDirection = preset.previousSettings.VaneHorizontalDirection;
725
724
  deviceData.Device.VaneVerticalDirection = preset.previousSettings.VaneVerticalDirection;
726
- deviceData.Device[fanKey] = preset.previousSettings[fanKey];
725
+ deviceData.Device.SetFanSpeed = preset.previousSettings.SetFanSpeed;
727
726
  break;
728
727
  };
729
728
 
@@ -765,7 +764,7 @@ class DeviceAta extends EventEmitter {
765
764
  })
766
765
  .onSet(async (state) => {
767
766
  try {
768
- const key = this.accountType === 'melcloud' ? 'FanSpeed' : 'SetFanSpeed';
767
+ const fanKey = this.accountType === 'melcloud' ? 'FanSpeed' : 'SetFanSpeed';
769
768
  let effectiveFlags = null;
770
769
  switch (mode) {
771
770
  case 0: //POWER ON,OFF
@@ -1017,6 +1016,7 @@ class DeviceAta extends EventEmitter {
1017
1016
 
1018
1017
  //keys
1019
1018
  const presetsKey = this.accountType === 'melcloud' ? 'Presets' : 'Schedule';
1019
+ const presetsIdKey = this.accountType === 'melcloud' ? 'ID' : 'Id';
1020
1020
  const setTempKey = this.accountType === 'melcloud' ? 'SetTemperature' : 'SetPoint';
1021
1021
  const fanKey = this.accountType === 'melcloud' ? 'FanSpeed' : 'SetFanSpeed';
1022
1022
  const tempStepKey = this.accountType === 'melcloud' ? 'TemperatureIncrement' : 'HasHalfDegreeIncrements';
@@ -1030,16 +1030,17 @@ class DeviceAta extends EventEmitter {
1030
1030
  const supportCoolKey = this.accountType === 'melcloud' ? 'ModelSupportsCool' : 'HasCoolOperationMode';
1031
1031
 
1032
1032
  //presets schedule
1033
+ const scheduleEnabled = deviceData.ScheduleEnabled;
1033
1034
  const presetsOnServer = deviceData[presetsKey] ?? [];
1034
1035
 
1036
+
1035
1037
  //protection
1036
- const frostProtectionEnabled = deviceData.FrostProtection?.enabled;
1037
- const frostProtectionActive = deviceData.FrostProtection?.active;
1038
- const overheatProtectionEnabled = deviceData.OverheatProtection?.enabled;
1039
- const overheatProtectionActive = deviceData.OverheatProtection?.active;
1040
- const holidayModeEnabled = deviceData.HolidayMode?.enabled;
1041
- const holidayModeActive = deviceData.HolidayMode?.active;
1042
- const scheduleEnabled = deviceData.ScheduleEnabled;
1038
+ const frostProtectionEnabled = deviceData.FrostProtection?.Enabled;
1039
+ const frostProtectionActive = deviceData.FrostProtection?.Active;
1040
+ const overheatProtectionEnabled = deviceData.OverheatProtection?.Enabled;
1041
+ const overheatProtectionActive = deviceData.OverheatProtection?.Active;
1042
+ const holidayModeEnabled = deviceData.HolidayMode?.Enabled;
1043
+ const holidayModeActive = deviceData.HolidayMode?.Active;
1043
1044
 
1044
1045
  //device control
1045
1046
  const hideVaneControls = deviceData.HideVaneControls ?? false;
@@ -1283,14 +1284,14 @@ class DeviceAta extends EventEmitter {
1283
1284
  //update presets state
1284
1285
  if (this.presets.length > 0) {
1285
1286
  this.presets.forEach((preset, i) => {
1286
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
1287
+ const presetData = presetsOnServer.find(p => p[presetsIdKey] === preset.id);
1287
1288
 
1288
- preset.state = presetData ? (presetData.Power === power
1289
- && presetData[setTempKey] === setTemperature
1289
+ preset.state = presetData ? (this.accountType === 'melcloudhome' ? scheduleEnabled && presetData.Enabled : (presetData.Power === power
1290
+ && presetData.SetTemperature === setTemperature
1290
1291
  && presetData.OperationMode === operationMode
1291
1292
  && presetData.VaneHorizontalDirection === vaneHorizontalDirection
1292
1293
  && presetData.VaneVerticalDirection === vaneVerticalDirection
1293
- && presetData[fanKey] === setFanSpeed) : false;
1294
+ && presetData.FanSpeed === setFanSpeed)) : false;
1294
1295
 
1295
1296
  const characteristicType = preset.characteristicType;
1296
1297
  this.presetsServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
package/src/deviceatw.js CHANGED
@@ -1055,8 +1055,9 @@ class DeviceAtw extends EventEmitter {
1055
1055
  if (this.presets.length > 0) {
1056
1056
  if (this.logDebug) this.emit('debug', `Prepare presets services`);
1057
1057
  this.presetsServices = [];
1058
+ const presetsIdKey = this.accountType === 'melcloud' ? 'ID' : 'Id';
1058
1059
  this.presets.forEach((preset, i) => {
1059
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
1060
+ const presetData = presetsOnServer.find(p => p[presetsIdKey] === preset.id);
1060
1061
 
1061
1062
  //get preset name
1062
1063
  const name = preset.name;
@@ -1330,11 +1331,14 @@ class DeviceAtw extends EventEmitter {
1330
1331
  this.deviceData = deviceData;
1331
1332
 
1332
1333
  //keys
1334
+ const presetsKey = this.accountType === 'melcloud' ? 'Presets' : 'Schedule';
1335
+ const presetsIdKey = accountType === 'melcloud' ? 'ID' : 'Id';
1333
1336
  const tempStepKey = this.accountType === 'melcloud' ? 'TemperatureIncrement' : 'HasHalfDegreeIncrements';
1334
1337
  const errorKey = this.accountType === 'melcloud' ? 'HasError' : 'IsInError';
1335
1338
 
1336
1339
  //presets
1337
- const presetsOnServer = deviceData.Presets ?? [];
1340
+ const scheduleEnabled = deviceData.ScheduleEnabled;
1341
+ const presetsOnServer = deviceData[presetsKey] ?? [];
1338
1342
 
1339
1343
  //device info
1340
1344
  const hasHeatPump = ![1, 2, 3, 4, 5, 6, 7, 15].includes(this.hideZone);
@@ -1436,6 +1440,7 @@ class DeviceAtw extends EventEmitter {
1436
1440
  useFahrenheit: this.useFahrenheit,
1437
1441
  temperatureUnit: TemperatureDisplayUnits[this.useFahrenheit],
1438
1442
  isInError: isInError,
1443
+ scheduleEnabled: scheduleEnabled,
1439
1444
  zones: [],
1440
1445
  zonesSensors: []
1441
1446
  };
@@ -1816,9 +1821,9 @@ class DeviceAtw extends EventEmitter {
1816
1821
  //update presets state
1817
1822
  if (this.presets.length > 0) {
1818
1823
  this.presets.forEach((preset, i) => {
1819
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
1824
+ const presetData = presetsOnServer.find(p => p[presetsIdKey] === preset.id);
1820
1825
 
1821
- preset.state = presetData ? (presetData.Power === power
1826
+ preset.state = presetData ? (this.accountType === 'melcloudhome' ? scheduleEnabled && presetData.Enabled : (presetData.Power === power
1822
1827
  && presetData.EcoHotWater === ecoHotWater
1823
1828
  && presetData.OperationModeZone1 === operationModeZone1
1824
1829
  && presetData.OperationModeZone2 === operationModeZone2
@@ -1829,7 +1834,7 @@ class DeviceAtw extends EventEmitter {
1829
1834
  && presetData.SetHeatFlowTemperatureZone1 === setHeatFlowTemperatureZone1
1830
1835
  && presetData.SetHeatFlowTemperatureZone2 === setHeatFlowTemperatureZone2
1831
1836
  && presetData.SetCoolFlowTemperatureZone1 === setCoolFlowTemperatureZone1
1832
- && presetData.SetCoolFlowTemperatureZone2 === setCoolFlowTemperatureZone2) : false;
1837
+ && presetData.SetCoolFlowTemperatureZone2 === setCoolFlowTemperatureZone2)) : false;
1833
1838
 
1834
1839
  const characteristicType = preset.characteristicType;
1835
1840
  this.presetsServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
package/src/deviceerv.js CHANGED
@@ -675,7 +675,7 @@ class DeviceErv extends EventEmitter {
675
675
  if (this.logDebug) this.emit('debug', `Prepare presets services`);
676
676
  this.presetsServices = [];
677
677
  this.presets.forEach((preset, i) => {
678
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
678
+ const presetData = presetsOnServer.find(p => p.ID === preset.id);
679
679
 
680
680
  //get preset name
681
681
  const name = preset.name;
@@ -703,14 +703,14 @@ class DeviceErv extends EventEmitter {
703
703
  deviceData.Device.Power = presetData.Power;
704
704
  deviceData.Device.OperationMode = presetData.OperationMode;
705
705
  deviceData.Device.VentilationMode = presetData.VentilationMode;
706
- deviceData.Device.SetFanSpeed = presetData.FanSpeed;
706
+ deviceData.Device.SetFanSpeed = presetData.SetFanSpeed;
707
707
  break;
708
708
  case false:
709
709
  deviceData.Device.SetTemperature = preset.previousSettings.SetTemperature;
710
710
  deviceData.Device.Power = preset.previousSettings.Power;
711
711
  deviceData.Device.OperationMode = preset.previousSettings.OperationMode;
712
712
  deviceData.Device.VentilationMode = preset.previousSettings.VentilationMode;
713
- deviceData.Device.SetFanSpeed = preset.previousSettings.FanSpeed;
713
+ deviceData.Device.SetFanSpeed = preset.previousSettings.SetFanSpeed;
714
714
  break;
715
715
  };
716
716
 
@@ -876,12 +876,15 @@ class DeviceErv extends EventEmitter {
876
876
  this.deviceData = deviceData;
877
877
 
878
878
  //keys
879
+ const presetsKey = this.accountType === 'melcloud' ? 'Presets' : 'Schedule';
880
+ const presetsIdKey = accountType === 'melcloud' ? 'ID' : 'Id';
879
881
  const fanKey = this.accountType === 'melcloud' ? 'FanSpeed' : 'SetFanSpeed';
880
882
  const tempStepKey = this.accountType === 'melcloud' ? 'TemperatureIncrement' : 'HasHalfDegreeIncrements';
881
883
  const errorKey = this.accountType === 'melcloud' ? 'HasError' : 'IsInError';
882
884
 
883
- //presets
884
- const presetsOnServer = deviceData.Presets ?? [];
885
+ //presets schedule
886
+ const scheduleEnabled = deviceData.ScheduleEnabled;
887
+ const presetsOnServer = deviceData[presetsKey] ?? [];
885
888
 
886
889
  //device control
887
890
  const hideRoomTemperature = deviceData.HideRoomTemperature;
@@ -970,7 +973,8 @@ class DeviceErv extends EventEmitter {
970
973
  maxTempCoolDry: maxTempCoolDry,
971
974
  useFahrenheit: this.useFahrenheit,
972
975
  temperatureUnit: TemperatureDisplayUnits[this.useFahrenheit],
973
- isInError: isInError
976
+ isInError: isInError,
977
+ scheduleEnabled: scheduleEnabled
974
978
  };
975
979
 
976
980
  //ventilation mode - 0, HEAT, 2, COOL, 4, 5, 6, FAN, AUTO
@@ -1113,13 +1117,13 @@ class DeviceErv extends EventEmitter {
1113
1117
  //update presets state
1114
1118
  if (this.presets.length > 0) {
1115
1119
  this.presets.forEach((preset, i) => {
1116
- const presetData = presetsOnServer.find(p => p.ID === preset.Id);
1120
+ const presetData = presetsOnServer.find(p => p[presetsIdKey] === preset.id);
1117
1121
 
1118
- preset.state = presetData ? (presetData.Power === power
1122
+ preset.state = presetData ? (this.accountType === 'melcloudhome' ? scheduleEnabled && presetData.Enabled : (presetData.Power === power
1119
1123
  && presetData.SetTemperature === setTemperature
1120
1124
  && presetData.OperationMode === operationMode
1121
1125
  && presetData.VentilationMode === ventilationMode
1122
- && presetData.FanSpeed === setFanSpeed) : false;
1126
+ && presetData.SetFanSpeed === setFanSpeed)) : false;
1123
1127
 
1124
1128
  const characteristicType = preset.characteristicType;
1125
1129
  this.presetsServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
package/src/melcloud.js CHANGED
@@ -236,11 +236,18 @@ class MelCloud extends EventEmitter {
236
236
 
237
237
  const devices = buildingsList.flatMap(building => {
238
238
  // Funkcja kapitalizująca klucze obiektu
239
- // Rekurencyjna funkcja kapitalizująca klucze w całym obiekcie (łącznie z tablicami)
239
+ const capitalizeKeys = obj =>
240
+ Object.fromEntries(
241
+ Object.entries(obj).map(([key, value]) => [
242
+ key.charAt(0).toUpperCase() + key.slice(1),
243
+ value
244
+ ])
245
+ );
246
+
247
+ // Rekurencyjna kapitalizacja kluczy w obiekcie lub tablicy
240
248
  const capitalizeKeysDeep = obj => {
241
- if (Array.isArray(obj)) {
242
- return obj.map(item => capitalizeKeysDeep(item));
243
- } else if (obj && typeof obj === 'object' && obj.constructor === Object) {
249
+ if (Array.isArray(obj)) return obj.map(capitalizeKeysDeep);
250
+ if (obj && typeof obj === 'object') {
244
251
  return Object.fromEntries(
245
252
  Object.entries(obj).map(([key, value]) => [
246
253
  key.charAt(0).toUpperCase() + key.slice(1),
@@ -252,7 +259,8 @@ class MelCloud extends EventEmitter {
252
259
  };
253
260
 
254
261
  // Funkcja tworząca finalny obiekt Device
255
- const createDevice = (device, type, headers = {}) => {
262
+ const createDevice = (device, type) => {
263
+ // Settings już kapitalizowane w nazwach
256
264
  const settingsArray = device.Settings || [];
257
265
 
258
266
  const settingsObject = Object.fromEntries(
@@ -260,23 +268,34 @@ class MelCloud extends EventEmitter {
260
268
  let parsedValue = value;
261
269
  if (value === "True") parsedValue = true;
262
270
  else if (value === "False") parsedValue = false;
263
- else if (!isNaN(Number(value)) && value.trim() !== "") parsedValue = Number(value);
271
+ else if (!isNaN(value) && value !== "") parsedValue = Number(value);
264
272
 
265
273
  const key = name.charAt(0).toUpperCase() + name.slice(1);
266
274
  return [key, parsedValue];
267
275
  })
268
276
  );
269
277
 
278
+ // Scal Capabilities + Settings + DeviceType w Device
270
279
  const deviceObject = {
271
- ...capitalizeKeysDeep(device.Capabilities || {}),
280
+ ...capitalizeKeys(device.Capabilities || {}),
272
281
  ...settingsObject,
273
282
  DeviceType: type
274
283
  };
275
284
 
285
+ // Kapitalizacja brakujących obiektów/tablic
286
+ if (device.FrostProtection) device.FrostProtection = { ...capitalizeKeys(device.FrostProtection || {}) };
287
+ if (device.OverheatProtection) device.OverheatProtection = { ...capitalizeKeys(device.OverheatProtection || {}) };
288
+ if (device.HolidayMode) device.HolidayMode = { ...capitalizeKeys(device.HolidayMode || {}) };
289
+
290
+ if (Array.isArray(device.Schedule)) {
291
+ device.Schedule = device.Schedule.map(capitalizeKeysDeep);
292
+ }
293
+
294
+ // Usuń stare pola Settings i Capabilities
276
295
  const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
277
296
 
278
297
  return {
279
- ...capitalizeKeysDeep(rest),
298
+ ...rest,
280
299
  Type: type,
281
300
  DeviceID: Id,
282
301
  DeviceName: GivenDisplayName,
@@ -285,11 +304,10 @@ class MelCloud extends EventEmitter {
285
304
  };
286
305
  };
287
306
 
288
- // Mapowanie urządzeń
289
307
  return [
290
- ...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeysDeep(d), 0)),
291
- ...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeysDeep(d), 1)),
292
- ...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeysDeep(d), 3))
308
+ ...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
309
+ ...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
310
+ ...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3))
293
311
  ];
294
312
  });
295
313