homebridge-melcloud-control 4.10.19 → 4.10.20-beta.1
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/package.json +2 -2
- package/src/functions.js +19 -0
- package/src/melcloudata.js +38 -18
- package/src/melcloudatw.js +10 -0
- package/src/melclouderv.js +41 -22
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.10.
|
|
4
|
+
"version": "4.10.20-beta.1",
|
|
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,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@homebridge/plugin-ui-utils": "^2.2.6",
|
|
39
|
-
"mqtt": "^5.
|
|
39
|
+
"mqtt": "^5.16.0",
|
|
40
40
|
"axios": "^1.20.0",
|
|
41
41
|
"axios-cookiejar-support": "^7.0.0",
|
|
42
42
|
"tough-cookie": "^6.0.2",
|
package/src/functions.js
CHANGED
|
@@ -77,6 +77,25 @@ class Functions extends EventEmitter {
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
//returns only the payload keys whose value differs from the matching PascalCase field in current device state
|
|
81
|
+
filterChanges(payload, current) {
|
|
82
|
+
if (!payload || typeof payload !== 'object' || !current) return { ...payload };
|
|
83
|
+
|
|
84
|
+
const changes = {};
|
|
85
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
86
|
+
if (value === undefined) continue;
|
|
87
|
+
const pascalKey = key.charAt(0).toUpperCase() + key.slice(1);
|
|
88
|
+
if (current[pascalKey] !== value) changes[key] = value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return changes;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//true if at least one key in payload differs from the matching PascalCase field in current device state
|
|
95
|
+
hasChanges(payload, current) {
|
|
96
|
+
return Object.keys(this.filterChanges(payload, current)).length > 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
toPascalCaseKeys(object) {
|
|
81
100
|
if (object !== null && typeof object === 'object') {
|
|
82
101
|
return Object.fromEntries(
|
package/src/melcloudata.js
CHANGED
|
@@ -207,15 +207,33 @@ class MelCloudAta extends EventEmitter {
|
|
|
207
207
|
payload = { data: payload.LoginData };
|
|
208
208
|
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
209
209
|
break;
|
|
210
|
-
default:
|
|
210
|
+
default: {
|
|
211
|
+
const changes = this.functions.filterChanges(payload, deviceData.Device);
|
|
212
|
+
if (Object.keys(changes).length === 0) {
|
|
213
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
211
217
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
212
|
-
|
|
218
|
+
changes.setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
|
-
|
|
221
|
+
//rebuild effective flags from only the fields that actually changed, Power always stays forced
|
|
222
|
+
const flagBits = {
|
|
223
|
+
operationMode: AirConditioner.EffectiveFlags.OperationMode,
|
|
224
|
+
setTemperature: AirConditioner.EffectiveFlags.SetTemperature,
|
|
225
|
+
setFanSpeed: AirConditioner.EffectiveFlags.SetFanSpeed,
|
|
226
|
+
vaneVerticalDirection: AirConditioner.EffectiveFlags.VaneVertical,
|
|
227
|
+
vaneHorizontalDirection: AirConditioner.EffectiveFlags.VaneHorizontal,
|
|
228
|
+
prohibitSetTemperature: AirConditioner.EffectiveFlags.Prohibit,
|
|
229
|
+
prohibitOperationMode: AirConditioner.EffectiveFlags.Prohibit,
|
|
230
|
+
prohibitPower: AirConditioner.EffectiveFlags.Prohibit,
|
|
231
|
+
};
|
|
232
|
+
flag = Object.keys(changes).reduce((acc, key) => acc | (flagBits[key] ?? 0), AirConditioner.EffectiveFlags.Power);
|
|
233
|
+
|
|
216
234
|
payload = this.functions.toPascalCaseKeys({
|
|
217
|
-
...
|
|
218
|
-
power:
|
|
235
|
+
...changes,
|
|
236
|
+
power: changes.power !== false,
|
|
219
237
|
deviceID: deviceData.Device.DeviceID,
|
|
220
238
|
effectiveFlags: flag,
|
|
221
239
|
hasPendingCommand: true,
|
|
@@ -224,6 +242,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
224
242
|
path = ApiUrls.Post.Ata;
|
|
225
243
|
update = true;
|
|
226
244
|
break;
|
|
245
|
+
}
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
|
|
@@ -288,9 +307,15 @@ class MelCloudAta extends EventEmitter {
|
|
|
288
307
|
if (scene) scene.Enabled = payload.enabled;
|
|
289
308
|
payload = {};
|
|
290
309
|
break;
|
|
291
|
-
default:
|
|
310
|
+
default: {
|
|
311
|
+
const changes = this.functions.filterChanges(payload, deviceData.Device);
|
|
312
|
+
if (Object.keys(changes).length === 0) {
|
|
313
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
292
317
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
293
|
-
|
|
318
|
+
changes.setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
|
|
294
319
|
|
|
295
320
|
if (this.deviceData.Device.DefaultCoolingSetTemperature !== deviceData.Device.DefaultCoolingSetTemperature || this.deviceData.Device.DefaultHeatingSetTemperature !== deviceData.Device.DefaultHeatingSetTemperature) {
|
|
296
321
|
const temps = {
|
|
@@ -301,22 +326,17 @@ class MelCloudAta extends EventEmitter {
|
|
|
301
326
|
}
|
|
302
327
|
}
|
|
303
328
|
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
306
|
-
if (
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
//cleanup undefined
|
|
310
|
-
Object.keys(payload).forEach(key => {
|
|
311
|
-
if (payload[key] === undefined) {
|
|
312
|
-
delete payload[key];
|
|
313
|
-
}
|
|
314
|
-
});
|
|
329
|
+
if (changes.setFanSpeed != null) changes.setFanSpeed = String(changes.setFanSpeed);
|
|
330
|
+
if (changes.operationMode != null) changes.operationMode = AirConditioner.OperationModeMapEnumToString[changes.operationMode];
|
|
331
|
+
if (changes.vaneHorizontalDirection != null) changes.vaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapEnumToString[changes.vaneHorizontalDirection];
|
|
332
|
+
if (changes.vaneVerticalDirection != null) changes.vaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapEnumToString[changes.vaneVerticalDirection];
|
|
315
333
|
|
|
334
|
+
payload = changes;
|
|
316
335
|
method = 'PUT';
|
|
317
336
|
path = ApiUrls.Home.Put.Ata.replace('deviceid', deviceData.DeviceID);
|
|
318
337
|
deviceData.Device = { ...deviceData.Device, ...payload };
|
|
319
338
|
break;
|
|
339
|
+
}
|
|
320
340
|
}
|
|
321
341
|
|
|
322
342
|
//send payload
|
package/src/melcloudatw.js
CHANGED
|
@@ -187,6 +187,11 @@ class MelCloudAtw extends EventEmitter {
|
|
|
187
187
|
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
188
188
|
break;
|
|
189
189
|
default:
|
|
190
|
+
if (!this.functions.hasChanges(payload, deviceData.Device)) {
|
|
191
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
190
195
|
flag = !flag ? HeatPump.EffectiveFlags.Power : HeatPump.EffectiveFlags.Power + flag;
|
|
191
196
|
payload = this.functions.toPascalCaseKeys({
|
|
192
197
|
...payload,
|
|
@@ -252,6 +257,11 @@ class MelCloudAtw extends EventEmitter {
|
|
|
252
257
|
payload = {};
|
|
253
258
|
break;
|
|
254
259
|
default:
|
|
260
|
+
if (!this.functions.hasChanges(payload, deviceData.Device)) {
|
|
261
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
255
265
|
if (payload.operationMode != null) payload.operationMode = HeatPump.OperationModeMapEnumToString[payload.operationMode];
|
|
256
266
|
if (payload.operationModeZone1 != null) payload.operationModeZone1 = HeatPump.OperationModeZoneMapEnumToString[payload.operationModeZone1];
|
|
257
267
|
if (payload.operationModeZone2 != null) payload.operationModeZone2 = HeatPump.OperationModeZoneMapEnumToString[payload.operationModeZone2];
|
package/src/melclouderv.js
CHANGED
|
@@ -182,32 +182,49 @@ class MelCloudErv extends EventEmitter {
|
|
|
182
182
|
payload = { data: payload.LoginData };
|
|
183
183
|
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
184
184
|
break;
|
|
185
|
-
default:
|
|
185
|
+
default: {
|
|
186
|
+
const changes = this.functions.filterChanges(payload, deviceData.Device);
|
|
187
|
+
if (Object.keys(changes).length === 0) {
|
|
188
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
//set target temp based on display mode and ventilation mode
|
|
187
193
|
switch (displayType) {
|
|
188
194
|
case 1: //Heather/Cooler
|
|
189
195
|
switch (deviceData.Device.VentilationMode) {
|
|
190
196
|
case 0: //LOSNAY
|
|
191
|
-
|
|
197
|
+
changes.setTemperature = deviceData.Device.DefaultHeatingSetTemperature;
|
|
192
198
|
break;
|
|
193
199
|
case 1: //BYPASS
|
|
194
|
-
|
|
200
|
+
changes.setTemperature = deviceData.Device.DefaultCoolingSetTemperature;
|
|
195
201
|
break;
|
|
196
202
|
case 2: //AUTO
|
|
197
203
|
const setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
|
|
198
|
-
|
|
204
|
+
changes.setTemperature = setTemperature;
|
|
199
205
|
break;
|
|
200
206
|
};
|
|
201
207
|
case 2: //Thermostat
|
|
202
|
-
|
|
208
|
+
changes.setTemperature = deviceData.Device.SetTemperature;
|
|
203
209
|
break;
|
|
204
210
|
};
|
|
205
211
|
|
|
206
|
-
//
|
|
207
|
-
|
|
212
|
+
//rebuild effective flags from only the fields that actually changed, Power always stays forced
|
|
213
|
+
const flagBits = {
|
|
214
|
+
operationMode: Ventilation.EffectiveFlags.OperationMode,
|
|
215
|
+
ventilationMode: Ventilation.EffectiveFlags.VentilationMode,
|
|
216
|
+
setFanSpeed: Ventilation.EffectiveFlags.SetFanSpeed,
|
|
217
|
+
setTemperature: Ventilation.EffectiveFlags.SetTemperature,
|
|
218
|
+
nightPurgeMode: Ventilation.EffectiveFlags.NightPurgeMode,
|
|
219
|
+
prohibitSetTemperature: Ventilation.EffectiveFlags.Prohibit,
|
|
220
|
+
prohibitOperationMode: Ventilation.EffectiveFlags.Prohibit,
|
|
221
|
+
prohibitPower: Ventilation.EffectiveFlags.Prohibit,
|
|
222
|
+
};
|
|
223
|
+
flag = Object.keys(changes).reduce((acc, key) => acc | (flagBits[key] ?? 0), Ventilation.EffectiveFlags.Power);
|
|
224
|
+
|
|
208
225
|
payload = this.functions.toPascalCaseKeys({
|
|
209
|
-
...
|
|
210
|
-
power:
|
|
226
|
+
...changes,
|
|
227
|
+
power: changes.power !== false,
|
|
211
228
|
deviceID: deviceData.Device.DeviceID,
|
|
212
229
|
effectiveFlags: flag,
|
|
213
230
|
hasPendingCommand: true,
|
|
@@ -215,6 +232,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
215
232
|
path = ApiUrls.Post.Erv;
|
|
216
233
|
update = true;
|
|
217
234
|
break;
|
|
235
|
+
}
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
|
|
@@ -254,9 +272,15 @@ class MelCloudErv extends EventEmitter {
|
|
|
254
272
|
if (scene) scene.Enabled = payload.enabled;
|
|
255
273
|
payload = {};
|
|
256
274
|
break;
|
|
257
|
-
default:
|
|
275
|
+
default: {
|
|
276
|
+
const changes = this.functions.filterChanges(payload, deviceData.Device);
|
|
277
|
+
if (Object.keys(changes).length === 0) {
|
|
278
|
+
if (this.logDebug) this.emit('debug', `Ignore send, no changes: ${JSON.stringify(payload)}`);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
258
282
|
if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
|
|
259
|
-
|
|
283
|
+
changes.setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
|
|
260
284
|
|
|
261
285
|
if (this.deviceData.Device.DefaultCoolingSetTemperature !== deviceData.Device.DefaultCoolingSetTemperature || this.deviceData.Device.DefaultHeatingSetTemperature !== deviceData.Device.DefaultHeatingSetTemperature) {
|
|
262
286
|
const temps = {
|
|
@@ -267,21 +291,16 @@ class MelCloudErv extends EventEmitter {
|
|
|
267
291
|
}
|
|
268
292
|
}
|
|
269
293
|
|
|
270
|
-
if (
|
|
271
|
-
if (
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
//cleanup undefined
|
|
275
|
-
Object.keys(payload).forEach(key => {
|
|
276
|
-
if (payload[key] === undefined) {
|
|
277
|
-
delete payload[key];
|
|
278
|
-
}
|
|
279
|
-
});
|
|
294
|
+
if (changes.setFanSpeed != null) changes.setFanSpeed = String(changes.setFanSpeed);
|
|
295
|
+
if (changes.operationMode != null) changes.operationMode = Ventilation.OperationModeMapEnumToString[changes.operationMode];
|
|
296
|
+
if (changes.ventilationMode != null) changes.ventilationMode = Ventilation.VentilationModeMapEnumToString[changes.ventilationMode];
|
|
280
297
|
|
|
298
|
+
payload = changes;
|
|
281
299
|
method = 'PUT';
|
|
282
300
|
path = ApiUrls.Home.Put.Erv.replace('deviceid', deviceData.DeviceID);
|
|
283
301
|
deviceData.Device = { ...deviceData.Device, ...payload };
|
|
284
|
-
break
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
285
304
|
}
|
|
286
305
|
|
|
287
306
|
if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
|