homebridge-melcloud-control 4.0.0-beta.31 → 4.0.0-beta.33
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/config.schema.json +31 -1
- package/index.js +8 -6
- package/package.json +1 -1
- package/src/melcloud.js +76 -55
package/config.schema.json
CHANGED
|
@@ -201,6 +201,34 @@
|
|
|
201
201
|
}
|
|
202
202
|
]
|
|
203
203
|
},
|
|
204
|
+
"displayType": {
|
|
205
|
+
"title": "Account Type",
|
|
206
|
+
"type": "integer",
|
|
207
|
+
"minimum": 0,
|
|
208
|
+
"maximum": 2,
|
|
209
|
+
"default": 1,
|
|
210
|
+
"description": "Here select the language used in MELCloud account.",
|
|
211
|
+
"oneOf": [
|
|
212
|
+
{
|
|
213
|
+
"title": "None/Disabled",
|
|
214
|
+
"enum": [
|
|
215
|
+
0
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"title": "MELCLoud",
|
|
220
|
+
"enum": [
|
|
221
|
+
1
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"title": "MELCLoud Home",
|
|
226
|
+
"enum": [
|
|
227
|
+
2
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
},
|
|
204
232
|
"ataDevices": {
|
|
205
233
|
"title": "Devices ATA",
|
|
206
234
|
"type": "array",
|
|
@@ -1798,7 +1826,8 @@
|
|
|
1798
1826
|
"name",
|
|
1799
1827
|
"user",
|
|
1800
1828
|
"passwd",
|
|
1801
|
-
"language"
|
|
1829
|
+
"language",
|
|
1830
|
+
"displayType"
|
|
1802
1831
|
]
|
|
1803
1832
|
}
|
|
1804
1833
|
}
|
|
@@ -1817,6 +1846,7 @@
|
|
|
1817
1846
|
"type": "password"
|
|
1818
1847
|
},
|
|
1819
1848
|
"accounts[].language",
|
|
1849
|
+
"accounts[].displayType",
|
|
1820
1850
|
{
|
|
1821
1851
|
"key": "accounts[]",
|
|
1822
1852
|
"type": "tabarray",
|
package/index.js
CHANGED
|
@@ -30,6 +30,9 @@ class MelCloudPlatform {
|
|
|
30
30
|
api.on('didFinishLaunching', async () => {
|
|
31
31
|
//loop through accounts
|
|
32
32
|
for (const account of config.accounts) {
|
|
33
|
+
const displayType = account.displayType || 1;
|
|
34
|
+
if (displayType === 0) continue;
|
|
35
|
+
|
|
33
36
|
const accountName = account.name;
|
|
34
37
|
const user = account.user;
|
|
35
38
|
const passwd = account.passwd;
|
|
@@ -76,7 +79,6 @@ class MelCloudPlatform {
|
|
|
76
79
|
const accountFile = `${prefDir}/${accountName}_Account`;
|
|
77
80
|
const buildingsFile = `${prefDir}/${accountName}_Buildings`;
|
|
78
81
|
const devicesFile = `${prefDir}/${accountName}_Devices`;
|
|
79
|
-
const cookiesFile = `${prefDir}/${accountName}_Cookies`;
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
//set account refresh interval
|
|
@@ -88,7 +90,7 @@ class MelCloudPlatform {
|
|
|
88
90
|
.on('start', async () => {
|
|
89
91
|
try {
|
|
90
92
|
//melcloud account
|
|
91
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
93
|
+
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile, logLevel.warn, logLevel.debug, false)
|
|
92
94
|
.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
|
|
93
95
|
.on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
|
|
94
96
|
.on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
|
|
@@ -98,15 +100,14 @@ class MelCloudPlatform {
|
|
|
98
100
|
//connect
|
|
99
101
|
let response;
|
|
100
102
|
try {
|
|
101
|
-
response = await melCloud.connect(
|
|
102
|
-
return;
|
|
103
|
+
response = await melCloud.connect(displayType);
|
|
103
104
|
} catch (error) {
|
|
104
105
|
if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const accountInfo = response.accountInfo ?? false;
|
|
109
|
-
const contextKey = response.contextKey
|
|
110
|
+
const contextKey = displayType === 1 ? response.contextKey : response;
|
|
110
111
|
const useFahrenheit = response.useFahrenheit ?? false;
|
|
111
112
|
|
|
112
113
|
if (contextKey === false) {
|
|
@@ -116,7 +117,8 @@ class MelCloudPlatform {
|
|
|
116
117
|
//check devices list
|
|
117
118
|
let devicesInMelcloud;
|
|
118
119
|
try {
|
|
119
|
-
devicesInMelcloud = await melCloud.checkDevicesList(contextKey);
|
|
120
|
+
devicesInMelcloud = await melCloud.checkDevicesList(displayType, contextKey);
|
|
121
|
+
return
|
|
120
122
|
} catch (error) {
|
|
121
123
|
if (logLevel.error) log.error(`${accountName}, Check devices list error: ${error.message ?? error}`);
|
|
122
124
|
return;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.0.0-beta.
|
|
4
|
+
"version": "4.0.0-beta.33",
|
|
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/melcloud.js
CHANGED
|
@@ -7,7 +7,7 @@ import Functions from './functions.js';
|
|
|
7
7
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
8
8
|
|
|
9
9
|
class MelCloud extends EventEmitter {
|
|
10
|
-
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
10
|
+
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
11
11
|
super();
|
|
12
12
|
this.user = user;
|
|
13
13
|
this.passwd = passwd;
|
|
@@ -15,7 +15,6 @@ class MelCloud extends EventEmitter {
|
|
|
15
15
|
this.accountFile = accountFile;
|
|
16
16
|
this.buildingsFile = buildingsFile;
|
|
17
17
|
this.devicesFile = devicesFile;
|
|
18
|
-
this.cookiesFile = cookiesFile;
|
|
19
18
|
this.logWarn = logWarn;
|
|
20
19
|
this.logDebug = logDebug;
|
|
21
20
|
this.requestConfig = requestConfig;
|
|
@@ -58,7 +57,7 @@ class MelCloud extends EventEmitter {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
async
|
|
60
|
+
async checkMelcloudDevicesList(contextKey) {
|
|
62
61
|
try {
|
|
63
62
|
const axiosInstanceGet = axios.create({
|
|
64
63
|
method: 'GET',
|
|
@@ -109,7 +108,7 @@ class MelCloud extends EventEmitter {
|
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
async
|
|
111
|
+
async connectToMelCloud() {
|
|
113
112
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
114
113
|
|
|
115
114
|
try {
|
|
@@ -156,25 +155,20 @@ class MelCloud extends EventEmitter {
|
|
|
156
155
|
|
|
157
156
|
this.emit('success', `Connect to MELCloud Success`);
|
|
158
157
|
|
|
159
|
-
return {
|
|
160
|
-
accountInfo,
|
|
161
|
-
contextKey,
|
|
162
|
-
useFahrenheit
|
|
163
|
-
};
|
|
158
|
+
return { accountInfo, contextKey, useFahrenheit };
|
|
164
159
|
} catch (error) {
|
|
165
160
|
throw new Error(`Connect to MELCloud error: ${error.message}`);
|
|
166
161
|
}
|
|
167
162
|
}
|
|
168
163
|
|
|
169
|
-
async
|
|
164
|
+
async checkMelcloudHomeDevicesList(cookies) {
|
|
170
165
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
171
166
|
|
|
172
167
|
try {
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
const c2 = coocies.C2.trim();
|
|
168
|
+
const c1 = cookies.c1.trim();
|
|
169
|
+
const c2 = cookies.c2.trim();
|
|
176
170
|
|
|
177
|
-
const
|
|
171
|
+
const cookie = [
|
|
178
172
|
'__Secure-monitorandcontrol=chunks-2',
|
|
179
173
|
`__Secure-monitorandcontrolC1=${c1}`,
|
|
180
174
|
`__Secure-monitorandcontrolC2=${c2}`,
|
|
@@ -190,7 +184,7 @@ class MelCloud extends EventEmitter {
|
|
|
190
184
|
headers: {
|
|
191
185
|
'Accept': '*/*',
|
|
192
186
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
193
|
-
'Cookie':
|
|
187
|
+
'Cookie': cookie,
|
|
194
188
|
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
195
189
|
'DNT': '1',
|
|
196
190
|
'Origin': 'https://melcloudhome.com',
|
|
@@ -202,62 +196,74 @@ class MelCloud extends EventEmitter {
|
|
|
202
196
|
}
|
|
203
197
|
});
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
199
|
+
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
200
|
+
const listDevicesData = await axiosInstance.get(ApiUrlsHome.GetUserContext);
|
|
201
|
+
const buildingsList = listDevicesData.data.buildings;
|
|
202
|
+
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
208
203
|
|
|
209
|
-
|
|
204
|
+
if (!buildingsList) {
|
|
205
|
+
if (this.logWarn) this.emit('warn', `No building found`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
210
|
+
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
211
|
+
|
|
212
|
+
const devices = buildingsList.flatMap(building => [
|
|
213
|
+
...(building.airToAirUnits || []),
|
|
214
|
+
...(building.airToWaterUnits || [])
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
const devicesCount = devices.length;
|
|
218
|
+
if (devicesCount === 0) {
|
|
219
|
+
if (this.logWarn) this.emit('warn', `No devices found`);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
210
222
|
|
|
211
|
-
|
|
223
|
+
await this.functions.saveData(this.devicesFile, devices);
|
|
224
|
+
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
225
|
+
|
|
226
|
+
return devices;
|
|
212
227
|
} catch (error) {
|
|
213
228
|
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
|
|
217
|
-
async
|
|
232
|
+
async connectToMelCloudHome() {
|
|
233
|
+
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
234
|
+
|
|
218
235
|
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
|
219
236
|
const page = await browser.newPage();
|
|
220
237
|
|
|
221
238
|
try {
|
|
222
|
-
|
|
239
|
+
// Open MELCloud Home
|
|
223
240
|
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
|
|
224
|
-
|
|
225
|
-
// Kliknij przycisk logowania
|
|
226
241
|
const buttons = await page.$$('button.btn--blue');
|
|
227
242
|
let loginBtn = null;
|
|
228
243
|
for (const btn of buttons) {
|
|
229
244
|
const text = await page.evaluate(el => el.textContent, btn);
|
|
230
|
-
if (text.trim() === 'Zaloguj') {
|
|
245
|
+
if (text.trim() === 'Zaloguj' || text.trim() === 'Log In') {
|
|
231
246
|
loginBtn = btn;
|
|
232
247
|
break;
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
|
|
236
|
-
if (!loginBtn
|
|
237
|
-
|
|
238
|
-
await Promise.all([
|
|
239
|
-
loginBtn.click(),
|
|
240
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })
|
|
241
|
-
]);
|
|
251
|
+
if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
|
|
242
252
|
|
|
243
|
-
//
|
|
253
|
+
// Set credentials and login
|
|
254
|
+
await Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
244
255
|
await page.waitForSelector('input[name="username"]', { timeout: 15000 });
|
|
245
|
-
|
|
246
|
-
this.emit('warn', 'Typing credentials...');
|
|
247
256
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
248
257
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
249
258
|
|
|
250
|
-
// Kliknij przycisk logowania
|
|
251
259
|
const button1 = await page.$('input[type="submit"]');
|
|
252
|
-
await Promise.all([
|
|
253
|
-
button1.click(),
|
|
254
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })
|
|
255
|
-
]);
|
|
260
|
+
await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
256
261
|
|
|
262
|
+
// Get cookies C1 and C2
|
|
257
263
|
let c1 = null, c2 = null;
|
|
258
264
|
const start = Date.now();
|
|
259
265
|
|
|
260
|
-
// Loop max 20s
|
|
266
|
+
// Loop max 20s
|
|
261
267
|
while ((!c1 || !c2) && Date.now() - start < 20000) {
|
|
262
268
|
const cookies = await page.cookies();
|
|
263
269
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
@@ -266,30 +272,45 @@ class MelCloud extends EventEmitter {
|
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
if (!c1 || !c2) {
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
|
|
276
|
+
return null;
|
|
271
277
|
}
|
|
272
278
|
|
|
273
|
-
|
|
274
|
-
const data = { C1: c1, C2: c2, date: new Date().toISOString() };
|
|
275
|
-
await this.functions.saveData(this.cookiesFile, data);
|
|
279
|
+
this.emit('success', `Connect to MELCloud Home Success`);
|
|
276
280
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.emit('error', `Login failed: ${err.message}`);
|
|
281
|
-
return null;
|
|
281
|
+
return { c1, c2 };
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
282
284
|
} finally {
|
|
283
285
|
await browser.close();
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
async connect(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
async connect(type) {
|
|
290
|
+
let response = null;
|
|
291
|
+
switch (type) {
|
|
292
|
+
case 1:
|
|
293
|
+
response = await this.connectToMelCloud();
|
|
294
|
+
return response
|
|
295
|
+
case 2:
|
|
296
|
+
response = await this.connectToMelCloudHome();
|
|
297
|
+
return response
|
|
291
298
|
default:
|
|
292
|
-
return
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async checkDevicesList(type, key) {
|
|
304
|
+
let devices = null;
|
|
305
|
+
switch (type) {
|
|
306
|
+
case 1:
|
|
307
|
+
devices = await this.checkMelcloudDevicesList(key);
|
|
308
|
+
return devices
|
|
309
|
+
case 2:
|
|
310
|
+
devices = await this.checkMelcloudHomeDevicesList(key);
|
|
311
|
+
return devices
|
|
312
|
+
default:
|
|
313
|
+
return null;
|
|
293
314
|
}
|
|
294
315
|
}
|
|
295
316
|
|