homebridge-melcloud-control 3.9.0-beta.7 → 3.9.0-beta.9

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.
@@ -6,342 +6,264 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>MELCloud Account Configuration</title>
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css">
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
10
10
  </head>
11
11
 
12
12
  <body>
13
-
14
13
  <div class="container mt-4">
15
14
  <div class="text-center">
16
15
  <img src="homebridge-melcloud-control.png" alt="Image" height="120" />
17
16
  </div>
18
17
 
19
- <div id="melCloudAccount" class="card card-body mt-3">
20
- <form id="configForm">
21
- <div class="text-center">
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>
26
- </div>
27
-
28
- <div class="mb-3">
29
- <label for="name" class="form-label">Name</label>
30
- <input id="name" type="text" class="form-control" required>
31
- </div>
32
-
33
- <div class="mb-3">
34
- <label for="user" class="form-label">User Name</label>
35
- <input id="user" type="text" class="form-control" required>
36
- </div>
37
-
38
- <div class="mb-3">
39
- <label for="passwd" class="form-label">Password</label>
40
- <input id="passwd" type="password" class="form-control" autocomplete="off" required>
41
- </div>
18
+ <!-- Tabs -->
19
+ <ul class="nav nav-tabs mt-3" id="accountTabs" role="tablist"></ul>
42
20
 
43
- <div class="mb-3">
44
- <label for="language" class="form-label">Language</label>
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>
71
- </select>
72
- </div>
21
+ <!-- Tab contents -->
22
+ <div class="tab-content mt-3" id="accountTabsContent"></div>
73
23
 
74
- <div class="text-center">
75
- <button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
76
- <button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
77
- </div>
78
- </form>
24
+ <!-- Info Section -->
25
+ <div class="mt-3">
26
+ <div id="info"></div>
27
+ <div id="info1"></div>
28
+ <div id="info2"></div>
79
29
  </div>
80
-
81
- <div id="accountButton" class="mt-3"></div>
82
30
  </div>
83
31
 
84
32
  <script>
85
33
  (async () => {
86
34
  const pluginConfig = await homebridge.getPluginConfig();
87
35
  if (!pluginConfig.length) {
88
- pluginConfig.push({});
36
+ pluginConfig.push({ accounts: [] });
89
37
  await homebridge.updatePluginConfig(pluginConfig);
90
38
  homebridge.showSchemaForm();
91
39
  return;
92
40
  }
93
- this.configButtonState = false;
94
-
95
- const accountsCount = pluginConfig[0].accounts.length;
96
- this.deviceIndex = 0;
97
- for (let i = 0; i < accountsCount; i++) {
98
- const button = document.createElement("button");
99
- button.setAttribute("type", "button");
100
- button.setAttribute("id", `button${i}`);
101
- button.setAttribute("class", "btn btn-primary");
102
- button.style.textTransform = 'none';
103
- button.innerText = pluginConfig[0].accounts[i].name;
104
- document.getElementById("accountButton").appendChild(button);
105
-
106
- document.getElementById(`button${i}`).addEventListener('click', async () => {
107
- for (let j = 0; j < accountsCount; j++) {
108
- j === i
109
- ? document.getElementById(`button${j}`).setAttribute("class", "btn btn-secondary")
110
- : document.getElementById(`button${j}`).setAttribute("class", "btn btn-primary");
111
- }
112
-
113
- document.getElementById('accountName').innerHTML = pluginConfig[0].accounts[i].name || '';
114
- document.getElementById('name').value = pluginConfig[0].accounts[i].name || '';
115
- document.getElementById('user').value = pluginConfig[0].accounts[i].user || '';
116
- document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
117
- document.getElementById('language').value = pluginConfig[0].accounts[i].language || '';
118
41
 
119
- const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
120
- document.getElementById('logIn').disabled = !accountConfigured;
42
+ const accounts = pluginConfig[0].accounts;
43
+ const tabs = document.getElementById("accountTabs");
44
+ const content = document.getElementById("accountTabsContent");
45
+
46
+ function createAccountForm(account, i) {
47
+ // Tab header
48
+ const li = document.createElement("li");
49
+ li.classList.add("nav-item");
50
+ li.id = `tab-li-${i}`;
51
+ li.innerHTML = `
52
+ <button class="nav-link ${i === 0 ? 'active' : ''}"
53
+ id="tab-${i}" data-bs-toggle="tab"
54
+ data-bs-target="#pane-${i}"
55
+ type="button" role="tab">
56
+ ${account.name || 'Account ' + (i + 1)}
57
+ </button>`;
58
+ tabs.insertBefore(li, document.getElementById("addTab"));
59
+
60
+ // Tab content pane
61
+ const pane = document.createElement("div");
62
+ pane.classList.add("tab-pane", "fade", i === 0 ? "show" : "", i === 0 ? "active" : "");
63
+ pane.id = `pane-${i}`;
64
+ pane.role = "tabpanel";
65
+ pane.innerHTML = `
66
+ <form id="configForm-${i}">
67
+ <div class="mb-3">
68
+ <label class="form-label">Name</label>
69
+ <input type="text" class="form-control" id="name-${i}" value="${account.name || ''}">
70
+ </div>
71
+ <div class="mb-3">
72
+ <label class="form-label">User Name</label>
73
+ <input type="text" class="form-control" id="user-${i}" value="${account.user || ''}">
74
+ </div>
75
+ <div class="mb-3">
76
+ <label class="form-label">Password</label>
77
+ <input type="password" class="form-control" id="passwd-${i}" value="${account.passwd || ''}">
78
+ </div>
79
+ <div class="mb-3">
80
+ <label class="form-label">Language</label>
81
+ <select class="form-control" id="language-${i}">
82
+ <option value="0" ${account.language == 0 ? 'selected' : ''}>English</option>
83
+ <option value="1" ${account.language == 1 ? 'selected' : ''}>Български</option>
84
+ <option value="2" ${account.language == 2 ? 'selected' : ''}>Česky</option>
85
+ <option value="3" ${account.language == 3 ? 'selected' : ''}>Dansk</option>
86
+ <option value="4" ${account.language == 4 ? 'selected' : ''}>Deutsch</option>
87
+ <option value="5" ${account.language == 5 ? 'selected' : ''}>Eesti</option>
88
+ <option value="6" ${account.language == 6 ? 'selected' : ''}>Español</option>
89
+ <option value="7" ${account.language == 7 ? 'selected' : ''}>Français</option>
90
+ <option value="8" ${account.language == 8 ? 'selected' : ''}>Italiano</option>
91
+ <option value="9" ${account.language == 9 ? 'selected' : ''}>Latviešu</option>
92
+ <option value="10" ${account.language == 10 ? 'selected' : ''}>Lietuvių</option>
93
+ <option value="11" ${account.language == 11 ? 'selected' : ''}>Magyar</option>
94
+ <option value="12" ${account.language == 12 ? 'selected' : ''}>Nederlands</option>
95
+ <option value="13" ${account.language == 13 ? 'selected' : ''}>Norsk</option>
96
+ <option value="14" ${account.language == 14 ? 'selected' : ''}>Polski</option>
97
+ <option value="15" ${account.language == 15 ? 'selected' : ''}>Português</option>
98
+ <option value="16" ${account.language == 16 ? 'selected' : ''}>Русский</option>
99
+ <option value="17" ${account.language == 17 ? 'selected' : ''}>Suomi</option>
100
+ <option value="18" ${account.language == 18 ? 'selected' : ''}>Svenska</option>
101
+ <option value="19" ${account.language == 19 ? 'selected' : ''}>Türkçe</option>
102
+ </select>
103
+ </div>
104
+ <button type="button" class="btn btn-secondary" id="logIn-${i}">Connect</button>
105
+ <button type="button" class="btn btn-danger ms-2" id="remove-${i}">❌ Remove Account</button>
106
+ </form>`;
107
+ content.appendChild(pane);
108
+
109
+ // Handle input updates
110
+ pane.querySelector("form").addEventListener("input", async () => {
111
+ account.name = document.getElementById(`name-${i}`).value;
112
+ account.user = document.getElementById(`user-${i}`).value;
113
+ account.passwd = document.getElementById(`passwd-${i}`).value;
114
+ account.language = document.getElementById(`language-${i}`).value;
121
115
 
122
116
  await homebridge.updatePluginConfig(pluginConfig);
123
- this.deviceIndex = i;
117
+ await homebridge.savePluginConfig(pluginConfig);
118
+ document.querySelector(`#tab-${i}`).innerText = account.name || `Account ${i + 1}`;
124
119
  });
125
- if (i === accountsCount - 1) {
126
- document.getElementById(`button0`).click();
127
- await homebridge.updatePluginConfig(pluginConfig);
128
- }
129
- }
130
-
131
- document.getElementById('melCloudAccount').style.display = 'block';
132
-
133
- document.getElementById('configForm').addEventListener('input', async () => {
134
- pluginConfig[0].accounts[this.deviceIndex].name = document.querySelector('#name').value;
135
- pluginConfig[0].accounts[this.deviceIndex].user = document.querySelector('#user').value;
136
- pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
137
- pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
138
120
 
139
- 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;
140
- document.getElementById('logIn').disabled = !accountConfigured;
141
-
142
- await homebridge.updatePluginConfig(pluginConfig);
143
- await homebridge.savePluginConfig(pluginConfig);
144
- });
145
-
146
- document.getElementById('configButton').addEventListener('click', async () => {
147
- this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
148
- this.configButtonState
149
- ? document.getElementById(`configButton`).setAttribute("class", "btn btn-secondary")
150
- : document.getElementById(`configButton`).setAttribute("class", "btn btn-primary");
151
- this.configButtonState = !this.configButtonState;
152
- });
153
-
154
- function removeStaleDevices(configDevices, melcloudDevices) {
155
- const melcloudIds = melcloudDevices.map(d => d.DeviceID);
156
- const removedDevices = [];
157
- for (let i = configDevices.length - 1; i >= 0; i--) {
158
- const device = configDevices[i];
159
- if (device.id !== 0 && !melcloudIds.includes(device.id)) {
160
- removedDevices.push(device);
161
- configDevices.splice(i, 1);
162
- }
163
- }
164
- return removedDevices;
165
- }
166
-
167
- function updateInfo(id, text, color) {
168
- const el = document.getElementById(id);
169
- if (el) {
170
- el.innerHTML = text;
171
- el.style.color = color;
172
- }
173
- }
121
+ // Remove account
122
+ document.getElementById(`remove-${i}`).addEventListener("click", async () => {
123
+ accounts.splice(i, 1);
124
+ document.getElementById(`tab-li-${i}`).remove();
125
+ document.getElementById(`pane-${i}`).remove();
126
+
127
+ // Rebuild IDs
128
+ const panes = document.querySelectorAll(".tab-pane");
129
+ panes.forEach((p, index) => {
130
+ p.id = `pane-${index}`;
131
+ p.querySelector("form").id = `configForm-${index}`;
132
+ p.querySelector(`[id^="name-"]`).id = `name-${index}`;
133
+ p.querySelector(`[id^="user-"]`).id = `user-${index}`;
134
+ p.querySelector(`[id^="passwd-"]`).id = `passwd-${index}`;
135
+ p.querySelector(`[id^="language-"]`).id = `language-${index}`;
136
+ p.querySelector(`[id^="logIn-"]`).id = `logIn-${index}`;
137
+ p.querySelector(`[id^="remove-"]`).id = `remove-${index}`;
138
+ });
139
+ const tabButtons = document.querySelectorAll(".nav-link");
140
+ tabButtons.forEach((btn, idx) => {
141
+ btn.id = `tab-${idx}`;
142
+ btn.dataset.bsTarget = `#pane-${idx}`;
143
+ });
174
144
 
175
- document.getElementById('logIn').addEventListener('click', async () => {
176
- homebridge.showSpinner();
177
- document.getElementById(`logIn`).setAttribute("class", "btn btn-primary");
178
- updateInfo('info', 'Connecting...', 'yellow');
145
+ await homebridge.updatePluginConfig(pluginConfig);
146
+ await homebridge.savePluginConfig(pluginConfig);
179
147
 
180
- try {
181
- const account = pluginConfig[0].accounts[this.deviceIndex];
182
- const { name: accountName, user, passwd, language } = account;
183
- const payload = { accountName, user, passwd, language };
184
- const devicesInMelCloud = await homebridge.request('/connect', payload);
148
+ if (accounts.length > 0) new bootstrap.Tab(document.getElementById("tab-0")).show();
149
+ });
185
150
 
186
- const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
187
- const devicesByTypeInMelCloud = { ata: [], atw: [], erv: [] };
151
+ // Login button
152
+ document.getElementById(`logIn-${i}`).addEventListener("click", async () => {
153
+ homebridge.showSpinner();
154
+ try {
155
+ const account = accounts[i];
156
+ const devicesInMelCloud = await homebridge.request('/connect', {
157
+ accountName: account.name,
158
+ user: account.user,
159
+ passwd: account.passwd,
160
+ language: account.language
161
+ });
188
162
 
189
- for (const deviceInMelcloud of devicesInMelCloud) {
190
- switch (deviceInMelcloud.Type) {
191
- case 0: devicesByTypeInMelCloud.ata.push(deviceInMelcloud); break;
192
- case 1: devicesByTypeInMelCloud.atw.push(deviceInMelcloud); break;
193
- case 3: devicesByTypeInMelCloud.erv.push(deviceInMelcloud); break;
163
+ const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
164
+ const devicesByType = { ata: [], atw: [], erv: [] };
165
+ for (const d of devicesInMelCloud) {
166
+ if (d.Type === 0) devicesByType.ata.push(d);
167
+ if (d.Type === 1) devicesByType.atw.push(d);
168
+ if (d.Type === 3) devicesByType.erv.push(d);
194
169
  }
195
- }
196
170
 
197
- const ataDevicesInConfig = account.ataDevices ?? (account.ataDevices = []);
198
- const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
199
- const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
200
-
201
- const removedAta = removeStaleDevices(ataDevicesInConfig, devicesByTypeInMelCloud.ata);
202
- const removedAtw = removeStaleDevices(atwDevicesInConfig, devicesByTypeInMelCloud.atw);
203
- const removedErv = removeStaleDevices(ervDevicesInConfig, devicesByTypeInMelCloud.erv);
204
-
205
- // === Handle ATA devices ===
206
- devicesByTypeInMelCloud.ata.forEach(device => {
207
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
208
- const deviceAta = {
209
- id: deviceId,
210
- type: deviceType,
211
- typeString: "Air Conditioner",
212
- name: deviceName,
213
- displayMode: 1,
214
- heatDryFanMode: 1,
215
- coolDryFanMode: 1,
216
- autoDryFanMode: 1,
217
- temperatureSensor: false,
218
- temperatureSensorOutdoor: false,
219
- presets: [],
220
- buttonsSensors: []
221
- };
222
- if (!ataDevicesInConfig.some(d => d.id === deviceId)) {
223
- ataDevicesInConfig.push(deviceAta);
224
- newDevices.ata.push(deviceAta);
225
- }
226
- devicePresets.forEach(preset => {
227
- const { ID: presetId, NumberDescription: presetName } = preset;
228
- preset.id = presetId;
229
- preset.name = presetName;
230
- preset.displayType = 0;
231
- preset.namePrefix = false;
232
- const ataDevice = ataDevicesInConfig.find(d => d.id === deviceId);
233
- if (ataDevice && !ataDevice.presets.some(p => p.id === presetId)) {
234
- ataDevice.presets.push(preset);
235
- newDevices.ataPresets.push(preset);
171
+ // Remove stale function
172
+ function removeStaleDevices(configDevices, melcloudDevices) {
173
+ const melcloudIds = melcloudDevices.map(d => d.DeviceID);
174
+ const removed = [];
175
+ for (let j = configDevices.length - 1; j >= 0; j--) {
176
+ const dev = configDevices[j];
177
+ if (dev.id !== 0 && !melcloudIds.includes(dev.id)) {
178
+ removed.push(dev);
179
+ configDevices.splice(j, 1);
180
+ }
236
181
  }
237
- });
238
- });
182
+ return removed;
183
+ }
239
184
 
240
- // === Handle ATW devices ===
241
- devicesByTypeInMelCloud.atw.forEach(device => {
242
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
243
- const deviceAtw = {
244
- id: deviceId,
245
- type: deviceType,
246
- typeString: "Heat Pump",
247
- name: deviceName,
248
- displayMode: 1,
249
- hideZone: 0,
250
- temperatureSensor: false,
251
- presets: [],
252
- buttonsSensors: []
253
- };
254
- if (!atwDevicesInConfig.some(d => d.id === deviceId)) {
255
- atwDevicesInConfig.push(deviceAtw);
256
- newDevices.atw.push(deviceAtw);
185
+ const ataDevices = account.ataDevices ?? (account.ataDevices = []);
186
+ const atwDevices = account.atwDevices ?? (account.atwDevices = []);
187
+ const ervDevices = account.ervDevices ?? (account.ervDevices = []);
188
+
189
+ const removedAta = removeStaleDevices(ataDevices, devicesByType.ata);
190
+ const removedAtw = removeStaleDevices(atwDevices, devicesByType.atw);
191
+ const removedErv = removeStaleDevices(ervDevices, devicesByType.erv);
192
+
193
+ // Add new devices & presets
194
+ function handleDevices(configArray, devicesArray, typeName, newDevicesArr, newPresetsArr) {
195
+ devicesArray.forEach(d => {
196
+ const existing = configArray.find(x => x.id === d.DeviceID);
197
+ if (!existing) {
198
+ const devObj = {
199
+ id: d.DeviceID,
200
+ type: d.Type,
201
+ typeString: typeName,
202
+ name: d.DeviceName,
203
+ displayMode: 1,
204
+ presets: [],
205
+ buttonsSensors: []
206
+ };
207
+ configArray.push(devObj);
208
+ newDevicesArr.push(devObj);
209
+ }
210
+ (d.Presets || []).forEach(p => {
211
+ const preset = { ...p, id: p.ID, name: p.NumberDescription, displayType: 0, namePrefix: false };
212
+ if (!existing?.presets.some(pr => pr.id === p.ID)) {
213
+ existing?.presets.push(preset);
214
+ newPresetsArr.push(preset);
215
+ }
216
+ });
217
+ });
257
218
  }
258
- devicePresets.forEach(preset => {
259
- const { ID: presetId, NumberDescription: presetName } = preset;
260
- preset.id = presetId;
261
- preset.name = presetName;
262
- preset.displayType = 0;
263
- preset.namePrefix = false;
264
- const atwDevice = atwDevicesInConfig.find(d => d.id === deviceId);
265
- if (atwDevice && !atwDevice.presets.some(p => p.id === presetId)) {
266
- atwDevice.presets.push(preset);
267
- newDevices.atwPresets.push(preset);
268
- }
269
- });
270
- });
271
219
 
272
- // === Handle ERV devices ===
273
- devicesByTypeInMelCloud.erv.forEach(device => {
274
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
275
- const deviceErv = {
276
- id: deviceId,
277
- type: deviceType,
278
- typeString: "ERV",
279
- name: deviceName,
280
- displayMode: 1,
281
- temperatureSensor: false,
282
- presets: [],
283
- buttonsSensors: []
284
- };
285
- if (!ervDevicesInConfig.some(d => d.id === deviceId)) {
286
- ervDevicesInConfig.push(deviceErv);
287
- newDevices.erv.push(deviceErv);
220
+ handleDevices(ataDevices, devicesByType.ata, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
221
+ handleDevices(atwDevices, devicesByType.atw, "Heat Pump", newDevices.atw, newDevices.atwPresets);
222
+ handleDevices(ervDevices, devicesByType.erv, "ERV", newDevices.erv, newDevices.ervPresets);
223
+
224
+ // Info display
225
+ function updateInfo(id, text, color) {
226
+ const el = document.getElementById(id);
227
+ if (el) { el.innerHTML = text; el.style.color = color; }
288
228
  }
289
- devicePresets.forEach(preset => {
290
- const { ID: presetId, NumberDescription: presetName } = preset;
291
- preset.id = presetId;
292
- preset.name = presetName;
293
- preset.displayType = 0;
294
- preset.namePrefix = false;
295
- const ervDevice = ervDevicesInConfig.find(d => d.id === deviceId);
296
- if (ervDevice && !ervDevice.presets.some(p => p.id === presetId)) {
297
- ervDevice.presets.push(preset);
298
- newDevices.ervPresets.push(preset);
299
- }
300
- });
301
- });
302
229
 
303
- // --- Display Info Section ---
304
- const textAta = `ATA: ${newDevices.ata.length}`;
305
- const textAtw = `ATW: ${newDevices.atw.length}`;
306
- const textErv = `ERV: ${newDevices.erv.length}`;
307
- const textAtaPresets = `ATA Presets: ${newDevices.ataPresets.length}`;
308
- const textAtwPresets = `ATW Presets: ${newDevices.atwPresets.length}`;
309
- const textErvPresets = `ERV Presets: ${newDevices.ervPresets.length}`;
310
- const textRemovedAta = `ATA: ${removedAta.length}`;
311
- const textRemovedAtw = `ATW: ${removedAtw.length}`;
312
- const textRemovedErv = `ERV: ${removedErv.length}`;
230
+ const textInfo = `Found devices: ATA(${newDevices.ata.length}), ATW(${newDevices.atw.length}), ERV(${newDevices.erv.length})`;
231
+ const textPresets = `Found presets: ATA(${newDevices.ataPresets.length}), ATW(${newDevices.atwPresets.length}), ERV(${newDevices.ervPresets.length})`;
232
+ const textRemoved = `Removed devices: ATA(${removedAta.length}), ATW(${removedAtw.length}), ERV(${removedErv.length})`;
313
233
 
314
- const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
315
- const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
316
- const removedCount = removedAta.length + removedAtw.length + removedErv.length;
234
+ updateInfo('info', textInfo, newDevices.ata.length + newDevices.atw.length + newDevices.erv.length > 0 ? 'green' : 'white');
235
+ updateInfo('info1', textPresets, newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length > 0 ? 'green' : 'white');
236
+ updateInfo('info2', textRemoved, removedAta.length + removedAtw.length + removedErv.length > 0 ? 'orange' : 'white');
317
237
 
318
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedCount === 0) {
319
- updateInfo('info', 'No changes detected.', 'white');
320
- } else {
321
- if (newDevicesCount > 0) {
322
- updateInfo('info', `Found new devices: ${textAta}, ${textAtw}, ${textErv}.`, 'green');
323
- }
324
- if (newPresetsCount > 0) {
325
- updateInfo('info1', `Found new presets: ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
326
- }
327
- if (removedCount > 0) {
328
- updateInfo('info2', `Removed devices: ${textRemovedAta}, ${textRemovedAtw}, ${textRemovedErv}.`, 'orange');
329
- }
238
+ await homebridge.updatePluginConfig(pluginConfig);
239
+ await homebridge.savePluginConfig(pluginConfig);
240
+ } catch (err) {
241
+ document.getElementById('info').innerHTML = `Error: ${err.message || err}`;
242
+ } finally {
243
+ homebridge.hideSpinner();
330
244
  }
245
+ });
246
+ }
331
247
 
332
- await homebridge.updatePluginConfig(pluginConfig);
333
- await homebridge.savePluginConfig(pluginConfig);
334
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
335
- homebridge.hideSpinner();
248
+ // Render accounts
249
+ accounts.forEach((acc, i) => createAccountForm(acc, i));
250
+
251
+ // Add "Add Account" tab
252
+ const addLi = document.createElement("li");
253
+ addLi.classList.add("nav-item");
254
+ addLi.innerHTML = `<button class="nav-link" id="addTab" type="button" role="tab">➕ Add Account</button>`;
255
+ tabs.appendChild(addLi);
336
256
 
337
- } catch (error) {
338
- updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
339
- updateInfo('info1', `Error: ${error}`, 'red');
340
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
341
- } finally {
342
- homebridge.hideSpinner();
343
- }
257
+ document.getElementById("addTab").addEventListener("click", async () => {
258
+ const newAccount = { name: "", user: "", passwd: "", language: 0 };
259
+ accounts.push(newAccount);
260
+ const i = accounts.length - 1;
261
+ createAccountForm(newAccount, i);
262
+ await homebridge.updatePluginConfig(pluginConfig);
263
+ await homebridge.savePluginConfig(pluginConfig);
264
+ new bootstrap.Tab(document.getElementById(`tab-${i}`)).show();
344
265
  });
266
+
345
267
  })();
346
268
  </script>
347
269
  </body>
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.7",
4
+ "version": "3.9.0-beta.9",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",