homebridge-melcloud-control 4.0.0-beta.39 → 4.0.0-beta.4

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 coociesFile = `${prefDir}/${accountName}_Coocies`;
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, coociesFile, 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
 
@@ -118,7 +120,6 @@ class MelCloudPlatform {
118
120
  let devicesInMelcloud;
119
121
  try {
120
122
  devicesInMelcloud = await melCloud.checkDevicesList(contextKey);
121
- return
122
123
  } catch (error) {
123
124
  if (logLevel.error) log.error(`${accountName}, Check devices list error: ${error.message ?? error}`);
124
125
  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.39",
4
+ "version": "4.0.0-beta.4",
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, coociesFile, 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.coociesFile = coociesFile;
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,127 +201,93 @@ 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 connectHomeCoocies() {
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');
233
223
 
234
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
235
- const page = await browser.newPage();
236
224
 
237
- 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
- }
249
225
 
250
- if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
226
+ const browser = await puppeteer.launch({
227
+ headless: true,
228
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
229
+ });
251
230
 
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 });
255
- await page.type('input[name="username"]', this.user, { delay: 50 });
256
- await page.type('input[name="password"]', this.passwd, { delay: 50 });
231
+ const page = await browser.newPage();
257
232
 
258
- const button1 = await page.$('input[type="submit"]');
259
- await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
233
+ // Otwórz stronę logowania
234
+ this.emit('warn', `Opening login page...`);
235
+ await page.goto(loginUrl, { waitUntil: 'networkidle2' });
236
+
237
+ // Wpisz login i hasło
238
+ this.emit('warn', `Typing credentials...`);
239
+ await page.type('input[name="username"]', this.user, { delay: 50 });
240
+ await page.type('input[name="password"]', this.passwd, { delay: 50 });
241
+
242
+ // Sprawdź kilka możliwych wariantów
243
+ const buttonSelectors = [
244
+ 'button[type="submit"]',
245
+ 'input[type="submit"]',
246
+ 'button[name="signIn"]',
247
+ 'button.btn-primary',
248
+ 'button:has-text("Sign in")',
249
+ 'button:has-text("Zaloguj")'
250
+ ];
251
+
252
+ // Kliknij "Sign in" (przycisk AWS Cognito)
253
+ let buttonFound = false;
254
+ for (const selector of buttonSelectors) {
255
+ const button = await page.$(selector);
256
+ if (button) {
257
+ console.log(`Found submit button: ${selector}`);
258
+ await Promise.all([
259
+ button.click(),
260
+ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 })
261
+ ]);
262
+ buttonFound = true;
263
+ break;
264
+ }
265
+ }
260
266
 
261
- // Get cookies C1 and C2
262
- let c1 = null, c2 = null;
263
- const start = Date.now();
267
+ if (!buttonFound) {
268
+ throw new Error('❌ Could not find login button on the page.');
269
+ }
264
270
 
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));
271
- }
271
+ // Poczekaj załaduje się MELCloud Home
272
+ this.emit('warn', `Waiting for redirect to melcloudhome.com...`);
273
+ await new Promise(resolve => setTimeout(resolve, 5000));
272
274
 
273
- if (!c1 || !c2) {
274
- if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
275
- return null;
276
- }
275
+ // Pobierz cookies dla domeny melcloudhome.com
276
+ const cookies = await page.cookies();
277
277
 
278
- const accountInfo = {};
279
- const contextKey = { c1, c2 };
280
- const useFahrenheit = false;
281
- this.contextKey = contextKey;
278
+ // Znajdź __Secure-monitorandcontrolC1 i C2
279
+ const c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || null;
280
+ const c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || null;
282
281
 
283
- this.emit('success', `Connect to MELCloud Home Success`);
282
+ // Zapisz do pliku
283
+ const data = { C1: c1, C2: c2, date: new Date().toISOString() };
284
+ await this.functions.saveData(this.coociesFile, data);
284
285
 
285
- return { accountInfo, contextKey, useFahrenheit };
286
- } catch (error) {
287
- throw new Error(`Connect to MELCloud Home error: ${error.message}`);
288
- } finally {
289
- await browser.close();
290
- }
291
- }
286
+ await browser.close();
292
287
 
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
- }
288
+ this.emit('warn', `Login successful.`);
306
289
 
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
- }
290
+ return false;
319
291
  }
320
292
 
321
293
  async send(accountInfo) {