homebridge-melcloud-control 3.9.0-beta.2 → 3.9.0-beta.20

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: 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>
23
+ <label id="info" class="d-block" style="font-size: 14px;"></label>
24
+ <label id="info1" class="d-block" style="font-size: 12px;"></label>
25
+ <label id="info2" class="d-block" style="font-size: 12px;"></label>
26
26
  </div>
27
27
 
28
28
  <div class="mb-3">
@@ -35,9 +35,14 @@
35
35
  <input id="user" type="text" class="form-control" required>
36
36
  </div>
37
37
 
38
- <div class="mb-3">
38
+ <div class="mb-3 position-relative">
39
39
  <label for="passwd" class="form-label">Password</label>
40
- <input id="passwd" type="password" class="form-control" autocomplete="off" required>
40
+ <div class="input-group">
41
+ <input id="passwd" type="password" class="form-control" autocomplete="off" required>
42
+ <button type="button" id="togglePasswd" class="btn btn-outline-secondary">
43
+ <i class="fas fa-eye"></i>
44
+ </button>
45
+ </div>
41
46
  </div>
42
47
 
43
48
  <div class="mb-3">
@@ -84,211 +89,188 @@
84
89
  <script>
85
90
  (async () => {
86
91
  const pluginConfig = await homebridge.getPluginConfig();
87
-
88
92
  if (!pluginConfig.length) {
89
93
  pluginConfig.push({});
90
94
  await homebridge.updatePluginConfig(pluginConfig);
91
95
  homebridge.showSchemaForm();
92
96
  return;
93
97
  }
94
- this.configButtonState = false;
95
98
 
96
- const accountsCount = pluginConfig[0].accounts.length;
97
99
  this.deviceIndex = 0;
98
100
 
101
+ const accountsCount = pluginConfig[0].accounts.length;
99
102
  for (let i = 0; i < accountsCount; i++) {
100
103
  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.type = "button";
105
+ button.id = `button${i}`;
106
+ button.className = "btn btn-primary";
104
107
  button.style.textTransform = 'none';
105
108
  button.innerText = pluginConfig[0].accounts[i].name;
106
109
  document.getElementById("accountButton").appendChild(button);
107
110
 
108
- document.getElementById(`button${i}`).addEventListener('click', async () => {
111
+ button.addEventListener('click', async () => {
109
112
  for (let j = 0; j < accountsCount; j++) {
110
- document.getElementById(`button${j}`).setAttribute(
111
- "class",
112
- j === i ? "btn btn-secondary" : "btn btn-primary"
113
- );
113
+ document.getElementById(`button${j}`).className = (j === i ? 'btn btn-secondary' : 'btn btn-primary');
114
114
  }
115
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
-
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;
116
+ const acc = pluginConfig[0].accounts[i];
117
+ document.getElementById('accountName').innerText = acc.name || '';
118
+ document.getElementById('name').value = acc.name || '';
119
+ document.getElementById('user').value = acc.user || '';
120
+ document.getElementById('passwd').value = acc.passwd || '';
121
+ document.getElementById('language').value = acc.language || '';
127
122
 
128
- const loginBtn = document.getElementById('logIn');
129
- loginBtn.disabled = !accountConfigured;
123
+ document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
130
124
 
131
- await homebridge.updatePluginConfig(pluginConfig);
132
125
  this.deviceIndex = i;
126
+ await homebridge.updatePluginConfig(pluginConfig);
133
127
  });
134
128
 
135
- if (i === accountsCount - 1) {
136
- document.getElementById(`button0`).click();
137
- await homebridge.updatePluginConfig(pluginConfig);
138
- }
129
+ if (i === accountsCount - 1) document.getElementById(`button0`).click();
139
130
  }
140
131
 
141
132
  document.getElementById('melCloudAccount').style.display = 'block';
142
133
 
143
134
  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;
148
-
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;
135
+ const acc = pluginConfig[0].accounts[this.deviceIndex];
136
+ acc.name = document.querySelector('#name').value;
137
+ acc.user = document.querySelector('#user').value;
138
+ acc.passwd = document.querySelector('#passwd').value;
139
+ acc.language = document.querySelector('#language').value;
154
140
 
155
- document.getElementById('logIn').disabled = !accountConfigured;
141
+ document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
156
142
 
157
143
  await homebridge.updatePluginConfig(pluginConfig);
158
144
  await homebridge.savePluginConfig(pluginConfig);
159
145
  });
160
146
 
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;
147
+ // --- Config Button Toggle ---
148
+ const configButton = document.getElementById('configButton');
149
+ let configButtonState = false;
150
+ configButton.addEventListener('click', () => {
151
+ configButtonState = !configButtonState;
152
+ homebridge[configButtonState ? 'showSchemaForm' : 'hideSchemaForm']();
153
+ configButton.className = configButtonState ? 'btn btn-primary' : 'btn btn-secondary';
167
154
  });
168
155
 
156
+ // Password toggle
157
+ document.getElementById('togglePasswd').addEventListener('click', () => {
158
+ const passwdInput = document.getElementById('passwd');
159
+ const icon = document.querySelector('#togglePasswd i');
160
+ if (passwdInput.type === 'password') {
161
+ passwdInput.type = 'text';
162
+ icon.classList.replace('fa-eye', 'fa-eye-slash');
163
+ } else {
164
+ passwdInput.type = 'password';
165
+ icon.classList.replace('fa-eye-slash', 'fa-eye');
166
+ }
167
+ });
168
+
169
+ // --- Device Handling & Login Logic ---
170
+ function removeStaleDevices(configDevices, melcloudDevices) {
171
+ const melcloudIds = melcloudDevices.map(d => d.DeviceID);
172
+ const removedDevices = [];
173
+ for (let i = configDevices.length - 1; i >= 0; i--) {
174
+ const device = configDevices[i];
175
+ if (device.id !== 0 && !melcloudIds.includes(device.id)) {
176
+ removedDevices.push(device);
177
+ configDevices.splice(i, 1);
178
+ }
179
+ }
180
+ return removedDevices;
181
+ }
182
+
183
+ function updateInfo(id, text, color) {
184
+ const el = document.getElementById(id);
185
+ if (el) {
186
+ el.innerText = text;
187
+ el.style.color = color;
188
+ }
189
+ }
190
+
169
191
  document.getElementById('logIn').addEventListener('click', async () => {
170
192
  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';
193
+ document.getElementById(`logIn`).className = "btn btn-primary";
194
+ updateInfo('info', 'Connecting...', 'yellow');
175
195
 
176
196
  try {
177
- const account = pluginConfig[0].accounts[this.deviceIndex];
178
- const { name: accountName, user, passwd, language } = account;
179
-
180
- const payload = { accountName, user, passwd, language };
197
+ const acc = pluginConfig[0].accounts[this.deviceIndex];
198
+ const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
181
199
  const devicesInMelCloud = await homebridge.request('/connect', payload);
182
200
 
201
+ // Initialize devices arrays
183
202
  const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
184
- const removedDevices = { ata: [], atw: [], erv: [] };
185
- const devicesByTypeInMelCloud = { ata: [], atw: [], erv: [] };
186
-
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;
192
- }
193
- }
194
-
195
- const ataDevicesInConfig = account.ataDevices ?? (account.ataDevices = []);
196
- const atwDevicesInConfig = account.atwDevices ?? (account.atwDevices = []);
197
- const ervDevicesInConfig = account.ervDevices ?? (account.ervDevices = []);
198
-
199
- const updateInfo = (id, text, color) => {
200
- const el = document.getElementById(id);
201
- if (el) {
202
- el.innerHTML = text;
203
- el.style.color = color;
204
- }
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 = configArray.length - 1; i >= 0; i--) {
212
- if (!melcloudIds.includes(configArray[i].id)) {
213
- removedArray.push(configArray[i]);
214
- configArray.splice(i, 1);
215
- }
216
- }
217
-
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);
203
+ const devicesByType = { ata: [], atw: [], erv: [] };
204
+
205
+ devicesInMelCloud.forEach(d => {
206
+ if (d.Type === 0) devicesByType.ata.push(d);
207
+ if (d.Type === 1) devicesByType.atw.push(d);
208
+ if (d.Type === 3) devicesByType.erv.push(d);
209
+ });
210
+
211
+ acc.ataDevices ??= [];
212
+ acc.atwDevices ??= [];
213
+ acc.ervDevices ??= [];
214
+
215
+ const removedAta = removeStaleDevices(acc.ataDevices, devicesByType.ata);
216
+ const removedAtw = removeStaleDevices(acc.atwDevices, devicesByType.atw);
217
+ const removedErv = removeStaleDevices(acc.ervDevices, devicesByType.erv);
218
+
219
+ // Function to handle device & presets
220
+ const handleDevices = (devicesInCloud, devicesInConfig, typeString, newArr, newPresets) => {
221
+ devicesInCloud.forEach(device => {
222
+ const { DeviceID: id, Type: type, DeviceName: name, Presets: presets = [] } = device;
223
+ const devObj = { id, type, typeString, name, displayMode: 1, presets: [], buttonsSensors: [] };
224
+ if (!devicesInConfig.some(d => d.id === id)) {
225
+ devicesInConfig.push(devObj);
226
+ newArr.push(devObj);
234
227
  }
235
-
236
- devicePresets.forEach((preset) => {
237
- const { ID: presetId, NumberDescription: presetName } = preset;
238
- Object.assign(preset, { id: presetId, name: presetName, displayType: 0, namePrefix: false });
239
-
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);
228
+ presets.forEach(p => {
229
+ p.id = p.ID;
230
+ p.name = p.NumberDescription;
231
+ p.displayType = 0;
232
+ p.namePrefix = false;
233
+ const devConfig = devicesInConfig.find(d => d.id === id);
234
+ if (devConfig && !devConfig.presets.some(x => x.id === p.ID)) {
235
+ devConfig.presets.push(p);
236
+ newPresets.push(p);
244
237
  }
245
238
  });
246
239
  });
247
- }
248
-
249
- const ataTemplate = { displayMode: 1, heatDryFanMode: 1, coolDryFanMode: 1, autoDryFanMode: 1, temperatureSensor: false, temperatureSensorOutdoor: false };
250
- 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 };
251
- const ervTemplate = { displayMode: 1, temperatureSensor: false, temperatureSensorOutdoor: false, temperatureSensorSupply: false };
240
+ };
252
241
 
253
- handleDevices(devicesByTypeInMelCloud.ata, ataDevicesInConfig, newDevices.ata, newDevices.ataPresets, removedDevices.ata, "Air Conditioner", ataTemplate);
254
- handleDevices(devicesByTypeInMelCloud.atw, atwDevicesInConfig, newDevices.atw, newDevices.atwPresets, removedDevices.atw, "Heat Pump", atwTemplate);
255
- handleDevices(devicesByTypeInMelCloud.erv, ervDevicesInConfig, newDevices.erv, newDevices.ervPresets, removedDevices.erv, "Energy Recovery Ventilation", ervTemplate);
242
+ handleDevices(devicesByType.ata, acc.ataDevices, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
243
+ handleDevices(devicesByType.atw, acc.atwDevices, "Heat Pump", newDevices.atw, newDevices.atwPresets);
244
+ handleDevices(devicesByType.erv, acc.ervDevices, "ERV", newDevices.erv, newDevices.ervPresets);
256
245
 
257
- const formatDeviceNames = (arr) => arr.map(d => d.name).join(', ') || 'None';
246
+ // Display summary
258
247
  const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
259
248
  const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
260
- const removedDevicesCount = removedDevices.ata.length + removedDevices.atw.length + removedDevices.erv.length;
249
+ const removedCount = removedAta.length + removedAtw.length + removedErv.length;
261
250
 
262
- if (newDevicesCount === 0 && newPresetsCount === 0 && removedDevicesCount === 0) {
263
- updateInfo('info', 'No changes (already synced).', 'white');
251
+ if (!newDevicesCount && !newPresetsCount && !removedCount) {
252
+ updateInfo('info', 'No changes detected.', 'white');
264
253
  } else {
265
- if (newDevicesCount > 0) {
266
- updateInfo('info', `New Devices Found: ATA: ${formatDeviceNames(newDevices.ata)}, ATW: ${formatDeviceNames(newDevices.atw)}, ERV: ${formatDeviceNames(newDevices.erv)}`, 'green');
267
- updateInfo('info2', 'Now You can configure it.', 'green');
268
- }
269
- if (newPresetsCount > 0) {
270
- updateInfo('info1', `New Presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}`, 'green');
271
- }
272
- if (removedDevicesCount > 0) {
273
- updateInfo('info', `Removed Devices: ATA: ${formatDeviceNames(removedDevices.ata)}, ATW: ${formatDeviceNames(removedDevices.atw)}, ERV: ${formatDeviceNames(removedDevices.erv)}`, 'orange');
274
- }
254
+ if (newDevicesCount) updateInfo('info', `Found new devices: ATA: ${newDevices.ata.length}, ATW: ${newDevices.atw.length}, ERV: ${newDevices.erv.length}.`, 'green');
255
+ if (newPresetsCount) updateInfo('info1', `Found new presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}.`, 'green');
256
+ if (removedCount) updateInfo('info2', `Removed devices: ATA: ${removedAta.length}, ATW: ${removedAtw.length}, ERV: ${removedErv.length}.`, 'orange');
275
257
  }
276
258
 
259
+ await homebridge.updatePluginConfig(pluginConfig);
277
260
  await homebridge.savePluginConfig(pluginConfig);
261
+ document.getElementById('logIn').className = "btn btn-secondary";
278
262
 
279
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
280
- homebridge.hideSpinner();
281
263
  } catch (error) {
282
264
  updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
283
- updateInfo('info1', `Error: ${error.message ?? JSON.stringify(error)}`, 'red');
284
- document.getElementById('logIn').setAttribute('class', 'btn btn-secondary');
265
+ updateInfo('info1', `Error: ${error}`, 'red');
266
+ document.getElementById('logIn').className = "btn btn-secondary";
285
267
  } finally {
286
268
  homebridge.hideSpinner();
287
269
  }
288
270
  });
271
+
289
272
  })();
290
273
  </script>
291
-
292
274
  </body>
293
275
 
294
276
  </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.2",
4
+ "version": "3.9.0-beta.20",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",