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 +18 -0
- package/package.json +3 -3
- package/src/deviceata.js +234 -105
- package/src/deviceatw.js +145 -78
- package/src/deviceerv.js +56 -61
- package/src/functions.js +49 -7
- package/src/melcloudata.js +6 -4
- package/src/melcloudhome.js +4 -5
- package/src/mqtt.js +80 -33
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.
|
|
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
|
-
"
|
|
39
|
+
"mqtt": "^5.14.1",
|
|
40
40
|
"axios": "^1.13.2",
|
|
41
41
|
"express": "^5.2.1",
|
|
42
|
-
"puppeteer": "^24.32.
|
|
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
|
-
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
this.
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
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
|
-
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
|
|
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.
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
1378
|
-
const
|
|
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
|
-
|
|
1474
|
-
|
|
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 &&
|
|
1649
|
-
this.frostProtectionControlService?.updateCharacteristic(Characteristic.
|
|
1650
|
-
this.
|
|
1651
|
-
this.
|
|
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 &&
|
|
1656
|
-
this.overheatProtectionControlService?.updateCharacteristic(Characteristic.
|
|
1657
|
-
this.
|
|
1658
|
-
this.
|
|
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
|
-
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
this.
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
|
|
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
|
-
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.
|
|
186
|
-
|
|
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.
|
|
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
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
2128
|
-
this.frostProtectionControlService?.updateCharacteristic(Characteristic.
|
|
2129
|
-
this.
|
|
2130
|
-
this.
|
|
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
|
-
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
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
|
-
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
.
|
|
178
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
package/src/melcloudata.js
CHANGED
|
@@ -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:
|
|
246
|
-
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:
|
|
256
|
-
max:
|
|
257
|
+
min: overMin,
|
|
258
|
+
max: overMax,
|
|
257
259
|
units: { ATA: [deviceData.DeviceID] }
|
|
258
260
|
};
|
|
259
261
|
method = 'POST';
|
package/src/melcloudhome.js
CHANGED
|
@@ -228,7 +228,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
228
228
|
// Get Chromium path
|
|
229
229
|
let chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
230
230
|
|
|
231
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
const result = await this.mqttClient.subscribeAsync(subscribeTopic, {
|
|
33
|
+
qos: 1,
|
|
34
|
+
properties: {
|
|
35
|
+
userProperties: {
|
|
36
|
+
type: 'subscription'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
21
40
|
|
|
22
|
-
//
|
|
23
|
-
|
|
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
|
|
40
|
-
}
|
|
41
|
-
})
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
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;
|