homebridge-melcloud-control 4.3.16-beta.1 → 4.3.16-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/CHANGELOG.md CHANGED
@@ -22,6 +22,24 @@ 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
+
34
+ # [4.3.16] - (09.12.2025)
35
+
36
+ ## Changes
37
+
38
+ - stability and performance improvements
39
+ - moved MQTT to v5
40
+ - bump dependencies
41
+ - cleanup
42
+
25
43
  # [4.3.11] - (07.12.2025)
26
44
 
27
45
  ## 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.1",
4
+ "version": "4.3.16-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",
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@homebridge/plugin-ui-utils": "^2.1.2",
39
- "async-mqtt": "^2.6.3",
39
+ "mqtt": "^5.14.1",
40
40
  "axios": "^1.13.2",
41
41
  "express": "^5.2.1",
42
- "puppeteer": "^24.32.0",
42
+ "puppeteer": "^24.32.1",
43
43
  "ws": "^8.18.3"
44
44
  },
45
45
  "keywords": [
package/src/deviceata.js CHANGED
@@ -113,34 +113,31 @@ class DeviceAta extends EventEmitter {
113
113
  const restFulEnabled = this.restFul.enable || false;
114
114
  if (restFulEnabled) {
115
115
  try {
116
-
117
- if (!this.restFulConnected) {
118
- this.restFul1 = new RestFul({
119
- port: this.restFul.port,
120
- logWarn: this.logWarn,
121
- logDebug: this.logDebug
116
+ this.restFul1 = new RestFul({
117
+ port: this.restFul.port,
118
+ logWarn: this.logWarn,
119
+ logDebug: this.logDebug
120
+ })
121
+ .on('connected', (message) => {
122
+ this.restFulConnected = true;
123
+ this.emit('success', message);
122
124
  })
123
- .on('connected', (message) => {
124
- this.restFulConnected = true;
125
- this.emit('success', message);
126
- })
127
- .on('set', async (key, value) => {
128
- try {
129
- await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
130
- } catch (error) {
131
- this.emit('warn', error);
132
- };
133
- })
134
- .on('debug', (debug) => {
135
- this.emit('debug', debug);
136
- })
137
- .on('warn', (warn) => {
138
- this.emit('warn', warn);
139
- })
140
- .on('error', (error) => {
141
- this.emit('error', error);
142
- });
143
- }
125
+ .on('set', async (key, value) => {
126
+ try {
127
+ await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
128
+ } catch (error) {
129
+ this.emit('warn', error);
130
+ };
131
+ })
132
+ .on('debug', (debug) => {
133
+ this.emit('debug', debug);
134
+ })
135
+ .on('warn', (warn) => {
136
+ this.emit('warn', warn);
137
+ })
138
+ .on('error', (error) => {
139
+ this.emit('error', error);
140
+ });
144
141
  } catch (error) {
145
142
  if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
146
143
  };
@@ -150,41 +147,39 @@ class DeviceAta extends EventEmitter {
150
147
  const mqttEnabled = this.mqtt.enable || false;
151
148
  if (mqttEnabled) {
152
149
  try {
153
- if (!this.mqttConnected) {
154
- this.mqtt1 = new Mqtt({
155
- host: this.mqtt.host,
156
- port: this.mqtt.port || 1883,
157
- clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
158
- prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
159
- user: this.mqtt.auth?.user,
160
- passwd: this.mqtt.auth?.passwd,
161
- logWarn: this.logWarn,
162
- logDebug: this.logDebug
150
+ this.mqtt1 = new Mqtt({
151
+ host: this.mqtt.host,
152
+ port: this.mqtt.port || 1883,
153
+ clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
154
+ prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
155
+ user: this.mqtt.auth?.user,
156
+ passwd: this.mqtt.auth?.passwd,
157
+ logWarn: this.logWarn,
158
+ logDebug: this.logDebug
159
+ })
160
+ .on('connected', (message) => {
161
+ this.mqttConnected = true;
162
+ this.emit('success', message);
163
163
  })
164
- .on('connected', (message) => {
165
- this.mqttConnected = true;
166
- this.emit('success', message);
167
- })
168
- .on('subscribed', (message) => {
169
- this.emit('success', message);
170
- })
171
- .on('set', async (key, value) => {
172
- try {
173
- await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
174
- } catch (error) {
175
- this.emit('warn', error);
176
- };
177
- })
178
- .on('debug', (debug) => {
179
- this.emit('debug', debug);
180
- })
181
- .on('warn', (warn) => {
182
- this.emit('warn', warn);
183
- })
184
- .on('error', (error) => {
185
- this.emit('error', error);
186
- });
187
- }
164
+ .on('subscribed', (message) => {
165
+ this.emit('success', message);
166
+ })
167
+ .on('set', async (key, value) => {
168
+ try {
169
+ await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
170
+ } catch (error) {
171
+ this.emit('warn', error);
172
+ };
173
+ })
174
+ .on('debug', (debug) => {
175
+ this.emit('debug', debug);
176
+ })
177
+ .on('warn', (warn) => {
178
+ this.emit('warn', warn);
179
+ })
180
+ .on('error', (error) => {
181
+ this.emit('error', error);
182
+ });
188
183
  } catch (error) {
189
184
  if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
190
185
  };
@@ -702,27 +697,95 @@ class DeviceAta extends EventEmitter {
702
697
  }
703
698
 
704
699
  //frost protection
705
- if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) {
700
+ if (this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
706
701
  //control
707
702
  if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
708
- this.frostProtectionControlService = new Service.Switch(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
709
- this.frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
710
- this.frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
711
- this.frostProtectionControlService.getCharacteristic(Characteristic.On)
703
+ const frostProtectionControlService = new Service.HeaterCooler(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
704
+ frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
705
+ frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
706
+ frostProtectionControlService.getCharacteristic(Characteristic.Active)
712
707
  .onGet(async () => {
713
- const state = this.accessory.frostProtectionEnabled;
708
+ const state = this.accessory.frostProtection.Enabled;
714
709
  return state;
715
710
  })
716
711
  .onSet(async (state) => {
717
712
  try {
718
- deviceData.FrostProtection.Enabled = state;
713
+ deviceData.FrostProtection.Enabled = state ? true : false;
719
714
  if (this.logInfo) this.emit('info', `Frost protection: ${state ? 'Enabled' : 'Disabled'}`);
720
715
  await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
721
716
  } catch (error) {
722
717
  if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
723
718
  };
724
719
  });
725
- accessory.addService(this.frostProtectionControlService);
720
+ frostProtectionControlService.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
721
+ .onGet(async () => {
722
+ const value = this.accessory.frostProtection.Active ? 2 : 1;
723
+ return value;
724
+ })
725
+ frostProtectionControlService.getCharacteristic(Characteristic.TargetHeaterCoolerState)
726
+ .setProps({
727
+ minValue: 0,
728
+ maxValue: 0,
729
+ validValues: [0]
730
+ })
731
+ .onGet(async () => {
732
+ const value = 0
733
+ return value;
734
+ })
735
+ .onSet(async (value) => {
736
+ try {
737
+ deviceData.FrostProtection.Enabled = true;
738
+ if (this.logInfo) this.emit('info', `Frost protection: Enabled`);
739
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
740
+ } catch (error) {
741
+ if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
742
+ };
743
+ });
744
+ frostProtectionControlService.getCharacteristic(Characteristic.CurrentTemperature)
745
+ .onGet(async () => {
746
+ const value = this.accessory.roomTemperature;
747
+ return value;
748
+ });
749
+ frostProtectionControlService.getCharacteristic(Characteristic.CoolingThresholdTemperature) //max
750
+ .setProps({
751
+ minValue: 6,
752
+ maxValue: 16,
753
+ minStep: 1
754
+ })
755
+ .onGet(async () => {
756
+ const value = this.accessory.frostProtection.Max;
757
+ return value;
758
+ })
759
+ .onSet(async (value) => {
760
+ try {
761
+ deviceData.FrostProtection.Max = value;
762
+ if (this.logInfo) this.emit('info', `Set frost protection max. temperature: ${value}${this.accessory.temperatureUnit}`);
763
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
764
+ } catch (error) {
765
+ if (this.logWarn) this.emit('warn', `Set frost protection max. temperature error: ${error}`);
766
+ };
767
+ });
768
+ frostProtectionControlService.getCharacteristic(Characteristic.HeatingThresholdTemperature) //min
769
+ .setProps({
770
+ minValue: 4,
771
+ maxValue: 14,
772
+ minStep: 1
773
+ })
774
+ .onGet(async () => {
775
+ const value = this.accessory.frostProtection.Min;
776
+ return value;
777
+ })
778
+ .onSet(async (value) => {
779
+ try {
780
+ deviceData.FrostProtection.Min = value;
781
+ if (this.logInfo) this.emit('info', `Set frost protection min. temperature: ${value}${this.accessory.temperatureUnit}`);
782
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
783
+ } catch (error) {
784
+ if (this.logWarn) this.emit('warn', `Set frost protection min. temperature error: ${error}`);
785
+ };
786
+ });
787
+ this.frostProtectionControlService = frostProtectionControlService;
788
+ accessory.addService(frostProtectionControlService);
726
789
 
727
790
  if (this.logDebug) this.emit('debug', `Prepare frost protection control sensor service`);
728
791
  this.frostProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Frost Protection Control`, `frostProtectionControlSensorService${deviceId}`);
@@ -730,7 +793,7 @@ class DeviceAta extends EventEmitter {
730
793
  this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`);
731
794
  this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
732
795
  .onGet(async () => {
733
- const state = this.accessory.frostProtectionEnabled;
796
+ const state = this.accessory.frostProtection.Enabled;
734
797
  return state;
735
798
  })
736
799
  accessory.addService(this.frostProtectionControlSensorService);
@@ -742,34 +805,102 @@ class DeviceAta extends EventEmitter {
742
805
  this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
743
806
  this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
744
807
  .onGet(async () => {
745
- const state = this.accessory.frostProtectionActive;
808
+ const state = this.accessory.frostProtection.Active;
746
809
  return state;
747
810
  })
748
811
  accessory.addService(this.frostProtectionSensorService);
749
812
  }
750
813
 
751
814
  //overheat protection
752
- if (this.overheatProtectionSupport && this.accessory.overheatProtectionEnabled !== null) {
815
+ if (this.overheatProtectionSupport && this.accessory.overheatProtection.Enabled !== null) {
753
816
  //control
754
817
  if (this.logDebug) this.emit('debug', `Prepare overheat protection control service`);
755
- this.overheatProtectionControlService = new Service.Switch(`${serviceName} Overheat Protection`, `overheatProtectionControlService${deviceId}`);
756
- this.overheatProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
757
- this.overheatProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection`);
758
- this.overheatProtectionControlService.getCharacteristic(Characteristic.On)
818
+ const overheatProtectionControlService = new Service.HeaterCooler(`${serviceName} Overheat Protection`, `overheatProtectionControlService${deviceId}`);
819
+ overheatProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
820
+ overheatProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection`);
821
+ overheatProtectionControlService.getCharacteristic(Characteristic.Active)
759
822
  .onGet(async () => {
760
- const state = this.accessory.overheatProtectionEnabled;
823
+ const state = this.accessory.overheatProtection.Enabled;
761
824
  return state;
762
825
  })
763
826
  .onSet(async (state) => {
764
827
  try {
765
- deviceData.OverheatProtection.Enabled = state;
828
+ deviceData.OverheatProtection.Enabled = state ? true : false;
766
829
  if (this.logInfo) this.emit('info', `Overheat protection: ${state ? 'Enabled' : 'Disabled'}`);
767
830
  await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'overheatprotection');
768
831
  } catch (error) {
769
832
  if (this.logWarn) this.emit('warn', `Set overheat protection error: ${error}`);
770
833
  };
771
834
  });
772
- accessory.addService(this.overheatProtectionControlService);
835
+ overheatProtectionControlService.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
836
+ .onGet(async () => {
837
+ const value = this.accessory.overheatProtection.Active ? 2 : 1;
838
+ return value;
839
+ })
840
+ overheatProtectionControlService.getCharacteristic(Characteristic.TargetHeaterCoolerState)
841
+ .setProps({
842
+ minValue: 0,
843
+ maxValue: 0,
844
+ validValues: [0]
845
+ })
846
+ .onGet(async () => {
847
+ const value = 0
848
+ return value;
849
+ })
850
+ .onSet(async (value) => {
851
+ try {
852
+ deviceData.OverheatProtection.Enabled = true;
853
+ if (this.logInfo) this.emit('info', `Set overheat protection: Enabled`);
854
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'overheatprotection');
855
+ } catch (error) {
856
+ if (this.logWarn) this.emit('warn', `Set overheat protection error: ${error}`);
857
+ };
858
+ });
859
+ overheatProtectionControlService.getCharacteristic(Characteristic.CurrentTemperature)
860
+ .onGet(async () => {
861
+ const value = this.accessory.roomTemperature;
862
+ return value;
863
+ });
864
+ overheatProtectionControlService.getCharacteristic(Characteristic.CoolingThresholdTemperature) //max
865
+ .setProps({
866
+ minValue: 33,
867
+ maxValue: 40,
868
+ minStep: 1
869
+ })
870
+ .onGet(async () => {
871
+ const value = this.accessory.overheatProtection.Max;
872
+ return value;
873
+ })
874
+ .onSet(async (value) => {
875
+ try {
876
+ deviceData.OverheatProtection.Max = value;
877
+ if (this.logInfo) this.emit('info', `Set overheat protection max. temperature: ${value}${this.accessory.temperatureUnit}`);
878
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'overheatprotection');
879
+ } catch (error) {
880
+ if (this.logWarn) this.emit('warn', `Set overheat protection max. temperature error: ${error}`);
881
+ };
882
+ });
883
+ overheatProtectionControlService.getCharacteristic(Characteristic.HeatingThresholdTemperature) //min
884
+ .setProps({
885
+ minValue: 31,
886
+ maxValue: 38,
887
+ minStep: 1
888
+ })
889
+ .onGet(async () => {
890
+ const value = this.accessory.overheatProtection.Min;
891
+ return value;
892
+ })
893
+ .onSet(async (value) => {
894
+ try {
895
+ deviceData.OverheatProtection.Min = value;
896
+ if (this.logInfo) this.emit('info', `Set overheat protection min. temperature: ${value}${this.accessory.temperatureUnit}`);
897
+ await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'overheatprotection');
898
+ } catch (error) {
899
+ if (this.logWarn) this.emit('warn', `Set overheat protection min. temperature error: ${error}`);
900
+ };
901
+ });
902
+ this.overheatProtectionControlService = overheatProtectionControlService;
903
+ accessory.addService(overheatProtectionControlService);
773
904
 
774
905
  if (this.logDebug) this.emit('debug', `Prepare overheat protection control sensor service`);
775
906
  this.overheatProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Overheat Protection Control`, `overheatProtectionControlSensorService${deviceId}`);
@@ -777,7 +908,7 @@ class DeviceAta extends EventEmitter {
777
908
  this.overheatProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection Control`);
778
909
  this.overheatProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
779
910
  .onGet(async () => {
780
- const state = this.accessory.overheatProtectionEnabled;
911
+ const state = this.accessory.overheatProtection.Enabled;
781
912
  return state;
782
913
  })
783
914
  accessory.addService(this.overheatProtectionControlSensorService);
@@ -788,7 +919,7 @@ class DeviceAta extends EventEmitter {
788
919
  this.overheatProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection`);
789
920
  this.overheatProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
790
921
  .onGet(async () => {
791
- const state = this.accessory.overheatProtectionActive;
922
+ const state = this.accessory.overheatProtection.Active;
792
923
  return state;
793
924
  })
794
925
  accessory.addService(this.overheatProtectionSensorService);
@@ -1374,10 +1505,8 @@ class DeviceAta extends EventEmitter {
1374
1505
  const holidayModeActive = deviceData.HolidayMode?.Active ?? false;
1375
1506
 
1376
1507
  //protection
1377
- const frostProtectionEnabled = deviceData.FrostProtection?.Enabled;
1378
- const frostProtectionActive = deviceData.FrostProtection?.Active ?? false;
1379
- const overheatProtectionEnabled = deviceData.OverheatProtection?.Enabled;
1380
- const overheatProtectionActive = deviceData.OverheatProtection?.Active ?? false;
1508
+ const frostProtection = deviceData.FrostProtection ?? {};
1509
+ const overheatProtection = deviceData.OverheatProtection ?? {};
1381
1510
 
1382
1511
  //device control
1383
1512
  const hideVaneControls = deviceData.HideVaneControls ?? false;
@@ -1470,10 +1599,8 @@ class DeviceAta extends EventEmitter {
1470
1599
  temperatureUnit: TemperatureDisplayUnits[this.accountInfo.useFahrenheit ? 1 : 0],
1471
1600
  isConnected: isConnected,
1472
1601
  isInError: isInError,
1473
- frostProtectionEnabled: frostProtectionEnabled,
1474
- frostProtectionActive: frostProtectionActive,
1475
- overheatProtectionEnabled: overheatProtectionEnabled,
1476
- overheatProtectionActive: overheatProtectionActive,
1602
+ frostProtection: frostProtection,
1603
+ overheatProtection: overheatProtection,
1477
1604
  holidayModeEnabled: holidayModeEnabled,
1478
1605
  holidayModeActive: holidayModeActive,
1479
1606
  scheduleEnabled: scheduleEnabled
@@ -1572,42 +1699,34 @@ class DeviceAta extends EventEmitter {
1572
1699
  obj.currentOperationMode = roomTemperature > setTemperature ? 0 : 1;
1573
1700
  obj.targetOperationMode = 1;
1574
1701
  break;
1575
-
1576
1702
  case 2: // DRY
1577
1703
  obj.currentOperationMode = 0;
1578
1704
  obj.targetOperationMode = resolveTargetOperation1(2, obj);
1579
1705
  break;
1580
-
1581
1706
  case 3: // COOL
1582
1707
  obj.currentOperationMode = roomTemperature < setTemperature ? 0 : 2;
1583
1708
  obj.targetOperationMode = 2;
1584
1709
  break;
1585
-
1586
1710
  case 7: // FAN
1587
1711
  obj.currentOperationMode = 0;
1588
1712
  obj.targetOperationMode = resolveTargetOperation1(3, obj);
1589
1713
  break;
1590
-
1591
1714
  case 8: // AUTO
1592
1715
  obj.currentOperationMode = roomTemperature < setTemperature ? 1 : roomTemperature > setTemperature ? 2 : 0;
1593
1716
  obj.targetOperationMode = 3;
1594
1717
  break;
1595
-
1596
1718
  case 9: // ISEE HEAT
1597
1719
  obj.currentOperationMode = roomTemperature > setTemperature ? 0 : 1;
1598
1720
  obj.targetOperationMode = 1;
1599
1721
  break;
1600
-
1601
1722
  case 10: // ISEE DRY
1602
1723
  obj.currentOperationMode = 0;
1603
1724
  obj.targetOperationMode = resolveTargetOperation1(2, obj);
1604
1725
  break;
1605
-
1606
1726
  case 11: // ISEE COOL
1607
1727
  obj.currentOperationMode = roomTemperature < setTemperature ? 0 : 2;
1608
1728
  obj.targetOperationMode = 2;
1609
1729
  break;
1610
-
1611
1730
  default:
1612
1731
  if (this.logWarn) this.emit('warn', `Unknown operating mode: ${operationMode}`);
1613
1732
  break;
@@ -1645,17 +1764,27 @@ class DeviceAta extends EventEmitter {
1645
1764
  this.errorService?.updateCharacteristic(Characteristic.ContactSensorState, isInError);
1646
1765
 
1647
1766
  //frost protection
1648
- if (this.frostProtectionSupport && frostProtectionEnabled !== null) {
1649
- this.frostProtectionControlService?.updateCharacteristic(Characteristic.On, frostProtectionEnabled);
1650
- this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionEnabled);
1651
- 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);
1652
1776
  }
1653
1777
 
1654
1778
  //overheat protection
1655
- if (this.overheatProtectionSupport && overheatProtectionEnabled !== null) {
1656
- this.overheatProtectionControlService?.updateCharacteristic(Characteristic.On, overheatProtectionEnabled);
1657
- this.overheatProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, overheatProtectionEnabled);
1658
- 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);
1659
1788
  }
1660
1789
 
1661
1790
  //holiday mode
package/src/deviceatw.js CHANGED
@@ -118,33 +118,31 @@ class DeviceAtw extends EventEmitter {
118
118
  if (restFulEnabled) {
119
119
  try {
120
120
 
121
- if (!this.restFulConnected) {
122
- this.restFul1 = new RestFul({
123
- port: this.restFul.port,
124
- logWarn: this.logWarn,
125
- logDebug: this.logDebug
121
+ this.restFul1 = new RestFul({
122
+ port: this.restFul.port,
123
+ logWarn: this.logWarn,
124
+ logDebug: this.logDebug
125
+ })
126
+ .on('connected', (message) => {
127
+ this.restFulConnected = true;
128
+ this.emit('success', message);
126
129
  })
127
- .on('connected', (message) => {
128
- this.restFulConnected = true;
129
- this.emit('success', message);
130
- })
131
- .on('set', async (key, value) => {
132
- try {
133
- await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
134
- } catch (error) {
135
- this.emit('warn', error);
136
- };
137
- })
138
- .on('debug', (debug) => {
139
- this.emit('debug', debug);
140
- })
141
- .on('warn', (warn) => {
142
- this.emit('warn', warn);
143
- })
144
- .on('error', (error) => {
145
- this.emit('error', error);
146
- });
147
- }
130
+ .on('set', async (key, value) => {
131
+ try {
132
+ await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
133
+ } catch (error) {
134
+ this.emit('warn', error);
135
+ };
136
+ })
137
+ .on('debug', (debug) => {
138
+ this.emit('debug', debug);
139
+ })
140
+ .on('warn', (warn) => {
141
+ this.emit('warn', warn);
142
+ })
143
+ .on('error', (error) => {
144
+ this.emit('error', error);
145
+ });
148
146
  } catch (error) {
149
147
  if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
150
148
  };
@@ -154,41 +152,39 @@ class DeviceAtw extends EventEmitter {
154
152
  const mqttEnabled = this.mqtt.enable || false;
155
153
  if (mqttEnabled) {
156
154
  try {
157
- if (!this.mqttConnected) {
158
- this.mqtt1 = new Mqtt({
159
- host: this.mqtt.host,
160
- port: this.mqtt.port || 1883,
161
- clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
162
- prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
163
- user: this.mqtt.auth?.user,
164
- passwd: this.mqtt.auth?.passwd,
165
- logWarn: this.logWarn,
166
- logDebug: this.logDebug
155
+ this.mqtt1 = new Mqtt({
156
+ host: this.mqtt.host,
157
+ port: this.mqtt.port || 1883,
158
+ clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
159
+ prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
160
+ user: this.mqtt.auth?.user,
161
+ passwd: this.mqtt.auth?.passwd,
162
+ logWarn: this.logWarn,
163
+ logDebug: this.logDebug
164
+ })
165
+ .on('connected', (message) => {
166
+ this.mqttConnected = true;
167
+ this.emit('success', message);
167
168
  })
168
- .on('connected', (message) => {
169
- this.mqttConnected = true;
170
- this.emit('success', message);
171
- })
172
- .on('subscribed', (message) => {
173
- this.emit('success', message);
174
- })
175
- .on('set', async (key, value) => {
176
- try {
177
- await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
178
- } catch (error) {
179
- this.emit('warn', error);
180
- };
181
- })
182
- .on('debug', (debug) => {
183
- this.emit('debug', debug);
184
- })
185
- .on('warn', (warn) => {
186
- this.emit('warn', warn);
187
- })
188
- .on('error', (error) => {
189
- this.emit('error', error);
190
- });
191
- }
169
+ .on('subscribed', (message) => {
170
+ this.emit('success', message);
171
+ })
172
+ .on('set', async (key, value) => {
173
+ try {
174
+ await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
175
+ } catch (error) {
176
+ this.emit('warn', error);
177
+ };
178
+ })
179
+ .on('debug', (debug) => {
180
+ this.emit('debug', debug);
181
+ })
182
+ .on('warn', (warn) => {
183
+ this.emit('warn', warn);
184
+ })
185
+ .on('error', (error) => {
186
+ this.emit('error', error);
187
+ });
192
188
  } catch (error) {
193
189
  if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
194
190
  };
@@ -1058,27 +1054,95 @@ class DeviceAtw extends EventEmitter {
1058
1054
  }
1059
1055
 
1060
1056
  //frost protection
1061
- if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) {
1057
+ if (this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
1062
1058
  //control
1063
1059
  if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
1064
- this.frostProtectionControlService = new Service.Switch(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
1065
- this.frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName);
1066
- this.frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
1067
- 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)
1068
1064
  .onGet(async () => {
1069
- const state = this.accessory.frostProtectionEnabled;
1065
+ const state = this.accessory.frostProtection.Enabled;
1070
1066
  return state;
1071
1067
  })
1072
1068
  .onSet(async (state) => {
1073
1069
  try {
1074
- deviceData.FrostProtection.Enabled = state;
1070
+ deviceData.FrostProtection.Enabled = state ? true : false;
1075
1071
  if (this.logInfo) this.emit('info', `Frost protection: ${state ? 'Enabled' : 'Disabled'}`);
1076
1072
  await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'frostprotection');
1077
1073
  } catch (error) {
1078
1074
  if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`);
1079
1075
  };
1080
1076
  });
1081
- 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);
1082
1146
 
1083
1147
  if (this.logDebug) this.emit('debug', `Prepare frost protection control sensor service`);
1084
1148
  this.frostProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Frost Protection Control`, `frostProtectionControlSensorService${deviceId}`);
@@ -1086,7 +1150,7 @@ class DeviceAtw extends EventEmitter {
1086
1150
  this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`);
1087
1151
  this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState)
1088
1152
  .onGet(async () => {
1089
- const state = this.accessory.frostProtectionEnabled;
1153
+ const state = this.accessory.frostProtection.Enabled;
1090
1154
  return state;
1091
1155
  })
1092
1156
  accessory.addService(this.frostProtectionControlSensorService);
@@ -1098,7 +1162,7 @@ class DeviceAtw extends EventEmitter {
1098
1162
  this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`);
1099
1163
  this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState)
1100
1164
  .onGet(async () => {
1101
- const state = this.accessory.frostProtectionActive;
1165
+ const state = this.accessory.frostProtection.Active;
1102
1166
  return state;
1103
1167
  })
1104
1168
  accessory.addService(this.frostProtectionSensorService);
@@ -1613,8 +1677,7 @@ class DeviceAtw extends EventEmitter {
1613
1677
  const holidayModeActive = deviceData.HolidayMode?.Active ?? false;
1614
1678
 
1615
1679
  //protection
1616
- const frostProtectionEnabled = deviceData.FrostProtection?.Enabled;
1617
- const frostProtectionActive = deviceData.FrostProtection?.Active ?? false;
1680
+ const frostProtection = deviceData.FrostProtection ?? {};
1618
1681
 
1619
1682
  //device info
1620
1683
  const supportsStanbyMode = deviceData.Device[supportStandbyKey];
@@ -1723,8 +1786,7 @@ class DeviceAtw extends EventEmitter {
1723
1786
  temperatureUnit: TemperatureDisplayUnits[this.accountInfo.useFahrenheit ? 1 : 0],
1724
1787
  isConnected: isConnected,
1725
1788
  isInError: isInError,
1726
- frostProtectionEnabled: frostProtectionEnabled,
1727
- frostProtectionActive: frostProtectionActive,
1789
+ frostProtection: frostProtection,
1728
1790
  scheduleEnabled: scheduleEnabled,
1729
1791
  holidayModeEnabled: holidayModeEnabled,
1730
1792
  holidayModeActive: holidayModeActive,
@@ -2124,10 +2186,15 @@ class DeviceAtw extends EventEmitter {
2124
2186
  this.errorService?.updateCharacteristic(Characteristic.ContactSensorState, isInError);
2125
2187
 
2126
2188
  //frost protection
2127
- if (this.frostProtectionSupport && frostProtectionEnabled !== null) {
2128
- this.frostProtectionControlService?.updateCharacteristic(Characteristic.On, frostProtectionEnabled);
2129
- this.frostProtectionControlSensorService?.updateCharacteristic(Characteristic.ContactSensorState, frostProtectionEnabled);
2130
- 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);
2131
2198
  }
2132
2199
 
2133
2200
  //holiday mode
package/src/deviceerv.js CHANGED
@@ -109,34 +109,31 @@ class DeviceErv extends EventEmitter {
109
109
  const restFulEnabled = this.restFul.enable || false;
110
110
  if (restFulEnabled) {
111
111
  try {
112
-
113
- if (!this.restFulConnected) {
114
- this.restFul1 = new RestFul({
115
- port: this.restFul.port,
116
- logWarn: this.logWarn,
117
- logDebug: this.logDebug
112
+ this.restFul1 = new RestFul({
113
+ port: this.restFul.port,
114
+ logWarn: this.logWarn,
115
+ logDebug: this.logDebug
116
+ })
117
+ .on('connected', (message) => {
118
+ this.restFulConnected = true;
119
+ this.emit('success', message);
118
120
  })
119
- .on('connected', (message) => {
120
- this.restFulConnected = true;
121
- this.emit('success', message);
122
- })
123
- .on('set', async (key, value) => {
124
- try {
125
- await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
126
- } catch (error) {
127
- this.emit('warn', error);
128
- };
129
- })
130
- .on('debug', (debug) => {
131
- this.emit('debug', debug);
132
- })
133
- .on('warn', (warn) => {
134
- this.emit('warn', warn);
135
- })
136
- .on('error', (error) => {
137
- this.emit('error', error);
138
- });
139
- }
121
+ .on('set', async (key, value) => {
122
+ try {
123
+ await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value);
124
+ } catch (error) {
125
+ this.emit('warn', error);
126
+ };
127
+ })
128
+ .on('debug', (debug) => {
129
+ this.emit('debug', debug);
130
+ })
131
+ .on('warn', (warn) => {
132
+ this.emit('warn', warn);
133
+ })
134
+ .on('error', (error) => {
135
+ this.emit('error', error);
136
+ });
140
137
  } catch (error) {
141
138
  if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
142
139
  };
@@ -146,41 +143,39 @@ class DeviceErv extends EventEmitter {
146
143
  const mqttEnabled = this.mqtt.enable || false;
147
144
  if (mqttEnabled) {
148
145
  try {
149
- if (!this.mqttConnected) {
150
- this.mqtt1 = new Mqtt({
151
- host: this.mqtt.host,
152
- port: this.mqtt.port || 1883,
153
- clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
154
- prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
155
- user: this.mqtt.auth?.user,
156
- passwd: this.mqtt.auth?.passwd,
157
- logWarn: this.logWarn,
158
- logDebug: this.logDebug
146
+ this.mqtt1 = new Mqtt({
147
+ host: this.mqtt.host,
148
+ port: this.mqtt.port || 1883,
149
+ clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`,
150
+ prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`,
151
+ user: this.mqtt.auth?.user,
152
+ passwd: this.mqtt.auth?.passwd,
153
+ logWarn: this.logWarn,
154
+ logDebug: this.logDebug
155
+ })
156
+ .on('connected', (message) => {
157
+ this.mqttConnected = true;
158
+ this.emit('success', message);
159
159
  })
160
- .on('connected', (message) => {
161
- this.mqttConnected = true;
162
- this.emit('success', message);
163
- })
164
- .on('subscribed', (message) => {
165
- this.emit('success', message);
166
- })
167
- .on('set', async (key, value) => {
168
- try {
169
- await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
170
- } catch (error) {
171
- this.emit('warn', error);
172
- };
173
- })
174
- .on('debug', (debug) => {
175
- this.emit('debug', debug);
176
- })
177
- .on('warn', (warn) => {
178
- this.emit('warn', warn);
179
- })
180
- .on('error', (error) => {
181
- this.emit('error', error);
182
- });
183
- }
160
+ .on('subscribed', (message) => {
161
+ this.emit('success', message);
162
+ })
163
+ .on('set', async (key, value) => {
164
+ try {
165
+ await this.setOverExternalIntegration('MQTT', this.deviceData, key, value);
166
+ } catch (error) {
167
+ this.emit('warn', error);
168
+ };
169
+ })
170
+ .on('debug', (debug) => {
171
+ this.emit('debug', debug);
172
+ })
173
+ .on('warn', (warn) => {
174
+ this.emit('warn', warn);
175
+ })
176
+ .on('error', (error) => {
177
+ this.emit('error', error);
178
+ });
184
179
  } catch (error) {
185
180
  if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
186
181
  };
package/src/functions.js CHANGED
@@ -56,7 +56,7 @@ class Functions extends EventEmitter {
56
56
 
57
57
  async ensureChromiumInstalled() {
58
58
  try {
59
- // --- Detect OS ---
59
+ // Detect OS
60
60
  const { stdout: osOut } = await execPromise("uname -s");
61
61
  const osName = osOut.trim();
62
62
  const { stdout: archOut } = await execPromise("uname -m");
@@ -66,7 +66,7 @@ class Functions extends EventEmitter {
66
66
  const isMac = osName === "Darwin";
67
67
  const isLinux = osName === "Linux";
68
68
 
69
- // --- Detect Docker ---
69
+ // Detect Docker
70
70
  let isDocker = false;
71
71
  try {
72
72
  await access("/.dockerenv");
@@ -77,7 +77,7 @@ class Functions extends EventEmitter {
77
77
  if (stdout.includes("docker") || stdout.includes("containerd")) isDocker = true;
78
78
  } catch { }
79
79
 
80
- // --- macOS ---
80
+ // macOS
81
81
  if (isMac) {
82
82
  const macCandidates = [
83
83
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
@@ -92,7 +92,7 @@ class Functions extends EventEmitter {
92
92
  return null;
93
93
  }
94
94
 
95
- // --- ARM / Raspberry Pi ---
95
+ // ARM / Raspberry Pi
96
96
  if (isARM && isLinux) {
97
97
  const armCandidates = [
98
98
  "/usr/bin/chromium-browser",
@@ -132,7 +132,7 @@ class Functions extends EventEmitter {
132
132
  return null;
133
133
  }
134
134
 
135
- // --- QNAP / Entware ---
135
+ // QNAP / Entware
136
136
  let entwareExists = false;
137
137
  try {
138
138
  await access("/opt/bin/opkg", fs.constants.X_OK);
@@ -147,7 +147,7 @@ class Functions extends EventEmitter {
147
147
  } catch { }
148
148
  }
149
149
 
150
- // --- Synology DSM 7 ---
150
+ // Synology DSM 7
151
151
  const synoCandidates = [
152
152
  "/var/packages/Chromium/target/usr/bin/chromium",
153
153
  "/usr/local/chromium/bin/chromium"
@@ -159,7 +159,7 @@ class Functions extends EventEmitter {
159
159
  } catch { }
160
160
  }
161
161
 
162
- // --- Linux x64 ---
162
+ // Linux x64
163
163
  if (isLinux) {
164
164
  const linuxCandidates = [
165
165
  "/usr/bin/chromium",
@@ -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';
@@ -228,7 +228,7 @@ class MelCloudHome extends EventEmitter {
228
228
  // Get Chromium path
229
229
  let chromiumPath = await this.functions.ensureChromiumInstalled();
230
230
 
231
- // === Fallback to Puppeteer's bundled Chromium ===
231
+ // Fallback to Puppeteer's bundled Chromium
232
232
  if (!chromiumPath) {
233
233
  try {
234
234
  const puppeteerPath = puppeteer.executablePath();
@@ -245,7 +245,7 @@ class MelCloudHome extends EventEmitter {
245
245
  }
246
246
  }
247
247
 
248
- // === Verify Chromium executable ===
248
+ // Verify Chromium executable
249
249
  try {
250
250
  const { stdout } = await execPromise(`"${chromiumPath}" --version`);
251
251
  if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
@@ -277,7 +277,7 @@ class MelCloudHome extends EventEmitter {
277
277
  page.setDefaultTimeout(GLOBAL_TIMEOUT);
278
278
  page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
279
279
 
280
- // === CDP session ===
280
+ // CDP session
281
281
  const client = await page.createCDPSession();
282
282
  await client.send('Network.enable')
283
283
  client.on('Network.webSocketCreated', ({ url }) => {
@@ -311,7 +311,7 @@ class MelCloudHome extends EventEmitter {
311
311
  .on('open', () => {
312
312
  this.socketConnected = true;
313
313
  this.connecting = false;
314
- if (this.logDebug) this.emit('debug', `Web Socket Connect Success`);
314
+ if (this.logDebug) this.emit('debug', `Web Socket Connected`);
315
315
 
316
316
  // heartbeat
317
317
  this.heartbeat = setInterval(() => {
@@ -400,7 +400,6 @@ class MelCloudHome extends EventEmitter {
400
400
  `__Secure-monitorandcontrolC2=${c2}`
401
401
  ].join('; ');
402
402
 
403
-
404
403
  const userAgent = await page.evaluate(() => navigator.userAgent);
405
404
  const headers = {
406
405
  'Accept': '*/*',
package/src/mqtt.js CHANGED
@@ -1,56 +1,103 @@
1
- import asyncMqtt from 'async-mqtt';
2
- const { connectAsync } = asyncMqtt;
1
+ import { connect } from 'mqtt';
3
2
  import EventEmitter from 'events';
4
3
 
5
4
  class Mqtt extends EventEmitter {
6
5
  constructor(config) {
7
6
  super();
7
+
8
+ const url = `mqtt://${config.host}:${config.port}`;
9
+ const subscribeTopic = `${config.prefix}/Set`;
10
+
8
11
  const options = {
9
12
  clientId: config.clientId,
10
13
  username: config.user,
11
- password: config.passwd
12
- }
13
- const url = `mqtt://${config.host}:${config.port}`;
14
- const subscribeTopic = `${config.prefix}/Set`;
14
+ password: config.passwd,
15
+ protocolVersion: 5,
16
+ clean: false,
17
+ properties: {
18
+ sessionExpiryInterval: 60 * 60, // 1 hour
19
+ userProperties: {
20
+ source: 'node-client'
21
+ }
22
+ }
23
+ };
24
+
25
+ this.mqttClient = connect(url, options);
26
+
27
+ // === CONNECTED ===
28
+ this.mqttClient.on('connect', async (packet) => {
29
+ this.emit('connected', 'MQTT v5 connected.');
15
30
 
16
- this.on('connect', async () => {
17
31
  try {
18
- //connect
19
- this.mqttClient = await connectAsync(url, options);
20
- this.emit('connected', 'MQTT Connected.');
32
+ const result = await this.mqttClient.subscribeAsync(subscribeTopic, {
33
+ qos: 1,
34
+ properties: {
35
+ userProperties: {
36
+ type: 'subscription'
37
+ }
38
+ }
39
+ });
21
40
 
22
- //subscribe
23
- await this.mqttClient.subscribe(subscribeTopic);
41
+ // MQTT v5 subscription results contain reason codes
42
+ if (config.logDebug) this.emit('debug', `Subscribed to ${subscribeTopic}, reason codes: ${JSON.stringify(result)}`);
24
43
  this.emit('subscribed', `MQTT Subscribe topic: ${subscribeTopic}`);
25
44
 
26
- //subscribed message
27
- this.mqttClient.on('message', (topic, message) => {
28
- try {
29
- const obj = JSON.parse(message.toString());
30
- if (config.logDebug) this.emit('debug', `MQTT Received topic: ${topic}, message: ${JSON.stringify(obj, null, 2)}`);
31
- const key = Object.keys(obj)[0];
32
- const value = Object.values(obj)[0];
33
- this.emit('set', key, value);
34
- } catch (error) {
35
- if (config.logWarn) this.emit('warn', `MQTT Parse object error: ${error}`);
36
- };
37
- });
38
45
  } catch (error) {
39
- if (config.logWarn) this.emit('warn', `MQTT Connect error: ${error}`);
40
- };
41
- }).on('publish', async (topic, message) => {
46
+ if (config.logWarn) this.emit('warn', `MQTT Subscribe error: ${error}`);
47
+ }
48
+ });
49
+
50
+ // === MESSAGE ===
51
+ this.mqttClient.on('message', (topic, payload, packet) => {
52
+ try {
53
+ const obj = JSON.parse(payload.toString());
54
+ if (config.logDebug) this.emit('debug', `MQTT Received:\nTopic: ${topic}\nPayload: ${JSON.stringify(obj, null, 2)}\nProperties: ${JSON.stringify(packet.properties, null, 2)}`);
55
+
56
+ const key = Object.keys(obj)[0];
57
+ const value = Object.values(obj)[0];
58
+ this.emit('set', key, value);
59
+
60
+ } catch (error) {
61
+ if (config.logWarn) this.emit('warn', `MQTT Parse error: ${error}`);
62
+ }
63
+ });
64
+
65
+ // === PUBLISH EVENT ===
66
+ this.on('publish', async (topic, message) => {
42
67
  try {
43
68
  const fullTopic = `${config.prefix}/${topic}`;
44
- const publishMessage = JSON.stringify(message, null, 2);
45
- await this.mqttClient.publish(fullTopic, publishMessage);
46
- if (config.logDebug) this.emit('debug', `MQTT Publish topic: ${fullTopic}, message: ${publishMessage}`);
69
+ const publishMessage = JSON.stringify(message);
70
+
71
+ await this.mqttClient.publishAsync(fullTopic, publishMessage, {
72
+ qos: 1,
73
+ properties: {
74
+ contentType: 'application/json',
75
+ userProperties: {
76
+ source: 'node',
77
+ action: 'set'
78
+ }
79
+ }
80
+ });
81
+
82
+ if (config.logDebug) this.emit('debug', `MQTT Publish:\nTopic: ${fullTopic}\nPayload: ${publishMessage}`);
47
83
  } catch (error) {
48
84
  if (config.logWarn) this.emit('warn', `MQTT Publish error: ${error}`);
49
- };
85
+ }
86
+ });
87
+
88
+ // === ERRORS / STATE ===
89
+ this.mqttClient.on('error', (err) => {
90
+ if (config.logWarn) this.emit('warn', `MQTT Error: ${err.message}`);
50
91
  });
51
92
 
52
- this.emit('connect');
93
+ this.mqttClient.on('reconnect', () => {
94
+ if (config.logDebug) this.emit('debug', 'MQTT Reconnecting...');
95
+ });
96
+
97
+ this.mqttClient.on('close', () => {
98
+ if (config.logDebug) this.emit('debug', 'MQTT Connection closed.');
99
+ });
53
100
  }
54
101
  }
55
102
 
56
- export default Mqtt;
103
+ export default Mqtt;