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.
@@ -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
- <body>
13
-
14
- <div class="container mt-3">
15
- <div class="text-center">
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
- <div class="text-center">
89
- <button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
90
- <button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
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
- </form>
93
- <div id="accountButton" class="d-flex flex-wrap justify-content-center gap-1 mt-3"></div>
94
- </div>
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
- <script>
98
- (async () => {
99
- const pluginConfig = await homebridge.getPluginConfig();
100
-
101
- if (!pluginConfig.length) {
102
- pluginConfig.push({});
103
- await homebridge.updatePluginConfig(pluginConfig);
104
- homebridge.showSchemaForm();
105
- return;
106
- }
107
-
108
- const accounts = pluginConfig[0].accounts || [];
109
- const accountsCount = accounts.length;
110
-
111
- const container = document.getElementById("accountButton");
112
- container.style.display = 'flex';
113
- container.style.flexWrap = 'wrap';
114
- container.style.justifyContent = 'center';
115
- container.style.gap = '0.25rem';
116
- container.style.alignItems = 'center';
117
-
118
- const formElements = {
119
- accountName: document.getElementById('accountName'),
120
- info: document.getElementById('info'),
121
- info1: document.getElementById('info1'),
122
- info2: document.getElementById('info2'),
123
- name: document.getElementById('name'),
124
- user: document.getElementById('user'),
125
- passwd: document.getElementById('passwd'),
126
- language: document.getElementById('language'),
127
- accountType: document.getElementById('accountType'),
128
- logIn: document.getElementById('logIn')
129
- };
130
-
131
- // Tworzenie przycisków
132
- accounts.forEach((account, i) => {
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
- const button = document.createElement("button");
136
- button.type = "button";
137
- button.id = `button${i}`;
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
- document.getElementById('melCloudAccount').style.display = 'block';
230
+ document.getElementById('logIn').addEventListener('click', async () => {
231
+ homebridge.showSpinner();
186
232
 
187
- // Config Button Toggle
188
- const configButton = document.getElementById('configButton');
189
- let configButtonState = false;
190
- configButton.addEventListener('click', () => {
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
- // Password toggle
197
- document.getElementById('togglePasswd').addEventListener('click', () => {
198
- const passwdInput = document.getElementById('passwd');
199
- const icon = document.querySelector('#togglePasswd i');
200
- if (passwdInput.type === 'password') {
201
- passwdInput.type = 'text';
202
- icon.classList.replace('fa-eye', 'fa-eye-slash');
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
- // Device Handling & Login Logic
210
- function removeStaleDevices(configDevices, melcloudDevices) {
211
- const melcloudIds = melcloudDevices.map(d => d.DeviceID);
212
- const removedDevices = [];
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
- for (let i = configDevices.length - 1; i >= 0; i--) {
215
- const device = configDevices[i];
216
- if (device.id !== "0" && !melcloudIds.includes(device.id)) {
217
- removedDevices.push(device);
218
- configDevices.splice(i, 1);
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
- function updateInfo(id, text, color) {
225
- const el = document.getElementById(id);
226
- if (el) {
227
- el.innerText = text;
228
- el.style.color = color;
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
- document.getElementById('logIn').addEventListener('click', async () => {
233
- document.getElementById(`logIn`).className = "btn btn-primary";
234
- updateInfo('info', '', 'white');
235
- updateInfo('info1', '', 'white');
236
- updateInfo('info2', '', 'white');
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
- // Initialize devices arrays
249
- const newDevices = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [], scenes: [] };
250
- const devicesByType = { ata: [], atw: [], erv: [] };
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
- //only for melcloud
299
- if (account.type === 'melcloud') {
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
- //only for melcloudhome
336
- if (account.type === 'melcloudhome') {
337
-
338
- // === Process schedules ===
339
- const schedulesInMelCloud = device.Schedule || [];
340
- const schedulesInConfig = deviceInConfig.schedules || [];
341
- const scheduleIds = new Set(schedulesInConfig.map(s => String(s.id)));
342
-
343
- let addedNewSchedule = false;
344
- schedulesInMelCloud.forEach((schedule, index) => {
345
- const scheduleId = String(schedule.Id);
346
- if (!scheduleIds.has(scheduleId)) {
347
- const scheduleObj = {
348
- id: scheduleId,
349
- displayType: 0,
350
- name: `Schedule ${index}`,
351
- namePrefix: false
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
- // === Process scenes ===
372
- const scenesInMelCloud = device.Scenes || [];
373
- const scenesInConfig = deviceInConfig.scenes || [];
374
- const sceneIds = new Set(scenesInConfig.map(s => String(s.id)));
375
-
376
- let addedNewScenes = false;
377
- scenesInMelCloud.forEach((scene, index) => {
378
- const sceneId = String(scene.Id);
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
- // Return filtered devicesInConfig to make sure upstream code uses it
407
- const filteredDevices = devicesInConfig.filter(d => String(d.id) !== '0');
408
- return filteredDevices;
409
- } catch (error) {
410
- updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
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
- await homebridge.updatePluginConfig(pluginConfig);
438
- await homebridge.savePluginConfig(pluginConfig);
439
- } catch (error) {
440
- updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
441
- document.getElementById('logIn').className = "btn btn-secondary";
442
- } finally {
443
- document.getElementById('logIn').className = "btn btn-secondary";
444
- homebridge.hideSpinner();
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
- </script>
450
- </body>
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
- </html>
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.8",
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",
@@ -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;