homebridge-melcloud-control 3.9.0-beta.1 → 3.9.0-beta.11

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,326 +6,279 @@
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
- <img src="homebridge-melcloud-control.png" alt="Image" height="120" />
15
+ <img src="homebridge-melcloud-control.png" alt="Image" height="100" />
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: 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
- </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>
42
-
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>
73
-
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>
79
- </div>
18
+ <!-- Tabs -->
19
+ <ul class="nav nav-tabs mt-3" id="accountTabs" role="tablist"></ul>
80
20
 
81
- <div id="accountButton" class="mt-3"></div>
21
+ <!-- Tab contents -->
22
+ <div class="tab-content mt-3" id="accountTabsContent"></div>
23
+
24
+ <!-- Info Section -->
25
+ <div class="mt-3">
26
+ <div id="info"></div>
27
+ <div id="info1"></div>
28
+ <div id="info2"></div>
29
+ </div>
82
30
  </div>
83
31
 
84
32
  <script>
85
33
  (async () => {
86
34
  const pluginConfig = await homebridge.getPluginConfig();
87
-
88
35
  if (!pluginConfig.length) {
89
- pluginConfig.push({});
36
+ pluginConfig.push({ accounts: [] });
90
37
  await homebridge.updatePluginConfig(pluginConfig);
91
38
  homebridge.showSchemaForm();
92
39
  return;
93
40
  }
94
- this.configButtonState = false;
95
-
96
- const accountsCount = pluginConfig[0].accounts.length;
97
- this.deviceIndex = 0;
98
-
99
- for (let i = 0; i < accountsCount; i++) {
100
- const button = document.createElement("button");
101
- button.setAttribute("type", "button");
102
- button.setAttribute("id", `button${i}`);
103
- button.setAttribute("class", "btn btn-primary");
104
- button.style.textTransform = 'none';
105
- button.innerText = pluginConfig[0].accounts[i].name;
106
- document.getElementById("accountButton").appendChild(button);
107
-
108
- document.getElementById(`button${i}`).addEventListener('click', async () => {
109
- 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
- );
114
- }
115
-
116
- document.getElementById('accountName').innerHTML = pluginConfig[0].accounts[i].name || '';
117
- document.getElementById('name').value = pluginConfig[0].accounts[i].name || '';
118
- document.getElementById('user').value = pluginConfig[0].accounts[i].user || '';
119
- document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
120
- document.getElementById('language').value = pluginConfig[0].accounts[i].language || '';
121
41
 
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;
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
+ <button type="button" class="btn btn-primary" id="configButton">⚙️ Config</button>
107
+ </form>`;
108
+ content.appendChild(pane);
109
+
110
+ // Handle input updates
111
+ pane.querySelector("form").addEventListener("input", async () => {
112
+ account.name = document.getElementById(`name-${i}`).value;
113
+ account.user = document.getElementById(`user-${i}`).value;
114
+ account.passwd = document.getElementById(`passwd-${i}`).value;
115
+ account.language = document.getElementById(`language-${i}`).value;
130
116
 
131
117
  await homebridge.updatePluginConfig(pluginConfig);
132
- this.deviceIndex = i;
118
+ await homebridge.savePluginConfig(pluginConfig);
119
+ document.querySelector(`#tab-${i}`).innerText = account.name || `Account ${i + 1}`;
133
120
  });
134
121
 
135
- if (i === accountsCount - 1) {
136
- document.getElementById(`button0`).click();
137
- await homebridge.updatePluginConfig(pluginConfig);
138
- }
139
- }
140
-
141
- document.getElementById('melCloudAccount').style.display = 'block';
142
-
143
- document.getElementById('configForm').addEventListener('input', async () => {
144
- pluginConfig[0].accounts[this.deviceIndex].name = document.querySelector('#name').value;
145
- pluginConfig[0].accounts[this.deviceIndex].user = document.querySelector('#user').value;
146
- pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
147
- pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
122
+ // Remove account
123
+ document.getElementById(`remove-${i}`).addEventListener("click", async () => {
124
+ accounts.splice(i, 1);
125
+ document.getElementById(`tab-li-${i}`).remove();
126
+ document.getElementById(`pane-${i}`).remove();
127
+
128
+ // Rebuild IDs
129
+ const panes = document.querySelectorAll(".tab-pane");
130
+ panes.forEach((p, index) => {
131
+ p.id = `pane-${index}`;
132
+ p.querySelector("form").id = `configForm-${index}`;
133
+ p.querySelector(`[id^="name-"]`).id = `name-${index}`;
134
+ p.querySelector(`[id^="user-"]`).id = `user-${index}`;
135
+ p.querySelector(`[id^="passwd-"]`).id = `passwd-${index}`;
136
+ p.querySelector(`[id^="language-"]`).id = `language-${index}`;
137
+ p.querySelector(`[id^="logIn-"]`).id = `logIn-${index}`;
138
+ p.querySelector(`[id^="remove-"]`).id = `remove-${index}`;
139
+ });
140
+ const tabButtons = document.querySelectorAll(".nav-link");
141
+ tabButtons.forEach((btn, idx) => {
142
+ btn.id = `tab-${idx}`;
143
+ btn.dataset.bsTarget = `#pane-${idx}`;
144
+ });
148
145
 
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;
156
-
157
- await homebridge.updatePluginConfig(pluginConfig);
158
- await homebridge.savePluginConfig(pluginConfig);
159
- });
160
-
161
- 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");
166
- this.configButtonState = !this.configButtonState;
167
- });
168
-
169
- document.getElementById('logIn').addEventListener('click', async () => {
170
- homebridge.showSpinner();
171
-
172
- document.getElementById(`logIn`).setAttribute("class", "btn btn-primary");
173
- document.getElementById('info').innerHTML = 'Connecting...';
174
- document.getElementById('info').style.color = 'yellow';
146
+ await homebridge.updatePluginConfig(pluginConfig);
147
+ await homebridge.savePluginConfig(pluginConfig);
175
148
 
176
- try {
177
- const account = pluginConfig[0].accounts[this.deviceIndex];
178
- const { name: accountName, user, passwd, language } = account;
149
+ if (accounts.length > 0) new bootstrap.Tab(document.getElementById("tab-0")).show();
150
+ });
179
151
 
180
- const payload = { accountName, user, passwd, language };
181
- const devicesInMelCloud = await homebridge.request('/connect', payload);
152
+ document.getElementById('configButton').addEventListener('click', async () => {
153
+ if (this.configButtonState) {
154
+ homebridge.hideSchemaForm();
155
+ document.getElementById('configButton').className = "btn btn-secondary";
156
+ } else {
157
+ homebridge.showSchemaForm();
158
+ document.getElementById('configButton').className = "btn btn-primary";
159
+ }
160
+ this.configButtonState = !this.configButtonState;
161
+ });
182
162
 
183
- const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
184
- const removedDevices = { ata: [], atw: [], erv: [] };
185
- const devicesByTypeInMelCloud = { ata: [], atw: [], erv: [] };
163
+ // Initialize state
164
+ this.configButtonState = false;
165
+
166
+ // Login button
167
+ document.getElementById(`logIn-${i}`).addEventListener("click", async () => {
168
+ homebridge.showSpinner();
169
+ try {
170
+ const account = accounts[i];
171
+ const devicesInMelCloud = await homebridge.request('/connect', {
172
+ accountName: account.name,
173
+ user: account.user,
174
+ passwd: account.passwd,
175
+ language: account.language
176
+ });
186
177
 
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;
178
+ const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
179
+ const devicesByType = { ata: [], atw: [], erv: [] };
180
+ for (const d of devicesInMelCloud) {
181
+ if (d.Type === 0) devicesByType.ata.push(d);
182
+ if (d.Type === 1) devicesByType.atw.push(d);
183
+ if (d.Type === 3) devicesByType.erv.push(d);
192
184
  }
193
- }
194
185
 
195
- const ataDevicesInConfig = account.ataDevices ?? (account.ataDevices = []);
196
- const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
197
- const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
186
+ // Remove stale function
187
+ function removeStaleDevices(configDevices, melcloudDevices) {
188
+ const melcloudIds = melcloudDevices.map(d => d.DeviceID);
189
+ const removed = [];
190
+ for (let j = configDevices.length - 1; j >= 0; j--) {
191
+ const dev = configDevices[j];
192
+ if (dev.id !== 0 && !melcloudIds.includes(dev.id)) {
193
+ removed.push(dev);
194
+ configDevices.splice(j, 1);
195
+ }
196
+ }
197
+ return removed;
198
+ }
198
199
 
199
- const updateInfo = (id, text, color) => {
200
- const el = document.getElementById(id);
201
- if (el) {
202
- el.innerHTML = text;
203
- el.style.color = color;
200
+ const ataDevices = account.ataDevices ?? (account.ataDevices = []);
201
+ const atwDevices = account.atwDevices ?? (account.atwDevices = []);
202
+ const ervDevices = account.ervDevices ?? (account.ervDevices = []);
203
+
204
+ const removedAta = removeStaleDevices(ataDevices, devicesByType.ata);
205
+ const removedAtw = removeStaleDevices(atwDevices, devicesByType.atw);
206
+ const removedErv = removeStaleDevices(ervDevices, devicesByType.erv);
207
+
208
+ // Add new devices & presets
209
+ function handleDevices(configArray, devicesArray, typeName, newDevicesArr, newPresetsArr) {
210
+ devicesArray.forEach(d => {
211
+ const existing = configArray.find(x => x.id === d.DeviceID);
212
+ if (!existing) {
213
+ const devObj = {
214
+ id: d.DeviceID,
215
+ type: d.Type,
216
+ typeString: typeName,
217
+ name: d.DeviceName,
218
+ displayMode: 1,
219
+ presets: [],
220
+ buttonsSensors: []
221
+ };
222
+ configArray.push(devObj);
223
+ newDevicesArr.push(devObj);
224
+ }
225
+ (d.Presets || []).forEach(p => {
226
+ const preset = { ...p, id: p.ID, name: p.NumberDescription, displayType: 0, namePrefix: false };
227
+ if (!existing?.presets.some(pr => pr.id === p.ID)) {
228
+ existing?.presets.push(preset);
229
+ newPresetsArr.push(preset);
230
+ }
231
+ });
232
+ });
204
233
  }
205
- };
206
234
 
207
- function handleDevices(devices, configArray, newArray, newPresetsArray, removedArray, typeString, template) {
208
- const melcloudIds = devices.map(d => d.DeviceID);
235
+ handleDevices(ataDevices, devicesByType.ata, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
236
+ handleDevices(atwDevices, devicesByType.atw, "Heat Pump", newDevices.atw, newDevices.atwPresets);
237
+ handleDevices(ervDevices, devicesByType.erv, "ERV", newDevices.erv, newDevices.ervPresets);
209
238
 
210
- // remove stale devices
211
- for (let i = configArray.length - 1; i >= 0; i--) {
212
- if (!melcloudIds.includes(configArray[i].id)) {
213
- removedArray.push(configArray[i]);
214
- configArray.splice(i, 1);
215
- }
239
+ // Info display
240
+ function updateInfo(id, text, color) {
241
+ const el = document.getElementById(id);
242
+ if (el) { el.innerHTML = text; el.style.color = color; }
216
243
  }
217
244
 
218
- devices.forEach((device) => {
219
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
220
-
221
- const deviceObj = {
222
- id: deviceId,
223
- type: deviceType,
224
- typeString,
225
- name: deviceName,
226
- ...template,
227
- presets: [],
228
- buttonsSensors: []
229
- };
230
-
231
- if (!configArray.some(d => d.id === deviceId)) {
232
- configArray.push(deviceObj);
233
- newArray.push(deviceObj);
234
- }
245
+ const textInfo = `Found devices: ATA(${newDevices.ata.length}), ATW(${newDevices.atw.length}), ERV(${newDevices.erv.length})`;
246
+ const textPresets = `Found presets: ATA(${newDevices.ataPresets.length}), ATW(${newDevices.atwPresets.length}), ERV(${newDevices.ervPresets.length})`;
247
+ const textRemoved = `Removed devices: ATA(${removedAta.length}), ATW(${removedAtw.length}), ERV(${removedErv.length})`;
235
248
 
236
- devicePresets.forEach((preset) => {
237
- const { ID: presetId, NumberDescription: presetName } = preset;
238
- Object.assign(preset, { id: presetId, name: presetName, displayType: 0, namePrefix: false });
249
+ updateInfo('info', textInfo, newDevices.ata.length + newDevices.atw.length + newDevices.erv.length > 0 ? 'green' : 'white');
250
+ updateInfo('info1', textPresets, newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length > 0 ? 'green' : 'white');
251
+ updateInfo('info2', textRemoved, removedAta.length + removedAtw.length + removedErv.length > 0 ? 'orange' : 'white');
239
252
 
240
- const existingDevice = configArray.find(d => d.id === deviceId);
241
- if (existingDevice && !existingDevice.presets.some(p => p.id === presetId)) {
242
- existingDevice.presets.push(preset);
243
- newPresetsArray.push(preset);
244
- }
245
- });
246
- });
253
+ await homebridge.updatePluginConfig(pluginConfig);
254
+ await homebridge.savePluginConfig(pluginConfig);
255
+ } catch (err) {
256
+ document.getElementById('info').innerHTML = `Error: ${err.message || err}`;
257
+ } finally {
258
+ homebridge.hideSpinner();
247
259
  }
260
+ });
261
+ }
248
262
 
249
- const ataTemplate = {
250
- displayMode: 1,
251
- heatDryFanMode: 1,
252
- coolDryFanMode: 1,
253
- autoDryFanMode: 1,
254
- temperatureSensor: false,
255
- temperatureSensorOutdoor: false,
256
- };
257
-
258
- const atwTemplate = {
259
- displayMode: 1,
260
- hideZone: 0,
261
- temperatureSensor: false,
262
- temperatureSensorFlow: false,
263
- temperatureSensorReturn: false,
264
- temperatureSensorFlowZone1: false,
265
- temperatureSensorReturnZone1: false,
266
- temperatureSensorFlowWaterTank: false,
267
- temperatureSensorReturnWaterTank: false,
268
- temperatureSensorFlowZone2: false,
269
- temperatureSensorReturnZone2: false,
270
- temperatureSensorOutdoor: false,
271
- };
272
-
273
- const ervTemplate = {
274
- displayMode: 1,
275
- temperatureSensor: false,
276
- temperatureSensorOutdoor: false,
277
- temperatureSensorSupply: false,
278
- };
279
-
280
- handleDevices(devicesByTypeInMelCloud.ata, ataDevicesInConfig, newDevices.ata, newDevices.ataPresets, removedDevices.ata, "Air Conditioner", ataTemplate);
281
- handleDevices(devicesByTypeInMelCloud.atw, atwDevicesInConfig, newDevices.atw, newDevices.atwPresets, removedDevices.atw, "Heat Pump", atwTemplate);
282
- handleDevices(devicesByTypeInMelCloud.erv, ervDevicesInConfig, newDevices.erv, newDevices.ervPresets, removedDevices.erv, "Energy Recovery Ventilation", ervTemplate);
283
-
284
- const textAta = `ATA: ${newDevices.ata.length}`;
285
- const textAtw = `ATW: ${newDevices.atw.length}`;
286
- const textErv = `ERV: ${newDevices.erv.length}`;
287
- const textRemovedAta = `ATA: ${removedDevices.ata.length}`;
288
- const textRemovedAtw = `ATW: ${removedDevices.atw.length}`;
289
- const textRemovedErv = `ERV: ${removedDevices.erv.length}`;
290
- const textAtaPresets = `ATA Presets: ${newDevices.ataPresets.length}`;
291
- const textAtwPresets = `ATW Presets: ${newDevices.atwPresets.length}`;
292
- const textErvPresets = `ERV Presets: ${newDevices.ervPresets.length}`;
293
-
294
- const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
295
- const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
296
- const removedDevicesCount = removedDevices.ata.length + removedDevices.atw.length + removedDevices.erv.length;
297
-
298
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedDevicesCount === 0) {
299
- updateInfo('info', 'No changes (already synced).', 'white');
300
- } else {
301
- if (newDevicesCount > 0) {
302
- updateInfo('info', `Found, ${textAta}, ${textAtw}, ${textErv}.`, 'green');
303
- updateInfo('info2', 'Now You can configure it.', 'green');
304
- }
305
- if (newPresetsCount > 0) {
306
- updateInfo('info1', `${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
307
- if (newDevicesCount === 0) {
308
- updateInfo('info', `Found, ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
309
- updateInfo('info1', 'Now You can configure it.', 'green');
310
- }
311
- }
312
- if (removedDevicesCount > 0) {
313
- updateInfo('info', `Removed, ${textRemovedAta}, ${textRemovedAtw}, ${textRemovedErv}.`, 'orange');
314
- }
315
- }
263
+ // Render accounts
264
+ accounts.forEach((acc, i) => createAccountForm(acc, i));
316
265
 
317
- await homebridge.savePluginConfig(pluginConfig);
266
+ // Add "Add Account" tab
267
+ const addLi = document.createElement("li");
268
+ addLi.classList.add("nav-item");
269
+ addLi.innerHTML = `<button class="nav-link" id="addTab" type="button" role="tab">➕ Add Account</button>`;
270
+ tabs.appendChild(addLi);
318
271
 
319
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
320
- homebridge.hideSpinner();
321
- } catch (error) {
322
- updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
323
- updateInfo('info1', `Error: ${error.message ?? JSON.stringify(error)}`, 'red');
324
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
325
- } finally {
326
- homebridge.hideSpinner();
327
- }
272
+ document.getElementById("addTab").addEventListener("click", async () => {
273
+ const newAccount = { name: "", user: "", passwd: "", language: 0 };
274
+ accounts.push(newAccount);
275
+ const i = accounts.length - 1;
276
+ createAccountForm(newAccount, i);
277
+ await homebridge.updatePluginConfig(pluginConfig);
278
+ await homebridge.savePluginConfig(pluginConfig);
279
+ new bootstrap.Tab(document.getElementById(`tab-${i}`)).show();
328
280
  });
281
+
329
282
  })();
330
283
  </script>
331
284
  </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.1",
4
+ "version": "3.9.0-beta.11",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",