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.
- package/homebridge-ui/public/index.html +214 -317
- package/package.json +1 -1
|
@@ -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/
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
145
|
+
await homebridge.updatePluginConfig(pluginConfig);
|
|
146
|
+
await homebridge.savePluginConfig(pluginConfig);
|
|
204
147
|
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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.
|
|
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",
|