homebridge-melcloud-control 4.3.8 → 4.3.9-beta.1
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 +399 -409
- package/package.json +1 -1
- package/src/melcloudhome.js +1 -1
|
@@ -9,444 +9,434 @@
|
|
|
9
9
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js"></script>
|
|
10
10
|
</head>
|
|
11
11
|
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<img src="homebridge-melcloud-control.png" alt="Image" height="120" />
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<div id="melCloudAccount" class="card card-body mt-2">
|
|
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: 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
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<div class="mb-2">
|
|
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-2">
|
|
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-2 position-relative">
|
|
39
|
-
<label for="passwd" class="form-label">Password</label>
|
|
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>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div class="mb-2">
|
|
49
|
-
<label for="language" class="form-label">Language</label>
|
|
50
|
-
<select id="language" name="language" class="form-control">
|
|
51
|
-
<option value="0">English</option>
|
|
52
|
-
<option value="1">Български</option>
|
|
53
|
-
<option value="2">Čeština</option>
|
|
54
|
-
<option value="3">Dansk</option>
|
|
55
|
-
<option value="4">Deutsch</option>
|
|
56
|
-
<option value="5">Eesti</option>
|
|
57
|
-
<option value="6">Español</option>
|
|
58
|
-
<option value="7">Français</option>
|
|
59
|
-
<option value="8">Հայերեն</option>
|
|
60
|
-
<option value="9">Latviešu</option>
|
|
61
|
-
<option value="10">Lietuvių</option>
|
|
62
|
-
<option value="11">Magyar</option>
|
|
63
|
-
<option value="12">Nederlands</option>
|
|
64
|
-
<option value="13">Norwegian</option>
|
|
65
|
-
<option value="14">Polski</option>
|
|
66
|
-
<option value="15">Português</option>
|
|
67
|
-
<option value="16">Русский</option>
|
|
68
|
-
<option value="17">Suomi</option>
|
|
69
|
-
<option value="18">Svenska</option>
|
|
70
|
-
<option value="19">Українська</option>
|
|
71
|
-
<option value="20">Türkçe</option>
|
|
72
|
-
<option value="21">Ελληνικά</option>
|
|
73
|
-
<option value="22">Hrvatski</option>
|
|
74
|
-
<option value="23">Română</option>
|
|
75
|
-
<option value="24">Slovenščina</option>
|
|
76
|
-
</select>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div class="mb-2">
|
|
80
|
-
<label for="accountType" class="form-label">Account Type</label>
|
|
81
|
-
<select id="accountType" name="accountType" class="form-control">
|
|
82
|
-
<option value="disabled">None/Disabled</option>
|
|
83
|
-
<option value="melcloud">MELCloud</option>
|
|
84
|
-
<option value="melcloudhome">MELCloud Home</option>
|
|
85
|
-
</select>
|
|
86
|
-
</div>
|
|
12
|
+
<div class="container mt-3">
|
|
13
|
+
<div class="text-center">
|
|
14
|
+
<img src="homebridge-melcloud-control.png" alt="Image" height="120" />
|
|
15
|
+
</div>
|
|
87
16
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
17
|
+
<div id="melCloudAccount" class="card card-body mt-2">
|
|
18
|
+
<form id="configForm">
|
|
19
|
+
<div class="text-center">
|
|
20
|
+
<label id="accountName" class="fw-bold" style="font-size: 23px;">Account</label><br>
|
|
21
|
+
<label id="info" class="d-block" style="font-size: 14px;"></label>
|
|
22
|
+
<label id="info1" class="d-block" style="font-size: 12px;"></label>
|
|
23
|
+
<label id="info2" class="d-block" style="font-size: 12px;"></label>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mb-2">
|
|
27
|
+
<label for="name" class="form-label">Name</label>
|
|
28
|
+
<input id="name" type="text" class="form-control" required>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="mb-2">
|
|
32
|
+
<label for="user" class="form-label">User Name</label>
|
|
33
|
+
<input id="user" type="text" class="form-control" required>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="mb-2 position-relative">
|
|
37
|
+
<label for="passwd" class="form-label">Password</label>
|
|
38
|
+
<div class="input-group">
|
|
39
|
+
<input id="passwd" type="password" class="form-control" autocomplete="off" required>
|
|
40
|
+
<button type="button" id="togglePasswd" class="btn btn-outline-secondary">
|
|
41
|
+
<i class="fas fa-eye"></i>
|
|
42
|
+
</button>
|
|
91
43
|
</div>
|
|
92
|
-
</
|
|
93
|
-
|
|
94
|
-
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mb-2">
|
|
47
|
+
<label for="language" class="form-label">Language</label>
|
|
48
|
+
<select id="language" name="language" class="form-control">
|
|
49
|
+
<option value="0">English</option>
|
|
50
|
+
<option value="1">Български</option>
|
|
51
|
+
<option value="2">Čeština</option>
|
|
52
|
+
<option value="3">Dansk</option>
|
|
53
|
+
<option value="4">Deutsch</option>
|
|
54
|
+
<option value="5">Eesti</option>
|
|
55
|
+
<option value="6">Español</option>
|
|
56
|
+
<option value="7">Français</option>
|
|
57
|
+
<option value="8">Հայերեն</option>
|
|
58
|
+
<option value="9">Latviešu</option>
|
|
59
|
+
<option value="10">Lietuvių</option>
|
|
60
|
+
<option value="11">Magyar</option>
|
|
61
|
+
<option value="12">Nederlands</option>
|
|
62
|
+
<option value="13">Norwegian</option>
|
|
63
|
+
<option value="14">Polski</option>
|
|
64
|
+
<option value="15">Português</option>
|
|
65
|
+
<option value="16">Русский</option>
|
|
66
|
+
<option value="17">Suomi</option>
|
|
67
|
+
<option value="18">Svenska</option>
|
|
68
|
+
<option value="19">Українська</option>
|
|
69
|
+
<option value="20">Türkçe</option>
|
|
70
|
+
<option value="21">Ελληνικά</option>
|
|
71
|
+
<option value="22">Hrvatski</option>
|
|
72
|
+
<option value="23">Română</option>
|
|
73
|
+
<option value="24">Slovenščina</option>
|
|
74
|
+
</select>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="mb-2">
|
|
78
|
+
<label for="accountType" class="form-label">Account Type</label>
|
|
79
|
+
<select id="accountType" name="accountType" class="form-control">
|
|
80
|
+
<option value="disabled">None/Disabled</option>
|
|
81
|
+
<option value="melcloud">MELCloud</option>
|
|
82
|
+
<option value="melcloudhome">MELCloud Home</option>
|
|
83
|
+
</select>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="text-center">
|
|
87
|
+
<button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
|
|
88
|
+
<button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
|
|
89
|
+
</div>
|
|
90
|
+
</form>
|
|
91
|
+
<div id="accountButton" class="d-flex flex-wrap justify-content-center gap-1 mt-3"></div>
|
|
95
92
|
</div>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<script>
|
|
96
|
+
(async () => {
|
|
97
|
+
const pluginConfig = await homebridge.getPluginConfig();
|
|
98
|
+
|
|
99
|
+
if (!pluginConfig.length) {
|
|
100
|
+
pluginConfig.push({});
|
|
101
|
+
await homebridge.updatePluginConfig(pluginConfig);
|
|
102
|
+
homebridge.showSchemaForm();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const accounts = pluginConfig[0].accounts || [];
|
|
107
|
+
const accountsCount = accounts.length;
|
|
108
|
+
|
|
109
|
+
const container = document.getElementById("accountButton");
|
|
110
|
+
container.style.display = 'flex';
|
|
111
|
+
container.style.flexWrap = 'wrap';
|
|
112
|
+
container.style.justifyContent = 'center';
|
|
113
|
+
container.style.gap = '0.25rem';
|
|
114
|
+
container.style.alignItems = 'center';
|
|
115
|
+
|
|
116
|
+
const formElements = {
|
|
117
|
+
accountName: document.getElementById('accountName'),
|
|
118
|
+
info: document.getElementById('info'),
|
|
119
|
+
info1: document.getElementById('info1'),
|
|
120
|
+
info2: document.getElementById('info2'),
|
|
121
|
+
name: document.getElementById('name'),
|
|
122
|
+
user: document.getElementById('user'),
|
|
123
|
+
passwd: document.getElementById('passwd'),
|
|
124
|
+
language: document.getElementById('language'),
|
|
125
|
+
accountType: document.getElementById('accountType'),
|
|
126
|
+
logIn: document.getElementById('logIn')
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Tworzenie przycisków
|
|
130
|
+
accounts.forEach((account, i) => {
|
|
131
|
+
this.account = account;
|
|
132
|
+
|
|
133
|
+
const button = document.createElement("button");
|
|
134
|
+
button.type = "button";
|
|
135
|
+
button.id = `button${i}`;
|
|
136
|
+
button.className = "btn btn-primary";
|
|
137
|
+
button.style.textTransform = 'none';
|
|
138
|
+
button.innerText = account.name || `Account ${i + 1}`;
|
|
139
|
+
container.appendChild(button);
|
|
140
|
+
|
|
141
|
+
button.addEventListener('click', async () => {
|
|
133
142
|
this.account = account;
|
|
134
143
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
button.className = "btn btn-primary";
|
|
139
|
-
button.style.textTransform = 'none';
|
|
140
|
-
button.innerText = account.name || `Account ${i + 1}`;
|
|
141
|
-
container.appendChild(button);
|
|
142
|
-
|
|
143
|
-
button.addEventListener('click', async () => {
|
|
144
|
-
this.account = account;
|
|
145
|
-
|
|
146
|
-
// Zmieniamy klasę wszystkich przycisków
|
|
147
|
-
accounts.forEach((_, j) => {
|
|
148
|
-
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Ustawiamy formularz
|
|
152
|
-
formElements.accountName.innerText = account.name || '';
|
|
153
|
-
formElements.info.innerText = ``;
|
|
154
|
-
formElements.info1.innerText = '';
|
|
155
|
-
formElements.info2.innerText = '';
|
|
156
|
-
formElements.name.value = account.name || '';
|
|
157
|
-
formElements.user.value = account.user || '';
|
|
158
|
-
formElements.passwd.value = account.passwd || '';
|
|
159
|
-
formElements.language.value = account.language || '0';
|
|
160
|
-
formElements.accountType.value = account.type || 'disabled';
|
|
161
|
-
formElements.logIn.disabled = !(account.name && account.user && account.passwd && account.language && account.type);
|
|
144
|
+
// Zmieniamy klasę wszystkich przycisków
|
|
145
|
+
accounts.forEach((_, j) => {
|
|
146
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
162
147
|
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Klikamy pierwszy przycisk po zakończeniu pętli
|
|
166
|
-
if (accountsCount > 0) document.getElementById('button0').click();
|
|
167
|
-
|
|
168
|
-
// Jeden listener input dla całego formularza
|
|
169
|
-
document.getElementById('configForm').addEventListener('input', async () => {
|
|
170
|
-
const account = this.account;
|
|
171
|
-
if (!account) return;
|
|
172
|
-
|
|
173
|
-
account.name = formElements.name.value;
|
|
174
|
-
account.user = formElements.user.value;
|
|
175
|
-
account.passwd = formElements.passwd.value;
|
|
176
|
-
account.language = formElements.language.value;
|
|
177
|
-
account.type = formElements.accountType.value;
|
|
178
148
|
|
|
149
|
+
// Ustawiamy formularz
|
|
150
|
+
formElements.accountName.innerText = account.name || '';
|
|
151
|
+
formElements.info.innerText = ``;
|
|
152
|
+
formElements.info1.innerText = '';
|
|
153
|
+
formElements.info2.innerText = '';
|
|
154
|
+
formElements.name.value = account.name || '';
|
|
155
|
+
formElements.user.value = account.user || '';
|
|
156
|
+
formElements.passwd.value = account.passwd || '';
|
|
157
|
+
formElements.language.value = account.language || '0';
|
|
158
|
+
formElements.accountType.value = account.type || 'disabled';
|
|
179
159
|
formElements.logIn.disabled = !(account.name && account.user && account.passwd && account.language && account.type);
|
|
180
|
-
|
|
181
|
-
await homebridge.updatePluginConfig(pluginConfig);
|
|
182
|
-
await homebridge.savePluginConfig(pluginConfig);
|
|
183
160
|
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Klikamy pierwszy przycisk po zakończeniu pętli
|
|
164
|
+
if (accountsCount > 0) document.getElementById('button0').click();
|
|
165
|
+
|
|
166
|
+
// Jeden listener input dla całego formularza
|
|
167
|
+
document.getElementById('configForm').addEventListener('input', async () => {
|
|
168
|
+
const account = this.account;
|
|
169
|
+
if (!account) return;
|
|
170
|
+
|
|
171
|
+
account.name = formElements.name.value;
|
|
172
|
+
account.user = formElements.user.value;
|
|
173
|
+
account.passwd = formElements.passwd.value;
|
|
174
|
+
account.language = formElements.language.value;
|
|
175
|
+
account.type = formElements.accountType.value;
|
|
176
|
+
|
|
177
|
+
formElements.logIn.disabled = !(account.name && account.user && account.passwd && account.language && account.type);
|
|
178
|
+
|
|
179
|
+
await homebridge.updatePluginConfig(pluginConfig);
|
|
180
|
+
await homebridge.savePluginConfig(pluginConfig);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
document.getElementById('melCloudAccount').style.display = 'block';
|
|
184
|
+
|
|
185
|
+
// Config Button Toggle
|
|
186
|
+
const configButton = document.getElementById('configButton');
|
|
187
|
+
let configButtonState = false;
|
|
188
|
+
configButton.addEventListener('click', () => {
|
|
189
|
+
configButtonState = !configButtonState;
|
|
190
|
+
homebridge[configButtonState ? 'showSchemaForm' : 'hideSchemaForm']();
|
|
191
|
+
configButton.className = configButtonState ? 'btn btn-primary' : 'btn btn-secondary';
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Password toggle
|
|
195
|
+
document.getElementById('togglePasswd').addEventListener('click', () => {
|
|
196
|
+
const passwdInput = document.getElementById('passwd');
|
|
197
|
+
const icon = document.querySelector('#togglePasswd i');
|
|
198
|
+
if (passwdInput.type === 'password') {
|
|
199
|
+
passwdInput.type = 'text';
|
|
200
|
+
icon.classList.replace('fa-eye', 'fa-eye-slash');
|
|
201
|
+
} else {
|
|
202
|
+
passwdInput.type = 'password';
|
|
203
|
+
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Device Handling & Login Logic
|
|
208
|
+
function removeStaleDevices(configDevices, melcloudDevices) {
|
|
209
|
+
const melcloudIds = melcloudDevices.map(d => d.DeviceID);
|
|
210
|
+
const removedDevices = [];
|
|
211
|
+
|
|
212
|
+
for (let i = configDevices.length - 1; i >= 0; i--) {
|
|
213
|
+
const device = configDevices[i];
|
|
214
|
+
if (device.id !== "0" && !melcloudIds.includes(device.id)) {
|
|
215
|
+
removedDevices.push(device);
|
|
216
|
+
configDevices.splice(i, 1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return removedDevices;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function updateInfo(id, text, color) {
|
|
223
|
+
const el = document.getElementById(id);
|
|
224
|
+
if (el) {
|
|
225
|
+
el.innerText = text;
|
|
226
|
+
el.style.color = color;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
184
229
|
|
|
185
|
-
|
|
230
|
+
document.getElementById('logIn').addEventListener('click', async () => {
|
|
231
|
+
homebridge.showSpinner();
|
|
186
232
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
configButtonState = !configButtonState;
|
|
192
|
-
homebridge[configButtonState ? 'showSchemaForm' : 'hideSchemaForm']();
|
|
193
|
-
configButton.className = configButtonState ? 'btn btn-primary' : 'btn btn-secondary';
|
|
194
|
-
});
|
|
233
|
+
document.getElementById(`logIn`).className = "btn btn-primary";
|
|
234
|
+
updateInfo('info', '', 'white');
|
|
235
|
+
updateInfo('info1', '', 'white');
|
|
236
|
+
updateInfo('info2', '', 'white');
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
} else {
|
|
204
|
-
passwdInput.type = 'password';
|
|
205
|
-
icon.classList.replace('fa-eye-slash', 'fa-eye');
|
|
238
|
+
try {
|
|
239
|
+
const account = this.account;
|
|
240
|
+
const response = await homebridge.request('/connect', account);
|
|
241
|
+
if (!response.State) {
|
|
242
|
+
homebridge.hideSpinner();
|
|
243
|
+
updateInfo('info', response.Info);
|
|
244
|
+
return;
|
|
206
245
|
}
|
|
207
|
-
});
|
|
208
246
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
247
|
+
// Initialize devices arrays
|
|
248
|
+
const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [], scenes: [] };
|
|
249
|
+
const devicesInMelCloudByType = { ata: [], atw: [], erv: [] };
|
|
250
|
+
const scenesInMelCloud = response.Scenes ?? []
|
|
213
251
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (device.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return removedDevices;
|
|
222
|
-
}
|
|
252
|
+
response.Devices.forEach(device => {
|
|
253
|
+
if (device.Type === 0) devicesInMelCloudByType.ata.push(device);
|
|
254
|
+
if (device.Type === 1) devicesInMelCloudByType.atw.push(device);
|
|
255
|
+
if (device.Type === 3) devicesInMelCloudByType.erv.push(device);
|
|
256
|
+
});
|
|
223
257
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
258
|
+
account.ataDevices ??= [];
|
|
259
|
+
account.atwDevices ??= [];
|
|
260
|
+
account.ervDevices ??= [];
|
|
261
|
+
|
|
262
|
+
const removedAta = removeStaleDevices(account.ataDevices, devicesInMelCloudByType.ata);
|
|
263
|
+
const removedAtw = removeStaleDevices(account.atwDevices, devicesInMelCloudByType.atw);
|
|
264
|
+
const removedErv = removeStaleDevices(account.ervDevices, devicesInMelCloudByType.erv);
|
|
265
|
+
|
|
266
|
+
const handleDevices = (devicesInMelCloud, devicesInConfig, typeString, newDevices, newPresets, newSchedules, newScenes) => {
|
|
267
|
+
try {
|
|
268
|
+
const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
|
|
269
|
+
|
|
270
|
+
devicesInMelCloud.forEach(device => {
|
|
271
|
+
const deviceId = String(device.DeviceID);
|
|
272
|
+
let deviceInConfig = configDevicesMap.get(deviceId);
|
|
273
|
+
|
|
274
|
+
// === Create device if missing ===
|
|
275
|
+
if (!deviceInConfig) {
|
|
276
|
+
deviceInConfig = {
|
|
277
|
+
id: deviceId,
|
|
278
|
+
type: device.Type,
|
|
279
|
+
typeString,
|
|
280
|
+
displayType: 0,
|
|
281
|
+
name: device.DeviceName,
|
|
282
|
+
presets: [],
|
|
283
|
+
schedules: [],
|
|
284
|
+
scenes: [],
|
|
285
|
+
buttonsSensors: []
|
|
286
|
+
};
|
|
287
|
+
devicesInConfig.push(deviceInConfig);
|
|
288
|
+
newDevices.push(deviceInConfig);
|
|
289
|
+
configDevicesMap.set(deviceId, deviceInConfig);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
//only for melcloud
|
|
293
|
+
if (account.type === 'melcloud') {
|
|
294
|
+
|
|
295
|
+
// === Process presets ===
|
|
296
|
+
const presetsInMelCloud = device.Presets || [];
|
|
297
|
+
const presetsInConfig = deviceInConfig.presets || [];
|
|
298
|
+
const presetIds = new Set(presetsInConfig.map(p => String(p.id)));
|
|
299
|
+
|
|
300
|
+
let addedNewPreset = false;
|
|
301
|
+
presetsInMelCloud.forEach((preset, index) => {
|
|
302
|
+
const presetId = String(preset.ID);
|
|
303
|
+
if (!presetIds.has(presetId)) {
|
|
304
|
+
const presetObj = {
|
|
305
|
+
id: presetId,
|
|
306
|
+
displayType: 0,
|
|
307
|
+
name: preset.NumberDescription || `Preset ${index}`,
|
|
308
|
+
namePrefix: false
|
|
309
|
+
};
|
|
310
|
+
presetsInConfig.push(presetObj);
|
|
311
|
+
newPresets.push(presetObj);
|
|
312
|
+
presetIds.add(presetId);
|
|
313
|
+
addedNewPreset = true;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
231
316
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
homebridge.showSpinner();
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
const account = this.account;
|
|
241
|
-
const response = await homebridge.request('/connect', account);
|
|
242
|
-
if (!response.State) {
|
|
243
|
-
homebridge.hideSpinner();
|
|
244
|
-
updateInfo('info', response.Info);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
317
|
+
// === Remove placeholder presets/schedules (id === '0') if new ones were added ===
|
|
318
|
+
if (addedNewPreset) {
|
|
319
|
+
const beforeCount = presetsInConfig.length;
|
|
320
|
+
deviceInConfig.presets = presetsInConfig.filter(p => String(p.id) !== '0');
|
|
321
|
+
const removedCount = beforeCount - deviceInConfig.presets.length;
|
|
247
322
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
response.Devices.forEach(device => {
|
|
253
|
-
device.Scenes = response.Scenes ?? [];
|
|
254
|
-
if (device.Type === 0) devicesByType.ata.push(device);
|
|
255
|
-
if (device.Type === 1) devicesByType.atw.push(device);
|
|
256
|
-
if (device.Type === 3) devicesByType.erv.push(device);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
account.ataDevices ??= [];
|
|
260
|
-
account.atwDevices ??= [];
|
|
261
|
-
account.ervDevices ??= [];
|
|
262
|
-
|
|
263
|
-
const removedAta = removeStaleDevices(account.ataDevices, devicesByType.ata);
|
|
264
|
-
const removedAtw = removeStaleDevices(account.atwDevices, devicesByType.atw);
|
|
265
|
-
const removedErv = removeStaleDevices(account.ervDevices, devicesByType.erv);
|
|
266
|
-
|
|
267
|
-
const handleDevices = (devicesInMelCloud, devicesInConfig, typeString, newArr, newPresets, newSchedules, newScenes) => {
|
|
268
|
-
try {
|
|
269
|
-
const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
|
|
270
|
-
const isMelcloud = account.type === 'melcloud';
|
|
271
|
-
|
|
272
|
-
const idKey = isMelcloud ? 'ID' : 'Id';
|
|
273
|
-
const typeKey = isMelcloud ? 'Presets' : 'Schedule';
|
|
274
|
-
const typeKey1 = isMelcloud ? 'presets' : 'schedules';
|
|
275
|
-
|
|
276
|
-
devicesInMelCloud.forEach(device => {
|
|
277
|
-
const deviceId = String(device.DeviceID);
|
|
278
|
-
let deviceInConfig = configDevicesMap.get(deviceId);
|
|
279
|
-
|
|
280
|
-
// === Create device if missing ===
|
|
281
|
-
if (!deviceInConfig) {
|
|
282
|
-
deviceInConfig = {
|
|
283
|
-
id: deviceId,
|
|
284
|
-
type: device.Type,
|
|
285
|
-
typeString,
|
|
286
|
-
displayType: 0,
|
|
287
|
-
name: device.DeviceName,
|
|
288
|
-
presets: [],
|
|
289
|
-
schedules: [],
|
|
290
|
-
scenes: [],
|
|
291
|
-
buttonsSensors: []
|
|
292
|
-
};
|
|
293
|
-
devicesInConfig.push(deviceInConfig);
|
|
294
|
-
newArr.push(deviceInConfig);
|
|
295
|
-
configDevicesMap.set(deviceId, deviceInConfig);
|
|
323
|
+
if (removedCount > 0 && removedCount < beforeCount) {
|
|
324
|
+
updateInfo('info2', `Removed ${removedCount} placeholder preset from device ${device.DeviceID}`, 'yellow');
|
|
325
|
+
}
|
|
296
326
|
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//only for melcloudhome
|
|
330
|
+
if (account.type === 'melcloudhome') {
|
|
331
|
+
|
|
332
|
+
// === Process schedules ===
|
|
333
|
+
const schedulesInMelCloud = device.Schedule || [];
|
|
334
|
+
const schedulesInConfig = deviceInConfig.schedules || [];
|
|
335
|
+
const scheduleIds = new Set(schedulesInConfig.map(s => String(s.id)));
|
|
336
|
+
|
|
337
|
+
let addedNewSchedule = false;
|
|
338
|
+
schedulesInMelCloud.forEach((schedule, index) => {
|
|
339
|
+
const scheduleId = String(schedule.Id);
|
|
340
|
+
if (!scheduleIds.has(scheduleId)) {
|
|
341
|
+
const scheduleObj = {
|
|
342
|
+
id: scheduleId,
|
|
343
|
+
displayType: 0,
|
|
344
|
+
name: `Schedule ${index}`,
|
|
345
|
+
namePrefix: false
|
|
346
|
+
};
|
|
347
|
+
schedulesInConfig.push(scheduleObj);
|
|
348
|
+
newSchedules.push(scheduleObj);
|
|
349
|
+
scheduleIds.add(scheduleId);
|
|
350
|
+
addedNewSchedule = true;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// === Remove placeholder schedules (id === '0') if new ones were added ===
|
|
355
|
+
if (addedNewSchedule) {
|
|
356
|
+
const beforeCount = schedulesInConfig.length;
|
|
357
|
+
deviceInConfig.schedules = schedulesInConfig.filter(s => String(s.id) !== '0');
|
|
358
|
+
const removedCount = beforeCount - deviceInConfig.schedules.length;
|
|
297
359
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// === Process presets ===
|
|
302
|
-
const presetsInMelCloud = device.Presets || [];
|
|
303
|
-
const presetsInConfig = deviceInConfig.presets || [];
|
|
304
|
-
const presetIds = new Set(presetsInConfig.map(p => String(p.id)));
|
|
305
|
-
|
|
306
|
-
let addedNewPreset = false;
|
|
307
|
-
presetsInMelCloud.forEach((preset, index) => {
|
|
308
|
-
const presetId = String(preset.ID);
|
|
309
|
-
if (!presetIds.has(presetId)) {
|
|
310
|
-
const presetObj = {
|
|
311
|
-
id: presetId,
|
|
312
|
-
displayType: 0,
|
|
313
|
-
name: preset.NumberDescription || `Preset ${index}`,
|
|
314
|
-
namePrefix: false
|
|
315
|
-
};
|
|
316
|
-
presetsInConfig.push(presetObj);
|
|
317
|
-
newPresets.push(presetObj);
|
|
318
|
-
presetIds.add(presetId);
|
|
319
|
-
addedNewPreset = true;
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// === Remove placeholder presets/schedules (id === '0') if new ones were added ===
|
|
324
|
-
if (addedNewPreset) {
|
|
325
|
-
const beforeCount = presetsInConfig.length;
|
|
326
|
-
deviceInConfig.presets = presetsInConfig.filter(p => String(p.id) !== '0');
|
|
327
|
-
const removedCount = beforeCount - deviceInConfig.presets.length;
|
|
328
|
-
|
|
329
|
-
if (removedCount > 0 && removedCount < beforeCount) {
|
|
330
|
-
updateInfo('info2', `Removed ${removedCount} placeholder preset from device ${device.DeviceID}`, 'yellow');
|
|
331
|
-
}
|
|
360
|
+
if (removedCount > 0 && removedCount < beforeCount) {
|
|
361
|
+
updateInfo('info2', `Removed ${removedCount} placeholder schedule from device ${device.DeviceID}`, 'yellow');
|
|
332
362
|
}
|
|
333
363
|
}
|
|
334
364
|
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
schedulesInConfig.push(scheduleObj);
|
|
354
|
-
newSchedules.push(scheduleObj);
|
|
355
|
-
scheduleIds.add(scheduleId);
|
|
356
|
-
addedNewSchedule = true;
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// === Remove placeholder schedules (id === '0') if new ones were added ===
|
|
361
|
-
if (addedNewSchedule) {
|
|
362
|
-
const beforeCount = schedulesInConfig.length;
|
|
363
|
-
deviceInConfig.schedules = schedulesInConfig.filter(s => String(s.id) !== '0');
|
|
364
|
-
const removedCount = beforeCount - deviceInConfig.schedules.length;
|
|
365
|
-
|
|
366
|
-
if (removedCount > 0 && removedCount < beforeCount) {
|
|
367
|
-
updateInfo('info2', `Removed ${removedCount} placeholder schedule from device ${device.DeviceID}`, 'yellow');
|
|
368
|
-
}
|
|
365
|
+
// === Process scenes ===
|
|
366
|
+
const scenesInConfig = deviceInConfig.scenes || [];
|
|
367
|
+
const sceneIds = new Set(scenesInConfig.map(s => String(s.id)));
|
|
368
|
+
|
|
369
|
+
let addedNewScenes = false;
|
|
370
|
+
scenesInMelCloud.forEach((scene, index) => {
|
|
371
|
+
const sceneId = String(scene.Id);
|
|
372
|
+
if (!sceneIds.has(sceneId)) {
|
|
373
|
+
const sceneObj = {
|
|
374
|
+
id: sceneId,
|
|
375
|
+
displayType: 0,
|
|
376
|
+
name: scene.Name || `Scene ${index}`,
|
|
377
|
+
namePrefix: false
|
|
378
|
+
};
|
|
379
|
+
scenesInConfig.push(sceneObj);
|
|
380
|
+
newScenes.push(sceneObj);
|
|
381
|
+
sceneIds.add(sceneId);
|
|
382
|
+
addedNewScenes = true;
|
|
369
383
|
}
|
|
384
|
+
});
|
|
370
385
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (!sceneIds.has(sceneId)) {
|
|
380
|
-
const sceneObj = {
|
|
381
|
-
id: sceneId,
|
|
382
|
-
displayType: 0,
|
|
383
|
-
name: scene.Name || `Scene ${index}`,
|
|
384
|
-
namePrefix: false
|
|
385
|
-
};
|
|
386
|
-
scenesInConfig.push(sceneObj);
|
|
387
|
-
newScenes.push(sceneObj);
|
|
388
|
-
sceneIds.add(sceneId);
|
|
389
|
-
addedNewScenes = true;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// === Remove placeholder scenes (id === '0') if new ones were added ===
|
|
394
|
-
if (addedNewScenes) {
|
|
395
|
-
const beforeCount = scenesInConfig.length;
|
|
396
|
-
deviceInConfig.scenes = scenesInConfig.filter(s => String(s.id) !== '0');
|
|
397
|
-
const removedCount = beforeCount - deviceInConfig.scenes.length;
|
|
398
|
-
|
|
399
|
-
if (removedCount > 0 && removedCount < beforeCount) {
|
|
400
|
-
updateInfo('info2', `Removed ${removedCount} placeholder scene from device ${device.DeviceID}`, 'yellow');
|
|
401
|
-
}
|
|
386
|
+
// === Remove placeholder scenes (id === '0') if new ones were added ===
|
|
387
|
+
if (addedNewScenes) {
|
|
388
|
+
const beforeCount = scenesInConfig.length;
|
|
389
|
+
deviceInConfig.scenes = scenesInConfig.filter(s => String(s.id) !== '0');
|
|
390
|
+
const removedCount = beforeCount - deviceInConfig.scenes.length;
|
|
391
|
+
|
|
392
|
+
if (removedCount > 0 && removedCount < beforeCount) {
|
|
393
|
+
updateInfo('info2', `Removed ${removedCount} placeholder scene from device ${device.DeviceID}`, 'yellow');
|
|
402
394
|
}
|
|
403
395
|
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
account.ataDevices = handleDevices(devicesByType.ata, account.ataDevices, "Air Conditioner", newDevices.ata, newDevices.ataPresets, newDevices.ataSchedules, newDevices.scenes);
|
|
415
|
-
account.atwDevices = handleDevices(devicesByType.atw, account.atwDevices, "Heat Pump", newDevices.atw, newDevices.atwPresets, newDevices.atwSchedules, newDevices.scenes);
|
|
416
|
-
account.ervDevices = handleDevices(devicesByType.erv, account.ervDevices, "Energy Recovery Ventilation", newDevices.erv, newDevices.ervPresets, newDevices.ervSchedules, newDevices.scenes);
|
|
417
|
-
|
|
418
|
-
const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
|
|
419
|
-
const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
|
|
420
|
-
const newSchedulesCount = newDevices.ataSchedules.length + newDevices.atwSchedules.length + newDevices.ervSchedules.length;
|
|
421
|
-
const newScenesCount = newDevices.scenes.length;
|
|
422
|
-
const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
|
|
423
|
-
|
|
424
|
-
if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
|
|
425
|
-
updateInfo('info', 'No changes detected.', 'white');
|
|
426
|
-
} else {
|
|
427
|
-
if (newDevicesCount)
|
|
428
|
-
updateInfo('info', `Found new devices: ${newDevices.ata.length ? `ATA: ${newDevices.ata.length},` : ''} ${newDevices.atw.length ? `ATW: ${newDevices.atw.length},` : ''} ${newDevices.erv.length ? `ERV: ${newDevices.erv.length},` : ''}.`, 'green');
|
|
429
|
-
if (newPresetsCount)
|
|
430
|
-
updateInfo('info1', `Found new presets: ${newDevices.ataPresets.length ? `ATA: ${newDevices.ataPresets.length},` : ''} ${newDevices.atwPresets.length ? `ATW: ${newDevices.atwPresets.length},` : ''} ${newDevices.ervPresets.length ? `ERV: ${newDevices.ervPresets.length}` : ''}.`, 'green');
|
|
431
|
-
if (newScenesCount || newSchedulesCount)
|
|
432
|
-
updateInfo('info1', `Found new ${newSchedulesCount ? `schedules:` : ''} ${newDevices.ataSchedules.length ? `ATA: ${newDevices.ataSchedules.length},` : ''} ${newDevices.atwSchedules.length ? `ATW: ${newDevices.atwSchedules.length},` : ''} ${newDevices.ervSchedules.length ? `ERV: ${newDevices.ervSchedules.length},` : ''} ${newScenesCount ? `scenes: ${newScenesCount}` : ''}.`, 'green');
|
|
433
|
-
if (removedDevicesCount)
|
|
434
|
-
updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Return filtered devicesInConfig to make sure upstream code uses it
|
|
400
|
+
const filteredDevices = devicesInConfig.filter(d => String(d.id) !== '0');
|
|
401
|
+
return filteredDevices;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
|
|
435
404
|
}
|
|
405
|
+
};
|
|
436
406
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
407
|
+
account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.scenes);
|
|
408
|
+
account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.scenes);
|
|
409
|
+
account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.scenes);
|
|
410
|
+
|
|
411
|
+
const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
|
|
412
|
+
const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
|
|
413
|
+
const newSchedulesCount = newInMelCloud.ataSchedules.length + newInMelCloud.atwSchedules.length + newInMelCloud.ervSchedules.length;
|
|
414
|
+
const newScenesCount = newInMelCloud.scenes.length;
|
|
415
|
+
const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
|
|
416
|
+
|
|
417
|
+
if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
|
|
418
|
+
updateInfo('info', 'No changes detected.', 'white');
|
|
419
|
+
} else {
|
|
420
|
+
if (newDevicesCount)
|
|
421
|
+
updateInfo('info', `Found new devices: ${newInMelCloud.ata.length ? `ATA: ${newInMelCloud.ata.length},` : ''} ${newInMelCloud.atw.length ? `ATW: ${newInMelCloud.atw.length},` : ''} ${newInMelCloud.erv.length ? `ERV: ${newInMelCloud.erv.length},` : ''}.`, 'green');
|
|
422
|
+
if (newPresetsCount)
|
|
423
|
+
updateInfo('info1', `Found new presets: ${newInMelCloud.ataPresets.length ? `ATA: ${newInMelCloud.ataPresets.length},` : ''} ${newInMelCloud.atwPresets.length ? `ATW: ${newInMelCloud.atwPresets.length},` : ''} ${newInMelCloud.ervPresets.length ? `ERV: ${newInMelCloud.ervPresets.length}` : ''}.`, 'green');
|
|
424
|
+
if (newScenesCount || newSchedulesCount)
|
|
425
|
+
updateInfo('info1', `Found new ${newSchedulesCount ? `schedules:` : ''} ${newInMelCloud.ataSchedules.length ? `ATA: ${newInMelCloud.ataSchedules.length},` : ''} ${newInMelCloud.atwSchedules.length ? `ATW: ${newInMelCloud.atwSchedules.length},` : ''} ${newInMelCloud.ervSchedules.length ? `ERV: ${newInMelCloud.ervSchedules.length},` : ''} ${newScenesCount ? `scenes: ${newScenesCount}` : ''}.`, 'green');
|
|
426
|
+
if (removedDevicesCount)
|
|
427
|
+
updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
|
|
445
428
|
}
|
|
446
|
-
});
|
|
447
429
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
430
|
+
await homebridge.updatePluginConfig(pluginConfig);
|
|
431
|
+
await homebridge.savePluginConfig(pluginConfig);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
|
|
434
|
+
document.getElementById('logIn').className = "btn btn-secondary";
|
|
435
|
+
} finally {
|
|
436
|
+
document.getElementById('logIn').className = "btn btn-secondary";
|
|
437
|
+
homebridge.hideSpinner();
|
|
438
|
+
}
|
|
439
|
+
});
|
|
451
440
|
|
|
452
|
-
|
|
441
|
+
})();
|
|
442
|
+
</script>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.9-beta.1",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/melcloudhome.js
CHANGED
|
@@ -264,7 +264,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
devicesList.State = true;
|
|
267
|
-
devicesList.Info = `Found ${devicesCount} devices and ${scenes.length} scenes`;
|
|
267
|
+
devicesList.Info = `Found ${devicesCount} devices ${scenes.length > 0 ? `and ${scenes.length} scenes` : ''}`;
|
|
268
268
|
devicesList.Devices = devices;
|
|
269
269
|
devicesList.Scenes = scenes;
|
|
270
270
|
devicesList.Headers = this.headers;
|