homebridge-melcloud-control 4.0.0-beta.40 → 4.0.0-beta.6
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 +1 -31
- package/homebridge-ui/public/index.html +3 -14
- package/homebridge-ui/server.js +1 -2
- package/index.js +9 -7
- package/package.json +1 -1
- package/src/melcloud.js +75 -107
package/config.schema.json
CHANGED
|
@@ -201,34 +201,6 @@
|
|
|
201
201
|
}
|
|
202
202
|
]
|
|
203
203
|
},
|
|
204
|
-
"displayType": {
|
|
205
|
-
"title": "Account Type",
|
|
206
|
-
"type": "string",
|
|
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
|
-
},
|
|
232
204
|
"ataDevices": {
|
|
233
205
|
"title": "Devices ATA",
|
|
234
206
|
"type": "array",
|
|
@@ -1826,8 +1798,7 @@
|
|
|
1826
1798
|
"name",
|
|
1827
1799
|
"user",
|
|
1828
1800
|
"passwd",
|
|
1829
|
-
"language"
|
|
1830
|
-
"displayType"
|
|
1801
|
+
"language"
|
|
1831
1802
|
]
|
|
1832
1803
|
}
|
|
1833
1804
|
}
|
|
@@ -1846,7 +1817,6 @@
|
|
|
1846
1817
|
"type": "password"
|
|
1847
1818
|
},
|
|
1848
1819
|
"accounts[].language",
|
|
1849
|
-
"accounts[].displayType",
|
|
1850
1820
|
{
|
|
1851
1821
|
"key": "accounts[]",
|
|
1852
1822
|
"type": "tabarray",
|
|
@@ -76,15 +76,6 @@
|
|
|
76
76
|
</select>
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
-
<div class="mb-2">
|
|
80
|
-
<label for="displayType" class="form-label">Account Type</label>
|
|
81
|
-
<select id="displayType" name="displayType" class="form-control">
|
|
82
|
-
<option value="0">None/Disabled</option>
|
|
83
|
-
<option value="1">MELCloud</option>
|
|
84
|
-
<option value="2">MELCloud Home</option>
|
|
85
|
-
</select>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
79
|
<div class="text-center">
|
|
89
80
|
<button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
|
|
90
81
|
<button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
|
|
@@ -128,8 +119,7 @@
|
|
|
128
119
|
document.getElementById('user').value = acc.user || '';
|
|
129
120
|
document.getElementById('passwd').value = acc.passwd || '';
|
|
130
121
|
document.getElementById('language').value = acc.language || '';
|
|
131
|
-
document.getElementById('
|
|
132
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
122
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
133
123
|
this.deviceIndex = i;
|
|
134
124
|
});
|
|
135
125
|
|
|
@@ -144,9 +134,8 @@
|
|
|
144
134
|
acc.user = document.querySelector('#user').value;
|
|
145
135
|
acc.passwd = document.querySelector('#passwd').value;
|
|
146
136
|
acc.language = document.querySelector('#language').value;
|
|
147
|
-
acc.displayType = document.querySelector('#displayType').value;
|
|
148
137
|
|
|
149
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.
|
|
138
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
150
139
|
|
|
151
140
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
152
141
|
await homebridge.savePluginConfig(pluginConfig);
|
|
@@ -203,7 +192,7 @@
|
|
|
203
192
|
|
|
204
193
|
try {
|
|
205
194
|
const acc = pluginConfig[0].accounts[this.deviceIndex];
|
|
206
|
-
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language
|
|
195
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
207
196
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
208
197
|
|
|
209
198
|
// Initialize devices arrays
|
package/homebridge-ui/server.js
CHANGED
|
@@ -17,11 +17,10 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
17
17
|
const user = payload.user;
|
|
18
18
|
const passwd = payload.passwd;
|
|
19
19
|
const language = payload.language;
|
|
20
|
-
const displayType = payload.displayType;
|
|
21
20
|
const accountFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Account`;
|
|
22
21
|
const buildingsFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Buildings`;
|
|
23
22
|
const devicesFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Devices`;
|
|
24
|
-
const melCloud = new MelCloud(
|
|
23
|
+
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
|
|
25
24
|
|
|
26
25
|
try {
|
|
27
26
|
const response = await melCloud.connect();
|
package/index.js
CHANGED
|
@@ -30,9 +30,6 @@ 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
|
-
|
|
36
33
|
const accountName = account.name;
|
|
37
34
|
const user = account.user;
|
|
38
35
|
const passwd = account.passwd;
|
|
@@ -67,7 +64,7 @@ class MelCloudPlatform {
|
|
|
67
64
|
passwd: 'removed',
|
|
68
65
|
mqtt: {
|
|
69
66
|
auth: {
|
|
70
|
-
...
|
|
67
|
+
...device.mqtt?.auth,
|
|
71
68
|
passwd: 'removed',
|
|
72
69
|
}
|
|
73
70
|
},
|
|
@@ -79,6 +76,7 @@ class MelCloudPlatform {
|
|
|
79
76
|
const accountFile = `${prefDir}/${accountName}_Account`;
|
|
80
77
|
const buildingsFile = `${prefDir}/${accountName}_Buildings`;
|
|
81
78
|
const devicesFile = `${prefDir}/${accountName}_Devices`;
|
|
79
|
+
const cookiesFile = `${prefDir}/${accountName}cookies`;
|
|
82
80
|
|
|
83
81
|
|
|
84
82
|
//set account refresh interval
|
|
@@ -90,13 +88,17 @@ class MelCloudPlatform {
|
|
|
90
88
|
.on('start', async () => {
|
|
91
89
|
try {
|
|
92
90
|
//melcloud account
|
|
93
|
-
const melCloud = new MelCloud(
|
|
91
|
+
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile, cookiesFile, logLevel.warn, logLevel.debug, false)
|
|
94
92
|
.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
|
|
95
93
|
.on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
|
|
96
94
|
.on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
|
|
97
95
|
.on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${msg}`))
|
|
98
96
|
.on('error', (msg) => logLevel.error && log.error(`${accountName}, ${msg}`));
|
|
99
97
|
|
|
98
|
+
await melCloud.connectHomeCoocies()
|
|
99
|
+
return;
|
|
100
|
+
|
|
101
|
+
|
|
100
102
|
//connect
|
|
101
103
|
let response;
|
|
102
104
|
try {
|
|
@@ -107,10 +109,10 @@ class MelCloudPlatform {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
const accountInfo = response.accountInfo ?? false;
|
|
110
|
-
const contextKey = response.contextKey;
|
|
112
|
+
const contextKey = response.contextKey ?? false;
|
|
111
113
|
const useFahrenheit = response.useFahrenheit ?? false;
|
|
112
114
|
|
|
113
|
-
if (
|
|
115
|
+
if (contextKey === false) {
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
118
|
|
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.6",
|
|
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,15 +7,15 @@ import Functions from './functions.js';
|
|
|
7
7
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
8
8
|
|
|
9
9
|
class MelCloud extends EventEmitter {
|
|
10
|
-
constructor(
|
|
10
|
+
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile, cookiesFile, logWarn, logDebug, requestConfig) {
|
|
11
11
|
super();
|
|
12
|
-
this.displayType = displayType;
|
|
13
12
|
this.user = user;
|
|
14
13
|
this.passwd = passwd;
|
|
15
14
|
this.language = language;
|
|
16
15
|
this.accountFile = accountFile;
|
|
17
16
|
this.buildingsFile = buildingsFile;
|
|
18
17
|
this.devicesFile = devicesFile;
|
|
18
|
+
this.cookiesFile = cookiesFile;
|
|
19
19
|
this.logWarn = logWarn;
|
|
20
20
|
this.logDebug = logDebug;
|
|
21
21
|
this.requestConfig = requestConfig;
|
|
@@ -58,7 +58,7 @@ class MelCloud extends EventEmitter {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async
|
|
61
|
+
async checkDevicesList(contextKey) {
|
|
62
62
|
try {
|
|
63
63
|
const axiosInstanceGet = axios.create({
|
|
64
64
|
method: 'GET',
|
|
@@ -109,7 +109,7 @@ class MelCloud extends EventEmitter {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
async
|
|
112
|
+
async connect() {
|
|
113
113
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
114
114
|
|
|
115
115
|
try {
|
|
@@ -156,18 +156,24 @@ class MelCloud extends EventEmitter {
|
|
|
156
156
|
|
|
157
157
|
this.emit('success', `Connect to MELCloud Success`);
|
|
158
158
|
|
|
159
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
accountInfo,
|
|
161
|
+
contextKey,
|
|
162
|
+
useFahrenheit
|
|
163
|
+
};
|
|
160
164
|
} catch (error) {
|
|
161
165
|
throw new Error(`Connect to MELCloud error: ${error.message}`);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
async
|
|
169
|
+
async connectHome(cookieC1, cookieC2) {
|
|
170
|
+
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
171
|
+
|
|
166
172
|
try {
|
|
167
|
-
const c1 =
|
|
168
|
-
const c2 =
|
|
173
|
+
const c1 = cookieC1.trim();
|
|
174
|
+
const c2 = cookieC2.trim();
|
|
169
175
|
|
|
170
|
-
const
|
|
176
|
+
const cookieString = [
|
|
171
177
|
'__Secure-monitorandcontrol=chunks-2',
|
|
172
178
|
`__Secure-monitorandcontrolC1=${c1}`,
|
|
173
179
|
`__Secure-monitorandcontrolC2=${c2}`,
|
|
@@ -175,7 +181,7 @@ class MelCloud extends EventEmitter {
|
|
|
175
181
|
|
|
176
182
|
const axiosInstance = axios.create({
|
|
177
183
|
baseURL: ApiUrlsHome.BaseURL,
|
|
178
|
-
timeout:
|
|
184
|
+
timeout: 10000,
|
|
179
185
|
httpsAgent: new Agent({
|
|
180
186
|
keepAlive: false,
|
|
181
187
|
rejectUnauthorized: false
|
|
@@ -183,8 +189,8 @@ class MelCloud extends EventEmitter {
|
|
|
183
189
|
headers: {
|
|
184
190
|
'Accept': '*/*',
|
|
185
191
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
186
|
-
'Cookie':
|
|
187
|
-
'User-Agent': 'homebridge-melcloud-
|
|
192
|
+
'Cookie': cookieString,
|
|
193
|
+
'User-Agent': 'homebridge-melcloud-home/0.2.0',
|
|
188
194
|
'DNT': '1',
|
|
189
195
|
'Origin': 'https://melcloudhome.com',
|
|
190
196
|
'Referer': 'https://melcloudhome.com/dashboard',
|
|
@@ -195,128 +201,90 @@ class MelCloud extends EventEmitter {
|
|
|
195
201
|
}
|
|
196
202
|
});
|
|
197
203
|
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
202
|
-
|
|
203
|
-
if (!buildingsList) {
|
|
204
|
-
if (this.logWarn) this.emit('warn', `No building found`);
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
209
|
-
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
210
|
-
|
|
211
|
-
const devices = buildingsList.flatMap(building => [
|
|
212
|
-
...(building.airToAirUnits || []),
|
|
213
|
-
...(building.airToWaterUnits || [])
|
|
214
|
-
]);
|
|
215
|
-
|
|
216
|
-
const devicesCount = devices.length;
|
|
217
|
-
if (devicesCount === 0) {
|
|
218
|
-
if (this.logWarn) this.emit('warn', `No devices found`);
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
204
|
+
const response = await axiosInstance.get(ApiUrlsHome.GetUserContext);
|
|
205
|
+
const homeData = response.data;
|
|
206
|
+
if (this.logDebug) this.emit('debug', `[MELCloudHome] Response data: ${JSON.stringify(homeData, null, 2)}`);
|
|
221
207
|
|
|
222
|
-
|
|
223
|
-
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
208
|
+
this.emit('success', `Connect to MELCloud Home Success`);
|
|
224
209
|
|
|
225
|
-
return
|
|
210
|
+
return
|
|
226
211
|
} catch (error) {
|
|
227
212
|
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
228
213
|
}
|
|
229
214
|
}
|
|
230
215
|
|
|
231
|
-
async
|
|
232
|
-
|
|
216
|
+
async connectHomeCookies() {
|
|
217
|
+
const loginUrl = new URL('https://live-melcloudhome.auth.eu-west-1.amazoncognito.com/login');
|
|
218
|
+
loginUrl.searchParams.set('client_id', '3g4d5l5kivuqi7oia68gib7uso');
|
|
219
|
+
loginUrl.searchParams.set('redirect_uri', 'https://auth.melcloudhome.com/signin-oidc-meu');
|
|
220
|
+
loginUrl.searchParams.set('response_type', 'code');
|
|
221
|
+
loginUrl.searchParams.set('scope', 'openid profile');
|
|
222
|
+
loginUrl.searchParams.set('response_mode', 'form_post');
|
|
223
|
+
|
|
224
|
+
const browser = await puppeteer.launch({
|
|
225
|
+
headless: true,
|
|
226
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
227
|
+
});
|
|
233
228
|
|
|
234
|
-
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
|
235
229
|
const page = await browser.newPage();
|
|
236
230
|
|
|
237
231
|
try {
|
|
238
|
-
|
|
239
|
-
await page.goto(
|
|
240
|
-
const buttons = await page.$$('button.btn--blue');
|
|
241
|
-
let loginBtn = null;
|
|
242
|
-
for (const btn of buttons) {
|
|
243
|
-
const text = await page.evaluate(el => el.textContent, btn);
|
|
244
|
-
if (text.trim() === 'Zaloguj' || text.trim() === 'Log In') {
|
|
245
|
-
loginBtn = btn;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
232
|
+
this.emit('warn', 'Opening login page...');
|
|
233
|
+
await page.goto(loginUrl.toString(), { waitUntil: 'networkidle2' });
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Set credentials and login
|
|
253
|
-
await Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
254
|
-
await page.waitForSelector('input[name="username"]', { timeout: 15000 });
|
|
235
|
+
this.emit('warn', 'Typing credentials...');
|
|
255
236
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
256
237
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
257
238
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
239
|
+
// Wyszukiwanie przycisku logowania
|
|
240
|
+
const buttonSelectors = [
|
|
241
|
+
'button[type="submit"]',
|
|
242
|
+
'input[type="submit"]',
|
|
243
|
+
'button[name="signIn"]',
|
|
244
|
+
'button.btn-primary'
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
let buttonFound = false;
|
|
248
|
+
for (const selector of buttonSelectors) {
|
|
249
|
+
const button = await page.$(selector);
|
|
250
|
+
if (button) {
|
|
251
|
+
this.emit('warn', `Found submit button: ${selector}`);
|
|
252
|
+
await Promise.all([
|
|
253
|
+
button.click(),
|
|
254
|
+
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 }) // czekamy na redirect
|
|
255
|
+
]);
|
|
256
|
+
buttonFound = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
271
259
|
}
|
|
272
260
|
|
|
273
|
-
if (!
|
|
274
|
-
|
|
275
|
-
return null;
|
|
261
|
+
if (!buttonFound) {
|
|
262
|
+
throw new Error('❌ Could not find login button on the page.');
|
|
276
263
|
}
|
|
277
264
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.contextKey = contextKey;
|
|
265
|
+
// ✅ Poczekaj aż dashboard MELCloud Home będzie gotowy
|
|
266
|
+
this.emit('warn', 'Waiting for MELCloud Home dashboard...');
|
|
267
|
+
await page.waitForSelector('#dashboard, .dashboard-container', { timeout: 20000 });
|
|
282
268
|
|
|
283
|
-
|
|
269
|
+
// Pobranie cookies po zalogowaniu
|
|
270
|
+
const cookies = await page.cookies();
|
|
271
|
+
const c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || null;
|
|
272
|
+
const c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || null;
|
|
284
273
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
274
|
+
const data = { C1: c1, C2: c2, date: new Date().toISOString() };
|
|
275
|
+
await this.functions.saveData(this.cookiesFile, data);
|
|
276
|
+
|
|
277
|
+
this.emit('warn', 'Login successful.');
|
|
278
|
+
return data;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
this.emit('error', `Login failed: ${err.message}`);
|
|
281
|
+
return null;
|
|
288
282
|
} finally {
|
|
289
283
|
await browser.close();
|
|
290
284
|
}
|
|
291
285
|
}
|
|
292
286
|
|
|
293
|
-
async connect() {
|
|
294
|
-
let response = null;
|
|
295
|
-
switch (this.displayType) {
|
|
296
|
-
case "1":
|
|
297
|
-
response = await this.connectToMelCloud();
|
|
298
|
-
return response
|
|
299
|
-
case "2":
|
|
300
|
-
response = await this.connectToMelCloudHome();
|
|
301
|
-
return response
|
|
302
|
-
default:
|
|
303
|
-
return null
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
287
|
|
|
307
|
-
async checkDevicesList(key) {
|
|
308
|
-
let devices = null;
|
|
309
|
-
switch (this.displayType) {
|
|
310
|
-
case "1":
|
|
311
|
-
devices = await this.checkMelcloudDevicesList(key);
|
|
312
|
-
return devices
|
|
313
|
-
case "2":
|
|
314
|
-
devices = await this.checkMelcloudHomeDevicesList(key);
|
|
315
|
-
return devices
|
|
316
|
-
default:
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
288
|
|
|
321
289
|
async send(accountInfo) {
|
|
322
290
|
try {
|