homebridge-melcloud-control 3.9.0-beta.16 → 3.9.0-beta.18
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.
- package/config.schema.json +1 -1
- package/homebridge-ui/public/index.html +108 -172
- package/package.json +1 -1
package/config.schema.json
CHANGED
|
@@ -35,9 +35,13 @@
|
|
|
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
40
|
<input id="passwd" type="password" class="form-control" autocomplete="off" required>
|
|
41
|
+
<span id="togglePassword"
|
|
42
|
+
style="position:absolute; top:50%; right:10px; cursor:pointer; transform:translateY(-50%)">
|
|
43
|
+
<i class="fas fa-eye"></i>
|
|
44
|
+
</span>
|
|
41
45
|
</div>
|
|
42
46
|
|
|
43
47
|
<div class="mb-3">
|
|
@@ -90,67 +94,88 @@
|
|
|
90
94
|
homebridge.showSchemaForm();
|
|
91
95
|
return;
|
|
92
96
|
}
|
|
93
|
-
this.configButtonState = false;
|
|
94
97
|
|
|
95
|
-
const accountsCount = pluginConfig[0].accounts.length;
|
|
96
98
|
this.deviceIndex = 0;
|
|
99
|
+
|
|
100
|
+
const accountsCount = pluginConfig[0].accounts.length;
|
|
97
101
|
for (let i = 0; i < accountsCount; i++) {
|
|
98
102
|
const button = document.createElement("button");
|
|
99
|
-
button.
|
|
100
|
-
button.
|
|
101
|
-
button.
|
|
103
|
+
button.type = "button";
|
|
104
|
+
button.id = `button${i}`;
|
|
105
|
+
button.className = "btn btn-primary";
|
|
102
106
|
button.style.textTransform = 'none';
|
|
103
107
|
button.innerText = pluginConfig[0].accounts[i].name;
|
|
104
108
|
document.getElementById("accountButton").appendChild(button);
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
button.addEventListener('click', async () => {
|
|
107
111
|
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");
|
|
112
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-secondary' : 'btn btn-primary');
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
document.getElementById('
|
|
115
|
-
document.getElementById('
|
|
116
|
-
document.getElementById('
|
|
117
|
-
document.getElementById('
|
|
115
|
+
const acc = pluginConfig[0].accounts[i];
|
|
116
|
+
document.getElementById('accountName').innerText = acc.name || '';
|
|
117
|
+
document.getElementById('name').value = acc.name || '';
|
|
118
|
+
document.getElementById('user').value = acc.user || '';
|
|
119
|
+
document.getElementById('passwd').value = acc.passwd || '';
|
|
120
|
+
document.getElementById('language').value = acc.language || '';
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
document.getElementById('logIn').disabled = !accountConfigured;
|
|
122
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
121
123
|
|
|
122
|
-
await homebridge.updatePluginConfig(pluginConfig);
|
|
123
124
|
this.deviceIndex = i;
|
|
124
|
-
});
|
|
125
|
-
if (i === accountsCount - 1) {
|
|
126
|
-
document.getElementById(`button0`).click();
|
|
127
125
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
128
|
-
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (i === accountsCount - 1) document.getElementById(`button0`).click();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
document.getElementById('melCloudAccount').style.display = 'block';
|
|
132
132
|
|
|
133
133
|
document.getElementById('configForm').addEventListener('input', async () => {
|
|
134
|
-
pluginConfig[0].accounts[this.deviceIndex]
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
const acc = pluginConfig[0].accounts[this.deviceIndex];
|
|
135
|
+
acc.name = document.querySelector('#name').value;
|
|
136
|
+
acc.user = document.querySelector('#user').value;
|
|
137
|
+
acc.passwd = document.querySelector('#passwd').value;
|
|
138
|
+
acc.language = document.querySelector('#language').value;
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
document.getElementById('logIn').disabled = !accountConfigured;
|
|
140
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
141
141
|
|
|
142
142
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
143
143
|
await homebridge.savePluginConfig(pluginConfig);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
// --- Config Button Toggle ---
|
|
147
|
+
const configButton = document.getElementById('configButton');
|
|
148
|
+
let configButtonState = false;
|
|
149
|
+
configButton.addEventListener('click', () => {
|
|
150
|
+
configButtonState = !configButtonState;
|
|
151
|
+
homebridge[configButtonState ? 'showSchemaForm' : 'hideSchemaForm']();
|
|
152
|
+
configButton.className = configButtonState ? 'btn btn-primary' : 'btn btn-secondary';
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// --- Improved Show/Hide Password ---
|
|
156
|
+
const passwdInput = document.getElementById('passwd');
|
|
157
|
+
const togglePassword = document.getElementById('togglePassword');
|
|
158
|
+
|
|
159
|
+
togglePassword.addEventListener('click', () => {
|
|
160
|
+
const hidden = passwdInput.type === 'password';
|
|
161
|
+
passwdInput.type = hidden ? 'text' : 'password';
|
|
162
|
+
togglePassword.innerHTML = `<i class="fas fa-${hidden ? 'eye-slash' : 'eye'}"></i>`;
|
|
163
|
+
togglePassword.dataset.clicked = hidden; // remember permanent toggle
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
togglePassword.addEventListener('mouseenter', () => {
|
|
167
|
+
passwdInput.type = 'text';
|
|
168
|
+
togglePassword.innerHTML = `<i class="fas fa-eye-slash"></i>`;
|
|
152
169
|
});
|
|
153
170
|
|
|
171
|
+
togglePassword.addEventListener('mouseleave', () => {
|
|
172
|
+
if (!togglePassword.dataset.clicked || togglePassword.dataset.clicked === 'false') {
|
|
173
|
+
passwdInput.type = 'password';
|
|
174
|
+
togglePassword.innerHTML = `<i class="fas fa-eye"></i>`;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// --- Device Handling & Login Logic ---
|
|
154
179
|
function removeStaleDevices(configDevices, melcloudDevices) {
|
|
155
180
|
const melcloudIds = melcloudDevices.map(d => d.DeviceID);
|
|
156
181
|
const removedDevices = [];
|
|
@@ -167,181 +192,92 @@
|
|
|
167
192
|
function updateInfo(id, text, color) {
|
|
168
193
|
const el = document.getElementById(id);
|
|
169
194
|
if (el) {
|
|
170
|
-
el.
|
|
195
|
+
el.innerText = text;
|
|
171
196
|
el.style.color = color;
|
|
172
197
|
}
|
|
173
198
|
}
|
|
174
199
|
|
|
175
200
|
document.getElementById('logIn').addEventListener('click', async () => {
|
|
176
201
|
homebridge.showSpinner();
|
|
177
|
-
document.getElementById(`logIn`).
|
|
202
|
+
document.getElementById(`logIn`).className = "btn btn-primary";
|
|
178
203
|
updateInfo('info', 'Connecting...', 'yellow');
|
|
179
204
|
|
|
180
205
|
try {
|
|
181
|
-
const
|
|
182
|
-
const {
|
|
183
|
-
const payload = { accountName, user, passwd, language };
|
|
206
|
+
const acc = pluginConfig[0].accounts[this.deviceIndex];
|
|
207
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
184
208
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
185
209
|
|
|
210
|
+
// Initialize devices arrays
|
|
186
211
|
const newDevices = { ata: [], ataPresets: [], atw: [], atwPresets: [], erv: [], ervPresets: [] };
|
|
187
|
-
const
|
|
188
|
-
|
|
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;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
212
|
+
const devicesByType = { ata: [], atw: [], erv: [] };
|
|
196
213
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
214
|
+
devicesInMelCloud.forEach(d => {
|
|
215
|
+
if (d.Type === 0) devicesByType.ata.push(d);
|
|
216
|
+
if (d.Type === 1) devicesByType.atw.push(d);
|
|
217
|
+
if (d.Type === 3) devicesByType.erv.push(d);
|
|
238
218
|
});
|
|
239
219
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
buttonsSensors: []
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
newDevices.atw.push(deviceAtw);
|
|
257
|
-
}
|
|
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);
|
|
220
|
+
acc.ataDevices ??= [];
|
|
221
|
+
acc.atwDevices ??= [];
|
|
222
|
+
acc.ervDevices ??= [];
|
|
223
|
+
|
|
224
|
+
const removedAta = removeStaleDevices(acc.ataDevices, devicesByType.ata);
|
|
225
|
+
const removedAtw = removeStaleDevices(acc.atwDevices, devicesByType.atw);
|
|
226
|
+
const removedErv = removeStaleDevices(acc.ervDevices, devicesByType.erv);
|
|
227
|
+
|
|
228
|
+
// Function to handle device & presets
|
|
229
|
+
const handleDevices = (devicesInCloud, devicesInConfig, typeString, newArr, newPresets) => {
|
|
230
|
+
devicesInCloud.forEach(device => {
|
|
231
|
+
const { DeviceID: id, Type: type, DeviceName: name, Presets: presets = [] } = device;
|
|
232
|
+
const devObj = { id, type, typeString, name, displayMode: 1, presets: [], buttonsSensors: [] };
|
|
233
|
+
if (!devicesInConfig.some(d => d.id === id)) {
|
|
234
|
+
devicesInConfig.push(devObj);
|
|
235
|
+
newArr.push(devObj);
|
|
268
236
|
}
|
|
237
|
+
presets.forEach(p => {
|
|
238
|
+
p.id = p.ID;
|
|
239
|
+
p.name = p.NumberDescription;
|
|
240
|
+
p.displayType = 0;
|
|
241
|
+
p.namePrefix = false;
|
|
242
|
+
const devConfig = devicesInConfig.find(d => d.id === id);
|
|
243
|
+
if (devConfig && !devConfig.presets.some(x => x.id === p.ID)) {
|
|
244
|
+
devConfig.presets.push(p);
|
|
245
|
+
newPresets.push(p);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
269
248
|
});
|
|
270
|
-
}
|
|
249
|
+
};
|
|
271
250
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
288
|
-
}
|
|
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
|
-
|
|
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}`;
|
|
251
|
+
handleDevices(devicesByType.ata, acc.ataDevices, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
|
|
252
|
+
handleDevices(devicesByType.atw, acc.atwDevices, "Heat Pump", newDevices.atw, newDevices.atwPresets);
|
|
253
|
+
handleDevices(devicesByType.erv, acc.ervDevices, "ERV", newDevices.erv, newDevices.ervPresets);
|
|
313
254
|
|
|
255
|
+
// Display summary
|
|
314
256
|
const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
|
|
315
257
|
const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
|
|
316
258
|
const removedCount = removedAta.length + removedAtw.length + removedErv.length;
|
|
317
259
|
|
|
318
|
-
if (newDevicesCount
|
|
260
|
+
if (!newDevicesCount && !newPresetsCount && !removedCount) {
|
|
319
261
|
updateInfo('info', 'No changes detected.', 'white');
|
|
320
262
|
} else {
|
|
321
|
-
if (newDevicesCount
|
|
322
|
-
|
|
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
|
-
}
|
|
263
|
+
if (newDevicesCount) updateInfo('info', `Found new devices: ATA: ${newDevices.ata.length}, ATW: ${newDevices.atw.length}, ERV: ${newDevices.erv.length}.`, 'green');
|
|
264
|
+
if (newPresetsCount) updateInfo('info1', `Found new presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}.`, 'green');
|
|
265
|
+
if (removedCount) updateInfo('info2', `Removed devices: ATA: ${removedAta.length}, ATW: ${removedAtw.length}, ERV: ${removedErv.length}.`, 'orange');
|
|
330
266
|
}
|
|
331
267
|
|
|
332
268
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
333
269
|
await homebridge.savePluginConfig(pluginConfig);
|
|
334
|
-
document.getElementById('logIn').
|
|
335
|
-
homebridge.hideSpinner();
|
|
270
|
+
document.getElementById('logIn').className = "btn btn-secondary";
|
|
336
271
|
|
|
337
272
|
} catch (error) {
|
|
338
273
|
updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
|
|
339
274
|
updateInfo('info1', `Error: ${error}`, 'red');
|
|
340
|
-
document.getElementById('logIn').
|
|
275
|
+
document.getElementById('logIn').className = "btn btn-secondary";
|
|
341
276
|
} finally {
|
|
342
277
|
homebridge.hideSpinner();
|
|
343
278
|
}
|
|
344
279
|
});
|
|
280
|
+
|
|
345
281
|
})();
|
|
346
282
|
</script>
|
|
347
283
|
</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.
|
|
4
|
+
"version": "3.9.0-beta.18",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|