homebridge-melcloud-control 4.3.11-beta.8 → 4.3.11
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 +8 -0
- package/index.js +2 -2
- package/package.json +1 -1
- package/src/constants.js +2 -1
- package/src/deviceata.js +3 -3
- package/src/deviceatw.js +2 -2
- package/src/deviceerv.js +2 -2
- package/src/functions.js +16 -17
- package/src/melcloud.js +5 -6
- package/src/melcloudata.js +6 -31
- package/src/melcloudatw.js +5 -28
- package/src/melclouderv.js +5 -28
- package/src/melcloudhome.js +74 -80
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
|
|
109
|
-
if (logLevel.warn) log.warn(`${accountName}, ${accountInfo
|
|
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
|
|
4
|
+
"version": "4.3.11",
|
|
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: "
|
|
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/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
|
|
|
@@ -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[
|
|
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
|
}
|
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
|
}
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
71
|
+
// Docker detection
|
|
72
72
|
let isDocker = false;
|
|
73
73
|
try {
|
|
74
|
-
await access('/.dockerenv', fs.constants.F_OK);
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
135
|
-
const
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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';
|
package/src/melcloudata.js
CHANGED
|
@@ -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.
|
|
26
|
+
this.client = melcloud.client;
|
|
28
27
|
|
|
29
28
|
//handle melcloud events
|
|
30
29
|
let deviceData = null;
|
|
31
|
-
melcloud.on('
|
|
32
|
-
this.
|
|
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
|
-
//
|
|
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
|
}
|
package/src/melcloudatw.js
CHANGED
|
@@ -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.
|
|
26
|
+
this.client = melcloud.client;
|
|
28
27
|
|
|
29
28
|
//handle melcloud events
|
|
30
29
|
let deviceData = null;
|
|
31
|
-
melcloud.on('
|
|
32
|
-
this.
|
|
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
|
}
|
package/src/melclouderv.js
CHANGED
|
@@ -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.
|
|
26
|
+
this.client = melcloud.client;
|
|
28
27
|
|
|
29
28
|
//handle melcloud events
|
|
30
29
|
let deviceData = null;
|
|
31
|
-
melcloud.on('
|
|
32
|
-
this.
|
|
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
|
}
|
package/src/melcloudhome.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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;
|
|
@@ -274,19 +224,23 @@ class MelCloudHome extends EventEmitter {
|
|
|
274
224
|
let browser;
|
|
275
225
|
try {
|
|
276
226
|
const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false };
|
|
227
|
+
|
|
228
|
+
// Get Chromium path
|
|
277
229
|
let chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
278
230
|
|
|
279
231
|
// === Fallback to Puppeteer's built-in Chromium ===
|
|
280
232
|
if (!chromiumPath) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
+
}
|
|
290
244
|
}
|
|
291
245
|
|
|
292
246
|
if (!chromiumPath) {
|
|
@@ -303,6 +257,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
303
257
|
return accountInfo;
|
|
304
258
|
}
|
|
305
259
|
|
|
260
|
+
// Launch Chromium
|
|
306
261
|
if (this.logDebug) this.emit('debug', `Launching Chromium...`);
|
|
307
262
|
browser = await puppeteer.launch({
|
|
308
263
|
headless: true,
|
|
@@ -326,18 +281,67 @@ class MelCloudHome extends EventEmitter {
|
|
|
326
281
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
327
282
|
|
|
328
283
|
// === CDP session ===
|
|
329
|
-
let hash = null;
|
|
330
284
|
const client = await page.createCDPSession();
|
|
331
285
|
await client.send('Network.enable')
|
|
332
286
|
client.on('Network.webSocketCreated', ({ url }) => {
|
|
333
287
|
try {
|
|
334
|
-
if (url.startsWith(
|
|
288
|
+
if (url.startsWith(`${ApiUrlsHome.WebSocketURL}`)) {
|
|
335
289
|
const params = new URL(url).searchParams;
|
|
336
|
-
hash = params.get('hash');
|
|
290
|
+
const hash = params.get('hash');
|
|
337
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
|
+
}
|
|
338
342
|
}
|
|
339
|
-
} catch (
|
|
340
|
-
this.emit('error', `CDP WebSocketCreated handler error: ${
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (this.logError) this.emit('error', `CDP WebSocketCreated handler error: ${error.message}`);
|
|
341
345
|
}
|
|
342
346
|
});
|
|
343
347
|
|
|
@@ -414,23 +418,13 @@ class MelCloudHome extends EventEmitter {
|
|
|
414
418
|
'User-Agent': userAgent,
|
|
415
419
|
'x-csrf': '1'
|
|
416
420
|
};
|
|
417
|
-
this.emit('headers', headers);
|
|
418
421
|
|
|
419
|
-
this.
|
|
420
|
-
this.axiosInstance = axios.create({
|
|
422
|
+
this.client = axios.create({
|
|
421
423
|
baseURL: ApiUrlsHome.BaseURL,
|
|
422
424
|
timeout: 30000,
|
|
423
425
|
headers: headers
|
|
424
426
|
})
|
|
425
|
-
|
|
426
|
-
this.webSocketOptions = {
|
|
427
|
-
Hash: hash,
|
|
428
|
-
Headers: {
|
|
429
|
-
'Origin': ApiUrlsHome.BaseURL,
|
|
430
|
-
'Pragma': 'no-cache',
|
|
431
|
-
'Cache-Control': 'no-cache'
|
|
432
|
-
}
|
|
433
|
-
};
|
|
427
|
+
this.emit('client', this.client);
|
|
434
428
|
|
|
435
429
|
accountInfo.State = true;
|
|
436
430
|
accountInfo.Info = 'Connect Success';
|