homebridge-melcloud-control 4.3.9-beta.0 → 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,439 +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
- homebridge.showSpinner();
234
-
235
- document.getElementById(`logIn`).className = "btn btn-primary";
236
- updateInfo('info', '', 'white');
237
- updateInfo('info1', '', 'white');
238
- updateInfo('info2', '', 'white');
239
-
240
- try {
241
- const account = this.account;
242
- const response = await homebridge.request('/connect', account);
243
- if (!response.State) {
244
- homebridge.hideSpinner();
245
- updateInfo('info', response.Info);
246
- return;
247
- }
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;
248
322
 
249
- // Initialize devices arrays
250
- const newInMelCloud = { ata: [], ataPresets: [], ataSchedules: [], atw: [], atwPresets: [], atwSchedules: [], erv: [], ervPresets: [], ervSchedules: [], scenes: [] };
251
- const devicesInMelCloudByType = { ata: [], atw: [], erv: [] };
252
- const scenesInMelCloud = response.Scenes ?? []
253
-
254
- response.Devices.forEach(device => {
255
- if (device.Type === 0) devicesInMelCloudByType.ata.push(device);
256
- if (device.Type === 1) devicesInMelCloudByType.atw.push(device);
257
- if (device.Type === 3) devicesInMelCloudByType.erv.push(device);
258
- });
259
-
260
- account.ataDevices ??= [];
261
- account.atwDevices ??= [];
262
- account.ervDevices ??= [];
263
-
264
- const removedAta = removeStaleDevices(account.ataDevices, devicesInMelCloudByType.ata);
265
- const removedAtw = removeStaleDevices(account.atwDevices, devicesInMelCloudByType.atw);
266
- const removedErv = removeStaleDevices(account.ervDevices, devicesInMelCloudByType.erv);
267
-
268
- const handleDevices = (devicesInMelCloud, devicesInConfig, typeString, newDevices, newPresets, newSchedules, newScenes) => {
269
- try {
270
- const configDevicesMap = new Map(devicesInConfig.map(dev => [String(dev.id), dev]));
271
-
272
- devicesInMelCloud.forEach(device => {
273
- const deviceId = String(device.DeviceID);
274
- let deviceInConfig = configDevicesMap.get(deviceId);
275
-
276
- // === Create device if missing ===
277
- if (!deviceInConfig) {
278
- deviceInConfig = {
279
- id: deviceId,
280
- type: device.Type,
281
- typeString,
282
- displayType: 0,
283
- name: device.DeviceName,
284
- presets: [],
285
- schedules: [],
286
- scenes: [],
287
- buttonsSensors: []
288
- };
289
- devicesInConfig.push(deviceInConfig);
290
- newDevices.push(deviceInConfig);
291
- configDevicesMap.set(deviceId, deviceInConfig);
323
+ if (removedCount > 0 && removedCount < beforeCount) {
324
+ updateInfo('info2', `Removed ${removedCount} placeholder preset from device ${device.DeviceID}`, 'yellow');
325
+ }
292
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;
293
359
 
294
- //only for melcloud
295
- if (account.type === 'melcloud') {
296
-
297
- // === Process presets ===
298
- const presetsInMelCloud = device.Presets || [];
299
- const presetsInConfig = deviceInConfig.presets || [];
300
- const presetIds = new Set(presetsInConfig.map(p => String(p.id)));
301
-
302
- let addedNewPreset = false;
303
- presetsInMelCloud.forEach((preset, index) => {
304
- const presetId = String(preset.ID);
305
- if (!presetIds.has(presetId)) {
306
- const presetObj = {
307
- id: presetId,
308
- displayType: 0,
309
- name: preset.NumberDescription || `Preset ${index}`,
310
- namePrefix: false
311
- };
312
- presetsInConfig.push(presetObj);
313
- newPresets.push(presetObj);
314
- presetIds.add(presetId);
315
- addedNewPreset = true;
316
- }
317
- });
318
-
319
- // === Remove placeholder presets/schedules (id === '0') if new ones were added ===
320
- if (addedNewPreset) {
321
- const beforeCount = presetsInConfig.length;
322
- deviceInConfig.presets = presetsInConfig.filter(p => String(p.id) !== '0');
323
- const removedCount = beforeCount - deviceInConfig.presets.length;
324
-
325
- if (removedCount > 0 && removedCount < beforeCount) {
326
- updateInfo('info2', `Removed ${removedCount} placeholder preset from device ${device.DeviceID}`, 'yellow');
327
- }
360
+ if (removedCount > 0 && removedCount < beforeCount) {
361
+ updateInfo('info2', `Removed ${removedCount} placeholder schedule from device ${device.DeviceID}`, 'yellow');
328
362
  }
329
363
  }
330
364
 
331
- //only for melcloudhome
332
- if (account.type === 'melcloudhome') {
333
-
334
- // === Process schedules ===
335
- const schedulesInMelCloud = device.Schedule || [];
336
- const schedulesInConfig = deviceInConfig.schedules || [];
337
- const scheduleIds = new Set(schedulesInConfig.map(s => String(s.id)));
338
-
339
- let addedNewSchedule = false;
340
- schedulesInMelCloud.forEach((schedule, index) => {
341
- const scheduleId = String(schedule.Id);
342
- if (!scheduleIds.has(scheduleId)) {
343
- const scheduleObj = {
344
- id: scheduleId,
345
- displayType: 0,
346
- name: `Schedule ${index}`,
347
- namePrefix: false
348
- };
349
- schedulesInConfig.push(scheduleObj);
350
- newSchedules.push(scheduleObj);
351
- scheduleIds.add(scheduleId);
352
- addedNewSchedule = true;
353
- }
354
- });
355
-
356
- // === Remove placeholder schedules (id === '0') if new ones were added ===
357
- if (addedNewSchedule) {
358
- const beforeCount = schedulesInConfig.length;
359
- deviceInConfig.schedules = schedulesInConfig.filter(s => String(s.id) !== '0');
360
- const removedCount = beforeCount - deviceInConfig.schedules.length;
361
-
362
- if (removedCount > 0 && removedCount < beforeCount) {
363
- updateInfo('info2', `Removed ${removedCount} placeholder schedule from device ${device.DeviceID}`, 'yellow');
364
- }
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;
365
383
  }
384
+ });
366
385
 
367
- // === Process scenes ===
368
- const scenesInConfig = deviceInConfig.scenes || [];
369
- const sceneIds = new Set(scenesInConfig.map(s => String(s.id)));
370
-
371
- let addedNewScenes = false;
372
- scenesInMelCloud.forEach((scene, index) => {
373
- const sceneId = String(scene.Id);
374
- if (!sceneIds.has(sceneId)) {
375
- const sceneObj = {
376
- id: sceneId,
377
- displayType: 0,
378
- name: scene.Name || `Scene ${index}`,
379
- namePrefix: false
380
- };
381
- scenesInConfig.push(sceneObj);
382
- newScenes.push(sceneObj);
383
- sceneIds.add(sceneId);
384
- addedNewScenes = true;
385
- }
386
- });
387
-
388
- // === Remove placeholder scenes (id === '0') if new ones were added ===
389
- if (addedNewScenes) {
390
- const beforeCount = scenesInConfig.length;
391
- deviceInConfig.scenes = scenesInConfig.filter(s => String(s.id) !== '0');
392
- const removedCount = beforeCount - deviceInConfig.scenes.length;
393
-
394
- if (removedCount > 0 && removedCount < beforeCount) {
395
- updateInfo('info2', `Removed ${removedCount} placeholder scene from device ${device.DeviceID}`, 'yellow');
396
- }
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');
397
394
  }
398
395
  }
399
- });
400
-
401
- // Return filtered devicesInConfig to make sure upstream code uses it
402
- const filteredDevices = devicesInConfig.filter(d => String(d.id) !== '0');
403
- return filteredDevices;
404
- } catch (error) {
405
- updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
406
- }
407
- };
408
-
409
- account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.scenes);
410
- account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.scenes);
411
- account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.scenes);
412
-
413
- const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
414
- const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
415
- const newSchedulesCount = newInMelCloud.ataSchedules.length + newInMelCloud.atwSchedules.length + newInMelCloud.ervSchedules.length;
416
- const newScenesCount = newInMelCloud.scenes.length;
417
- const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
418
-
419
- if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
420
- updateInfo('info', 'No changes detected.', 'white');
421
- } else {
422
- if (newDevicesCount)
423
- 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');
424
- if (newPresetsCount)
425
- 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');
426
- if (newScenesCount || newSchedulesCount)
427
- 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');
428
- if (removedDevicesCount)
429
- 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');
430
404
  }
405
+ };
431
406
 
432
- await homebridge.updatePluginConfig(pluginConfig);
433
- await homebridge.savePluginConfig(pluginConfig);
434
- } catch (error) {
435
- updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
436
- document.getElementById('logIn').className = "btn btn-secondary";
437
- } finally {
438
- document.getElementById('logIn').className = "btn btn-secondary";
439
- 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');
440
428
  }
441
- });
442
429
 
443
- })();
444
- </script>
445
- </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
+ });
446
440
 
447
- </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.9-beta.0",
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",