homebridge-melcloud-control 4.0.0-beta.30 → 4.0.0-beta.32
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 +29 -0
- package/index.js +9 -6
- package/package.json +1 -1
- package/src/melcloud.js +77 -56
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",
|
|
@@ -1817,6 +1845,7 @@
|
|
|
1817
1845
|
"type": "password"
|
|
1818
1846
|
},
|
|
1819
1847
|
"accounts[].language",
|
|
1848
|
+
"accounts[].displayType",
|
|
1820
1849
|
{
|
|
1821
1850
|
"key": "accounts[]",
|
|
1822
1851
|
"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;
|
|
@@ -64,7 +67,7 @@ class MelCloudPlatform {
|
|
|
64
67
|
passwd: 'removed',
|
|
65
68
|
mqtt: {
|
|
66
69
|
auth: {
|
|
67
|
-
...
|
|
70
|
+
...account.mqtt?.auth,
|
|
68
71
|
passwd: 'removed',
|
|
69
72
|
}
|
|
70
73
|
},
|
|
@@ -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,14 +100,14 @@ class MelCloudPlatform {
|
|
|
98
100
|
//connect
|
|
99
101
|
let response;
|
|
100
102
|
try {
|
|
101
|
-
response = await melCloud.connect(
|
|
103
|
+
response = await melCloud.connect(displayType);
|
|
102
104
|
} catch (error) {
|
|
103
105
|
if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
const accountInfo = response.accountInfo ?? false;
|
|
108
|
-
const contextKey = response.contextKey
|
|
110
|
+
const contextKey = displayType === 1 ? response.contextKey : response;
|
|
109
111
|
const useFahrenheit = response.useFahrenheit ?? false;
|
|
110
112
|
|
|
111
113
|
if (contextKey === false) {
|
|
@@ -115,7 +117,8 @@ class MelCloudPlatform {
|
|
|
115
117
|
//check devices list
|
|
116
118
|
let devicesInMelcloud;
|
|
117
119
|
try {
|
|
118
|
-
devicesInMelcloud = await melCloud.checkDevicesList(contextKey);
|
|
120
|
+
devicesInMelcloud = await melCloud.checkDevicesList(displayType, contextKey);
|
|
121
|
+
return
|
|
119
122
|
} catch (error) {
|
|
120
123
|
if (logLevel.error) log.error(`${accountName}, Check devices list error: ${error.message ?? error}`);
|
|
121
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.32",
|
|
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,8 +184,8 @@ class MelCloud extends EventEmitter {
|
|
|
190
184
|
headers: {
|
|
191
185
|
'Accept': '*/*',
|
|
192
186
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
193
|
-
'Cookie':
|
|
194
|
-
'User-Agent': 'homebridge-melcloud-
|
|
187
|
+
'Cookie': cookie,
|
|
188
|
+
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
195
189
|
'DNT': '1',
|
|
196
190
|
'Origin': 'https://melcloudhome.com',
|
|
197
191
|
'Referer': 'https://melcloudhome.com/dashboard',
|
|
@@ -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
|
|