homebridge-melcloud-control 4.2.5-beta.3 → 4.2.5-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +22 -21
- package/config.schema.json +11 -11
- package/index.js +6 -15
- package/package.json +1 -1
- package/src/constants.js +1 -0
- package/src/deviceata.js +20 -19
- package/src/deviceatw.js +21 -23
- package/src/deviceerv.js +21 -20
- package/src/melcloud.js +11 -28
- package/src/melcloudata.js +47 -41
- package/src/melcloudatw.js +47 -40
- package/src/melclouderv.js +59 -52
- package/src/melcloudhome.js +27 -52
package/src/melclouderv.js
CHANGED
|
@@ -5,7 +5,7 @@ import Functions from './functions.js';
|
|
|
5
5
|
import { ApiUrls, ApiUrlsHome, Ventilation } from './constants.js';
|
|
6
6
|
|
|
7
7
|
class MelCloudErv extends EventEmitter {
|
|
8
|
-
constructor(account, device, devicesFile, defaultTempsFile) {
|
|
8
|
+
constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
|
|
9
9
|
super();
|
|
10
10
|
this.accountType = account.type;
|
|
11
11
|
this.logWarn = account.log?.warn;
|
|
@@ -16,6 +16,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
16
16
|
this.deviceId = device.id;
|
|
17
17
|
this.devicesFile = devicesFile;
|
|
18
18
|
this.defaultTempsFile = defaultTempsFile;
|
|
19
|
+
this.accountFile = accountFile;
|
|
19
20
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
20
21
|
.on('warn', warn => this.emit('warn', warn))
|
|
21
22
|
.on('error', error => this.emit('error', error))
|
|
@@ -26,7 +27,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
26
27
|
this.headers = {};
|
|
27
28
|
|
|
28
29
|
//lock flags
|
|
29
|
-
this.locks =
|
|
30
|
+
this.locks = false;
|
|
30
31
|
this.impulseGenerator = new ImpulseGenerator()
|
|
31
32
|
.on('checkState', () => this.handleWithLock(async () => {
|
|
32
33
|
await this.checkState();
|
|
@@ -53,24 +54,19 @@ class MelCloudErv extends EventEmitter {
|
|
|
53
54
|
try {
|
|
54
55
|
//read device info from file
|
|
55
56
|
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
57
|
+
if (!devicesData) return;
|
|
58
|
+
|
|
56
59
|
this.headers = devicesData.Headers;
|
|
57
|
-
const scenes = devicesData.Scenes ?? [];
|
|
58
60
|
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
59
|
-
|
|
60
61
|
if (this.accountType === 'melcloudhome') {
|
|
61
|
-
deviceData.Scenes =
|
|
62
|
+
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
62
63
|
|
|
63
64
|
//read default temps
|
|
64
65
|
const temps = await this.functions.readData(this.defaultTempsFile, true);
|
|
65
66
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
66
67
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
const safeConfig = {
|
|
70
|
-
...deviceData,
|
|
71
|
-
Headers: 'removed',
|
|
72
|
-
};
|
|
73
|
-
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(safeConfig, null, 2)}`);
|
|
69
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
74
70
|
|
|
75
71
|
//device
|
|
76
72
|
const serialNumber = deviceData.SerialNumber || '4.0.0';
|
|
@@ -132,50 +128,61 @@ class MelCloudErv extends EventEmitter {
|
|
|
132
128
|
let path = '';
|
|
133
129
|
switch (accountType) {
|
|
134
130
|
case "melcloud":
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
case
|
|
146
|
-
|
|
147
|
-
|
|
131
|
+
switch (flag) {
|
|
132
|
+
case 'account':
|
|
133
|
+
flagData.Account.LoginData.UseFahrenheit = flagData.UseFahrenheit;
|
|
134
|
+
payload = { data: flagData.LoginData };
|
|
135
|
+
path = ApiUrls.UpdateApplicationOptions;
|
|
136
|
+
await this.functions.saveData(this.accountFile, flagData);
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
//set target temp based on display mode and ventilation mode
|
|
140
|
+
switch (displayType) {
|
|
141
|
+
case 1: //Heather/Cooler
|
|
142
|
+
switch (deviceData.Device.VentilationMode) {
|
|
143
|
+
case 0: //LOSNAY
|
|
144
|
+
deviceData.Device.SetTemperature = deviceData.Device.DefaultHeatingSetTemperature;
|
|
145
|
+
break;
|
|
146
|
+
case 1: //BYPASS
|
|
147
|
+
deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature;
|
|
148
|
+
break;
|
|
149
|
+
case 2: //AUTO
|
|
150
|
+
const setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
|
|
151
|
+
deviceData.Device.SetTemperature = setTemperature;
|
|
152
|
+
break;
|
|
153
|
+
};
|
|
154
|
+
case 2: //Thermostat
|
|
155
|
+
deviceData.Device.SetTemperature = deviceData.Device.SetTemperature;
|
|
148
156
|
break;
|
|
149
157
|
};
|
|
150
|
-
case 2: //Thermostat
|
|
151
|
-
deviceData.Device.SetTemperature = deviceData.Device.SetTemperature;
|
|
152
|
-
break;
|
|
153
|
-
};
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
//device state
|
|
160
|
+
deviceData.Device.EffectiveFlags = flag;
|
|
161
|
+
payload = {
|
|
162
|
+
DeviceID: deviceData.Device.DeviceID,
|
|
163
|
+
EffectiveFlags: deviceData.Device.EffectiveFlags,
|
|
164
|
+
Power: deviceData.Device.Power,
|
|
165
|
+
SetTemperature: deviceData.Device.SetTemperature,
|
|
166
|
+
SetFanSpeed: deviceData.Device.SetFanSpeed,
|
|
167
|
+
OperationMode: deviceData.Device.OperationMode,
|
|
168
|
+
VentilationMode: deviceData.Device.VentilationMode,
|
|
169
|
+
DefaultCoolingSetTemperature: deviceData.Device.DefaultCoolingSetTemperature,
|
|
170
|
+
DefaultHeatingSetTemperature: deviceData.Device.DefaultHeatingSetTemperature,
|
|
171
|
+
HideRoomTemperature: deviceData.Device.HideRoomTemperature,
|
|
172
|
+
HideSupplyTemperature: deviceData.Device.HideSupplyTemperature,
|
|
173
|
+
HideOutdoorTemperature: deviceData.Device.HideOutdoorTemperature,
|
|
174
|
+
NightPurgeMode: deviceData.Device.NightPurgeMode,
|
|
175
|
+
HasPendingCommand: true
|
|
176
|
+
}
|
|
177
|
+
path = ApiUrls.SetErv;
|
|
178
|
+
break;
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
175
|
-
await axios(
|
|
182
|
+
await axios(path, {
|
|
176
183
|
method: 'POST',
|
|
177
184
|
baseURL: ApiUrls.BaseURL,
|
|
178
|
-
timeout:
|
|
185
|
+
timeout: 30000,
|
|
179
186
|
headers: this.headers,
|
|
180
187
|
data: payload
|
|
181
188
|
});
|
|
@@ -228,7 +235,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
228
235
|
};
|
|
229
236
|
method = 'PUT';
|
|
230
237
|
path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
|
|
231
|
-
this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings
|
|
238
|
+
this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
|
|
232
239
|
break
|
|
233
240
|
}
|
|
234
241
|
|
|
@@ -238,7 +245,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
238
245
|
await axios(path, {
|
|
239
246
|
method: method,
|
|
240
247
|
baseURL: ApiUrlsHome.BaseURL,
|
|
241
|
-
timeout:
|
|
248
|
+
timeout: 30000,
|
|
242
249
|
headers: this.headers,
|
|
243
250
|
data: payload
|
|
244
251
|
});
|
|
@@ -254,12 +261,12 @@ class MelCloudErv extends EventEmitter {
|
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
updateData(deviceData) {
|
|
257
|
-
this.
|
|
264
|
+
this.locks = true;
|
|
258
265
|
this.emit('deviceState', deviceData);
|
|
259
266
|
|
|
260
267
|
setTimeout(() => {
|
|
261
|
-
this.
|
|
262
|
-
},
|
|
268
|
+
this.locks = false
|
|
269
|
+
}, 5000);
|
|
263
270
|
}
|
|
264
271
|
};
|
|
265
272
|
export default MelCloudErv;
|
package/src/melcloudhome.js
CHANGED
|
@@ -9,7 +9,7 @@ import Functions from './functions.js';
|
|
|
9
9
|
import { ApiUrlsHome, LanguageLocaleMap } from './constants.js';
|
|
10
10
|
const execPromise = promisify(exec);
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class MelCloudHome extends EventEmitter {
|
|
13
13
|
constructor(account, accountFile, buildingsFile, devicesFile, pluginStart = false) {
|
|
14
14
|
super();
|
|
15
15
|
this.accountType = account.type;
|
|
@@ -61,16 +61,10 @@ class MelCloud extends EventEmitter {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// MELCloud Home
|
|
65
64
|
async checkScenesList() {
|
|
66
65
|
try {
|
|
67
66
|
if (this.logDebug) this.emit('debug', `Scanning for scenes`);
|
|
68
|
-
const listScenesData = await
|
|
69
|
-
method: 'GET',
|
|
70
|
-
baseURL: ApiUrlsHome.BaseURL,
|
|
71
|
-
timeout: 25000,
|
|
72
|
-
headers: this.headers
|
|
73
|
-
});
|
|
67
|
+
const listScenesData = await this.axiosInstance(ApiUrlsHome.GetUserScenes, { method: 'GET', });
|
|
74
68
|
|
|
75
69
|
const scenesList = listScenesData.data;
|
|
76
70
|
if (this.logDebug) this.emit('debug', `Scenes: ${JSON.stringify(scenesList, null, 2)}`);
|
|
@@ -102,12 +96,7 @@ class MelCloud extends EventEmitter {
|
|
|
102
96
|
try {
|
|
103
97
|
const devicesList = { State: false, Info: null, Devices: [], Scenes: [] }
|
|
104
98
|
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
105
|
-
const listDevicesData = await
|
|
106
|
-
method: 'GET',
|
|
107
|
-
baseURL: ApiUrlsHome.BaseURL,
|
|
108
|
-
timeout: 25000,
|
|
109
|
-
headers: this.headers
|
|
110
|
-
});
|
|
99
|
+
const listDevicesData = await this.axiosInstance(ApiUrlsHome.GetUserContext, { method: 'GET' });
|
|
111
100
|
|
|
112
101
|
const userContext = listDevicesData.data;
|
|
113
102
|
const buildings = userContext.buildings ?? [];
|
|
@@ -205,10 +194,17 @@ class MelCloud extends EventEmitter {
|
|
|
205
194
|
return devicesList;
|
|
206
195
|
}
|
|
207
196
|
|
|
208
|
-
|
|
197
|
+
// Get scenes
|
|
198
|
+
let scenes = [];
|
|
199
|
+
try {
|
|
200
|
+
scenes = await this.checkScenesList();
|
|
201
|
+
if (this.logDebug) this.emit('debug', `Found ${scenes.length} svenes`);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (this.logDebug) this.emit('debug', `Get scenes error: ${error} `);
|
|
204
|
+
}
|
|
209
205
|
|
|
210
206
|
devicesList.State = true;
|
|
211
|
-
devicesList.Info = `Found ${devicesCount} devices`;
|
|
207
|
+
devicesList.Info = `Found ${devicesCount} devices and ${scenes.length} scenes`;
|
|
212
208
|
devicesList.Devices = devices;
|
|
213
209
|
devicesList.Scenes = scenes;
|
|
214
210
|
devicesList.Headers = this.headers;
|
|
@@ -218,12 +214,6 @@ class MelCloud extends EventEmitter {
|
|
|
218
214
|
|
|
219
215
|
return devicesList;
|
|
220
216
|
} catch (error) {
|
|
221
|
-
if (error.response?.status === 401) {
|
|
222
|
-
if (this.logWarn) this.emit('warn', 'Check devices list not possible, cookies expired, trying to get new.');
|
|
223
|
-
await this.connect();
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
217
|
throw new Error(`Check devices list error: ${error.message}`);
|
|
228
218
|
}
|
|
229
219
|
}
|
|
@@ -234,7 +224,7 @@ class MelCloud extends EventEmitter {
|
|
|
234
224
|
|
|
235
225
|
let browser;
|
|
236
226
|
try {
|
|
237
|
-
const accountInfo = { State: false, Info: '',
|
|
227
|
+
const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false };
|
|
238
228
|
let chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
239
229
|
|
|
240
230
|
// === Fallback to Puppeteer's built-in Chromium ===
|
|
@@ -286,14 +276,6 @@ class MelCloud extends EventEmitter {
|
|
|
286
276
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
287
277
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
288
278
|
|
|
289
|
-
// Clear cookies before navigation
|
|
290
|
-
try {
|
|
291
|
-
const client = await page.createCDPSession();
|
|
292
|
-
await client.send('Network.clearBrowserCookies');
|
|
293
|
-
} catch (error) {
|
|
294
|
-
if (this.logError) this.emit('error', `Clear cookies error: ${error.message}`);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
279
|
try {
|
|
298
280
|
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
299
281
|
} catch (error) {
|
|
@@ -303,7 +285,7 @@ class MelCloud extends EventEmitter {
|
|
|
303
285
|
|
|
304
286
|
// Wait extra to ensure UI is rendered
|
|
305
287
|
await new Promise(r => setTimeout(r, 3000));
|
|
306
|
-
const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT /
|
|
288
|
+
const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 3 });
|
|
307
289
|
const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
|
|
308
290
|
|
|
309
291
|
if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
|
|
@@ -329,13 +311,13 @@ class MelCloud extends EventEmitter {
|
|
|
329
311
|
accountInfo.Info = 'Submit button not found';
|
|
330
312
|
return accountInfo;
|
|
331
313
|
}
|
|
332
|
-
await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT /
|
|
314
|
+
await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 3 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
333
315
|
|
|
334
316
|
// Extract cookies
|
|
335
317
|
let c1 = null, c2 = null;
|
|
336
318
|
const start = Date.now();
|
|
337
319
|
while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
|
|
338
|
-
const cookies = await
|
|
320
|
+
const cookies = await browser.cookies();
|
|
339
321
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
340
322
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
341
323
|
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
@@ -352,7 +334,9 @@ class MelCloud extends EventEmitter {
|
|
|
352
334
|
`__Secure-monitorandcontrolC2=${c2}`
|
|
353
335
|
].join('; ');
|
|
354
336
|
|
|
355
|
-
|
|
337
|
+
|
|
338
|
+
const userAgent = await page.evaluate(() => navigator.userAgent);
|
|
339
|
+
const headers = {
|
|
356
340
|
'Accept': '*/*',
|
|
357
341
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
358
342
|
'Accept-Language': LanguageLocaleMap[this.language],
|
|
@@ -362,12 +346,18 @@ class MelCloud extends EventEmitter {
|
|
|
362
346
|
'Sec-Fetch-Dest': 'empty',
|
|
363
347
|
'Sec-Fetch-Mode': 'cors',
|
|
364
348
|
'Sec-Fetch-Site': 'same-origin',
|
|
349
|
+
'User-Agent': userAgent,
|
|
365
350
|
'x-csrf': '1'
|
|
366
351
|
};
|
|
352
|
+
this.headers = headers;
|
|
353
|
+
this.axiosInstance = axios.create({
|
|
354
|
+
baseURL: ApiUrlsHome.BaseURL,
|
|
355
|
+
timeout: 30000,
|
|
356
|
+
headers: headers
|
|
357
|
+
})
|
|
367
358
|
|
|
368
359
|
accountInfo.State = true;
|
|
369
360
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
370
|
-
accountInfo.Headers = this.headers;
|
|
371
361
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
372
362
|
|
|
373
363
|
return accountInfo;
|
|
@@ -382,22 +372,7 @@ class MelCloud extends EventEmitter {
|
|
|
382
372
|
}
|
|
383
373
|
}
|
|
384
374
|
}
|
|
385
|
-
|
|
386
|
-
async send(accountInfo) {
|
|
387
|
-
try {
|
|
388
|
-
await axios(ApiUrlsHome.UpdateApplicationOptions, {
|
|
389
|
-
method: 'POST',
|
|
390
|
-
baseURL: ApiUrlsHome.BaseURL,
|
|
391
|
-
timeout: 15000,
|
|
392
|
-
headers: accountInfo.Headers
|
|
393
|
-
});
|
|
394
|
-
await this.functions.saveData(this.accountFile, accountInfo);
|
|
395
|
-
return true;
|
|
396
|
-
} catch (error) {
|
|
397
|
-
throw new Error(`Send data error: ${error.message}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
375
|
}
|
|
401
376
|
|
|
402
|
-
export default
|
|
377
|
+
export default MelCloudHome;
|
|
403
378
|
|