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

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">
@@ -43,31 +43,7 @@
43
43
  <div class="mb-3">
44
44
  <label for="language" class="form-label">Language</label>
45
45
  <select id="language" name="language" class="form-control">
46
- <option value="0">English</option>
47
- <option value="1">Български</option>
48
- <option value="2">Čeština</option>
49
- <option value="3">Dansk</option>
50
- <option value="4">Deutsch</option>
51
- <option value="5">Eesti</option>
52
- <option value="6">Español</option>
53
- <option value="7">Français</option>
54
- <option value="8">Հայերեն</option>
55
- <option value="9">Latviešu</option>
56
- <option value="10">Lietuvių</option>
57
- <option value="11">Magyar</option>
58
- <option value="12">Nederlands</option>
59
- <option value="13">Norwegian</option>
60
- <option value="14">Polski</option>
61
- <option value="15">Português</option>
62
- <option value="16">Русский</option>
63
- <option value="17">Suomi</option>
64
- <option value="18">Svenska</option>
65
- <option value="19">Українська</option>
66
- <option value="20">Türkçe</option>
67
- <option value="21">Ελληνικά</option>
68
- <option value="22">Hrvatski</option>
69
- <option value="23">Română</option>
70
- <option value="24">Slovenščina</option>
46
+ <!-- languages options omitted for brevity -->
71
47
  </select>
72
48
  </div>
73
49
 
@@ -84,7 +60,6 @@
84
60
  <script>
85
61
  (async () => {
86
62
  const pluginConfig = await homebridge.getPluginConfig();
87
-
88
63
  if (!pluginConfig.length) {
89
64
  pluginConfig.push({});
90
65
  await homebridge.updatePluginConfig(pluginConfig);
@@ -95,7 +70,6 @@
95
70
 
96
71
  const accountsCount = pluginConfig[0].accounts.length;
97
72
  this.deviceIndex = 0;
98
-
99
73
  for (let i = 0; i < accountsCount; i++) {
100
74
  const button = document.createElement("button");
101
75
  button.setAttribute("type", "button");
@@ -107,10 +81,9 @@
107
81
 
108
82
  document.getElementById(`button${i}`).addEventListener('click', async () => {
109
83
  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
- );
84
+ j === i
85
+ ? document.getElementById(`button${j}`).setAttribute("class", "btn btn-secondary")
86
+ : document.getElementById(`button${j}`).setAttribute("class", "btn btn-primary");
114
87
  }
115
88
 
116
89
  document.getElementById('accountName').innerHTML = pluginConfig[0].accounts[i].name || '';
@@ -119,19 +92,12 @@
119
92
  document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
120
93
  document.getElementById('language').value = pluginConfig[0].accounts[i].language || '';
121
94
 
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;
95
+ const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
96
+ document.getElementById('logIn').disabled = !accountConfigured;
130
97
 
131
98
  await homebridge.updatePluginConfig(pluginConfig);
132
99
  this.deviceIndex = i;
133
100
  });
134
-
135
101
  if (i === accountsCount - 1) {
136
102
  document.getElementById(`button0`).click();
137
103
  await homebridge.updatePluginConfig(pluginConfig);
@@ -146,12 +112,7 @@
146
112
  pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
147
113
  pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
148
114
 
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
-
115
+ 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;
155
116
  document.getElementById('logIn').disabled = !accountConfigured;
156
117
 
157
118
  await homebridge.updatePluginConfig(pluginConfig);
@@ -160,35 +121,52 @@
160
121
 
161
122
  document.getElementById('configButton').addEventListener('click', async () => {
162
123
  this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
163
- document
164
- .getElementById('configButton')
165
- .setAttribute("class", this.configButtonState ? "btn btn-secondary" : "btn btn-primary");
124
+ this.configButtonState
125
+ ? document.getElementById(`configButton`).setAttribute("class", "btn btn-secondary")
126
+ : document.getElementById(`configButton`).setAttribute("class", "btn btn-primary");
166
127
  this.configButtonState = !this.configButtonState;
167
128
  });
168
129
 
130
+ function removeStaleDevices(configDevices, melcloudDevices) {
131
+ const melcloudIds = melcloudDevices.map(d => d.DeviceID);
132
+ const removedDevices = [];
133
+ for (let i = configDevices.length - 1; i >= 0; i--) {
134
+ const device = configDevices[i];
135
+ if (device.id !== 0 && !melcloudIds.includes(device.id)) {
136
+ removedDevices.push(device);
137
+ configDevices.splice(i, 1);
138
+ }
139
+ }
140
+ return removedDevices;
141
+ }
142
+
143
+ function updateInfo(id, text, color) {
144
+ const el = document.getElementById(id);
145
+ if (el) {
146
+ el.innerHTML = text;
147
+ el.style.color = color;
148
+ }
149
+ }
150
+
169
151
  document.getElementById('logIn').addEventListener('click', async () => {
170
152
  homebridge.showSpinner();
171
-
172
153
  document.getElementById(`logIn`).setAttribute("class", "btn btn-primary");
173
- document.getElementById('info').innerHTML = 'Connecting...';
174
- document.getElementById('info').style.color = 'yellow';
154
+ updateInfo('info', 'Connecting...', 'yellow');
175
155
 
176
156
  try {
177
157
  const account = pluginConfig[0].accounts[this.deviceIndex];
178
158
  const { name: accountName, user, passwd, language } = account;
179
-
180
159
  const payload = { accountName, user, passwd, language };
181
160
  const devicesInMelCloud = await homebridge.request('/connect', payload);
182
161
 
183
162
  const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
184
- const removedDevices = { ata: [], atw: [], erv: [] };
185
163
  const devicesByTypeInMelCloud = { ata: [], atw: [], erv: [] };
186
164
 
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;
165
+ for (const deviceInMelcloud of devicesInMelCloud) {
166
+ switch (deviceInMelcloud.Type) {
167
+ case 0: devicesByTypeInMelCloud.ata.push(deviceInMelcloud); break;
168
+ case 1: devicesByTypeInMelCloud.atw.push(deviceInMelcloud); break;
169
+ case 3: devicesByTypeInMelCloud.erv.push(deviceInMelcloud); break;
192
170
  }
193
171
  }
194
172
 
@@ -196,93 +174,145 @@
196
174
  const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
197
175
  const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
198
176
 
199
- const updateInfo = (id, text, color) => {
200
- const el = document.getElementById(id);
201
- if (el) {
202
- el.innerHTML = text;
203
- el.style.color = color;
177
+ const removedAta = removeStaleDevices(ataDevicesInConfig, devicesByTypeInMelCloud.ata);
178
+ const removedAtw = removeStaleDevices(atwDevicesInConfig, devicesByTypeInMelCloud.atw);
179
+ const removedErv = removeStaleDevices(ervDevicesInConfig, devicesByTypeInMelCloud.erv);
180
+
181
+ // === Handle ATA devices ===
182
+ devicesByTypeInMelCloud.ata.forEach(device => {
183
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
184
+ const deviceAta = {
185
+ id: deviceId,
186
+ type: deviceType,
187
+ typeString: "Air Conditioner",
188
+ name: deviceName,
189
+ displayMode: 1,
190
+ heatDryFanMode: 1,
191
+ coolDryFanMode: 1,
192
+ autoDryFanMode: 1,
193
+ temperatureSensor: false,
194
+ temperatureSensorOutdoor: false,
195
+ presets: [],
196
+ buttonsSensors: []
197
+ };
198
+ if (!ataDevicesInConfig.some(d => d.id === deviceId)) {
199
+ ataDevicesInConfig.push(deviceAta);
200
+ newDevices.ata.push(deviceAta);
204
201
  }
205
- };
206
-
207
- function handleDevices(devices, configArray, newArray, newPresetsArray, removedArray, typeString, template) {
208
- const melcloudIds = devices.map(d => d.DeviceID);
209
-
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);
202
+ devicePresets.forEach(preset => {
203
+ const { ID: presetId, NumberDescription: presetName } = preset;
204
+ preset.id = presetId;
205
+ preset.name = presetName;
206
+ preset.displayType = 0;
207
+ preset.namePrefix = false;
208
+ const ataDevice = ataDevicesInConfig.find(d => d.id === deviceId);
209
+ if (ataDevice && !ataDevice.presets.some(p => p.id === presetId)) {
210
+ ataDevice.presets.push(preset);
211
+ newDevices.ataPresets.push(preset);
217
212
  }
213
+ });
214
+ });
215
+
216
+ // === Handle ATW devices ===
217
+ devicesByTypeInMelCloud.atw.forEach(device => {
218
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
219
+ const deviceAtw = {
220
+ id: deviceId,
221
+ type: deviceType,
222
+ typeString: "Heat Pump",
223
+ name: deviceName,
224
+ displayMode: 1,
225
+ hideZone: 0,
226
+ temperatureSensor: false,
227
+ presets: [],
228
+ buttonsSensors: []
229
+ };
230
+ if (!atwDevicesInConfig.some(d => d.id === deviceId)) {
231
+ atwDevicesInConfig.push(deviceAtw);
232
+ newDevices.atw.push(deviceAtw);
218
233
  }
219
-
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);
234
+ devicePresets.forEach(preset => {
235
+ const { ID: presetId, NumberDescription: presetName } = preset;
236
+ preset.id = presetId;
237
+ preset.name = presetName;
238
+ preset.displayType = 0;
239
+ preset.namePrefix = false;
240
+ const atwDevice = atwDevicesInConfig.find(d => d.id === deviceId);
241
+ if (atwDevice && !atwDevice.presets.some(p => p.id === presetId)) {
242
+ atwDevice.presets.push(preset);
243
+ newDevices.atwPresets.push(preset);
236
244
  }
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
245
  });
249
- }
250
-
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 };
254
-
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);
246
+ });
247
+
248
+ // === Handle ERV devices ===
249
+ devicesByTypeInMelCloud.erv.forEach(device => {
250
+ const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
251
+ const deviceErv = {
252
+ id: deviceId,
253
+ type: deviceType,
254
+ typeString: "ERV",
255
+ name: deviceName,
256
+ displayMode: 1,
257
+ temperatureSensor: false,
258
+ presets: [],
259
+ buttonsSensors: []
260
+ };
261
+ if (!ervDevicesInConfig.some(d => d.id === deviceId)) {
262
+ ervDevicesInConfig.push(deviceErv);
263
+ newDevices.erv.push(deviceErv);
264
+ }
265
+ devicePresets.forEach(preset => {
266
+ const { ID: presetId, NumberDescription: presetName } = preset;
267
+ preset.id = presetId;
268
+ preset.name = presetName;
269
+ preset.displayType = 0;
270
+ preset.namePrefix = false;
271
+ const ervDevice = ervDevicesInConfig.find(d => d.id === deviceId);
272
+ if (ervDevice && !ervDevice.presets.some(p => p.id === presetId)) {
273
+ ervDevice.presets.push(preset);
274
+ newDevices.ervPresets.push(preset);
275
+ }
276
+ });
277
+ });
278
+
279
+ // --- Display Info Section ---
280
+ const textAta = `ATA: ${newDevices.ata.length}`;
281
+ const textAtw = `ATW: ${newDevices.atw.length}`;
282
+ const textErv = `ERV: ${newDevices.erv.length}`;
283
+ const textAtaPresets = `ATA Presets: ${newDevices.ataPresets.length}`;
284
+ const textAtwPresets = `ATW Presets: ${newDevices.atwPresets.length}`;
285
+ const textErvPresets = `ERV Presets: ${newDevices.ervPresets.length}`;
286
+ const textRemovedAta = `ATA: ${removedAta.length}`;
287
+ const textRemovedAtw = `ATW: ${removedAtw.length}`;
288
+ const textRemovedErv = `ERV: ${removedErv.length}`;
258
289
 
259
- const formatDeviceNames = (arr) => arr.map(d => d.name).join(', ') || 'None';
260
290
  const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
261
291
  const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
262
- const removedDevicesCount = removedDevices.ata.length + removedDevices.atw.length + removedDevices.erv.length;
292
+ const removedCount = removedAta.length + removedAtw.length + removedErv.length;
263
293
 
264
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedDevicesCount === 0) {
265
- updateInfo('info', 'No changes (already synced).', 'white');
294
+ if (newDevicesCount === 0 && newPresetsCount === 0 && removedCount === 0) {
295
+ updateInfo('info', 'No changes detected.', 'white');
266
296
  } else {
267
297
  if (newDevicesCount > 0) {
268
- updateInfo('info', `New Devices Found: ATA: ${formatDeviceNames(newDevices.ata)}, ATW: ${formatDeviceNames(newDevices.atw)}, ERV: ${formatDeviceNames(newDevices.erv)}`, 'green');
269
- updateInfo('info2', 'Now You can configure it.', 'green');
298
+ updateInfo('info', `Found new devices: ${textAta}, ${textAtw}, ${textErv}.`, 'green');
270
299
  }
271
300
  if (newPresetsCount > 0) {
272
- updateInfo('info1', `New Presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}`, 'green');
301
+ updateInfo('info1', `Found new presets: ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
273
302
  }
274
- if (removedDevicesCount > 0) {
275
- updateInfo('info', `Removed Devices: ATA: ${formatDeviceNames(removedDevices.ata)}, ATW: ${formatDeviceNames(removedDevices.atw)}, ERV: ${formatDeviceNames(removedDevices.erv)}`, 'orange');
303
+ if (removedCount > 0) {
304
+ updateInfo('info2', `Removed devices: ${textRemovedAta}, ${textRemovedAtw}, ${textRemovedErv}.`, 'orange');
276
305
  }
277
306
  }
278
307
 
308
+ await homebridge.updatePluginConfig(pluginConfig);
279
309
  await homebridge.savePluginConfig(pluginConfig);
280
-
281
310
  document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
282
311
  homebridge.hideSpinner();
312
+
283
313
  } catch (error) {
284
314
  updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
285
- updateInfo('info1', `Error: ${error.message ?? JSON.stringify(error)}`, 'red');
315
+ updateInfo('info1', `Error: ${error}`, 'red');
286
316
  document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
287
317
  } finally {
288
318
  homebridge.hideSpinner();
@@ -290,7 +320,6 @@
290
320
  });
291
321
  })();
292
322
  </script>
293
-
294
323
  </body>
295
324
 
296
325
  </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.6",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",