homebridge-melcloud-control 3.9.0-beta.8 → 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,367 +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
- <!-- Password field with toggle -->
39
- <div class="mb-3 position-relative">
40
- <label for="passwd" class="form-label">Password</label>
41
- <div class="input-group">
42
- <input id="passwd" type="password" class="form-control" autocomplete="off" required>
43
- <button type="button" id="togglePasswd" class="btn btn-outline-secondary">
44
- <i class="fas fa-eye"></i>
45
- </button>
46
- </div>
47
- </div>
18
+ <!-- Tabs -->
19
+ <ul class="nav nav-tabs mt-3" id="accountTabs" role="tablist"></ul>
48
20
 
49
- <div class="mb-3">
50
- <label for="language" class="form-label">Language</label>
51
- <select id="language" name="language" class="form-control">
52
- <option value="0">English</option>
53
- <option value="1">Български</option>
54
- <option value="2">Čeština</option>
55
- <option value="3">Dansk</option>
56
- <option value="4">Deutsch</option>
57
- <option value="5">Eesti</option>
58
- <option value="6">Español</option>
59
- <option value="7">Français</option>
60
- <option value="8">Հայերեն</option>
61
- <option value="9">Latviešu</option>
62
- <option value="10">Lietuvių</option>
63
- <option value="11">Magyar</option>
64
- <option value="12">Nederlands</option>
65
- <option value="13">Norwegian</option>
66
- <option value="14">Polski</option>
67
- <option value="15">Português</option>
68
- <option value="16">Русский</option>
69
- <option value="17">Suomi</option>
70
- <option value="18">Svenska</option>
71
- <option value="19">Українська</option>
72
- <option value="20">Türkçe</option>
73
- <option value="21">Ελληνικά</option>
74
- <option value="22">Hrvatski</option>
75
- <option value="23">Română</option>
76
- <option value="24">Slovenščina</option>
77
- </select>
78
- </div>
21
+ <!-- Tab contents -->
22
+ <div class="tab-content mt-3" id="accountTabsContent"></div>
79
23
 
80
- <div class="text-center">
81
- <button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
82
- <button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
83
- </div>
84
- </form>
24
+ <!-- Info Section -->
25
+ <div class="mt-3">
26
+ <div id="info"></div>
27
+ <div id="info1"></div>
28
+ <div id="info2"></div>
85
29
  </div>
86
-
87
- <div id="accountButton" class="mt-3"></div>
88
30
  </div>
89
31
 
90
32
  <script>
91
33
  (async () => {
92
34
  const pluginConfig = await homebridge.getPluginConfig();
93
35
  if (!pluginConfig.length) {
94
- pluginConfig.push({});
36
+ pluginConfig.push({ accounts: [] });
95
37
  await homebridge.updatePluginConfig(pluginConfig);
96
38
  homebridge.showSchemaForm();
97
39
  return;
98
40
  }
99
- this.configButtonState = false;
100
-
101
- const accountsCount = pluginConfig[0].accounts.length;
102
- this.deviceIndex = 0;
103
- for (let i = 0; i < accountsCount; i++) {
104
- const button = document.createElement("button");
105
- button.setAttribute("type", "button");
106
- button.setAttribute("id", `button${i}`);
107
- button.setAttribute("class", "btn btn-primary");
108
- button.style.textTransform = 'none';
109
- button.innerText = pluginConfig[0].accounts[i].name;
110
- document.getElementById("accountButton").appendChild(button);
111
-
112
- document.getElementById(`button${i}`).addEventListener('click', async () => {
113
- for (let j = 0; j < accountsCount; j++) {
114
- j === i
115
- ? document.getElementById(`button${j}`).setAttribute("class", "btn btn-secondary")
116
- : document.getElementById(`button${j}`).setAttribute("class", "btn btn-primary");
117
- }
118
41
 
119
- document.getElementById('accountName').innerHTML = pluginConfig[0].accounts[i].name || '';
120
- document.getElementById('name').value = pluginConfig[0].accounts[i].name || '';
121
- document.getElementById('user').value = pluginConfig[0].accounts[i].user || '';
122
- document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
123
- document.getElementById('language').value = pluginConfig[0].accounts[i].language || '';
124
-
125
- const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
126
- 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;
127
115
 
128
116
  await homebridge.updatePluginConfig(pluginConfig);
129
- this.deviceIndex = i;
117
+ await homebridge.savePluginConfig(pluginConfig);
118
+ document.querySelector(`#tab-${i}`).innerText = account.name || `Account ${i + 1}`;
130
119
  });
131
- if (i === accountsCount - 1) {
132
- document.getElementById(`button0`).click();
133
- await homebridge.updatePluginConfig(pluginConfig);
134
- }
135
- }
136
-
137
- document.getElementById('melCloudAccount').style.display = 'block';
138
-
139
- document.getElementById('configForm').addEventListener('input', async () => {
140
- pluginConfig[0].accounts[this.deviceIndex].name = document.querySelector('#name').value;
141
- pluginConfig[0].accounts[this.deviceIndex].user = document.querySelector('#user').value;
142
- pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
143
- pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
144
-
145
- 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;
146
- document.getElementById('logIn').disabled = !accountConfigured;
147
120
 
148
- await homebridge.updatePluginConfig(pluginConfig);
149
- await homebridge.savePluginConfig(pluginConfig);
150
- });
151
-
152
- document.getElementById('configButton').addEventListener('click', async () => {
153
- this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
154
- this.configButtonState
155
- ? document.getElementById(`configButton`).setAttribute("class", "btn btn-secondary")
156
- : document.getElementById(`configButton`).setAttribute("class", "btn btn-primary");
157
- this.configButtonState = !this.configButtonState;
158
- });
159
-
160
- function removeStaleDevices(configDevices, melcloudDevices) {
161
- const melcloudIds = melcloudDevices.map(d => d.DeviceID);
162
- const removedDevices = [];
163
- for (let i = configDevices.length - 1; i >= 0; i--) {
164
- const device = configDevices[i];
165
- if (device.id !== 0 && !melcloudIds.includes(device.id)) {
166
- removedDevices.push(device);
167
- configDevices.splice(i, 1);
168
- }
169
- }
170
- return removedDevices;
171
- }
172
-
173
- function updateInfo(id, text, color) {
174
- const el = document.getElementById(id);
175
- if (el) {
176
- el.innerHTML = text;
177
- el.style.color = color;
178
- }
179
- }
180
-
181
- // === Password toggle ===
182
- document.getElementById('togglePasswd').addEventListener('click', () => {
183
- const passwdInput = document.getElementById('passwd');
184
- const icon = document.querySelector('#togglePasswd i');
185
- if (passwdInput.type === 'password') {
186
- passwdInput.type = 'text';
187
- icon.classList.replace('fa-eye', 'fa-eye-slash');
188
- } else {
189
- passwdInput.type = 'password';
190
- icon.classList.replace('fa-eye-slash', 'fa-eye');
191
- }
192
- });
193
-
194
- document.getElementById('logIn').addEventListener('click', async () => {
195
- homebridge.showSpinner();
196
- document.getElementById(`logIn`).setAttribute("class", "btn btn-primary");
197
-
198
- // Reset feedback before each login attempt
199
- updateInfo('info', '', '');
200
- updateInfo('info1', '', '');
201
- updateInfo('info2', '', '');
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
+ });
202
144
 
203
- updateInfo('info', 'Connecting...', 'yellow');
145
+ await homebridge.updatePluginConfig(pluginConfig);
146
+ await homebridge.savePluginConfig(pluginConfig);
204
147
 
205
- try {
206
- const account = pluginConfig[0].accounts[this.deviceIndex];
207
- const { name: accountName, user, passwd, language } = account;
208
- const payload = { accountName, user, passwd, language };
209
- const devicesInMelCloud = await homebridge.request('/connect', payload);
148
+ if (accounts.length > 0) new bootstrap.Tab(document.getElementById("tab-0")).show();
149
+ });
210
150
 
211
- const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
212
- 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
+ });
213
162
 
214
- for (const deviceInMelcloud of devicesInMelCloud) {
215
- switch (deviceInMelcloud.Type) {
216
- case 0: devicesByTypeInMelCloud.ata.push(deviceInMelcloud); break;
217
- case 1: devicesByTypeInMelCloud.atw.push(deviceInMelcloud); break;
218
- 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);
219
169
  }
220
- }
221
-
222
- const ataDevicesInConfig = account.ataDevices ?? (account.ataDevices = []);
223
- const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
224
- const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
225
-
226
- const removedAta = removeStaleDevices(ataDevicesInConfig, devicesByTypeInMelCloud.ata);
227
- const removedAtw = removeStaleDevices(atwDevicesInConfig, devicesByTypeInMelCloud.atw);
228
- const removedErv = removeStaleDevices(ervDevicesInConfig, devicesByTypeInMelCloud.erv);
229
170
 
230
- // === Handle ATA devices ===
231
- devicesByTypeInMelCloud.ata.forEach(device => {
232
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
233
- const deviceAta = {
234
- id: deviceId,
235
- type: deviceType,
236
- typeString: "Air Conditioner",
237
- name: deviceName,
238
- displayMode: 1,
239
- heatDryFanMode: 1,
240
- coolDryFanMode: 1,
241
- autoDryFanMode: 1,
242
- temperatureSensor: false,
243
- temperatureSensorOutdoor: false,
244
- presets: [],
245
- buttonsSensors: []
246
- };
247
- if (!ataDevicesInConfig.some(d => d.id === deviceId)) {
248
- ataDevicesInConfig.push(deviceAta);
249
- newDevices.ata.push(deviceAta);
250
- }
251
- devicePresets.forEach(preset => {
252
- const { ID: presetId, NumberDescription: presetName } = preset;
253
- preset.id = presetId;
254
- preset.name = presetName;
255
- preset.displayType = 0;
256
- preset.namePrefix = false;
257
- const ataDevice = ataDevicesInConfig.find(d => d.id === deviceId);
258
- if (ataDevice && !ataDevice.presets.some(p => p.id === presetId)) {
259
- ataDevice.presets.push(preset);
260
- 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
+ }
261
181
  }
262
- });
263
- });
182
+ return removed;
183
+ }
264
184
 
265
- // === Handle ATW devices ===
266
- devicesByTypeInMelCloud.atw.forEach(device => {
267
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
268
- const deviceAtw = {
269
- id: deviceId,
270
- type: deviceType,
271
- typeString: "Heat Pump",
272
- name: deviceName,
273
- displayMode: 1,
274
- hideZone: 0,
275
- temperatureSensor: false,
276
- presets: [],
277
- buttonsSensors: []
278
- };
279
- if (!atwDevicesInConfig.some(d => d.id === deviceId)) {
280
- atwDevicesInConfig.push(deviceAtw);
281
- 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
+ });
282
218
  }
283
- devicePresets.forEach(preset => {
284
- const { ID: presetId, NumberDescription: presetName } = preset;
285
- preset.id = presetId;
286
- preset.name = presetName;
287
- preset.displayType = 0;
288
- preset.namePrefix = false;
289
- const atwDevice = atwDevicesInConfig.find(d => d.id === deviceId);
290
- if (atwDevice && !atwDevice.presets.some(p => p.id === presetId)) {
291
- atwDevice.presets.push(preset);
292
- newDevices.atwPresets.push(preset);
293
- }
294
- });
295
- });
296
219
 
297
- // === Handle ERV devices ===
298
- devicesByTypeInMelCloud.erv.forEach(device => {
299
- const { DeviceID: deviceId, Type: deviceType, DeviceName: deviceName, Presets: devicePresets = [] } = device;
300
- const deviceErv = {
301
- id: deviceId,
302
- type: deviceType,
303
- typeString: "ERV",
304
- name: deviceName,
305
- displayMode: 1,
306
- temperatureSensor: false,
307
- presets: [],
308
- buttonsSensors: []
309
- };
310
- if (!ervDevicesInConfig.some(d => d.id === deviceId)) {
311
- ervDevicesInConfig.push(deviceErv);
312
- 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; }
313
228
  }
314
- devicePresets.forEach(preset => {
315
- const { ID: presetId, NumberDescription: presetName } = preset;
316
- preset.id = presetId;
317
- preset.name = presetName;
318
- preset.displayType = 0;
319
- preset.namePrefix = false;
320
- const ervDevice = ervDevicesInConfig.find(d => d.id === deviceId);
321
- if (ervDevice && !ervDevice.presets.some(p => p.id === presetId)) {
322
- ervDevice.presets.push(preset);
323
- newDevices.ervPresets.push(preset);
324
- }
325
- });
326
- });
327
229
 
328
- // --- Display Info Section ---
329
- const textAta = `ATA: ${newDevices.ata.length}`;
330
- const textAtw = `ATW: ${newDevices.atw.length}`;
331
- const textErv = `ERV: ${newDevices.erv.length}`;
332
- const textAtaPresets = `ATA Presets: ${newDevices.ataPresets.length}`;
333
- const textAtwPresets = `ATW Presets: ${newDevices.atwPresets.length}`;
334
- const textErvPresets = `ERV Presets: ${newDevices.ervPresets.length}`;
335
- const textRemovedAta = `ATA: ${removedAta.length}`;
336
- const textRemovedAtw = `ATW: ${removedAtw.length}`;
337
- 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})`;
338
233
 
339
- const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
340
- const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
341
- 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');
342
237
 
343
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedCount === 0) {
344
- updateInfo('info', 'No changes detected.', 'white');
345
- } else {
346
- if (newDevicesCount > 0) {
347
- updateInfo('info', `Found new devices: ${textAta}, ${textAtw}, ${textErv}.`, 'green');
348
- }
349
- if (newPresetsCount > 0) {
350
- updateInfo('info1', `Found new presets: ${textAtaPresets}, ${textAtwPresets}, ${textErvPresets}.`, 'green');
351
- }
352
- if (removedCount > 0) {
353
- updateInfo('info2', `Removed devices: ${textRemovedAta}, ${textRemovedAtw}, ${textRemovedErv}.`, 'orange');
354
- }
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();
355
244
  }
245
+ });
246
+ }
356
247
 
357
- await homebridge.updatePluginConfig(pluginConfig);
358
- await homebridge.savePluginConfig(pluginConfig);
359
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
360
- 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);
361
256
 
362
- } catch (error) {
363
- updateInfo('info', 'Check your credentials and try again.', 'yellow');
364
- updateInfo('info1', `Error: ${error?.message || 'Unknown error'}`, 'red');
365
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
366
- } finally {
367
- homebridge.hideSpinner();
368
- }
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();
369
265
  });
266
+
370
267
  })();
371
268
  </script>
372
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.8",
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",