homebridge-melcloud-control 3.9.0-beta.4 → 3.9.0-beta.5

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.
@@ -20,9 +20,9 @@
20
20
  <form id="configForm">
21
21
  <div class="text-center">
22
22
  <label id="accountName" class="fw-bold" style="font-size: 23px;">Account</label><br>
23
- <label id="info" class="d-block" style="font-size: 16px;"></label>
24
- <label id="info1" class="d-block" style="font-size: 14px;"></label>
25
- <label id="info2" class="d-block" style="font-size: 14px;"></label>
23
+ <label id="info" class="d-block" style="font-size: 17px;"></label>
24
+ <label id="info1" class="d-block" style="font-size: 15px;"></label>
25
+ <label id="info2" class="d-block" style="font-size: 15px;"></label>
26
26
  </div>
27
27
 
28
28
  <div class="mb-3">
@@ -83,6 +83,7 @@
83
83
 
84
84
  <script>
85
85
  (async () => {
86
+ //get the plugin config array
86
87
  const pluginConfig = await homebridge.getPluginConfig();
87
88
 
88
89
  if (!pluginConfig.length) {
@@ -93,10 +94,11 @@
93
94
  }
94
95
  this.configButtonState = false;
95
96
 
97
+ //get accounts count
96
98
  const accountsCount = pluginConfig[0].accounts.length;
97
99
  this.deviceIndex = 0;
98
-
99
100
  for (let i = 0; i < accountsCount; i++) {
101
+ //create buttons
100
102
  const button = document.createElement("button");
101
103
  button.setAttribute("type", "button");
102
104
  button.setAttribute("id", `button${i}`);
@@ -105,12 +107,10 @@
105
107
  button.innerText = pluginConfig[0].accounts[i].name;
106
108
  document.getElementById("accountButton").appendChild(button);
107
109
 
110
+ //get actuall value on account select
108
111
  document.getElementById(`button${i}`).addEventListener('click', async () => {
109
112
  for (let j = 0; j < accountsCount; j++) {
110
- document.getElementById(`button${j}`).setAttribute(
111
- "class",
112
- j === i ? "btn btn-secondary" : "btn btn-primary"
113
- );
113
+ const setRemoveAtribute = j === i ? document.getElementById(`button${j}`).setAttribute("class", "btn btn-secondary") : document.getElementById(`button${j}`).setAttribute("class", "btn btn-primary");
114
114
  }
115
115
 
116
116
  document.getElementById('accountName').innerHTML = pluginConfig[0].accounts[i].name || '';
@@ -119,53 +119,54 @@
119
119
  document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
120
120
  document.getElementById('language').value = pluginConfig[0].accounts[i].language || '';
121
121
 
122
- const accountConfigured =
123
- pluginConfig[0].accounts[i].name &&
124
- pluginConfig[0].accounts[i].user &&
125
- pluginConfig[0].accounts[i].passwd &&
126
- pluginConfig[0].accounts[i].language;
127
-
128
- const loginBtn = document.getElementById('logIn');
129
- loginBtn.disabled = !accountConfigured;
122
+ const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
123
+ const button = document.getElementById('logIn');
124
+ const setButtonState = accountConfigured ? button.disabled = false : button.disabled = true;
130
125
 
131
126
  await homebridge.updatePluginConfig(pluginConfig);
132
127
  this.deviceIndex = i;
133
128
  });
129
+ const click = i === accountsCount - 1 ? document.getElementById(`button0`).click() : false;
130
+ const update = i === accountsCount - 1 ? await homebridge.updatePluginConfig(pluginConfig) : false;
131
+ };
134
132
 
135
- if (i === accountsCount - 1) {
136
- document.getElementById(`button0`).click();
137
- await homebridge.updatePluginConfig(pluginConfig);
138
- }
139
- }
140
-
133
+ //load melCloudAccount form
141
134
  document.getElementById('melCloudAccount').style.display = 'block';
142
135
 
136
+ //watch for changes to the form
143
137
  document.getElementById('configForm').addEventListener('input', async () => {
144
138
  pluginConfig[0].accounts[this.deviceIndex].name = document.querySelector('#name').value;
145
139
  pluginConfig[0].accounts[this.deviceIndex].user = document.querySelector('#user').value;
146
140
  pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
147
141
  pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
148
142
 
149
- const accountConfigured =
150
- pluginConfig[0].accounts[this.deviceIndex].name &&
151
- pluginConfig[0].accounts[this.deviceIndex].user &&
152
- pluginConfig[0].accounts[this.deviceIndex].passwd &&
153
- pluginConfig[0].accounts[this.deviceIndex].language;
154
-
155
- document.getElementById('logIn').disabled = !accountConfigured;
143
+ const accountConfigured = pluginConfig[0].accounts[this.deviceIndex].name && pluginConfig[0].accounts[this.deviceIndex].user && pluginConfig[0].accounts[this.deviceIndex].passwd && pluginConfig[0].accounts[this.deviceIndex].language;
144
+ const button = document.getElementById('logIn');
145
+ const setButtonState = accountConfigured ? button.disabled = false : button.disabled = true;
156
146
 
157
147
  await homebridge.updatePluginConfig(pluginConfig);
158
148
  await homebridge.savePluginConfig(pluginConfig);
159
149
  });
160
150
 
151
+ //watch for changes to the config button
161
152
  document.getElementById('configButton').addEventListener('click', async () => {
162
- this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
163
- document
164
- .getElementById('configButton')
165
- .setAttribute("class", this.configButtonState ? "btn btn-secondary" : "btn btn-primary");
153
+ const showHideSettings = this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
154
+ const setRemoveAtribute = this.configButtonState ? document.getElementById(`configButton`).setAttribute("class", "btn btn-secondary") : document.getElementById(`configButton`).setAttribute("class", "btn btn-primary");
166
155
  this.configButtonState = !this.configButtonState;
167
156
  });
168
157
 
158
+ // Helper to remove stale devices by comparing with MELCloud
159
+ function removeStaleDevices(configDevices, melcloudDevices) {
160
+ const melcloudIds = melcloudDevices.map(d => d.DeviceID);
161
+ for (let i = configDevices.length - 1; i >= 0; i--) {
162
+ const device = configDevices[i];
163
+ if (device.id !== 0 && !melcloudIds.includes(device.id)) {
164
+ configDevices.splice(i, 1);
165
+ }
166
+ }
167
+ }
168
+
169
+ //watch for click on the login button
169
170
  document.getElementById('logIn').addEventListener('click', async () => {
170
171
  homebridge.showSpinner();
171
172
 
@@ -180,22 +181,37 @@
180
181
  const payload = { accountName, user, passwd, language };
181
182
  const devicesInMelCloud = await homebridge.request('/connect', payload);
182
183
 
183
- const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
184
- const removedDevices = { ata: [], atw: [], erv: [] };
184
+ const newDevices = {
185
+ ata: [],
186
+ ataPresets: [],
187
+ atw: [],
188
+ atwPresets: [],
189
+ erv: [],
190
+ ervPresets: [],
191
+ };
192
+
185
193
  const devicesByTypeInMelCloud = { ata: [], atw: [], erv: [] };
186
194
 
187
- for (const device of devicesInMelCloud) {
188
- switch (device.Type) {
189
- case 0: devicesByTypeInMelCloud.ata.push(device); break;
190
- case 1: devicesByTypeInMelCloud.atw.push(device); break;
191
- case 3: devicesByTypeInMelCloud.erv.push(device); break;
195
+ // Categorize devices by type
196
+ for (const deviceInMelcloud of devicesInMelCloud) {
197
+ switch (deviceInMelcloud.Type) {
198
+ case 0: devicesByTypeInMelCloud.ata.push(deviceInMelcloud); break;
199
+ case 1: devicesByTypeInMelCloud.atw.push(deviceInMelcloud); break;
200
+ case 3: devicesByTypeInMelCloud.erv.push(deviceInMelcloud); break;
192
201
  }
193
202
  }
194
203
 
204
+ // Ensure config arrays exist
195
205
  const ataDevicesInConfig = account.ataDevices ?? (account.ataDevices = []);
196
206
  const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
197
207
  const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
198
208
 
209
+ // Remove stale devices
210
+ removeStaleDevices(ataDevicesInConfig, devicesByTypeInMelCloud.ata);
211
+ removeStaleDevices(atwDevicesInConfig, devicesByTypeInMelCloud.atw);
212
+ removeStaleDevices(ervDevicesInConfig, devicesByTypeInMelCloud.erv);
213
+
214
+ // Helper for updating UI
199
215
  const updateInfo = (id, text, color) => {
200
216
  const el = document.getElementById(id);
201
217
  if (el) {
@@ -204,93 +220,170 @@
204
220
  }
205
221
  };
206
222
 
207
- function handleDevices(devices, configArray, newArray, newPresetsArray, removedArray, typeString, template) {
208
- const melcloudIds = devices.map(d => d.DeviceID);
223
+ // Handle ATA devices
224
+ devicesByTypeInMelCloud.ata.forEach((device) => {
225
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
226
+ const deviceAta = {
227
+ id: deviceId,
228
+ type: deviceType,
229
+ typeString: "Air Conditioner",
230
+ name: deviceName,
231
+ displayMode: 1,
232
+ heatDryFanMode: 1,
233
+ coolDryFanMode: 1,
234
+ autoDryFanMode: 1,
235
+ temperatureSensor: false,
236
+ temperatureSensorOutdoor: false,
237
+ presets: [],
238
+ buttonsSensors: []
239
+ };
240
+
241
+ if (!ataDevicesInConfig.some(device => device.id === deviceId)) {
242
+ ataDevicesInConfig.push(deviceAta);
243
+ newDevices.ata.push(deviceAta);
244
+ }
209
245
 
210
- // remove stale devices
211
- for (let i = ataDevicesInConfig.length - 1; i >= 0; i--) {
212
- const device = ataDevicesInConfig[i];
213
- // Only remove if it's a real device (id != 0) and missing in MELCloud
214
- if (device.id !== 0 && !melcloudIds.includes(device.id)) {
215
- removedDevices.ata.push(device);
216
- ataDevicesInConfig.splice(i, 1);
246
+ devicePresets.forEach((preset) => {
247
+ const { ID: presetId, NumberDescription: presetName } = preset;
248
+ preset.id = presetId;
249
+ preset.name = presetName;
250
+ preset.displayType = 0;
251
+ preset.namePrefix = false;
252
+
253
+ const ataDevice = ataDevicesInConfig.find((device) => device.id === deviceId);
254
+ if (ataDevice && !ataDevice.presets.some((p) => p.id === presetId)) {
255
+ ataDevice.presets.push(preset);
256
+ newDevices.ataPresets.push(preset);
217
257
  }
258
+ });
259
+ });
260
+
261
+ // Handle ATW devices
262
+ devicesByTypeInMelCloud.atw.forEach((device) => {
263
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
264
+ const deviceAtw = {
265
+ id: deviceId,
266
+ type: deviceType,
267
+ typeString: "Heat Pump",
268
+ name: deviceName,
269
+ displayMode: 1,
270
+ hideZone: 0,
271
+ temperatureSensor: false,
272
+ temperatureSensorFlow: false,
273
+ temperatureSensorReturn: false,
274
+ temperatureSensorFlowZone1: false,
275
+ temperatureSensorReturnZone1: false,
276
+ temperatureSensorFlowWaterTank: false,
277
+ temperatureSensorReturnWaterTank: false,
278
+ temperatureSensorFlowZone2: false,
279
+ temperatureSensorReturnZone2: false,
280
+ temperatureSensorOutdoor: false,
281
+ presets: [],
282
+ buttonsSensors: []
283
+ };
284
+
285
+ if (!atwDevicesInConfig.some(device => device.id === deviceId)) {
286
+ atwDevicesInConfig.push(deviceAtw);
287
+ newDevices.atw.push(deviceAtw);
218
288
  }
219
289
 
220
- devices.forEach((device) => {
221
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
222
-
223
- const deviceObj = {
224
- id: deviceId,
225
- type: deviceType,
226
- typeString,
227
- name: deviceName,
228
- ...template,
229
- presets: [],
230
- buttonsSensors: []
231
- };
232
-
233
- if (!configArray.some(d => d.id === deviceId)) {
234
- configArray.push(deviceObj);
235
- newArray.push(deviceObj);
290
+ devicePresets.forEach((preset) => {
291
+ const { ID: presetId, NumberDescription: presetName } = preset;
292
+ preset.id = presetId;
293
+ preset.name = presetName;
294
+ preset.displayType = 0;
295
+ preset.namePrefix = false;
296
+
297
+ const atwDevice = atwDevicesInConfig.find((device) => device.id === deviceId);
298
+ if (atwDevice && !atwDevice.presets.some((p) => p.id === presetId)) {
299
+ atwDevice.presets.push(preset);
300
+ newDevices.atwPresets.push(preset);
236
301
  }
237
-
238
- devicePresets.forEach((preset) => {
239
- const { ID: presetId, NumberDescription: presetName } = preset;
240
- Object.assign(preset, { id: presetId, name: presetName, displayType: 0, namePrefix: false });
241
-
242
- const existingDevice = configArray.find(d => d.id === deviceId);
243
- if (existingDevice && !existingDevice.presets.some(p => p.id === presetId)) {
244
- existingDevice.presets.push(preset);
245
- newPresetsArray.push(preset);
246
- }
247
- });
248
302
  });
249
- }
303
+ });
304
+
305
+ // Handle ERV devices
306
+ devicesByTypeInMelCloud.erv.forEach((device) => {
307
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
308
+ const deviceErv = {
309
+ id: deviceId,
310
+ type: deviceType,
311
+ typeString: "Energy Recovery Ventilation",
312
+ name: deviceName,
313
+ displayMode: 1,
314
+ temperatureSensor: false,
315
+ temperatureSensorOutdoor: false,
316
+ temperatureSensorSupply: false,
317
+ presets: [],
318
+ buttonsSensors: []
319
+ };
320
+
321
+ if (!ervDevicesInConfig.some(device => device.id === deviceId)) {
322
+ ervDevicesInConfig.push(deviceErv);
323
+ newDevices.erv.push(deviceErv);
324
+ }
250
325
 
251
- const ataTemplate = { displayMode: 1, heatDryFanMode: 1, coolDryFanMode: 1, autoDryFanMode: 1, temperatureSensor: false, temperatureSensorOutdoor: false };
252
- const atwTemplate = { displayMode: 1, hideZone: 0, temperatureSensor: false, temperatureSensorFlow: false, temperatureSensorReturn: false, temperatureSensorFlowZone1: false, temperatureSensorReturnZone1: false, temperatureSensorFlowWaterTank: false, temperatureSensorReturnWaterTank: false, temperatureSensorFlowZone2: false, temperatureSensorReturnZone2: false, temperatureSensorOutdoor: false };
253
- const ervTemplate = { displayMode: 1, temperatureSensor: false, temperatureSensorOutdoor: false, temperatureSensorSupply: false };
326
+ devicePresets.forEach((preset) => {
327
+ const { ID: presetId, NumberDescription: presetName } = preset;
328
+ preset.id = presetId;
329
+ preset.name = presetName;
330
+ preset.displayType = 0;
331
+ preset.namePrefix = false;
332
+
333
+ const ervDevice = ervDevicesInConfig.find((device) => device.id === deviceId);
334
+ if (ervDevice && !ervDevice.presets.some((p) => p.id === presetId)) {
335
+ ervDevice.presets.push(preset);
336
+ newDevices.ervPresets.push(preset);
337
+ }
338
+ });
339
+ });
254
340
 
255
- handleDevices(devicesByTypeInMelCloud.ata, ataDevicesInConfig, newDevices.ata, newDevices.ataPresets, removedDevices.ata, "Air Conditioner", ataTemplate);
256
- handleDevices(devicesByTypeInMelCloud.atw, atwDevicesInConfig, newDevices.atw, newDevices.atwPresets, removedDevices.atw, "Heat Pump", atwTemplate);
257
- handleDevices(devicesByTypeInMelCloud.erv, ervDevicesInConfig, newDevices.erv, newDevices.ervPresets, removedDevices.erv, "Energy Recovery Ventilation", ervTemplate);
341
+ // Display info
342
+ const textAta = `ATA: ${newDevices.ata.length}`;
343
+ const textAtw = `ATW: ${newDevices.atw.length}`;
344
+ const textErv = `ERV: ${newDevices.erv.length}`;
345
+ const textAtaPresets = `ATA Presets: ${newDevices.ataPresets.length}`;
346
+ const textAtwPresets = `ATW Presets: ${newDevices.atwPresets.length}`;
347
+ const textErvPresets = `ERV Presets: ${newDevices.ervPresets.length}`;
258
348
 
259
- const formatDeviceNames = (arr) => arr.map(d => d.name).join(', ') || 'None';
260
349
  const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
261
350
  const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
262
- const removedDevicesCount = removedDevices.ata.length + removedDevices.atw.length + removedDevices.erv.length;
263
351
 
264
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedDevicesCount === 0) {
265
- updateInfo('info', 'No changes (already synced).', 'white');
352
+ if (newDevicesCount === 0 && newPresetsCount === 0) {
353
+ updateInfo('info', 'No new devices found.', 'white');
266
354
  } else {
267
355
  if (newDevicesCount > 0) {
268
- updateInfo('info', `New Devices Found: ATA: ${formatDeviceNames(newDevices.ata)}, ATW: ${formatDeviceNames(newDevices.atw)}, ERV: ${formatDeviceNames(newDevices.erv)}`, 'green');
356
+ updateInfo('info', `Found, ${textAta}, ${textAtw}, ${textErv}.`, 'green');
269
357
  updateInfo('info2', 'Now You can configure it.', 'green');
270
358
  }
271
359
  if (newPresetsCount > 0) {
272
- updateInfo('info1', `New Presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}`, 'green');
273
- }
274
- if (removedDevicesCount > 0) {
275
- updateInfo('info', `Removed Devices: ATA: ${formatDeviceNames(removedDevices.ata)}, ATW: ${formatDeviceNames(removedDevices.atw)}, ERV: ${formatDeviceNames(removedDevices.erv)}`, 'orange');
360
+ updateInfo('info1', `${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
361
+ if (newDevicesCount === 0) {
362
+ updateInfo('info', `Found, ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
363
+ updateInfo('info1', 'Now You can configure it.', 'green');
364
+ }
276
365
  }
277
366
  }
278
367
 
368
+ // Save updated config
369
+ await homebridge.updatePluginConfig(pluginConfig);
279
370
  await homebridge.savePluginConfig(pluginConfig);
280
371
 
372
+ // Update button
281
373
  document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
282
374
  homebridge.hideSpinner();
375
+
283
376
  } catch (error) {
284
377
  updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
285
- updateInfo('info1', `Error: ${error.message ?? JSON.stringify(error)}`, 'red');
378
+ updateInfo('info1', `Error: ${error}`, 'red');
286
379
  document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
287
380
  } finally {
288
381
  homebridge.hideSpinner();
289
382
  }
383
+
290
384
  });
291
385
  })();
292
386
  </script>
293
-
294
387
  </body>
295
388
 
296
389
  </html>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "3.9.0-beta.4",
4
+ "version": "3.9.0-beta.5",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",