homebridge-melcloud-control 4.4.1-beta.9 → 4.5.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/CHANGELOG.md +11 -0
- package/README.md +26 -26
- package/homebridge-ui/public/index.html +147 -134
- package/index.js +20 -5
- package/package.json +2 -2
- package/src/constants.js +10 -10
- package/src/deviceata.js +24 -13
- package/src/deviceatw.js +5 -5
- package/src/deviceerv.js +60 -55
- package/src/functions.js +105 -111
- package/src/melcloud.js +6 -4
- package/src/melcloudata.js +32 -1
- package/src/melcloudatw.js +17 -1
- package/src/melclouderv.js +17 -1
- package/src/melcloudhome.js +32 -26
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,17 @@ 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.5.0] - (19.12.2025)
|
|
26
|
+
|
|
27
|
+
## Changes
|
|
28
|
+
|
|
29
|
+
- stability and performance improvements
|
|
30
|
+
- remove oldnot existed presets, schedules, scenes fom config during (Connect To Melcloud)
|
|
31
|
+
- bump dependencies
|
|
32
|
+
- config schema updated
|
|
33
|
+
- redme updated
|
|
34
|
+
- cleanup
|
|
35
|
+
|
|
25
36
|
# [4.4.0] - (10.12.2025)
|
|
26
37
|
|
|
27
38
|
## Changes
|
package/README.md
CHANGED
|
@@ -22,24 +22,25 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
22
22
|
| Package | Installation | Role | Required |
|
|
23
23
|
| --- | --- | --- | --- |
|
|
24
24
|
| [Homebridge v2.0.0](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required |
|
|
25
|
-
| [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge
|
|
26
|
-
| [MELCloud](https://github.com/grzegorz914/homebridge-melcloud-control) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-melcloud-control/wiki) |
|
|
25
|
+
| [Homebridge UI <= v5.5.0](https://github.com/homebridge/homebridge-config-ui-x) | [Homebridge UI Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge User Interface | Required |
|
|
26
|
+
| [MELCloud](https://github.com/grzegorz914/homebridge-melcloud-control) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-melcloud-control/wiki) | Plug-In | Required |
|
|
27
27
|
|
|
28
28
|
### About The Plugin
|
|
29
29
|
|
|
30
|
-
* Support
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
30
|
+
* Support:
|
|
31
|
+
* Devices connected to MELCloud or MELCloud Home.
|
|
32
|
+
* Multiple accounts, buildings, floors, areas.
|
|
33
|
+
* Temperature display units `Celsius/Fahrenheit`.
|
|
34
|
+
* Assing individual operating mode for `Heat/Cool/Auto`.
|
|
35
|
+
* Presets, only MELCloud.
|
|
36
|
+
* Scenes, only MELCloud Home.
|
|
37
|
+
* Frost protection, only MELCloud Home.
|
|
38
|
+
* Overheat Protection, only MELCloud Home.
|
|
39
|
+
* Holiday Mode, only MELCloud Home.
|
|
40
|
+
* Physical lock controls `LOCK/UNLOCK`, only MELCloud.
|
|
41
|
+
* Functions, using extra `Buttons`, switch to `OFF` restore previous state.
|
|
42
|
+
* Automations, shortcuts and Siri.
|
|
43
|
+
* External integrations, [RESTFul](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-melcloud-control?tab=readme-ov-file#mqtt-integration).
|
|
43
44
|
* Control devices over local network You need use ESP module and [Tasmota Control](https://github.com/grzegorz914/homebridge-tasmota-control) plugin.
|
|
44
45
|
|
|
45
46
|
### Control Mode
|
|
@@ -58,18 +59,17 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
58
59
|
* Operating mode `POWER OFF/HEAT/COOL/AUTO`.
|
|
59
60
|
* Temperature `HEATING/COOLING/AUTO`.
|
|
60
61
|
* Temperature display unit `°F/°C`.
|
|
61
|
-
* Assign operating mode for `HEAT/AUTO`
|
|
62
62
|
* Buttons:
|
|
63
63
|
* For direct device control.
|
|
64
64
|
* Power `ON/OFF`.
|
|
65
65
|
* Operating mode `HEAT/DRY/COOL/FAN/AUTO`.
|
|
66
|
-
* Physical lock controls `LOCK/UNLOCK
|
|
66
|
+
* Physical lock controls `LOCK/UNLOCK`
|
|
67
67
|
* Vane H mode `AUTO/1/2/3/4/5/SPLIT/SWING`.
|
|
68
68
|
* Vane V mode `AUTO/1/2/3/4/5/SWING`.
|
|
69
69
|
* Fan speed mode `AUTO/1/2/3/4/5`.
|
|
70
|
-
*
|
|
71
|
-
* Frost protection `ON/OFF`.
|
|
72
|
-
* Overheat protection `ON/OFF`.
|
|
70
|
+
* Preset `SET/UNSET`
|
|
71
|
+
* Frost protection `ON/OFF/MINTEMP/MAXTEMP`.
|
|
72
|
+
* Overheat protection `ON/OFF/MINTEMP/MAXTEMP`.
|
|
73
73
|
* Holiday mode `ON/OFF`.
|
|
74
74
|
* Schedules `ON/OFF`.
|
|
75
75
|
* Scene `ON/OFF`.
|
|
@@ -81,7 +81,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
81
81
|
* Vane H mode `AUTO/1/2/3/4/5/SPLIT/SWING`.
|
|
82
82
|
* Vane V mode `AUTO/1/2/3/4/5/SWING`.
|
|
83
83
|
* Fan speed mode `AUTO/1/2/3/4/5/`.
|
|
84
|
-
*
|
|
84
|
+
* Preset `ACTIV/UNACTIV`.
|
|
85
85
|
* Room temperature.
|
|
86
86
|
* Outdoor temperature.
|
|
87
87
|
* Frost protection.
|
|
@@ -128,8 +128,8 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
128
128
|
* Power `ON/OFF`.
|
|
129
129
|
* Operating mode `HEAT/COOL/CURVE/HOLIDAY/AUTO HOT WATER/ECO HOT WATER/FORCE HOT WATER`.
|
|
130
130
|
* Physical lock controls `LOCK/UNLOCK`.
|
|
131
|
-
*
|
|
132
|
-
* Frost protection `ON/OFF`.
|
|
131
|
+
* Preset `SET/UNSET`.
|
|
132
|
+
* Frost protection `ON/OFF/MINTEMP/MAXTEMP`.
|
|
133
133
|
* Holiday mode `ON/OFF`.
|
|
134
134
|
* Schedules `ON/OFF`.
|
|
135
135
|
* Scene `ON/OFF`.
|
|
@@ -138,7 +138,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
138
138
|
* Power `ON/OFF`.
|
|
139
139
|
* Operating mode `HEAT/COOL/CURVE/HOLIDAY/AUTO HOT WATER/ECO HOT WATER/FORCE HOT WATER`.
|
|
140
140
|
* Physical lock controls `LOCK/UNLOCK`.
|
|
141
|
-
*
|
|
141
|
+
* Preset `ACTIV/UNACTIV`.
|
|
142
142
|
* Outdoor temperature.
|
|
143
143
|
* Zone 1 temperature.
|
|
144
144
|
* Zone 2 temperature.
|
|
@@ -168,7 +168,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
168
168
|
* Power `ON/OFF`.
|
|
169
169
|
* Operating mode `LOSSNAY/BYPASS/AUTO/NIGHT PURGE`.
|
|
170
170
|
* Fan speed mode `AUTO/1/2/3/4`.
|
|
171
|
-
*
|
|
171
|
+
* Preset `SET/UNSET`.
|
|
172
172
|
* Holiday mode `ON/OFF`.
|
|
173
173
|
* Schedules `ON/OFF`.
|
|
174
174
|
* Scene `ON/OFF`.
|
|
@@ -210,7 +210,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
210
210
|
* Target temperature is calculated as a middle value between `LO` and `HI` and the rest is calculated internally.
|
|
211
211
|
* Thermostat
|
|
212
212
|
* In this mode we can set only target temperature:
|
|
213
|
-
* Target temperature
|
|
213
|
+
* Target temperature is send to device and calculated internally:
|
|
214
214
|
* Calculation method in device internally:
|
|
215
215
|
* If the room temperature `<` Heating Setpoint, the unit will be set to HEAT with a setpoint of 23°C.
|
|
216
216
|
* In HEAT, if the room temperature `>` Heating Setpoint `+` 1°C, the unit will be set to FAN.
|
|
@@ -204,21 +204,7 @@
|
|
|
204
204
|
}
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
//
|
|
208
|
-
function removeStaleDevices(configDevices, melcloudDevices) {
|
|
209
|
-
const melcloudIds = melcloudDevices.map(d => d.DeviceID);
|
|
210
|
-
const removedDevices = [];
|
|
211
|
-
|
|
212
|
-
for (let i = configDevices.length - 1; i >= 0; i--) {
|
|
213
|
-
const device = configDevices[i];
|
|
214
|
-
if (!melcloudIds.includes(device.id)) {
|
|
215
|
-
removedDevices.push(device);
|
|
216
|
-
configDevices.splice(i, 1);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return removedDevices;
|
|
220
|
-
}
|
|
221
|
-
|
|
207
|
+
// Update info on page
|
|
222
208
|
function updateInfo(id, text, color) {
|
|
223
209
|
const el = document.getElementById(id);
|
|
224
210
|
if (el) {
|
|
@@ -227,10 +213,44 @@
|
|
|
227
213
|
}
|
|
228
214
|
}
|
|
229
215
|
|
|
216
|
+
// Generic remove function
|
|
217
|
+
function removeStaleEntities(configEntities, melcloudEntities, getConfigId, getMelcloudId) {
|
|
218
|
+
const serverIds = new Set((melcloudEntities ?? []).map(item => String(getMelcloudId(item))));
|
|
219
|
+
const removedEntities = [];
|
|
220
|
+
|
|
221
|
+
for (let i = configEntities.length - 1; i >= 0; i--) {
|
|
222
|
+
const entity = configEntities[i];
|
|
223
|
+
const entityId = String(getConfigId(entity));
|
|
224
|
+
|
|
225
|
+
if (!serverIds.has(entityId)) {
|
|
226
|
+
removedEntities.push(entity);
|
|
227
|
+
configEntities.splice(i, 1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return removedEntities;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Map UnitId → Scenes for quick lookup
|
|
235
|
+
function mapUnitIdToScenes(scenesInMelCloud) {
|
|
236
|
+
const map = new Map();
|
|
237
|
+
|
|
238
|
+
scenesInMelCloud.forEach(scene => {
|
|
239
|
+
const allSceneSettings = [...(scene.AtaSceneSettings ?? []), ...(scene.AtwSceneSettings ?? []), ...(scene.ErvSceneSettings ?? [])];
|
|
240
|
+
allSceneSettings.forEach(setting => {
|
|
241
|
+
const unitId = String(setting.UnitId);
|
|
242
|
+
if (!map.has(unitId)) map.set(unitId, []);
|
|
243
|
+
map.get(unitId).push(scene);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return map;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Login & Sync Logic
|
|
230
251
|
document.getElementById('logIn').addEventListener('click', async () => {
|
|
231
252
|
homebridge.showSpinner();
|
|
232
|
-
|
|
233
|
-
document.getElementById(`logIn`).className = "btn btn-primary";
|
|
253
|
+
document.getElementById('logIn').className = "btn btn-primary";
|
|
234
254
|
updateInfo('info', '', 'white');
|
|
235
255
|
updateInfo('info1', '', 'white');
|
|
236
256
|
updateInfo('info2', '', 'white');
|
|
@@ -238,161 +258,154 @@
|
|
|
238
258
|
try {
|
|
239
259
|
const account = this.account;
|
|
240
260
|
const response = await homebridge.request('/connect', account);
|
|
261
|
+
|
|
241
262
|
if (!response.State) {
|
|
242
263
|
homebridge.hideSpinner();
|
|
243
|
-
updateInfo('info', response.Info);
|
|
264
|
+
updateInfo('info', response.Info, 'red');
|
|
244
265
|
return;
|
|
245
266
|
}
|
|
246
267
|
|
|
247
|
-
//
|
|
248
|
-
const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [],
|
|
268
|
+
// Prepare MELCloud data
|
|
269
|
+
const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], ataScenes: [], atw: [], atwPresets: [], atwSchedules: [], atwScenes: [], erv: [], ervPresets: [], ervSchedules: [], ervScenes: [] };
|
|
249
270
|
const devicesInMelCloudByType = { ata: [], atw: [], erv: [] };
|
|
250
|
-
const scenesInMelCloud = response.Scenes ?? []
|
|
271
|
+
const scenesInMelCloud = response.Scenes ?? [];
|
|
251
272
|
|
|
273
|
+
// Split devices by type
|
|
252
274
|
response.Devices.forEach(device => {
|
|
253
275
|
if (device.Type === 0) devicesInMelCloudByType.ata.push(device);
|
|
254
276
|
if (device.Type === 1) devicesInMelCloudByType.atw.push(device);
|
|
255
277
|
if (device.Type === 3) devicesInMelCloudByType.erv.push(device);
|
|
256
278
|
});
|
|
257
279
|
|
|
280
|
+
// Clean up local config
|
|
258
281
|
account.ataDevices = (account.ataDevices ?? []).filter(d => String(d.id) !== '0');
|
|
259
282
|
account.atwDevices = (account.atwDevices ?? []).filter(d => String(d.id) !== '0');
|
|
260
283
|
account.ervDevices = (account.ervDevices ?? []).filter(d => String(d.id) !== '0');
|
|
261
284
|
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
285
|
+
const removedFromConfigAta = removeStaleEntities(account.ataDevices, devicesInMelCloudByType.ata, d => d.id, d => d.DeviceID);
|
|
286
|
+
const removedFromConfigAtw = removeStaleEntities(account.atwDevices, devicesInMelCloudByType.atw, d => d.id, d => d.DeviceID);
|
|
287
|
+
const removedFromConfigErv = removeStaleEntities(account.ervDevices, devicesInMelCloudByType.erv, d => d.id, d => d.DeviceID);
|
|
288
|
+
const removedFromConfig = { presets: [], schedules: [], scenes: [] };
|
|
289
|
+
|
|
290
|
+
// Map UnitId → Scenes
|
|
291
|
+
const unitIdToScenes = mapUnitIdToScenes(scenesInMelCloud);
|
|
265
292
|
|
|
293
|
+
// Generic device handler (obsługuje urządzenia, presety, harmonogramy i sceny)
|
|
266
294
|
const handleDevices = (devicesInMelCloud, devicesInConfig, deviceTypeString, newDevices, newPresets, newSchedules, newScenes) => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
displayType: 0,
|
|
345
|
-
name: scene.Name || `Scene ${index}`,
|
|
346
|
-
namePrefix: false
|
|
347
|
-
};
|
|
348
|
-
scenesInConfig.push(sceneObj);
|
|
349
|
-
newScenes.push(sceneObj);
|
|
350
|
-
sceneIds.add(sceneId);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Return filtered devicesInConfig to make sure upstream code uses it
|
|
357
|
-
return devicesInConfig;
|
|
358
|
-
} catch (error) {
|
|
359
|
-
updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
|
|
360
|
-
}
|
|
295
|
+
const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
|
|
296
|
+
|
|
297
|
+
devicesInMelCloud.forEach(device => {
|
|
298
|
+
const deviceId = String(device.DeviceID);
|
|
299
|
+
let deviceInConfig = configDevicesMap.get(deviceId);
|
|
300
|
+
|
|
301
|
+
if (!deviceInConfig) {
|
|
302
|
+
deviceInConfig = { id: deviceId, type: device.Type, deviceTypeString, displayType: 0, name: device.DeviceName };
|
|
303
|
+
devicesInConfig.push(deviceInConfig);
|
|
304
|
+
newDevices.push(deviceInConfig);
|
|
305
|
+
configDevicesMap.set(deviceId, deviceInConfig);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// PRESETS (melcloud)
|
|
309
|
+
if (account.type === 'melcloud') {
|
|
310
|
+
deviceInConfig.presets = (deviceInConfig.presets ?? []).filter(p => String(p.id) !== '0');
|
|
311
|
+
const presetsInMelCloud = device.Presets ?? [];
|
|
312
|
+
removedFromConfig.presets.push(...removeStaleEntities(deviceInConfig.presets, presetsInMelCloud, p => p.id, p => p.ID));
|
|
313
|
+
const presetIds = new Set(deviceInConfig.presets.map(p => String(p.id)));
|
|
314
|
+
presetsInMelCloud.forEach((preset, index) => {
|
|
315
|
+
const presetId = String(preset.ID);
|
|
316
|
+
if (!presetIds.has(presetId)) {
|
|
317
|
+
const presetObj = {
|
|
318
|
+
id: presetId,
|
|
319
|
+
displayType: 0,
|
|
320
|
+
name: preset.NumberDescription || `Preset ${index}`,
|
|
321
|
+
namePrefix: false
|
|
322
|
+
};
|
|
323
|
+
deviceInConfig.presets.push(presetObj);
|
|
324
|
+
newPresets.push(presetObj);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// SCHEDULES & SCENES (melcloudhome)
|
|
330
|
+
if (account.type === 'melcloudhome') {
|
|
331
|
+
// SCHEDULES
|
|
332
|
+
deviceInConfig.schedules = (deviceInConfig.schedules ?? []).filter(s => String(s.id) !== '0');
|
|
333
|
+
const schedulesInMelCloud = device.Schedule ?? [];
|
|
334
|
+
removedFromConfig.schedules.push(...removeStaleEntities(deviceInConfig.schedules, schedulesInMelCloud, s => s.id, s => s.Id));
|
|
335
|
+
const scheduleIds = new Set(deviceInConfig.schedules.map(s => String(s.id)));
|
|
336
|
+
schedulesInMelCloud.forEach((schedule, index) => {
|
|
337
|
+
const scheduleId = String(schedule.Id);
|
|
338
|
+
if (!scheduleIds.has(scheduleId)) {
|
|
339
|
+
const scheduleObj = {
|
|
340
|
+
id: scheduleId,
|
|
341
|
+
displayType: 0,
|
|
342
|
+
name: `Schedule ${index}`,
|
|
343
|
+
namePrefix: false
|
|
344
|
+
};
|
|
345
|
+
deviceInConfig.schedules.push(scheduleObj);
|
|
346
|
+
newSchedules.push(scheduleObj);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// SCENES
|
|
351
|
+
deviceInConfig.scenes = (deviceInConfig.scenes ?? []).filter(s => String(s.id) !== '0');
|
|
352
|
+
const scenesForDevice = unitIdToScenes.get(deviceId) ?? [];
|
|
353
|
+
removedFromConfig.scenes.push(...removeStaleEntities(deviceInConfig.scenes, scenesForDevice, s => s.id, s => s.Id));
|
|
354
|
+
const sceneIds = new Set(deviceInConfig.scenes.map(s => String(s.id)));
|
|
355
|
+
scenesForDevice.forEach((scene, index) => {
|
|
356
|
+
const sceneId = String(scene.Id);
|
|
357
|
+
if (!sceneIds.has(sceneId)) {
|
|
358
|
+
const sceneObj = {
|
|
359
|
+
id: sceneId,
|
|
360
|
+
displayType: 0,
|
|
361
|
+
name: scene.Name || `Scene ${index}`,
|
|
362
|
+
namePrefix: false
|
|
363
|
+
};
|
|
364
|
+
deviceInConfig.scenes.push(sceneObj);
|
|
365
|
+
newScenes.push(sceneObj);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return devicesInConfig;
|
|
361
372
|
};
|
|
362
373
|
|
|
363
|
-
|
|
364
|
-
account.
|
|
365
|
-
account.
|
|
374
|
+
// Execute device handlers
|
|
375
|
+
account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.ataScenes);
|
|
376
|
+
account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.atwScenes);
|
|
377
|
+
account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.ervScenes);
|
|
366
378
|
|
|
379
|
+
// Summary counts
|
|
367
380
|
const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
|
|
368
381
|
const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
|
|
369
382
|
const newSchedulesCount = newInMelCloud.ataSchedules.length + newInMelCloud.atwSchedules.length + newInMelCloud.ervSchedules.length;
|
|
370
|
-
const newScenesCount = newInMelCloud.
|
|
371
|
-
const removedDevicesCount =
|
|
383
|
+
const newScenesCount = newInMelCloud.ataScenes.length + newInMelCloud.atwScenes.length + newInMelCloud.ervScenes.length;
|
|
384
|
+
const removedDevicesCount = removedFromConfigAta.length + removedFromConfigAtw.length + removedFromConfigErv.length;
|
|
385
|
+
const removedPresetsCount = removedFromConfig.presets.length;
|
|
386
|
+
const removedSchedulesCount = removedFromConfig.schedules.length;
|
|
387
|
+
const removedScenesCount = removedFromConfig.scenes.length;
|
|
372
388
|
|
|
373
|
-
if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
|
|
389
|
+
if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount && !removedPresetsCount && !removedSchedulesCount && !removedScenesCount) {
|
|
374
390
|
updateInfo('info', 'No changes detected.', 'white');
|
|
375
391
|
} else {
|
|
376
|
-
if (newDevicesCount)
|
|
377
|
-
updateInfo('info', `Found new devices: ${newInMelCloud.ata.length ? `ATA: ${newInMelCloud.ata.length}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (removedDevicesCount)
|
|
383
|
-
updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
|
|
392
|
+
if (newDevicesCount || newPresetsCount || newSchedulesCount || newScenesCount) {
|
|
393
|
+
updateInfo('info', `Found new ${newDevicesCount ? `devices:` : ''} ${newInMelCloud.ata.length ? `ATA: ${newInMelCloud.ata.length}` : ''}${newInMelCloud.atw.length ? `, ATW: ${newInMelCloud.atw.length}` : ''}${newInMelCloud.erv.length ? `, ERV: ${newInMelCloud.erv.length},` : ''}${newPresetsCount ? `, presets:` : ''} ${newInMelCloud.ataPresets.length ? `ATA: ${newInMelCloud.ataPresets.length}` : ''}${newInMelCloud.atwPresets.length ? `, ATW: ${newInMelCloud.atwPresets.length}` : ''}${newInMelCloud.ervPresets.length ? `, ERV: ${newInMelCloud.ervPresets.length}` : ''}${newSchedulesCount ? `, schedules:` : ''} ${newInMelCloud.ataSchedules.length ? `ATA: ${newInMelCloud.ataSchedules.length}` : ''}${newInMelCloud.atwSchedules.length ? `, ATW: ${newInMelCloud.atwSchedules.length}` : ''}${newInMelCloud.ervSchedules.length ? `, ERV: ${newInMelCloud.ervSchedules.length}` : ''}${newScenesCount ? `, scenes:` : ''} ${newInMelCloud.ataScenes.length ? `,ATA: ${newInMelCloud.ataScenes.length}` : ''}${newInMelCloud.atwScenes.length ? `, ATW: ${newInMelCloud.atwScenes.length}` : ''}${newInMelCloud.ervScenes.length ? `, ERV: ${newInMelCloud.ervScenes.length}` : ''}.`, 'green');
|
|
394
|
+
}
|
|
395
|
+
if (removedDevicesCount || removedPresetsCount || removedSchedulesCount || removedScenesCount) {
|
|
396
|
+
updateInfo('info1', `Removed old ${removedDevicesCount ? `devices:` : ''} ${removedFromConfigAta.length ? `ATA: ${removedFromConfigAta.length}` : ''}${removedFromConfigAtw.length ? `, ATW: ${removedFromConfigAtw.length}` : ''}${removedFromConfigErv.length ? `, ERV: ${removedFromConfigErv.length}` : ''}${removedPresetsCount ? `, presets: ${removedPresetsCount}` : ''}${removedSchedulesCount ? `, schedules: ${removedSchedulesCount}` : ''}${removedScenesCount ? `, scenes: ${removedScenesCount}` : ''}.`, 'orange');
|
|
397
|
+
}
|
|
384
398
|
}
|
|
385
399
|
|
|
386
400
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
387
401
|
await homebridge.savePluginConfig(pluginConfig);
|
|
402
|
+
|
|
388
403
|
} catch (error) {
|
|
389
404
|
updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
|
|
390
|
-
document.getElementById('logIn').className = "btn btn-secondary";
|
|
391
405
|
} finally {
|
|
392
406
|
document.getElementById('logIn').className = "btn btn-secondary";
|
|
393
407
|
homebridge.hideSpinner();
|
|
394
408
|
}
|
|
395
409
|
});
|
|
396
|
-
|
|
397
410
|
})();
|
|
398
411
|
</script>
|
package/index.js
CHANGED
|
@@ -137,12 +137,27 @@ class MelCloudPlatform {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
//chack device from config exist on melcloud
|
|
140
|
-
const
|
|
141
|
-
if (!
|
|
140
|
+
const deviceInMelCloud = melcloudDevicesList.Devices.find(d => d.DeviceID === device.id);
|
|
141
|
+
if (!deviceInMelCloud) {
|
|
142
142
|
if (logLevel.warn) log.warn(`${name}, ${deviceTypeString}, ${deviceName}, not exist on server, please login to MELCLoud from plugin UI to fix this issue.`);
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
//presets
|
|
147
|
+
const presetIds = (deviceInMelCloud.Presets ?? []).map(p => String(p.ID));
|
|
148
|
+
const presets = account.type === 'melcloud' ? (device.presets || []).filter(p => (p.displayType ?? 0) > 0 && p.id !== '0' && presetIds.includes(p.id)) : [];
|
|
149
|
+
|
|
150
|
+
//schedules
|
|
151
|
+
const schedulesIds = (deviceInMelCloud.Schedule ?? []).map(s => String(s.Id));
|
|
152
|
+
const schedules = account.type === 'melcloudhome' ? (device.schedules || []).filter(s => (s.displayType ?? 0) > 0 && s.id !== '0' && schedulesIds.includes(s.id)) : [];
|
|
153
|
+
|
|
154
|
+
//scenes
|
|
155
|
+
const scenesIds = (melcloudDevicesList.Scenes ?? []).map(s => String(s.Id));
|
|
156
|
+
const scenes = account.type === 'melcloudhome' ? (device.scenes || []).filter(s => (s.displayType ?? 0) > 0 && s.id !== '0' && scenesIds.includes(s.id)) : [];
|
|
157
|
+
|
|
158
|
+
//buttons
|
|
159
|
+
const buttons = (device.buttonsSensors || []).filter(b => (b.displayType ?? 0) > 0);
|
|
160
|
+
|
|
146
161
|
// set rest ful port
|
|
147
162
|
account.restFul.port = (device.id).slice(-4).replace(/^0/, '9');
|
|
148
163
|
|
|
@@ -168,15 +183,15 @@ class MelCloudPlatform {
|
|
|
168
183
|
let configuredDevice;
|
|
169
184
|
switch (deviceType) {
|
|
170
185
|
case 0: //ATA
|
|
171
|
-
configuredDevice = new DeviceAta(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
186
|
+
configuredDevice = new DeviceAta(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
172
187
|
break;
|
|
173
188
|
case 1: //ATW
|
|
174
|
-
configuredDevice = new DeviceAtw(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
189
|
+
configuredDevice = new DeviceAtw(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
175
190
|
break;
|
|
176
191
|
case 2:
|
|
177
192
|
break;
|
|
178
193
|
case 3: //ERV
|
|
179
|
-
configuredDevice = new DeviceErv(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
194
|
+
configuredDevice = new DeviceErv(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
|
|
180
195
|
break;
|
|
181
196
|
default:
|
|
182
197
|
if (logLevel.warn) log.warn(`${name}, ${deviceTypeString}, ${deviceName}, unknown device: ${deviceType}.`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.5.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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"mqtt": "^5.14.1",
|
|
40
40
|
"axios": "^1.13.2",
|
|
41
41
|
"express": "^5.2.1",
|
|
42
|
-
"puppeteer": "^24.
|
|
42
|
+
"puppeteer": "^24.34.0",
|
|
43
43
|
"ws": "^8.18.3"
|
|
44
44
|
},
|
|
45
45
|
"keywords": [
|
package/src/constants.js
CHANGED
|
@@ -17,7 +17,7 @@ export const ApiUrls = {
|
|
|
17
17
|
Atw: "/Device/SetAtw",
|
|
18
18
|
Erv: "/Device/SetErv",
|
|
19
19
|
UpdateApplicationOptions: "/User/UpdateApplicationOptions",
|
|
20
|
-
|
|
20
|
+
HolidayMode: "/HolidayMode/Update",
|
|
21
21
|
EnergyCostReport: "/EnergyCost/Report",
|
|
22
22
|
},
|
|
23
23
|
Home: {
|
|
@@ -25,21 +25,21 @@ export const ApiUrls = {
|
|
|
25
25
|
WebSocket: "wss://ws.melcloudhome.com/?hash=",
|
|
26
26
|
Get: {
|
|
27
27
|
Configuration: "/api/configuration",
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
ListDevices: "/api/user/context",
|
|
29
|
+
Scenes: "/api/user/scenes",
|
|
30
30
|
},
|
|
31
31
|
Post: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Scene: "/api/scene", //
|
|
32
|
+
ProtectionFrost: "/api/protection/frost", //{"enabled":true,"min":13,"max":16,"units":{"ATA":["deviceid"]}}
|
|
33
|
+
ProtectionOverheat: "/api/protection/overheat", //{"enabled":true,"min":32,"max":35,"units":{"ATA":["deviceid"]}}
|
|
34
|
+
HolidayMode: "/api/holidaymode", //{"enabled":true,"startDate":"2025-11-11T17:42:24.913","endDate":"2026-06-01T09:18:00","units":{"ATA":["deviceid"]}}
|
|
35
|
+
Schedule: "/api/cloudschedule/deviceid", //{"days":[2],"time":"17:59:00","enabled":true,"id":"scheduleid","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
|
|
36
|
+
Scene: "/api/scene", //{"id": "sceneid", "userId": "userid","name": "Poza domem","enabled": false,"icon": "AwayIcon","ataSceneSettings": [{"unitId": "deviceid","ataSettings": { "power": false, "operationMode": "heat","setFanSpeed": "auto","vaneHorizontalDirection": "auto", "vaneVerticalDirection": "auto", "setTemperature": 21,"temperatureIncrementOverride": null,"inStandbyMode": null},"previousSettings": null}],"atwSceneSettings": []}
|
|
37
37
|
},
|
|
38
38
|
Put: {
|
|
39
|
-
Ata: "/api/ataunit/deviceid",
|
|
39
|
+
Ata: "/api/ataunit/deviceid", //{ power: true,setTemperature: 22, setFanSpeed: "auto", operationMode: "heat", vaneHorizontalDirection: "auto",vaneVerticalDirection: "auto", temperatureIncrementOverride: null, inStandbyMode: null}
|
|
40
40
|
Atw: "/api/atwunit/deviceid",
|
|
41
41
|
Erv: "/api/ervunit/deviceid",
|
|
42
|
-
ScheduleEnableDisable: "/api/cloudschedule/deviceid/enabled", //
|
|
42
|
+
ScheduleEnableDisable: "/api/cloudschedule/deviceid/enabled", // {"enabled": true}
|
|
43
43
|
SceneEnableDisable: "/api/scene/sceneid/enabledisable",
|
|
44
44
|
},
|
|
45
45
|
Delete: {
|