homebridge-melcloud-control 4.3.11-beta.9 → 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/functions.js +13 -91
- 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 +60 -71
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/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
|
-
//
|
|
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
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
136
|
-
const
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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;
|
|
@@ -331,18 +281,67 @@ class MelCloudHome extends EventEmitter {
|
|
|
331
281
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
332
282
|
|
|
333
283
|
// === CDP session ===
|
|
334
|
-
let hash = null;
|
|
335
284
|
const client = await page.createCDPSession();
|
|
336
285
|
await client.send('Network.enable')
|
|
337
286
|
client.on('Network.webSocketCreated', ({ url }) => {
|
|
338
287
|
try {
|
|
339
|
-
if (url.startsWith(
|
|
288
|
+
if (url.startsWith(`${ApiUrlsHome.WebSocketURL}`)) {
|
|
340
289
|
const params = new URL(url).searchParams;
|
|
341
|
-
hash = params.get('hash');
|
|
290
|
+
const hash = params.get('hash');
|
|
342
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
|
+
}
|
|
343
342
|
}
|
|
344
|
-
} catch (
|
|
345
|
-
this.emit('error', `CDP WebSocketCreated handler error: ${
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (this.logError) this.emit('error', `CDP WebSocketCreated handler error: ${error.message}`);
|
|
346
345
|
}
|
|
347
346
|
});
|
|
348
347
|
|
|
@@ -419,23 +418,13 @@ class MelCloudHome extends EventEmitter {
|
|
|
419
418
|
'User-Agent': userAgent,
|
|
420
419
|
'x-csrf': '1'
|
|
421
420
|
};
|
|
422
|
-
this.emit('headers', headers);
|
|
423
421
|
|
|
424
|
-
this.
|
|
425
|
-
this.axiosInstance = axios.create({
|
|
422
|
+
this.client = axios.create({
|
|
426
423
|
baseURL: ApiUrlsHome.BaseURL,
|
|
427
424
|
timeout: 30000,
|
|
428
425
|
headers: headers
|
|
429
426
|
})
|
|
430
|
-
|
|
431
|
-
this.webSocketOptions = {
|
|
432
|
-
Hash: hash,
|
|
433
|
-
Headers: {
|
|
434
|
-
'Origin': ApiUrlsHome.BaseURL,
|
|
435
|
-
'Pragma': 'no-cache',
|
|
436
|
-
'Cache-Control': 'no-cache'
|
|
437
|
-
}
|
|
438
|
-
};
|
|
427
|
+
this.emit('client', this.client);
|
|
439
428
|
|
|
440
429
|
accountInfo.State = true;
|
|
441
430
|
accountInfo.Info = 'Connect Success';
|