homebridge-melcloud-control 4.3.11-beta.9 → 4.3.12

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/CHANGELOG.md CHANGED
@@ -22,6 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
22
 
23
23
  - Do not use Homebridge UI > v5.5.0 because of break config.json
24
24
 
25
+ # [4.3.11] - (07.12.2025)
26
+
27
+ ## Changes
28
+
29
+ - stability and performance improvements
30
+ - bump dependencies
31
+ - cleanup
32
+
25
33
  # [4.3.9] - (01.12.2025)
26
34
 
27
35
  ## Changes
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.9",
4
+ "version": "4.3.12",
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",
package/src/functions.js CHANGED
@@ -58,17 +58,17 @@ 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
74
  await access('/.dockerenv', fs.constants.F_OK);
@@ -79,10 +79,9 @@ class Functions extends EventEmitter {
79
79
  const { stdout } = await execPromise('cat /proc/1/cgroup || true');
80
80
  if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
81
81
  } catch { }
82
+ if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container');
82
83
 
83
- if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container.');
84
-
85
- // === macOS ===
84
+ // macOS
86
85
  if (osName === 'Darwin') {
87
86
  chromiumPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
88
87
  try {
@@ -93,10 +92,9 @@ class Functions extends EventEmitter {
93
92
  }
94
93
  }
95
94
 
96
- // === ARM ===
97
- if (arch.startsWith('arm')) {
95
+ // ARM
96
+ if (arch.startsWith('arm') || arch.startsWith('aarch')) {
98
97
  try {
99
- chromiumPath = '/usr/bin/chromium-browser';
100
98
  await access(chromiumPath, fs.constants.X_OK);
101
99
  return chromiumPath;
102
100
  } catch {
@@ -109,7 +107,7 @@ class Functions extends EventEmitter {
109
107
  }
110
108
  }
111
109
 
112
- // === Linux x64 ===
110
+ // Linux x64
113
111
  if (osName === 'Linux') {
114
112
  let systemChromium = null;
115
113
  try {
@@ -117,7 +115,7 @@ class Functions extends EventEmitter {
117
115
  systemChromium = checkOut.trim() || null;
118
116
  } catch { }
119
117
 
120
- // --- Detect Entware (QNAP) ---
118
+ // Entware (QNAP)
121
119
  let entwareExists = false;
122
120
  try {
123
121
  await access('/opt/bin/opkg', fs.constants.X_OK);
@@ -132,101 +130,25 @@ class Functions extends EventEmitter {
132
130
  } catch { }
133
131
  }
134
132
 
135
- // --- Generic Linux installs missing libs for Puppeteer ---
136
- const depInstall = [
133
+ // Install missing libs
134
+ const depCommands = [
137
135
  'apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2',
138
136
  'apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib',
139
137
  'yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib'
140
138
  ];
141
- for (const cmd of depInstall) {
142
- try {
143
- await execPromise(`sudo ${cmd}`);
144
- } catch { }
145
- }
146
-
147
- // Set LD_LIBRARY_PATH so Puppeteer's Chromium can find libs
148
- process.env.LD_LIBRARY_PATH = `/usr/lib:/usr/lib64:${process.env.LD_LIBRARY_PATH || ''}`;
149
- return systemChromium;
150
- }
151
-
152
- if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}.`);
153
- return null;
154
- } catch (error) {
155
- if (this.logError) this.emit('error', `Chromium detection/install error: ${error.message}`);
156
- return null;
157
- }
158
- }
159
-
160
- async ensureChromiumInstalled() {
161
- let chromiumPath = '/usr/bin/chromium-browser';
162
-
163
- try {
164
- const { stdout: osOut } = await execPromise('uname -s');
165
- const osName = osOut.trim();
166
- if (this.logDebug) this.emit('debug', `Detected OS: ${osName}`);
167
-
168
- const { stdout: archOut } = await execPromise('uname -m');
169
- const arch = archOut.trim();
170
- if (this.logDebug) this.emit('debug', `Detected architecture: ${arch}`);
171
-
172
- // Docker detection
173
- let isDocker = false;
174
- try { await access('/.dockerenv', fs.constants.F_OK); isDocker = true; } catch { }
175
- try {
176
- const { stdout } = await execPromise('cat /proc/1/cgroup || true');
177
- if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
178
- } catch { }
179
- if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container.');
180
-
181
- // macOS
182
- if (osName === 'Darwin') {
183
- chromiumPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
184
- try { await access(chromiumPath, fs.constants.X_OK); return chromiumPath; } catch { return null; }
185
- }
186
-
187
- // ARM
188
- if (arch.startsWith('arm') || arch.startsWith('aarch')) {
189
- try { await access(chromiumPath, fs.constants.X_OK); return chromiumPath; }
190
- catch {
191
- try { await execPromise('sudo apt-get update -y && sudo apt-get install -y chromium-browser chromium-codecs-ffmpeg'); return chromiumPath; }
192
- catch { return null; }
193
- }
194
- }
195
-
196
- // Linux x64
197
- if (osName === 'Linux') {
198
- let systemChromium = null;
199
- try {
200
- const { stdout: checkOut } = await execPromise('which chromium || which chromium-browser || true');
201
- systemChromium = checkOut.trim() || null;
202
- } catch { }
203
139
 
204
- // Entware (QNAP)
205
- let entwareExists = false;
206
- try { await access('/opt/bin/opkg', fs.constants.X_OK); entwareExists = true; } catch { }
207
- if (entwareExists) {
140
+ for (const cmd of depCommands) {
208
141
  try {
209
- await execPromise('/opt/bin/opkg update');
210
- await execPromise('/opt/bin/opkg install nspr nss libx11 libxcomposite libxdamage libxrandr libatk libatk-bridge libcups libdrm libgbm libasound');
211
- process.env.LD_LIBRARY_PATH = `/opt/lib:${process.env.LD_LIBRARY_PATH || ''}`;
142
+ await execPromise(`sudo ${cmd}`);
212
143
  } catch { }
213
144
  }
214
145
 
215
- // Install missing libs
216
- const depCommands = [
217
- 'apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2',
218
- 'apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib',
219
- 'yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib'
220
- ];
221
- for (const cmd of depCommands) { try { await execPromise(`sudo ${cmd}`); } catch { } }
222
-
223
146
  process.env.LD_LIBRARY_PATH = `/usr/lib:/usr/lib64:${process.env.LD_LIBRARY_PATH || ''}`;
224
147
  return systemChromium;
225
148
  }
226
149
 
227
150
  if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}`);
228
151
  return null;
229
-
230
152
  } catch (error) {
231
153
  if (this.logError) this.emit('error', `Chromium detection/install error: ${error.message}`);
232
154
  return null;
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'
@@ -162,14 +162,13 @@ class MelCloud extends EventEmitter {
162
162
  'X-MitsContextKey': contextKey,
163
163
  'Content-Type': 'application/json'
164
164
  };
165
- this.emit('headers', headers);
166
-
167
- this.headers = headers;
168
- this.axiosInstance = axios.create({
165
+
166
+ this.client = axios.create({
169
167
  baseURL: ApiUrls.BaseURL,
170
168
  timeout: 30000,
171
169
  headers: headers
172
170
  });
171
+ this.emit('client', this.client);
173
172
 
174
173
  accountInfo.State = true;
175
174
  accountInfo.Info = 'Connect Success';
@@ -1,4 +1,3 @@
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';
@@ -24,12 +23,12 @@ class MelCloudAta extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = melcloud.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,4 +1,3 @@
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';
@@ -24,12 +23,12 @@ class MelCloudAtw extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = melcloud.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,4 +1,3 @@
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';
@@ -24,12 +23,12 @@ class MelCloudErv extends EventEmitter {
24
23
 
25
24
  //set default values
26
25
  this.deviceData = {};
27
- this.headers = melcloud.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);
@@ -183,7 +182,6 @@ class MelCloudErv extends EventEmitter {
183
182
  let method = null
184
183
  let payload = {};
185
184
  let path = '';
186
- let headers = this.headers;
187
185
  switch (accountType) {
188
186
  case "melcloud":
189
187
  switch (flag) {
@@ -237,14 +235,7 @@ class MelCloudErv 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 MelCloudErv 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
  if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
@@ -294,28 +282,17 @@ class MelCloudErv extends EventEmitter {
294
282
  };
295
283
  method = 'PUT';
296
284
  path = ApiUrlsHome.PutErv.replace('deviceid', deviceData.DeviceID);
297
- headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
298
285
  break
299
286
  }
300
287
 
301
- headers['Content-Type'] = 'application/json; charset=utf-8';
302
- headers.Origin = ApiUrlsHome.Origin;
303
288
  if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
304
-
305
- await axios(path, {
306
- method: method,
307
- baseURL: ApiUrlsHome.BaseURL,
308
- timeout: 30000,
309
- headers: headers,
310
- data: payload
311
- });
289
+ await this.client(path, { method: method, data: payload });
312
290
 
313
291
  return true;
314
292
  default:
315
293
  return;
316
294
  }
317
295
  } catch (error) {
318
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
319
296
  throw new Error(`Send data error: ${error.message}`);
320
297
  }
321
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 ?? [];
@@ -211,50 +205,6 @@ class MelCloudHome extends EventEmitter {
211
205
  if (this.logError) this.emit('error', `Get scenes error: ${error}`);
212
206
  }
213
207
 
214
- //web cocket connection
215
- if (!this.connecting && !this.socketConnected) {
216
- this.connecting = true;
217
-
218
- try {
219
- const url = `${ApiUrlsHome.WebSocketURL}${this.webSocketOptions.Hash}`;
220
- this.socket = new WebSocket(url, { headers: this.webSocketOptions.Headers })
221
- .on('error', (error) => {
222
- if (this.logError) this.emit('error', `Socket error: ${error}`);
223
- this.socket.close();
224
- })
225
- .on('close', () => {
226
- if (this.logDebug) this.emit('debug', `Socket closed`);
227
- this.cleanupSocket();
228
- })
229
- .on('open', () => {
230
- this.socketConnected = true;
231
- this.connecting = false;
232
- if (this.logSuccess) this.emit('success', `Socket Connect Success`);
233
-
234
- // heartbeat
235
- this.heartbeat = setInterval(() => {
236
- if (this.socket.readyState === this.socket.OPEN) {
237
- if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
238
- this.socket.ping();
239
- }
240
- }, 30000);
241
- })
242
- .on('pong', () => {
243
- if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
244
- })
245
- .on('message', (message) => {
246
- const parsedMessage = JSON.parse(message);
247
- if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
248
- if (parsedMessage.message === 'Forbidden') return;
249
-
250
- this.emit('webSocket', parsedMessage);
251
- });
252
- } catch (error) {
253
- if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
254
- this.cleanupSocket();
255
- }
256
- }
257
-
258
208
  devicesList.State = true;
259
209
  devicesList.Info = `Found ${devicesCount} devices ${scenes.length > 0 ? `and ${scenes.length} scenes` : ''}`;
260
210
  devicesList.Devices = devices;
@@ -280,16 +230,20 @@ class MelCloudHome extends EventEmitter {
280
230
 
281
231
  // === Fallback to Puppeteer's built-in Chromium ===
282
232
  if (!chromiumPath) {
283
- if (!arch.startsWith('arm')) {
284
- try {
285
- const puppeteerPath = puppeteer.executablePath();
286
- if (puppeteerPath && fs.existsSync(puppeteerPath)) {
233
+ try {
234
+ const puppeteerPath = puppeteer.executablePath();
235
+ if (puppeteerPath && puppeteerPath.length > 0) {
236
+ if (fs.existsSync(puppeteerPath)) {
287
237
  chromiumPath = puppeteerPath;
288
- if (this.logDebug) this.emit('debug', `Using Puppeteer Chromium at ${chromiumPath}`);
238
+ if (this.logDebug) this.emit('debug', `Using Puppeteer bundled Chromium at ${chromiumPath}`);
239
+ } else {
240
+ if (this.logDebug) this.emit('debug', `Puppeteer returned path, but file does not exist: ${puppeteerPath}`);
289
241
  }
290
- } catch { }
291
- } else {
292
- if (this.logDebug) this.emit('debug', 'Skipping Puppeteer Chromium on ARM (incompatible)');
242
+ } else {
243
+ if (this.logDebug) this.emit('debug', `Puppeteer returned empty Chromium path`);
244
+ }
245
+ } catch (error) {
246
+ if (this.logDebug) this.emit('debug', `Failed to get Puppeteer Chromium path: ${error.message}`);
293
247
  }
294
248
  }
295
249
 
@@ -331,18 +285,67 @@ class MelCloudHome extends EventEmitter {
331
285
  page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
332
286
 
333
287
  // === CDP session ===
334
- let hash = null;
335
288
  const client = await page.createCDPSession();
336
289
  await client.send('Network.enable')
337
290
  client.on('Network.webSocketCreated', ({ url }) => {
338
291
  try {
339
- if (url.startsWith('wss://ws.melcloudhome.com/?hash=')) {
292
+ if (url.startsWith(`${ApiUrlsHome.WebSocketURL}`)) {
340
293
  const params = new URL(url).searchParams;
341
- hash = params.get('hash');
294
+ const hash = params.get('hash');
342
295
  if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
296
+
297
+ //web socket connection
298
+ if (!this.connecting && !this.socketConnected) {
299
+ this.connecting = true;
300
+
301
+ try {
302
+ const headers = {
303
+ 'Origin': ApiUrlsHome.BaseURL,
304
+ 'Pragma': 'no-cache',
305
+ 'Cache-Control': 'no-cache'
306
+ };
307
+ const webSocket = new WebSocket(`${ApiUrlsHome.WebSocketURL}${hash}`, { headers: headers })
308
+ .on('error', (error) => {
309
+ if (this.logError) this.emit('error', `Socket error: ${error}`);
310
+ try {
311
+ webSocket.close();
312
+ } catch { }
313
+ })
314
+ .on('close', () => {
315
+ if (this.logDebug) this.emit('debug', `Socket closed`);
316
+ this.cleanupSocket();
317
+ })
318
+ .on('open', () => {
319
+ this.socketConnected = true;
320
+ this.connecting = false;
321
+ if (this.logSuccess) this.emit('success', `Socket Connect Success`);
322
+
323
+ // heartbeat
324
+ this.heartbeat = setInterval(() => {
325
+ if (webSocket.readyState === webSocket.OPEN) {
326
+ if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
327
+ webSocket.ping();
328
+ }
329
+ }, 30000);
330
+ })
331
+ .on('pong', () => {
332
+ if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
333
+ })
334
+ .on('message', (message) => {
335
+ const parsedMessage = JSON.parse(message);
336
+ if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
337
+ if (parsedMessage.message === 'Forbidden') return;
338
+
339
+ this.emit('webSocket', parsedMessage);
340
+ });
341
+ } catch (error) {
342
+ if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
343
+ this.cleanupSocket();
344
+ }
345
+ }
343
346
  }
344
- } catch (err) {
345
- this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
347
+ } catch (error) {
348
+ if (this.logError) this.emit('error', `CDP WebSocketCreated handler error: ${error.message}`);
346
349
  }
347
350
  });
348
351
 
@@ -419,23 +422,13 @@ class MelCloudHome extends EventEmitter {
419
422
  'User-Agent': userAgent,
420
423
  'x-csrf': '1'
421
424
  };
422
- this.emit('headers', headers);
423
425
 
424
- this.headers = headers;
425
- this.axiosInstance = axios.create({
426
+ this.client = axios.create({
426
427
  baseURL: ApiUrlsHome.BaseURL,
427
428
  timeout: 30000,
428
429
  headers: headers
429
430
  })
430
-
431
- this.webSocketOptions = {
432
- Hash: hash,
433
- Headers: {
434
- 'Origin': ApiUrlsHome.BaseURL,
435
- 'Pragma': 'no-cache',
436
- 'Cache-Control': 'no-cache'
437
- }
438
- };
431
+ this.emit('client', this.client);
439
432
 
440
433
  accountInfo.State = true;
441
434
  accountInfo.Info = 'Connect Success';