homebridge-melcloud-control 4.2.3-beta.2 → 4.2.3-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/homebridge-ui/server.js +2 -1
- package/index.js +4 -3
- package/package.json +1 -1
- package/src/constants.js +30 -0
- package/src/deviceata.js +5 -7
- package/src/deviceatw.js +17 -12
- package/src/deviceerv.js +4 -6
- package/src/melcloud.js +32 -364
- package/src/melcloudata.js +7 -20
- package/src/melcloudatw.js +52 -72
- package/src/melclouderv.js +39 -61
- package/src/melcloudhome.js +359 -0
package/src/melcloud.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import axios from 'axios';
|
|
3
2
|
import { exec } from 'child_process';
|
|
4
3
|
import { promisify } from 'util';
|
|
5
4
|
import EventEmitter from 'events';
|
|
6
|
-
import puppeteer from 'puppeteer';
|
|
7
5
|
import ImpulseGenerator from './impulsegenerator.js';
|
|
8
6
|
import Functions from './functions.js';
|
|
9
|
-
import { ApiUrls
|
|
10
|
-
const execPromise = promisify(exec);
|
|
7
|
+
import { ApiUrls } from './constants.js';
|
|
11
8
|
|
|
12
9
|
class MelCloud extends EventEmitter {
|
|
13
10
|
constructor(account, accountFile, buildingsFile, devicesFile, pluginStart = false) {
|
|
@@ -22,7 +19,8 @@ class MelCloud extends EventEmitter {
|
|
|
22
19
|
this.accountFile = accountFile;
|
|
23
20
|
this.buildingsFile = buildingsFile;
|
|
24
21
|
this.devicesFile = devicesFile;
|
|
25
|
-
this.
|
|
22
|
+
this.headers = {};
|
|
23
|
+
|
|
26
24
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
27
25
|
.on('warn', warn => this.emit('warn', warn))
|
|
28
26
|
.on('error', error => this.emit('error', error))
|
|
@@ -36,7 +34,7 @@ class MelCloud extends EventEmitter {
|
|
|
36
34
|
};
|
|
37
35
|
this.impulseGenerator = new ImpulseGenerator()
|
|
38
36
|
.on('connect', () => this.handleWithLock('connect', async () => {
|
|
39
|
-
await this.connect(
|
|
37
|
+
await this.connect();
|
|
40
38
|
}))
|
|
41
39
|
.on('checkDevicesList', () => this.handleWithLock('checkDevicesList', async () => {
|
|
42
40
|
await this.checkDevicesList();
|
|
@@ -61,23 +59,17 @@ class MelCloud extends EventEmitter {
|
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
// MELCloud
|
|
64
|
-
async
|
|
62
|
+
async checkDevicesList() {
|
|
65
63
|
try {
|
|
66
64
|
const devicesList = { State: false, Info: null, Devices: [] }
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
'Content-Type': 'application/json'
|
|
70
|
-
}
|
|
71
|
-
const axiosInstance = axios.create({
|
|
65
|
+
if (this.logDebug) this.emit('debug', `Scanning for devices...`);
|
|
66
|
+
const listDevicesData = await axios(ApiUrls.ListDevices, {
|
|
72
67
|
method: 'GET',
|
|
73
68
|
baseURL: ApiUrls.BaseURL,
|
|
74
69
|
timeout: 15000,
|
|
75
|
-
headers: headers
|
|
70
|
+
headers: this.headers
|
|
76
71
|
});
|
|
77
72
|
|
|
78
|
-
if (this.logDebug) this.emit('debug', `Scanning for devices...`);
|
|
79
|
-
const listDevicesData = await axiosInstance(ApiUrls.ListDevices);
|
|
80
|
-
|
|
81
73
|
if (!listDevicesData || !listDevicesData.data) {
|
|
82
74
|
devicesList.Info = 'Invalid or empty response from MELCloud API'
|
|
83
75
|
return devicesList;
|
|
@@ -114,7 +106,7 @@ class MelCloud extends EventEmitter {
|
|
|
114
106
|
// Zamiana ID na string
|
|
115
107
|
allDevices.forEach(device => {
|
|
116
108
|
device.DeviceID = String(device.DeviceID);
|
|
117
|
-
device.Headers = headers;
|
|
109
|
+
device.Headers = this.headers;
|
|
118
110
|
});
|
|
119
111
|
|
|
120
112
|
if (this.logDebug) this.emit('debug', `Found ${allDevices.length} devices in building: ${building.Name || 'Unnamed'}`);
|
|
@@ -138,24 +130,26 @@ class MelCloud extends EventEmitter {
|
|
|
138
130
|
}
|
|
139
131
|
}
|
|
140
132
|
|
|
141
|
-
async
|
|
133
|
+
async connect() {
|
|
142
134
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
143
135
|
|
|
144
136
|
try {
|
|
145
|
-
const accountInfo = { State: false, Info: '', LoginData: null,
|
|
137
|
+
const accountInfo = { State: false, Info: '', LoginData: null, Headers: {}, UseFahrenheit: false }
|
|
138
|
+
|
|
139
|
+
const payload = {
|
|
140
|
+
Email: this.user,
|
|
141
|
+
Password: this.passwd,
|
|
142
|
+
Language: this.language,
|
|
143
|
+
AppVersion: '1.34.12',
|
|
144
|
+
CaptchaChallenge: '',
|
|
145
|
+
CaptchaResponse: '',
|
|
146
|
+
Persist: true
|
|
147
|
+
};
|
|
146
148
|
const accountData = await axios(ApiUrls.ClientLogin, {
|
|
147
149
|
method: 'POST',
|
|
148
150
|
baseURL: ApiUrls.BaseURL,
|
|
149
151
|
timeout: 15000,
|
|
150
|
-
data:
|
|
151
|
-
Email: this.user,
|
|
152
|
-
Password: this.passwd,
|
|
153
|
-
Language: this.language,
|
|
154
|
-
AppVersion: '1.34.12',
|
|
155
|
-
CaptchaChallenge: '',
|
|
156
|
-
CaptchaResponse: '',
|
|
157
|
-
Persist: true
|
|
158
|
-
}
|
|
152
|
+
data: payload
|
|
159
153
|
});
|
|
160
154
|
const account = accountData.data;
|
|
161
155
|
const loginData = account.LoginData ?? [];
|
|
@@ -176,12 +170,16 @@ class MelCloud extends EventEmitter {
|
|
|
176
170
|
accountInfo.Info = 'Context key missing'
|
|
177
171
|
return accountInfo;
|
|
178
172
|
}
|
|
179
|
-
|
|
173
|
+
|
|
174
|
+
this.headers = {
|
|
175
|
+
'X-MitsContextKey': contextKey,
|
|
176
|
+
'Content-Type': 'application/json'
|
|
177
|
+
};
|
|
180
178
|
|
|
181
179
|
accountInfo.State = true;
|
|
182
180
|
accountInfo.Info = 'Connect to MELCloud Success';
|
|
183
181
|
accountInfo.LoginData = loginData;
|
|
184
|
-
accountInfo.
|
|
182
|
+
accountInfo.Headers = this.headers;
|
|
185
183
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
186
184
|
|
|
187
185
|
return accountInfo
|
|
@@ -190,346 +188,16 @@ class MelCloud extends EventEmitter {
|
|
|
190
188
|
}
|
|
191
189
|
}
|
|
192
190
|
|
|
193
|
-
// MELCloud Home
|
|
194
|
-
async checkMelcloudHomeDevicesList() {
|
|
195
|
-
try {
|
|
196
|
-
const devicesList = { State: false, Info: null, Devices: [] }
|
|
197
|
-
const headers = {
|
|
198
|
-
'Accept': '*/*',
|
|
199
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
200
|
-
'Cookie': this.contextKey,
|
|
201
|
-
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
202
|
-
'DNT': '1',
|
|
203
|
-
'Origin': 'https://melcloudhome.com',
|
|
204
|
-
'Referer': 'https://melcloudhome.com/dashboard',
|
|
205
|
-
'Sec-Fetch-Dest': 'empty',
|
|
206
|
-
'Sec-Fetch-Mode': 'cors',
|
|
207
|
-
'Sec-Fetch-Site': 'same-origin',
|
|
208
|
-
'X-CSRF': '1'
|
|
209
|
-
};
|
|
210
|
-
const axiosInstance = axios.create({
|
|
211
|
-
method: 'GET',
|
|
212
|
-
baseURL: ApiUrlsHome.BaseURL,
|
|
213
|
-
timeout: 25000,
|
|
214
|
-
headers: headers
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
218
|
-
const listDevicesData = await axiosInstance(ApiUrlsHome.GetUserContext);
|
|
219
|
-
const buildingsList = listDevicesData.data.buildings;
|
|
220
|
-
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
221
|
-
|
|
222
|
-
if (!buildingsList) {
|
|
223
|
-
devicesList.Info = 'No building found'
|
|
224
|
-
return devicesList;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
228
|
-
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
229
|
-
|
|
230
|
-
const devices = buildingsList.flatMap(building => {
|
|
231
|
-
// Funkcja kapitalizująca klucze obiektu
|
|
232
|
-
const capitalizeKeys = obj =>
|
|
233
|
-
Object.fromEntries(
|
|
234
|
-
Object.entries(obj).map(([key, value]) => [
|
|
235
|
-
key.charAt(0).toUpperCase() + key.slice(1),
|
|
236
|
-
value
|
|
237
|
-
])
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
// Rekurencyjna kapitalizacja kluczy w obiekcie lub tablicy
|
|
241
|
-
const capitalizeKeysDeep = obj => {
|
|
242
|
-
if (Array.isArray(obj)) return obj.map(capitalizeKeysDeep);
|
|
243
|
-
if (obj && typeof obj === 'object') {
|
|
244
|
-
return Object.fromEntries(
|
|
245
|
-
Object.entries(obj).map(([key, value]) => [
|
|
246
|
-
key.charAt(0).toUpperCase() + key.slice(1),
|
|
247
|
-
capitalizeKeysDeep(value)
|
|
248
|
-
])
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
return obj;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
// Funkcja tworząca finalny obiekt Device
|
|
255
|
-
const createDevice = (device, type) => {
|
|
256
|
-
// Settings już kapitalizowane w nazwach
|
|
257
|
-
const settingsArray = device.Settings || [];
|
|
258
|
-
|
|
259
|
-
const settingsObject = Object.fromEntries(
|
|
260
|
-
settingsArray.map(({ name, value }) => {
|
|
261
|
-
let parsedValue = value;
|
|
262
|
-
if (value === "True") parsedValue = true;
|
|
263
|
-
else if (value === "False") parsedValue = false;
|
|
264
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
265
|
-
|
|
266
|
-
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
267
|
-
return [key, parsedValue];
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
// Scal Capabilities + Settings + DeviceType w Device
|
|
272
|
-
const deviceObject = {
|
|
273
|
-
...capitalizeKeys(device.Capabilities || {}),
|
|
274
|
-
...settingsObject,
|
|
275
|
-
DeviceType: type,
|
|
276
|
-
IsConnected: device.IsConnected
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// Kapitalizacja brakujących obiektów/tablic
|
|
280
|
-
if (device.FrostProtection) device.FrostProtection = { ...capitalizeKeys(device.FrostProtection || {}) };
|
|
281
|
-
if (device.OverheatProtection) device.OverheatProtection = { ...capitalizeKeys(device.OverheatProtection || {}) };
|
|
282
|
-
if (device.HolidayMode) device.HolidayMode = { ...capitalizeKeys(device.HolidayMode || {}) };
|
|
283
|
-
if (Array.isArray(device.Schedule)) device.Schedule = device.Schedule.map(capitalizeKeysDeep);
|
|
284
|
-
|
|
285
|
-
// Usuń stare pola Settings i Capabilities
|
|
286
|
-
const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
...rest,
|
|
290
|
-
Type: type,
|
|
291
|
-
DeviceID: Id,
|
|
292
|
-
DeviceName: GivenDisplayName,
|
|
293
|
-
Device: deviceObject,
|
|
294
|
-
Headers: headers
|
|
295
|
-
};
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
return [
|
|
299
|
-
...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
|
|
300
|
-
...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
|
|
301
|
-
...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3))
|
|
302
|
-
];
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const devicesCount = devices.length;
|
|
306
|
-
if (devicesCount === 0) {
|
|
307
|
-
devicesList.Info = 'No devices found'
|
|
308
|
-
return devicesList;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
await this.functions.saveData(this.devicesFile, devices);
|
|
312
|
-
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
313
|
-
|
|
314
|
-
devicesList.State = true;
|
|
315
|
-
devicesList.Info = `Found ${devicesCount} devices`;
|
|
316
|
-
devicesList.Devices = devices;
|
|
317
|
-
return devicesList;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
if (error.response?.status === 401) {
|
|
320
|
-
await connectToMelCloudHome();
|
|
321
|
-
if (this.logWarn) this.emit('warn', 'Check devices list not possible, cookies expired, trying to get new.');
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
throw new Error(`Check devices list error: ${error.message}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async connectToMelCloudHome() {
|
|
330
|
-
if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
|
|
331
|
-
const GLOBAL_TIMEOUT = 90000;
|
|
332
|
-
|
|
333
|
-
let browser;
|
|
334
|
-
try {
|
|
335
|
-
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
336
|
-
let chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
337
|
-
|
|
338
|
-
// === Fallback to Puppeteer's built-in Chromium ===
|
|
339
|
-
if (!chromiumPath) {
|
|
340
|
-
try {
|
|
341
|
-
const puppeteerPath = puppeteer.executablePath();
|
|
342
|
-
if (puppeteerPath && fs.existsSync(puppeteerPath)) {
|
|
343
|
-
chromiumPath = puppeteerPath;
|
|
344
|
-
if (this.logDebug) this.emit('debug', `Using puppeteer Chromium at ${chromiumPath}`);
|
|
345
|
-
}
|
|
346
|
-
} catch { }
|
|
347
|
-
} else {
|
|
348
|
-
if (this.logDebug) this.emit('debug', `Using system Chromium at ${chromiumPath}`);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (!chromiumPath) {
|
|
352
|
-
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
|
|
353
|
-
return accountInfo;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Verify executable works
|
|
357
|
-
try {
|
|
358
|
-
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
359
|
-
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
360
|
-
} catch (error) {
|
|
361
|
-
accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
362
|
-
return accountInfo;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this.logDebug) this.emit('debug', `Launching Chromium...`);
|
|
366
|
-
browser = await puppeteer.launch({
|
|
367
|
-
headless: true,
|
|
368
|
-
executablePath: chromiumPath,
|
|
369
|
-
timeout: GLOBAL_TIMEOUT,
|
|
370
|
-
args: [
|
|
371
|
-
'--no-sandbox',
|
|
372
|
-
'--disable-setuid-sandbox',
|
|
373
|
-
'--disable-dev-shm-usage',
|
|
374
|
-
'--single-process',
|
|
375
|
-
'--disable-gpu',
|
|
376
|
-
'--no-zygote'
|
|
377
|
-
]
|
|
378
|
-
});
|
|
379
|
-
browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
|
|
380
|
-
|
|
381
|
-
const page = await browser.newPage();
|
|
382
|
-
page.on('error', error => this.emit('error', `Page crashed: ${error.message}`));
|
|
383
|
-
page.on('pageerror', error => this.emit('error', `Browser error: ${error.message}`));
|
|
384
|
-
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
385
|
-
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
386
|
-
|
|
387
|
-
// Clear cookies before navigation
|
|
388
|
-
try {
|
|
389
|
-
const client = await page.createCDPSession();
|
|
390
|
-
await client.send('Network.clearBrowserCookies');
|
|
391
|
-
} catch (error) {
|
|
392
|
-
if (this.logError) this.emit('error', `Clear cookies error: ${error.message}`);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
try {
|
|
396
|
-
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
397
|
-
} catch (error) {
|
|
398
|
-
accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
|
|
399
|
-
return accountInfo;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Wait extra to ensure UI is rendered
|
|
403
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
404
|
-
const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 4 });
|
|
405
|
-
const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
|
|
406
|
-
|
|
407
|
-
if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
|
|
408
|
-
accountInfo.Info = `Login button ${loginText} not found`;
|
|
409
|
-
return accountInfo;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
await loginBtn.click();
|
|
413
|
-
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 3 });
|
|
414
|
-
|
|
415
|
-
const usernameInput = await page.$('input[name="username"]');
|
|
416
|
-
const passwordInput = await page.$('input[name="password"]');
|
|
417
|
-
if (!usernameInput || !passwordInput) {
|
|
418
|
-
accountInfo.Info = 'Username or password input not found';
|
|
419
|
-
return accountInfo;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
423
|
-
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
424
|
-
|
|
425
|
-
const submitButton = await page.$('input[type="submit"], button[type="submit"]');
|
|
426
|
-
if (!submitButton) {
|
|
427
|
-
accountInfo.Info = 'Submit button not found';
|
|
428
|
-
return accountInfo;
|
|
429
|
-
}
|
|
430
|
-
await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
431
|
-
|
|
432
|
-
// Extract cookies
|
|
433
|
-
let c1 = null, c2 = null;
|
|
434
|
-
const start = Date.now();
|
|
435
|
-
while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
|
|
436
|
-
const cookies = await page.browserContext().cookies();
|
|
437
|
-
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
438
|
-
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
439
|
-
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (!c1 || !c2) {
|
|
443
|
-
accountInfo.Info = 'Cookies C1/C2 missing';
|
|
444
|
-
return accountInfo;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const contextKey = [
|
|
448
|
-
'__Secure-monitorandcontrol=chunks-2',
|
|
449
|
-
`__Secure-monitorandcontrolC1=${c1}`,
|
|
450
|
-
`__Secure-monitorandcontrolC2=${c2}`
|
|
451
|
-
].join('; ');
|
|
452
|
-
this.contextKey = contextKey;
|
|
453
|
-
|
|
454
|
-
accountInfo.State = true;
|
|
455
|
-
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
456
|
-
accountInfo.ContextKey = contextKey;
|
|
457
|
-
await this.functions.saveData(this.accountFile, accountInfo);
|
|
458
|
-
|
|
459
|
-
return accountInfo;
|
|
460
|
-
} catch (error) {
|
|
461
|
-
throw new Error(`Connect error: ${error.message}`);
|
|
462
|
-
} finally {
|
|
463
|
-
if (browser) {
|
|
464
|
-
try { await browser.close(); }
|
|
465
|
-
catch (closeErr) {
|
|
466
|
-
if (this.logError) this.emit('error', `Failed to close Puppeteer: ${closeErr.message}`);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async checkDevicesList() {
|
|
473
|
-
const TIMEOUT_MS = 30000; // 30 seconds timeout
|
|
474
|
-
try {
|
|
475
|
-
const devicesList = await Promise.race([
|
|
476
|
-
(async () => {
|
|
477
|
-
switch (this.accountType) {
|
|
478
|
-
case "melcloud":
|
|
479
|
-
return await this.checkMelcloudDevicesList();
|
|
480
|
-
case "melcloudhome":
|
|
481
|
-
return await this.checkMelcloudHomeDevicesList();
|
|
482
|
-
default:
|
|
483
|
-
return [];
|
|
484
|
-
}
|
|
485
|
-
})(),
|
|
486
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Device list timeout (30s)')), TIMEOUT_MS))
|
|
487
|
-
]);
|
|
488
|
-
|
|
489
|
-
return devicesList;
|
|
490
|
-
} catch (error) {
|
|
491
|
-
throw new Error(error);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
async connect() {
|
|
496
|
-
const TIMEOUT_MS = 120000;
|
|
497
|
-
|
|
498
|
-
try {
|
|
499
|
-
const response = await Promise.race([
|
|
500
|
-
(async () => {
|
|
501
|
-
switch (this.accountType) {
|
|
502
|
-
case "melcloud":
|
|
503
|
-
return await this.connectToMelCloud();
|
|
504
|
-
case "melcloudhome":
|
|
505
|
-
return await this.connectToMelCloudHome();
|
|
506
|
-
default:
|
|
507
|
-
return {};
|
|
508
|
-
}
|
|
509
|
-
})(),
|
|
510
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Connection timeout (90s)')), TIMEOUT_MS))
|
|
511
|
-
]);
|
|
512
|
-
|
|
513
|
-
return response;
|
|
514
|
-
} catch (error) {
|
|
515
|
-
throw new Error(error);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
191
|
async send(accountInfo) {
|
|
520
192
|
try {
|
|
521
|
-
const
|
|
193
|
+
const payload = { data: accountInfo.LoginData };
|
|
194
|
+
await axios(ApiUrls.UpdateApplicationOptions, {
|
|
522
195
|
method: 'POST',
|
|
523
196
|
baseURL: ApiUrls.BaseURL,
|
|
524
197
|
timeout: 15000,
|
|
525
|
-
headers:
|
|
526
|
-
|
|
527
|
-
'content-type': 'application/json'
|
|
528
|
-
}
|
|
198
|
+
headers: accountInfo.Headers,
|
|
199
|
+
data: payload
|
|
529
200
|
});
|
|
530
|
-
|
|
531
|
-
const payload = { data: accountInfo.LoginData };
|
|
532
|
-
await axiosInstance(ApiUrls.UpdateApplicationOptions, payload);
|
|
533
201
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
534
202
|
return true;
|
|
535
203
|
} catch (error) {
|
package/src/melcloudata.js
CHANGED
|
@@ -52,15 +52,9 @@ class MelCloudAta extends EventEmitter {
|
|
|
52
52
|
try {
|
|
53
53
|
//read device info from file
|
|
54
54
|
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
55
|
-
if (!Array.isArray(devicesData)) {
|
|
56
|
-
if (this.logWarn) this.emit('warn', `Device data not found`);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
55
|
const deviceData = devicesData.find(device => device.DeviceID === this.deviceId);
|
|
60
56
|
|
|
61
57
|
if (this.accountType === 'melcloudhome') {
|
|
62
|
-
deviceData.SerialNumber = deviceData.DeviceID || '4.0.0';
|
|
63
|
-
deviceData.Device.FirmwareAppVersion = deviceData.ConnectedInterfaceIdentifier || '4.0.0';
|
|
64
58
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
|
65
59
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
66
60
|
deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
|
|
@@ -80,13 +74,12 @@ class MelCloudAta extends EventEmitter {
|
|
|
80
74
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(safeConfig, null, 2)}`);
|
|
81
75
|
|
|
82
76
|
//device
|
|
83
|
-
const serialNumber = deviceData.SerialNumber;
|
|
84
|
-
const firmwareAppVersion = deviceData.Device?.FirmwareAppVersion;
|
|
77
|
+
const serialNumber = deviceData.SerialNumber || '4.0.0';
|
|
78
|
+
const firmwareAppVersion = deviceData.Device?.FirmwareAppVersion || '4.0.0';
|
|
85
79
|
|
|
86
80
|
//units
|
|
87
81
|
const units = Array.isArray(deviceData.Device?.Units) ? deviceData.Device?.Units : [];
|
|
88
82
|
const unitsCount = units.length;
|
|
89
|
-
const manufacturer = 'Mitsubishi';
|
|
90
83
|
|
|
91
84
|
const { indoor, outdoor } = units.reduce((acc, unit) => {
|
|
92
85
|
const target = unit.IsIndoor ? 'indoor' : 'outdoor';
|
|
@@ -118,14 +111,11 @@ class MelCloudAta extends EventEmitter {
|
|
|
118
111
|
|
|
119
112
|
//check state changes
|
|
120
113
|
const deviceDataHasNotChanged = JSON.stringify(deviceData) === JSON.stringify(this.deviceData);
|
|
121
|
-
if (deviceDataHasNotChanged)
|
|
122
|
-
if (this.logDebug) this.emit('debug', `Device state not changed`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
114
|
+
if (deviceDataHasNotChanged) return;
|
|
125
115
|
this.deviceData = deviceData;
|
|
126
116
|
|
|
127
117
|
//emit info
|
|
128
|
-
this.emit('deviceInfo',
|
|
118
|
+
this.emit('deviceInfo', indoor.model, outdoor.model, serialNumber, firmwareAppVersion);
|
|
129
119
|
|
|
130
120
|
//emit state
|
|
131
121
|
this.emit('deviceState', deviceData);
|
|
@@ -167,17 +157,15 @@ class MelCloudAta extends EventEmitter {
|
|
|
167
157
|
HideDryModeControl: deviceData.HideDryModeControl,
|
|
168
158
|
HasPendingCommand: true
|
|
169
159
|
};
|
|
170
|
-
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
171
160
|
|
|
172
|
-
|
|
161
|
+
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
|
|
162
|
+
await axios(ApiUrls.SetAta, {
|
|
173
163
|
method: 'POST',
|
|
174
164
|
baseURL: ApiUrls.BaseURL,
|
|
175
165
|
timeout: 10000,
|
|
176
166
|
headers: deviceData.Headers,
|
|
177
167
|
data: payload
|
|
178
168
|
});
|
|
179
|
-
|
|
180
|
-
await axiosInstancePost(ApiUrls.SetAta);
|
|
181
169
|
this.updateData(deviceData);
|
|
182
170
|
return true;
|
|
183
171
|
case "melcloudhome":
|
|
@@ -243,7 +231,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
243
231
|
break
|
|
244
232
|
}
|
|
245
233
|
|
|
246
|
-
if (this.logDebug) this.emit('
|
|
234
|
+
if (!this.logDebug) this.emit('warn', `Send Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
247
235
|
await axios(path, {
|
|
248
236
|
method: method,
|
|
249
237
|
baseURL: ApiUrlsHome.BaseURL,
|
|
@@ -270,6 +258,5 @@ class MelCloudAta extends EventEmitter {
|
|
|
270
258
|
this.lock = false
|
|
271
259
|
}, 3000);
|
|
272
260
|
}
|
|
273
|
-
|
|
274
261
|
};
|
|
275
262
|
export default MelCloudAta;
|