homebridge-melcloud-control 4.3.9-beta.0 → 4.3.9-beta.10

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,390 @@
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
- });
184
-
185
- document.getElementById('melCloudAccount').style.display = 'block';
186
-
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
160
  });
195
-
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');
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 (!melcloudIds.includes(device.id)) {
215
+ removedDevices.push(device);
216
+ configDevices.splice(i, 1);
206
217
  }
207
- });
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
+ }
208
229
 
209
- // Device Handling & Login Logic
210
- function removeStaleDevices(configDevices, melcloudDevices) {
211
- const melcloudIds = melcloudDevices.map(d => d.DeviceID);
212
- const removedDevices = [];
230
+ document.getElementById('logIn').addEventListener('click', async () => {
231
+ homebridge.showSpinner();
213
232
 
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
- }
233
+ document.getElementById(`logIn`).className = "btn btn-primary";
234
+ updateInfo('info', '', 'white');
235
+ updateInfo('info1', '', 'white');
236
+ updateInfo('info2', '', 'white');
223
237
 
224
- function updateInfo(id, text, color) {
225
- const el = document.getElementById(id);
226
- if (el) {
227
- el.innerText = text;
228
- el.style.color = color;
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;
229
245
  }
230
- }
231
246
 
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
- }
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 ?? []
251
+
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
+ });
248
257
 
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);
292
- }
293
-
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
- }
258
+ account.ataDevices = (account.ataDevices ?? []).filter(d => String(d.id) !== '0');
259
+ account.atwDevices = (account.atwDevices ?? []).filter(d => String(d.id) !== '0');
260
+ account.ervDevices = (account.ervDevices ?? []).filter(d => String(d.id) !== '0');
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, deviceTypeString, 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
+ deviceTypeString,
280
+ displayType: 0,
281
+ name: device.DeviceName
282
+ };
283
+ devicesInConfig.push(deviceInConfig);
284
+ newDevices.push(deviceInConfig);
285
+ configDevicesMap.set(deviceId, deviceInConfig);
286
+ }
287
+
288
+ //only for melcloud
289
+ if (account.type === 'melcloud') {
290
+
291
+ // === Process presets ===
292
+ const presetsInMelCloud = device.Presets || [];
293
+ const presetsInConfig = (deviceInConfig.presets ?? []).filter(p => String(p.id) !== '0');
294
+ const presetIds = new Set(presetsInConfig.map(p => String(p.id)));
295
+
296
+ presetsInMelCloud.forEach((preset, index) => {
297
+ const presetId = String(preset.ID);
298
+ if (!presetIds.has(presetId)) {
299
+ const presetObj = {
300
+ id: presetId,
301
+ displayType: 0,
302
+ name: preset.NumberDescription || `Preset ${index}`,
303
+ namePrefix: false
304
+ };
305
+ presetsInConfig.push(presetObj);
306
+ newPresets.push(presetObj);
307
+ presetIds.add(presetId);
328
308
  }
329
- }
330
-
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
- }
309
+ });
310
+ }
311
+
312
+ //only for melcloudhome
313
+ if (account.type === 'melcloudhome') {
314
+
315
+ // === Process schedules ===
316
+ const schedulesInMelCloud = device.Schedule || [];
317
+ const schedulesInConfig = (deviceInConfig.schedules ?? []).filter(s => String(s.id) !== '0');
318
+ const scheduleIds = new Set(schedulesInConfig.map(s => String(s.id)));
319
+
320
+ schedulesInMelCloud.forEach((schedule, index) => {
321
+ const scheduleId = String(schedule.Id);
322
+ if (!scheduleIds.has(scheduleId)) {
323
+ const scheduleObj = {
324
+ id: scheduleId,
325
+ displayType: 0,
326
+ name: `Schedule ${index}`,
327
+ namePrefix: false
328
+ };
329
+ schedulesInConfig.push(scheduleObj);
330
+ newSchedules.push(scheduleObj);
331
+ scheduleIds.add(scheduleId);
365
332
  }
366
-
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
- }
333
+ });
334
+
335
+ // === Process scenes ===
336
+ const scenesInConfig = (deviceInConfig.scenes ?? []).filter(s => String(s.id) !== '0');
337
+ const sceneIds = new Set(scenesInConfig.map(s => String(s.id)));
338
+
339
+ scenesInMelCloud.forEach((scene, index) => {
340
+ const sceneId = String(scene.Id);
341
+ if (!sceneIds.has(sceneId)) {
342
+ const sceneObj = {
343
+ id: sceneId,
344
+ displayType: 0,
345
+ name: scene.Name || `Scene ${index}`,
346
+ namePrefix: false
347
+ };
348
+ scenesInConfig.push(sceneObj);
349
+ newScenes.push(sceneObj);
350
+ sceneIds.add(sceneId);
397
351
  }
398
- }
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');
352
+ });
353
+ }
354
+ });
355
+
356
+ // Return filtered devicesInConfig to make sure upstream code uses it
357
+ return devicesInConfig;
358
+ } catch (error) {
359
+ updateInfo('info', `Error while processing device: ${JSON.stringify(error)}`, 'red');
430
360
  }
361
+ };
431
362
 
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();
363
+ account.ataDevices = handleDevices(devicesInMelCloudByType.ata, account.ataDevices, "Air Conditioner", newInMelCloud.ata, newInMelCloud.ataPresets, newInMelCloud.ataSchedules, newInMelCloud.scenes);
364
+ account.atwDevices = handleDevices(devicesInMelCloudByType.atw, account.atwDevices, "Heat Pump", newInMelCloud.atw, newInMelCloud.atwPresets, newInMelCloud.atwSchedules, newInMelCloud.scenes);
365
+ account.ervDevices = handleDevices(devicesInMelCloudByType.erv, account.ervDevices, "Energy Recovery Ventilation", newInMelCloud.erv, newInMelCloud.ervPresets, newInMelCloud.ervSchedules, newInMelCloud.scenes);
366
+
367
+ const newDevicesCount = newInMelCloud.ata.length + newInMelCloud.atw.length + newInMelCloud.erv.length;
368
+ const newPresetsCount = newInMelCloud.ataPresets.length + newInMelCloud.atwPresets.length + newInMelCloud.ervPresets.length;
369
+ const newSchedulesCount = newInMelCloud.ataSchedules.length + newInMelCloud.atwSchedules.length + newInMelCloud.ervSchedules.length;
370
+ const newScenesCount = newInMelCloud.scenes.length;
371
+ const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
372
+
373
+ if (!newDevicesCount && !newPresetsCount && !newSchedulesCount && !newScenesCount && !removedDevicesCount) {
374
+ updateInfo('info', 'No changes detected.', 'white');
375
+ } else {
376
+ if (newDevicesCount)
377
+ 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');
378
+ if (newPresetsCount)
379
+ 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');
380
+ if (newScenesCount || newSchedulesCount)
381
+ 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');
382
+ if (removedDevicesCount)
383
+ updateInfo('info2', `Removed devices: ${removedAta.length ? `ATA: ${removedAta.length},` : ''} ${removedAtw.length ? `ATW: ${removedAtw.length},` : ''} ${removedErv.length ? `ERV: ${removedErv.length}` : ''}.`, 'orange');
440
384
  }
441
- });
442
385
 
443
- })();
444
- </script>
445
- </body>
386
+ await homebridge.updatePluginConfig(pluginConfig);
387
+ await homebridge.savePluginConfig(pluginConfig);
388
+ } catch (error) {
389
+ updateInfo('info', `Prepare config error ${JSON.stringify(error)}`, 'red');
390
+ document.getElementById('logIn').className = "btn btn-secondary";
391
+ } finally {
392
+ document.getElementById('logIn').className = "btn btn-secondary";
393
+ homebridge.hideSpinner();
394
+ }
395
+ });
446
396
 
447
- </html>
397
+ })();
398
+ </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.10",
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/constants.js CHANGED
@@ -58,15 +58,19 @@ export const TemperatureDisplayUnits = ["°C", "°F"];
58
58
 
59
59
  export const AirConditioner = {
60
60
  SystemMapEnumToString: { 0: "Air Conditioner Off", 1: "Air Conditioner On", 2: "Air Conditioner Offline" },
61
- OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 7, "5": 8, "6": 6, "Fan": 7, "Automatic": 8, "Isee Heat": 9, "Isee Dry": 10, "Isee Cool": 11 },
61
+ OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Automatic": 8, "Isee Heat": 9, "Isee Dry": 10, "Isee Cool": 11 },
62
62
  OperationModeMapEnumToString: { 0: "0", 1: "Heat", 2: "Dry", 3: "Cool", 4: "4", 5: "5", 6: "6", 7: "Fan", 8: "Automatic", 9: "Isee Heat", 10: "Isee Dry", 11: "Isee Cool" },
63
+ OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11 },
63
64
  FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 },
64
65
  FanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
66
+ AktualFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 },
65
67
  AktualFanSpeedMapEnumToString: { 0: "Quiet", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
66
68
  VaneVerticalDirectionMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "Six": 6, "Swing": 7 },
67
69
  VaneVerticalDirectionMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Swing" },
70
+ VaneVerticalDirectionMapEnumToEnumWs: { 6: 7 },
68
71
  VaneHorizontalDirectionMapStringToEnum: { "Auto": 0, "Left": 1, "LeftCentre": 2, "Centre": 3, "RightCentre": 4, "Right": 5, "Six": 6, "Seven": 7, "Split": 8, "Nine": 9, "Ten": 10, "Eleven": 11, "Swing": 12 },
69
72
  VaneHorizontalDirectionMapEnumToString: { 0: "Auto", 1: "Left", 2: "LeftCentre", 3: "Centre", 4: "RightCentre", 5: "Right", 6: "Six", 7: "Seven", 8: "Split", 9: "Nine", 10: "Ten", 11: "Eleven", 12: "Swing" },
73
+ VaneHorizontalDirectionMapEnumToEnumWs: { 7: 12 },
70
74
  AirDirectionMapEnumToString: { 0: "Auto", 1: "Swing" },
71
75
  CurrentOperationModeMapEnumToStringHeatherCooler: { 0: "Inactive", 1: "Idle", 2: "Heating", 3: "Cooling" },
72
76
  CurrentOperationModeMapEnumToStringThermostat: { 0: "Inactive", 1: "Heating", 2: "Cooling" },
package/src/deviceata.js CHANGED
@@ -439,7 +439,8 @@ class DeviceAta extends EventEmitter {
439
439
  if (supportsSwingFunction) {
440
440
  melCloudService.getCharacteristic(Characteristic.SwingMode)
441
441
  .onGet(async () => {
442
- //Vane Horizontal: Auto, 1, 2, 3, 4, 5, 6, 7 = Sp;it, 12 = Swing //Vertical: Auto, 1, 2, 3, 4, 5, 7 = Swing
442
+ //Vane Horizontal: Auto, 1, 2, 3, 4, 5, 6, 7 = Split, 12 = Swing //Vertical: Auto, 1, 2, 3, 4, 5, 7 = Swing
443
+ //Home Vane Horizontal: Auto, 1, 2, 3, 4, 5, 6, 7 = Swing, 8 = Split //Vertical: Auto, 1, 2, 3, 4, 5, 6 = Swing
443
444
  const value = this.accessory.currentSwingMode;
444
445
  return value;
445
446
  })
@@ -35,6 +35,7 @@ class MelCloudAta extends EventEmitter {
35
35
  deviceData.Scenes = devicesData.Scenes ?? [];
36
36
 
37
37
  //update state
38
+ if (!this.logDebug) this.emit('debug', `Request update device settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
38
39
  await this.updateState(deviceData);
39
40
  }).on('webSocket', async (parsedMessage) => {
40
41
  try {
@@ -62,9 +63,12 @@ class MelCloudAta extends EventEmitter {
62
63
 
63
64
  //update device settings
64
65
  if (key in deviceData.Device) {
65
- deviceData.Device[key] = value;
66
+ let parsedValue = this.functions.convertValue(value);
67
+ deviceData.Device[key] = parsedValue;
66
68
  }
67
69
  }
70
+
71
+ if (!this.logDebug) this.emit('debug', `WS update device settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
68
72
  updateState = true;
69
73
  break;
70
74
  case 'unitHolidayModeTriggered':
@@ -88,21 +92,28 @@ class MelCloudAta extends EventEmitter {
88
92
  }
89
93
 
90
94
  //update state
91
- if (updateState) await this.updateState(deviceData);
95
+ if (updateState) await this.updateState(deviceData, 'ws');
92
96
  } catch (error) {
93
97
  if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
94
98
  }
95
99
  });
96
100
  }
97
101
 
98
- async updateState(deviceData) {
102
+ async updateState(deviceData, type) {
99
103
  try {
100
104
  if (this.accountType === 'melcloudhome') {
101
- deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
102
- deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
103
- deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
104
- deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
105
- deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
105
+
106
+ if (type === 'ws') {
107
+ deviceData.Device.OperationMode = AirConditioner.OperationModeMapEnumToEnumWs[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
108
+ deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapEnumToEnumWs[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
109
+ deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapEnumToEnumWs[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
110
+ } else {
111
+ deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
112
+ deviceData.Device.ActualFanSpeed = AirConditioner.AktualFanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
113
+ deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
114
+ deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
115
+ deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
116
+ }
106
117
 
107
118
  //read default temps
108
119
  const temps = await this.functions.readData(this.defaultTempsFile, true);
@@ -224,7 +235,7 @@ class MelCloudAta extends EventEmitter {
224
235
  }
225
236
 
226
237
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
227
-
238
+
228
239
  await axios(path, {
229
240
  method: 'POST',
230
241
  baseURL: ApiUrls.BaseURL,
@@ -313,7 +324,7 @@ class MelCloudAta extends EventEmitter {
313
324
  //sens payload
314
325
  headers['Content-Type'] = 'application/json; charset=utf-8';
315
326
  headers.Origin = ApiUrlsHome.Origin;
316
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
327
+ if (!this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
317
328
 
318
329
  await axios(path, {
319
330
  method: method,