homebridge-melcloud-control 4.3.11-beta.3 → 4.3.11-beta.30

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/index.js CHANGED
@@ -105,8 +105,8 @@ class MelCloudPlatform {
105
105
 
106
106
  //connect
107
107
  const accountInfo = await melcloud.connect();
108
- if (!accountInfo.State) {
109
- if (logLevel.warn) log.warn(`${accountName}, ${accountInfo.Info}`);
108
+ if (!accountInfo?.State) {
109
+ if (logLevel.warn) log.warn(`${accountName}, ${accountInfo?.Info}`);
110
110
  return;
111
111
  }
112
112
  if (logLevel.success) log.success(`${accountName}, ${accountInfo.Info}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.3.11-beta.3",
4
+ "version": "4.3.11-beta.30",
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/constants.js CHANGED
@@ -23,7 +23,7 @@ export const ApiUrlsHome = {
23
23
  GetConfiguration: "https://melcloudhome.com/api/configuration",
24
24
  GetUserContext: "/api/user/context",
25
25
  GetUserScenes: "/api/user/scenes",
26
- PostSchedule: " /api/cloudschedule/deviceid", // POST {"days":[2],"time":"17:59:00","enabled":true,"id":"53c5e804-0663-47d0-85c2-2d8ccd2573de","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
26
+ PostSchedule: "/api/cloudschedule/deviceid", // POST {"days":[2],"time":"17:59:00","enabled":true,"id":"53c5e804-0663-47d0-85c2-2d8ccd2573de","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
27
27
  PostProtectionFrost: "/api/protection/frost", // POST {"enabled":true,"min":13,"max":16,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
28
28
  PostProtectionOverheat: "/api/protection/overheat", // POST {"enabled":true,"min":32,"max":35,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
29
29
  PostHolidayMode: " /api/holidaymode", // POST {"enabled":true,"startDate":"2025-11-11T17:42:24.913","endDate":"2026-06-01T09:18:00","units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
@@ -35,6 +35,7 @@ export const ApiUrlsHome = {
35
35
  Enable: "/api/scene/sceneid/enable",
36
36
  Disable: "/api/scene/sceneid/disable",
37
37
  },
38
+ DeleteSchedule: "/api/cloudschedule/deviceid/scheduleid",
38
39
  Referers: {
39
40
  GetPutScenes: "https://melcloudhome.com/scenes",
40
41
  PostHolidayMode: "https://melcloudhome.com/ata/deviceid/holidaymode",
@@ -60,7 +61,7 @@ export const AirConditioner = {
60
61
  SystemMapEnumToString: { 0: "Air Conditioner Off", 1: "Air Conditioner On", 2: "Air Conditioner Offline" },
61
62
  OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Automatic": 8, "Heat Isee": 9, "Dry Isee": 10, "Cool Isee": 11 },
62
63
  OperationModeMapEnumToString: { 0: "0", 1: "Heat", 2: "Dry", 3: "Cool", 4: "4", 5: "5", 6: "6", 7: "Fan", 8: "Automatic", 9: "Heat Isee", 10: "Dry Isee", 11: "Cool Isee" },
63
- OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11 },
64
+ OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8 },
64
65
  FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
65
66
  FanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
66
67
  SetFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
package/src/deviceata.js CHANGED
@@ -142,7 +142,7 @@ class DeviceAta extends EventEmitter {
142
142
  });
143
143
  }
144
144
  } catch (error) {
145
- this.emit('warn', `RESTFul integration start error: ${error}`);
145
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
146
146
  };
147
147
  }
148
148
 
@@ -186,7 +186,7 @@ class DeviceAta extends EventEmitter {
186
186
  });
187
187
  }
188
188
  } catch (error) {
189
- this.emit('warn', `MQTT integration start error: ${error}`);
189
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
190
190
  };
191
191
  }
192
192
 
@@ -1325,7 +1325,7 @@ class DeviceAta extends EventEmitter {
1325
1325
  async start() {
1326
1326
  try {
1327
1327
  //melcloud device
1328
- this.melCloudAta = new MelCloudAta(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud, this.accountInfo)
1328
+ this.melCloudAta = new MelCloudAta(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1329
1329
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
1330
1330
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1331
1331
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
@@ -1861,7 +1861,7 @@ class DeviceAta extends EventEmitter {
1861
1861
  if (supportsFanSpeed) this.emit('info', `Current fan speed: ${AirConditioner.AktualFanSpeedMapEnumToString[actualFanSpeed]}`);
1862
1862
  if (vaneHorizontalDirection !== null) this.emit('info', `Vane horizontal: ${AirConditioner.VaneHorizontalDirectionMapEnumToString[vaneHorizontalDirection]}`);
1863
1863
  if (vaneVerticalDirection !== null) this.emit('info', `Vane vertical: ${AirConditioner.VaneVerticalDirectionMapEnumToString[vaneVerticalDirection]}`);
1864
- if (supportsSwingFunction) this.emit('info', `Air direction: ${AirConditioner.AirDirectionMapEnumToString[obj.currentSwingMode]}`);
1864
+ if (supportsSwingFunction) this.emit('info', `Air direction: ${AirConditioner.AirDirectionMapEnumToString[currentSwingMode]}`);
1865
1865
  this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
1866
1866
  this.emit('info', `Lock physical controls: ${obj.lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
1867
1867
  if (this.accountType === 'melcloudhome') this.emit('info', `Signal strength: ${deviceData.Rssi}dBm`);
package/src/deviceatw.js CHANGED
@@ -146,7 +146,7 @@ class DeviceAtw extends EventEmitter {
146
146
  });
147
147
  }
148
148
  } catch (error) {
149
- this.emit('warn', `RESTFul integration start error: ${error}`);
149
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
150
150
  };
151
151
  }
152
152
 
@@ -190,7 +190,7 @@ class DeviceAtw extends EventEmitter {
190
190
  });
191
191
  }
192
192
  } catch (error) {
193
- this.emit('warn', `MQTT integration start error: ${error}`);
193
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
194
194
  };
195
195
  }
196
196
  }
@@ -1567,7 +1567,7 @@ class DeviceAtw extends EventEmitter {
1567
1567
  async start() {
1568
1568
  try {
1569
1569
  //melcloud device
1570
- this.melCloudAtw = new MelCloudAtw(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud, this.accountInfo)
1570
+ this.melCloudAtw = new MelCloudAtw(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1571
1571
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, supportsHotWaterTank, supportsZone2) => {
1572
1572
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1573
1573
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
package/src/deviceerv.js CHANGED
@@ -138,7 +138,7 @@ class DeviceErv extends EventEmitter {
138
138
  });
139
139
  }
140
140
  } catch (error) {
141
- this.emit('warn', `RESTFul integration start error: ${error}`);
141
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
142
142
  };
143
143
  }
144
144
 
@@ -182,7 +182,7 @@ class DeviceErv extends EventEmitter {
182
182
  });
183
183
  }
184
184
  } catch (error) {
185
- this.emit('warn', `MQTT integration start error: ${error}`);
185
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
186
186
  };
187
187
  }
188
188
  }
@@ -1121,7 +1121,7 @@ class DeviceErv extends EventEmitter {
1121
1121
  async start() {
1122
1122
  try {
1123
1123
  //melcloud device
1124
- this.melCloudErv = new MelCloudErv(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud, this.accountInfo)
1124
+ this.melCloudErv = new MelCloudErv(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1125
1125
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
1126
1126
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1127
1127
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
package/src/functions.js CHANGED
@@ -58,30 +58,30 @@ class Functions extends EventEmitter {
58
58
  let chromiumPath = '/usr/bin/chromium-browser';
59
59
 
60
60
  try {
61
- // --- Detect OS ---
61
+ // Detect OS
62
62
  const { stdout: osOut } = await execPromise('uname -s');
63
63
  const osName = osOut.trim();
64
64
  if (this.logDebug) this.emit('debug', `Detected OS: ${osName}`);
65
65
 
66
- // --- Detect Architecture ---
66
+ // Detect Architecture
67
67
  const { stdout: archOut } = await execPromise('uname -m');
68
68
  const arch = archOut.trim();
69
69
  if (this.logDebug) this.emit('debug', `Detected architecture: ${arch}`);
70
70
 
71
- // --- Detect Docker ---
71
+ // Docker detection
72
72
  let isDocker = false;
73
73
  try {
74
- await access('/.dockerenv', fs.constants.F_OK); isDocker = true;
74
+ await access('/.dockerenv', fs.constants.F_OK);
75
+ isDocker = true;
75
76
  } catch { }
76
77
 
77
78
  try {
78
79
  const { stdout } = await execPromise('cat /proc/1/cgroup || true');
79
80
  if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
80
81
  } catch { }
82
+ if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container');
81
83
 
82
- if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container.');
83
-
84
- // === macOS ===
84
+ // macOS
85
85
  if (osName === 'Darwin') {
86
86
  chromiumPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
87
87
  try {
@@ -92,10 +92,9 @@ class Functions extends EventEmitter {
92
92
  }
93
93
  }
94
94
 
95
- // === ARM ===
96
- if (arch.startsWith('arm')) {
95
+ // ARM
96
+ if (arch.startsWith('arm') || arch.startsWith('aarch')) {
97
97
  try {
98
- chromiumPath = '/usr/bin/chromium-browser';
99
98
  await access(chromiumPath, fs.constants.X_OK);
100
99
  return chromiumPath;
101
100
  } catch {
@@ -108,7 +107,7 @@ class Functions extends EventEmitter {
108
107
  }
109
108
  }
110
109
 
111
- // === Linux x64 ===
110
+ // Linux x64
112
111
  if (osName === 'Linux') {
113
112
  let systemChromium = null;
114
113
  try {
@@ -116,7 +115,7 @@ class Functions extends EventEmitter {
116
115
  systemChromium = checkOut.trim() || null;
117
116
  } catch { }
118
117
 
119
- // --- Detect Entware (QNAP) ---
118
+ // Entware (QNAP)
120
119
  let entwareExists = false;
121
120
  try {
122
121
  await access('/opt/bin/opkg', fs.constants.X_OK);
@@ -131,24 +130,24 @@ class Functions extends EventEmitter {
131
130
  } catch { }
132
131
  }
133
132
 
134
- // --- Generic Linux installs missing libs for Puppeteer ---
135
- const depInstall = [
133
+ // Install missing libs
134
+ const depCommands = [
136
135
  'apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2',
137
136
  'apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib',
138
137
  'yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib'
139
138
  ];
140
- for (const cmd of depInstall) {
139
+
140
+ for (const cmd of depCommands) {
141
141
  try {
142
142
  await execPromise(`sudo ${cmd}`);
143
143
  } catch { }
144
144
  }
145
145
 
146
- // Set LD_LIBRARY_PATH so Puppeteer's Chromium can find libs
147
146
  process.env.LD_LIBRARY_PATH = `/usr/lib:/usr/lib64:${process.env.LD_LIBRARY_PATH || ''}`;
148
147
  return systemChromium;
149
148
  }
150
149
 
151
- if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}.`);
150
+ if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}`);
152
151
  return null;
153
152
  } catch (error) {
154
153
  if (this.logError) this.emit('error', `Chromium detection/install error: ${error.message}`);
package/src/melcloud.js CHANGED
@@ -17,8 +17,8 @@ class MelCloud extends EventEmitter {
17
17
 
18
18
  this.accountFile = accountFile;
19
19
  this.buildingsFile = buildingsFile;
20
- this.headers = {};
21
20
 
21
+ this.client = null;
22
22
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
23
23
  .on('warn', warn => this.emit('warn', warn))
24
24
  .on('error', error => this.emit('error', error))
@@ -56,7 +56,7 @@ class MelCloud extends EventEmitter {
56
56
  try {
57
57
  const devicesList = { State: false, Info: null, Devices: [], Scenes: [] }
58
58
  if (this.logDebug) this.emit('debug', `Scanning for devices...`);
59
- const listDevicesData = await this.axiosInstance(ApiUrls.ListDevices, { method: 'GET', });
59
+ const listDevicesData = await this.client(ApiUrls.ListDevices, { method: 'GET', });
60
60
 
61
61
  if (!listDevicesData || !listDevicesData.data) {
62
62
  devicesList.Info = 'Invalid or empty response from MELCloud API'
@@ -121,7 +121,7 @@ class MelCloud extends EventEmitter {
121
121
  if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
122
122
 
123
123
  try {
124
- const accountInfo = { State: false, Info: '', Account: null, UseFahrenheit: false, Headers: {} }
124
+ const accountInfo = { State: false, Info: '', Account: null, UseFahrenheit: false }
125
125
 
126
126
  const payload = {
127
127
  Email: this.user,
@@ -162,19 +162,18 @@ class MelCloud extends EventEmitter {
162
162
  'X-MitsContextKey': contextKey,
163
163
  'Content-Type': 'application/json'
164
164
  };
165
- this.emit('headers', headers);
166
- this.headers = headers;
167
- this.axiosInstance = axios.create({
165
+
166
+ this.client = axios.create({
168
167
  baseURL: ApiUrls.BaseURL,
169
168
  timeout: 30000,
170
169
  headers: headers
171
170
  });
171
+ this.emit('client', this.client);
172
172
 
173
173
  accountInfo.State = true;
174
174
  accountInfo.Info = 'Connect Success';
175
175
  accountInfo.UseFahrenheit = loginData.UseFahrenheit;
176
176
  accountInfo.Account = account;
177
- accountInfo.Headers = headers;
178
177
  await this.functions.saveData(this.accountFile, accountInfo);
179
178
 
180
179
  return accountInfo
@@ -1,10 +1,9 @@
1
- import axios from 'axios';
2
1
  import EventEmitter from 'events';
3
2
  import Functions from './functions.js';
4
3
  import { ApiUrls, ApiUrlsHome, AirConditioner } from './constants.js';
5
4
 
6
5
  class MelCloudAta extends EventEmitter {
7
- constructor(account, device, defaultTempsFile, accountFile, melcloud, accountInfo) {
6
+ constructor(account, device, defaultTempsFile, accountFile, melcloud) {
8
7
  super();
9
8
  this.accountType = account.type;
10
9
  this.logSuccess = account.log?.success;
@@ -24,12 +23,12 @@ class MelCloudAta extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = accountInfo.Headers;
26
+ this.client = melcloud.client;
28
27
 
29
28
  //handle melcloud events
30
29
  let deviceData = null;
31
- melcloud.on('headers', (headers) => {
32
- this.headers = headers;
30
+ melcloud.on('client', (client) => {
31
+ this.client = client;
33
32
  }).on('devicesList', async (devicesData) => {
34
33
  try {
35
34
  deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
@@ -197,7 +196,6 @@ class MelCloudAta extends EventEmitter {
197
196
  let method = null
198
197
  let payload = {};
199
198
  let path = '';
200
- let headers = this.headers;
201
199
  switch (accountType) {
202
200
  case "melcloud":
203
201
  switch (flag) {
@@ -235,14 +233,7 @@ class MelCloudAta extends EventEmitter {
235
233
  }
236
234
 
237
235
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
238
-
239
- await axios(path, {
240
- method: 'POST',
241
- baseURL: ApiUrls.BaseURL,
242
- timeout: 30000,
243
- headers: headers,
244
- data: payload
245
- });
236
+ await this.client(path, { method: 'POST', data: payload });
246
237
 
247
238
  this.emit('deviceState', deviceData);
248
239
  return true;
@@ -257,7 +248,6 @@ class MelCloudAta extends EventEmitter {
257
248
  };
258
249
  method = 'POST';
259
250
  path = ApiUrlsHome.PostProtectionFrost;
260
- headers.Referer = ApiUrlsHome.Referers.PostProtectionFrost.replace('deviceid', deviceData.DeviceID);
261
251
  break;
262
252
  case 'overheatprotection':
263
253
  payload = {
@@ -268,7 +258,6 @@ class MelCloudAta extends EventEmitter {
268
258
  };
269
259
  method = 'POST';
270
260
  path = ApiUrlsHome.PostProtectionOverheat;
271
- headers.Referer = ApiUrlsHome.Referers.PostProtectionOverheat.replace('deviceid', deviceData.DeviceID);
272
261
  break;
273
262
  case 'holidaymode':
274
263
  payload = {
@@ -279,18 +268,15 @@ class MelCloudAta extends EventEmitter {
279
268
  };
280
269
  method = 'POST';
281
270
  path = ApiUrlsHome.PostHolidayMode;
282
- headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
283
271
  break;
284
272
  case 'schedule':
285
273
  payload = { enabled: deviceData.ScheduleEnabled };
286
274
  method = 'PUT';
287
275
  path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
288
- this.headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
289
276
  break;
290
277
  case 'scene':
291
278
  method = 'PUT';
292
279
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
293
- headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
294
280
  break;
295
281
  default:
296
282
  if (displayType === 1 && deviceData.Device.OperationMode === 8) {
@@ -317,29 +303,18 @@ class MelCloudAta extends EventEmitter {
317
303
  };
318
304
  method = 'PUT';
319
305
  path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
320
- headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
321
306
  break;
322
307
  }
323
308
 
324
- //sens payload
325
- headers['Content-Type'] = 'application/json; charset=utf-8';
326
- headers.Origin = ApiUrlsHome.Origin;
309
+ //send payload
327
310
  if (!this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
328
-
329
- await axios(path, {
330
- method: method,
331
- baseURL: ApiUrlsHome.BaseURL,
332
- timeout: 30000,
333
- headers: headers,
334
- data: payload
335
- });
311
+ await this.client(path, { method: method, data: payload });
336
312
 
337
313
  return true;
338
314
  default:
339
315
  return;
340
316
  }
341
317
  } catch (error) {
342
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
343
318
  throw new Error(`Send data error: ${error.message}`);
344
319
  }
345
320
  }
@@ -1,10 +1,9 @@
1
- import axios from 'axios';
2
1
  import EventEmitter from 'events';
3
2
  import Functions from './functions.js';
4
3
  import { ApiUrls, ApiUrlsHome, HeatPump } from './constants.js';
5
4
 
6
5
  class MelCloudAtw extends EventEmitter {
7
- constructor(account, device, defaultTempsFile, accountFile, melcloud, accountInfo) {
6
+ constructor(account, device, defaultTempsFile, accountFile, melcloud) {
8
7
  super();
9
8
  this.accountType = account.type;
10
9
  this.logSuccess = account.log?.success;
@@ -24,12 +23,12 @@ class MelCloudAtw extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = accountInfo.Headers;
26
+ this.client = melcloud.client;
28
27
 
29
28
  //handle melcloud events
30
29
  let deviceData = null;
31
- melcloud.on('headers', (headers) => {
32
- this.headers = headers;
30
+ melcloud.on('client', (client) => {
31
+ this.client = client;
33
32
  }).on('devicesList', async (devicesData) => {
34
33
  try {
35
34
  deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
@@ -198,7 +197,6 @@ class MelCloudAtw extends EventEmitter {
198
197
  let method = null
199
198
  let payload = {};
200
199
  let path = '';
201
- let headers = this.headers;
202
200
  switch (accountType) {
203
201
  case "melcloud":
204
202
  switch (flag) {
@@ -237,14 +235,7 @@ class MelCloudAtw extends EventEmitter {
237
235
  }
238
236
 
239
237
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
240
-
241
- await axios(path, {
242
- method: 'POST',
243
- baseURL: ApiUrls.BaseURL,
244
- timeout: 30000,
245
- headers: headers,
246
- data: payload
247
- });
238
+ await this.client(path, { method: 'POST', data: payload });
248
239
 
249
240
  this.emit('deviceState', deviceData);
250
241
  return true;
@@ -259,18 +250,15 @@ class MelCloudAtw extends EventEmitter {
259
250
  };
260
251
  method = 'POST';
261
252
  path = ApiUrlsHome.PostHolidayMode;
262
- headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
263
253
  break;
264
254
  case 'schedule':
265
255
  payload = { enabled: deviceData.ScheduleEnabled };
266
256
  method = 'PUT';
267
257
  path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
268
- headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
269
258
  break;
270
259
  case 'scene':
271
260
  method = 'PUT';
272
261
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
273
- headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
274
262
  break;
275
263
  default:
276
264
  payload = {
@@ -290,28 +278,17 @@ class MelCloudAtw extends EventEmitter {
290
278
  };
291
279
  method = 'PUT';
292
280
  path = ApiUrlsHome.PutAtw.replace('deviceid', deviceData.DeviceID);
293
- headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
294
281
  break
295
282
  }
296
283
 
297
- headers['Content-Type'] = 'application/json; charset=utf-8';
298
- headers.Origin = ApiUrlsHome.Origin;
299
284
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
300
-
301
- await axios(path, {
302
- method: method,
303
- baseURL: ApiUrlsHome.BaseURL,
304
- timeout: 30000,
305
- headers: headers,
306
- data: payload
307
- });
285
+ await this.client(path, { method: method, data: payload });
308
286
 
309
287
  return true;
310
288
  default:
311
289
  return;
312
290
  }
313
291
  } catch (error) {
314
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
315
292
  throw new Error(`Send data error: ${error.message}`);
316
293
  }
317
294
  }
@@ -1,10 +1,9 @@
1
- import axios from 'axios';
2
1
  import EventEmitter from 'events';
3
2
  import Functions from './functions.js';
4
3
  import { ApiUrls, ApiUrlsHome, Ventilation } from './constants.js';
5
4
 
6
5
  class MelCloudErv extends EventEmitter {
7
- constructor(account, device, defaultTempsFile, accountFile, melcloud, accountInfo) {
6
+ constructor(account, device, defaultTempsFile, accountFile, melcloud) {
8
7
  super();
9
8
  this.accountType = account.type;
10
9
  this.logSuccess = account.log?.success;
@@ -24,14 +23,12 @@ class MelCloudErv extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = accountInfo.Headers;
28
-
29
- this.emit('debug', `Headers: ${JSON.stringify(melcloud.Headers, null, 2)}`);
26
+ this.client = melcloud.client;
30
27
 
31
28
  //handle melcloud events
32
29
  let deviceData = null;
33
- melcloud.on('headers', (headers) => {
34
- this.headers = headers;
30
+ melcloud.on('client', (client) => {
31
+ this.client = client;
35
32
  }).on('devicesList', async (devicesData) => {
36
33
  try {
37
34
  deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
@@ -185,7 +182,6 @@ class MelCloudErv extends EventEmitter {
185
182
  let method = null
186
183
  let payload = {};
187
184
  let path = '';
188
- let headers = this.headers;
189
185
  switch (accountType) {
190
186
  case "melcloud":
191
187
  switch (flag) {
@@ -239,14 +235,7 @@ class MelCloudErv extends EventEmitter {
239
235
  }
240
236
 
241
237
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
242
-
243
- await axios(path, {
244
- method: 'POST',
245
- baseURL: ApiUrls.BaseURL,
246
- timeout: 30000,
247
- headers: headers,
248
- data: payload
249
- });
238
+ await this.client(path, { method: 'POST', data: payload });
250
239
 
251
240
  this.emit('deviceState', deviceData);
252
241
  return true;
@@ -261,18 +250,15 @@ class MelCloudErv extends EventEmitter {
261
250
  };
262
251
  method = 'POST';
263
252
  path = ApiUrlsHome.PostHolidayMode;
264
- headers.Referer = ApiUrlsHome.Referers.PostHolidayMode.replace('deviceid', deviceData.DeviceID);
265
253
  break;
266
254
  case 'schedule':
267
255
  payload = { enabled: deviceData.ScheduleEnabled };
268
256
  method = 'PUT';
269
257
  path = ApiUrlsHome.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
270
- headers.Referer = ApiUrlsHome.Referers.PutScheduleEnabled.replace('deviceid', deviceData.DeviceID);
271
258
  break;
272
259
  case 'scene':
273
260
  method = 'PUT';
274
261
  path = ApiUrlsHome.PutScene[flagData.Enabled ? 'Enable' : 'Disable'].replace('sceneid', flagData.Id);
275
- headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
276
262
  break;
277
263
  default:
278
264
  if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
@@ -296,28 +282,17 @@ class MelCloudErv extends EventEmitter {
296
282
  };
297
283
  method = 'PUT';
298
284
  path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
299
- headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
300
285
  break
301
286
  }
302
287
 
303
- headers['Content-Type'] = 'application/json; charset=utf-8';
304
- headers.Origin = ApiUrlsHome.Origin;
305
288
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
306
-
307
- await axios(path, {
308
- method: method,
309
- baseURL: ApiUrlsHome.BaseURL,
310
- timeout: 30000,
311
- headers: headers,
312
- data: payload
313
- });
289
+ await this.client(path, { method: method, data: payload });
314
290
 
315
291
  return true;
316
292
  default:
317
293
  return;
318
294
  }
319
295
  } catch (error) {
320
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
321
296
  throw new Error(`Send data error: ${error.message}`);
322
297
  }
323
298
  }
@@ -21,12 +21,11 @@ class MelCloudHome extends EventEmitter {
21
21
  this.logWarn = account.log?.warn;
22
22
  this.logError = account.log?.error;
23
23
  this.logDebug = account.log?.debug;
24
+
24
25
  this.accountFile = accountFile;
25
26
  this.buildingsFile = buildingsFile;
26
27
 
27
- this.headers = {};
28
- this.webSocketOptions = {};
29
- this.socket = null;
28
+ this.client = null;
30
29
  this.connecting = false;
31
30
  this.socketConnected = false;
32
31
  this.heartbeat = null;
@@ -74,18 +73,13 @@ class MelCloudHome extends EventEmitter {
74
73
  this.heartbeat = null;
75
74
  }
76
75
 
77
- if (this.socket) {
78
- try { this.socket.close(); } catch { }
79
- this.socket = null;
80
- }
81
-
82
76
  this.socketConnected = false;
83
77
  }
84
78
 
85
79
  async checkScenesList() {
86
80
  try {
87
81
  if (this.logDebug) this.emit('debug', `Scanning for scenes`);
88
- const listScenesData = await this.axiosInstance(ApiUrlsHome.GetUserScenes, { method: 'GET', });
82
+ const listScenesData = await this.client(ApiUrlsHome.GetUserScenes, { method: 'GET', });
89
83
 
90
84
  const scenesList = listScenesData.data;
91
85
  if (this.logDebug) this.emit('debug', `Scenes: ${JSON.stringify(scenesList, null, 2)}`);
@@ -117,7 +111,7 @@ class MelCloudHome extends EventEmitter {
117
111
  try {
118
112
  const devicesList = { State: false, Info: null, Devices: [], Scenes: [] }
119
113
  if (this.logDebug) this.emit('debug', `Scanning for devices`);
120
- const listDevicesData = await this.axiosInstance(ApiUrlsHome.GetUserContext, { method: 'GET' });
114
+ const listDevicesData = await this.client(ApiUrlsHome.GetUserContext, { method: 'GET' });
121
115
 
122
116
  const userContext = listDevicesData.data;
123
117
  const buildings = userContext.buildings ?? [];
@@ -202,56 +196,15 @@ class MelCloudHome extends EventEmitter {
202
196
  // Get scenes
203
197
  let scenes = [];
204
198
  try {
205
- scenes = await this.checkScenesList();
206
- if (this.logDebug) this.emit('debug', `Found ${scenes.length} scenes`);
199
+ const scenesList = await this.checkScenesList();
200
+ if (this.logDebug) this.emit('debug', `Found ${scenesList.length} scenes`);
201
+ if (scenesList.length > 0) {
202
+ scenes = scenesList;
203
+ }
207
204
  } catch (error) {
208
205
  if (this.logError) this.emit('error', `Get scenes error: ${error}`);
209
206
  }
210
207
 
211
- //web cocket connection
212
- if (!this.connecting && !this.socketConnected) {
213
- this.connecting = true;
214
-
215
- try {
216
- const url = `${ApiUrlsHome.WebSocketURL}${this.webSocketOptions.Hash}`;
217
- this.socket = new WebSocket(url, { headers: this.webSocketOptions.Headers })
218
- .on('error', (error) => {
219
- if (this.logError) this.emit('error', `Socket error: ${error}`);
220
- this.socket.close();
221
- })
222
- .on('close', () => {
223
- if (this.logDebug) this.emit('debug', `Socket closed`);
224
- this.cleanupSocket();
225
- })
226
- .on('open', () => {
227
- this.socketConnected = true;
228
- this.connecting = false;
229
- if (this.logSuccess) this.emit('success', `Socket Connect Success`);
230
-
231
- // heartbeat
232
- this.heartbeat = setInterval(() => {
233
- if (this.socket.readyState === this.socket.OPEN) {
234
- if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
235
- this.socket.ping();
236
- }
237
- }, 30000);
238
- })
239
- .on('pong', () => {
240
- if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
241
- })
242
- .on('message', (message) => {
243
- const parsedMessage = JSON.parse(message);
244
- if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
245
- if (parsedMessage.message === 'Forbidden') return;
246
-
247
- this.emit('webSocket', parsedMessage);
248
- });
249
- } catch (error) {
250
- if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
251
- this.cleanupSocket();
252
- }
253
- }
254
-
255
208
  devicesList.State = true;
256
209
  devicesList.Info = `Found ${devicesCount} devices ${scenes.length > 0 ? `and ${scenes.length} scenes` : ''}`;
257
210
  devicesList.Devices = devices;
@@ -270,20 +223,24 @@ class MelCloudHome extends EventEmitter {
270
223
 
271
224
  let browser;
272
225
  try {
273
- const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false, Headers: {} };
226
+ const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false };
227
+
228
+ // Get Chromium path
274
229
  let chromiumPath = await this.functions.ensureChromiumInstalled();
275
230
 
276
231
  // === Fallback to Puppeteer's built-in Chromium ===
277
232
  if (!chromiumPath) {
278
- try {
279
- const puppeteerPath = puppeteer.executablePath();
280
- if (puppeteerPath && fs.existsSync(puppeteerPath)) {
281
- chromiumPath = puppeteerPath;
282
- if (this.logDebug) this.emit('debug', `Using puppeteer Chromium at ${chromiumPath}`);
283
- }
284
- } catch { }
285
- } else {
286
- if (this.logDebug) this.emit('debug', `Using system Chromium at ${chromiumPath}`);
233
+ if (!arch.startsWith('arm')) {
234
+ try {
235
+ const puppeteerPath = puppeteer.executablePath();
236
+ if (puppeteerPath && fs.existsSync(puppeteerPath)) {
237
+ chromiumPath = puppeteerPath;
238
+ if (this.logDebug) this.emit('debug', `Using Puppeteer Chromium at ${chromiumPath}`);
239
+ }
240
+ } catch { }
241
+ } else {
242
+ if (this.logDebug) this.emit('debug', 'Skipping Puppeteer Chromium on ARM (incompatible)');
243
+ }
287
244
  }
288
245
 
289
246
  if (!chromiumPath) {
@@ -300,6 +257,7 @@ class MelCloudHome extends EventEmitter {
300
257
  return accountInfo;
301
258
  }
302
259
 
260
+ // Launch Chromium
303
261
  if (this.logDebug) this.emit('debug', `Launching Chromium...`);
304
262
  browser = await puppeteer.launch({
305
263
  headless: true,
@@ -323,18 +281,67 @@ class MelCloudHome extends EventEmitter {
323
281
  page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
324
282
 
325
283
  // === CDP session ===
326
- let hash = null;
327
284
  const client = await page.createCDPSession();
328
285
  await client.send('Network.enable')
329
286
  client.on('Network.webSocketCreated', ({ url }) => {
330
287
  try {
331
- if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
288
+ if (url.startsWith(`${ApiUrlsHome.WebSocketURL}`)) {
332
289
  const params = new URL(url).searchParams;
333
- hash = params.get('hash');
290
+ const hash = params.get('hash');
334
291
  if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
292
+
293
+ //web socket connection
294
+ if (!this.connecting && !this.socketConnected) {
295
+ this.connecting = true;
296
+
297
+ try {
298
+ const headers = {
299
+ 'Origin': ApiUrlsHome.BaseURL,
300
+ 'Pragma': 'no-cache',
301
+ 'Cache-Control': 'no-cache'
302
+ };
303
+ const webSocket = new WebSocket(`${ApiUrlsHome.WebSocketURL}${hash}`, { headers: headers })
304
+ .on('error', (error) => {
305
+ if (this.logError) this.emit('error', `Socket error: ${error}`);
306
+ try {
307
+ webSocket.close();
308
+ } catch { }
309
+ })
310
+ .on('close', () => {
311
+ if (this.logDebug) this.emit('debug', `Socket closed`);
312
+ this.cleanupSocket();
313
+ })
314
+ .on('open', () => {
315
+ this.socketConnected = true;
316
+ this.connecting = false;
317
+ if (this.logSuccess) this.emit('success', `Socket Connect Success`);
318
+
319
+ // heartbeat
320
+ this.heartbeat = setInterval(() => {
321
+ if (webSocket.readyState === webSocket.OPEN) {
322
+ if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
323
+ webSocket.ping();
324
+ }
325
+ }, 30000);
326
+ })
327
+ .on('pong', () => {
328
+ if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
329
+ })
330
+ .on('message', (message) => {
331
+ const parsedMessage = JSON.parse(message);
332
+ if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
333
+ if (parsedMessage.message === 'Forbidden') return;
334
+
335
+ this.emit('webSocket', parsedMessage);
336
+ });
337
+ } catch (error) {
338
+ if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
339
+ this.cleanupSocket();
340
+ }
341
+ }
335
342
  }
336
- } catch (err) {
337
- this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
343
+ } catch (error) {
344
+ if (this.logError) this.emit('error', `CDP WebSocketCreated handler error: ${error.message}`);
338
345
  }
339
346
  });
340
347
 
@@ -411,27 +418,16 @@ class MelCloudHome extends EventEmitter {
411
418
  'User-Agent': userAgent,
412
419
  'x-csrf': '1'
413
420
  };
414
- this.emit('headers', headers);
415
421
 
416
- this.headers = headers;
417
- this.axiosInstance = axios.create({
422
+ this.client = axios.create({
418
423
  baseURL: ApiUrlsHome.BaseURL,
419
424
  timeout: 30000,
420
425
  headers: headers
421
426
  })
422
-
423
- this.webSocketOptions = {
424
- Hash: hash,
425
- Headers: {
426
- 'Origin': ApiUrlsHome.BaseURL,
427
- 'Pragma': 'no-cache',
428
- 'Cache-Control': 'no-cache'
429
- }
430
- };
427
+ this.emit('client', this.client);
431
428
 
432
429
  accountInfo.State = true;
433
430
  accountInfo.Info = 'Connect Success';
434
- accountInfo.Headers = headers;
435
431
  await this.functions.saveData(this.accountFile, accountInfo);
436
432
 
437
433
  return accountInfo;