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.
@@ -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('displayType').value = acc.displayType || '';
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.languaged && acc.displayType);
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, displayType: acc.displayType };
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
@@ -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(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
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
- ...account.mqtt?.auth,
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(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logLevel.warn, logLevel.debug, false)
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 (!contextKey) {
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.40",
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(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
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 checkMelcloudDevicesList(contextKey) {
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 connectToMelCloud() {
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 { accountInfo, contextKey, useFahrenheit };
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 checkMelcloudHomeDevicesList(cookies) {
169
+ async connectHome(cookieC1, cookieC2) {
170
+ if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
171
+
166
172
  try {
167
- const c1 = cookies.c1.trim();
168
- const c2 = cookies.c2.trim();
173
+ const c1 = cookieC1.trim();
174
+ const c2 = cookieC2.trim();
169
175
 
170
- const cookie = [
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: 20000,
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': cookie,
187
- 'User-Agent': 'homebridge-melcloud-control/4.0.0',
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
- if (this.logDebug) this.emit('debug', `Scanning for devices`);
199
- const listDevicesData = await axiosInstance.get(ApiUrlsHome.GetUserContext);
200
- const buildingsList = listDevicesData.data.buildings;
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
- await this.functions.saveData(this.devicesFile, devices);
223
- if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
208
+ this.emit('success', `Connect to MELCloud Home Success`);
224
209
 
225
- return devices;
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 connectToMelCloudHome() {
232
- if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
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
- // Open MELCloud Home
239
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
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
- if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
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
- const button1 = await page.$('input[type="submit"]');
259
- await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
260
-
261
- // Get cookies C1 and C2
262
- let c1 = null, c2 = null;
263
- const start = Date.now();
264
-
265
- // Loop max 20s
266
- while ((!c1 || !c2) && Date.now() - start < 20000) {
267
- const cookies = await page.cookies();
268
- c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
269
- c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
270
- if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
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 (!c1 || !c2) {
274
- if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
275
- return null;
261
+ if (!buttonFound) {
262
+ throw new Error(' Could not find login button on the page.');
276
263
  }
277
264
 
278
- const accountInfo = {};
279
- const contextKey = { c1, c2 };
280
- const useFahrenheit = false;
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
- this.emit('success', `Connect to MELCloud Home Success`);
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
- return { accountInfo, contextKey, useFahrenheit };
286
- } catch (error) {
287
- throw new Error(`Connect to MELCloud Home error: ${error.message}`);
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 {