homebridge-melcloud-control 4.4.1-beta.2 → 4.4.1-beta.21
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 +9 -0
- package/README.md +34 -34
- package/config.schema.json +54 -54
- package/index.js +32 -32
- package/package.json +2 -2
- package/src/constants.js +62 -50
- package/src/deviceata.js +2 -6
- package/src/deviceatw.js +2 -6
- package/src/deviceerv.js +2 -6
- package/src/functions.js +100 -108
- package/src/melcloud.js +4 -4
- package/src/melcloudata.js +9 -9
- package/src/melcloudatw.js +8 -8
- package/src/melclouderv.js +7 -7
- package/src/melcloudhome.js +33 -31
package/src/functions.js
CHANGED
|
@@ -56,162 +56,155 @@ class Functions extends EventEmitter {
|
|
|
56
56
|
|
|
57
57
|
async ensureChromiumInstalled() {
|
|
58
58
|
try {
|
|
59
|
-
|
|
60
|
-
const { stdout: osOut } = await execPromise("uname -s");
|
|
59
|
+
const { stdout: osOut } = await execPromise('uname -s');
|
|
61
60
|
const osName = osOut.trim();
|
|
62
|
-
const { stdout: archOut } = await execPromise("uname -m");
|
|
63
|
-
const arch = archOut.trim();
|
|
64
61
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const { stdout: archOut } = await execPromise('uname -m');
|
|
63
|
+
let arch = archOut.trim() || 'unknown';
|
|
64
|
+
|
|
65
|
+
// Normalizacja architektury
|
|
66
|
+
if (arch.startsWith('arm') || arch.startsWith('aarch')) arch = 'arm';
|
|
67
|
+
else if (arch.includes('64')) arch = 'x64';
|
|
68
|
+
else arch = 'x86';
|
|
69
|
+
|
|
70
|
+
const isARM = arch === 'arm';
|
|
71
|
+
const isMac = osName === 'Darwin';
|
|
72
|
+
const isLinux = osName === 'Linux';
|
|
73
|
+
const isQnap = fs.existsSync('/etc/config/uLinux.conf') || fs.existsSync('/etc/config/qpkg.conf');
|
|
68
74
|
|
|
69
75
|
// Detect Docker
|
|
70
76
|
let isDocker = false;
|
|
71
77
|
try {
|
|
72
|
-
await access(
|
|
78
|
+
await access('/.dockerenv');
|
|
73
79
|
isDocker = true;
|
|
74
80
|
} catch { }
|
|
75
81
|
try {
|
|
76
|
-
const { stdout } = await execPromise(
|
|
77
|
-
if (stdout.includes(
|
|
82
|
+
const { stdout } = await execPromise('cat /proc/1/cgroup || true');
|
|
83
|
+
if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
|
|
78
84
|
} catch { }
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
const result = { path: null, arch, system: null };
|
|
87
|
+
|
|
88
|
+
/* ===================== macOS ===================== */
|
|
81
89
|
if (isMac) {
|
|
82
90
|
const macCandidates = [
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
92
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium'
|
|
85
93
|
];
|
|
86
|
-
for (const
|
|
94
|
+
for (const path of macCandidates) {
|
|
87
95
|
try {
|
|
88
|
-
await access(
|
|
89
|
-
|
|
96
|
+
await access(path, fs.constants.X_OK);
|
|
97
|
+
result.path = path;
|
|
98
|
+
result.system = 'macOS';
|
|
99
|
+
return result;
|
|
90
100
|
} catch { }
|
|
91
101
|
}
|
|
92
|
-
return
|
|
102
|
+
return result;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
"/usr/bin/chromium",
|
|
100
|
-
"/snap/bin/chromium"
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
// Try existing
|
|
104
|
-
for (const p of armCandidates) {
|
|
105
|
+
/* ===================== QNAP ===================== */
|
|
106
|
+
if (isQnap) {
|
|
107
|
+
const qnapCandidates = ['/opt/bin/chromium', '/opt/bin/chromium-browser'];
|
|
108
|
+
for (const path of qnapCandidates) {
|
|
105
109
|
try {
|
|
106
|
-
await access(
|
|
107
|
-
|
|
110
|
+
await access(path, fs.constants.X_OK);
|
|
111
|
+
result.path = path;
|
|
112
|
+
result.system = 'Qnap';
|
|
113
|
+
return result;
|
|
108
114
|
} catch { }
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
try {
|
|
118
|
+
await access('/opt/bin/opkg', fs.constants.X_OK);
|
|
119
|
+
await execPromise('/opt/bin/opkg update');
|
|
120
|
+
await execPromise('opkg install chromium nspr nss libx11 libxcomposite libxdamage libxrandr atk libcups libdrm libgbm alsa-lib');
|
|
121
|
+
process.env.LD_LIBRARY_PATH = `/opt/lib:${process.env.LD_LIBRARY_PATH || ''}`;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (this.logError) this.emit('error', `Install package for Qnap error: ${error}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const path of qnapCandidates) {
|
|
113
127
|
try {
|
|
114
|
-
await
|
|
128
|
+
await access(path, fs.constants.X_OK);
|
|
129
|
+
result.path = path;
|
|
130
|
+
result.system = 'QNAP';
|
|
131
|
+
return result;
|
|
115
132
|
} catch { }
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ===================== Linux ARM ===================== */
|
|
138
|
+
if (isLinux && isARM) {
|
|
139
|
+
const armCandidates = ['/usr/bin/chromium-browser', '/usr/bin/chromium', '/snap/bin/chromium'];
|
|
140
|
+
|
|
141
|
+
for (const path of armCandidates) {
|
|
116
142
|
try {
|
|
117
|
-
await
|
|
143
|
+
await access(path, fs.constants.X_OK);
|
|
144
|
+
result.path = path;
|
|
145
|
+
result.system = 'Linux';
|
|
146
|
+
return result;
|
|
118
147
|
} catch { }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!isDocker) {
|
|
119
151
|
try {
|
|
120
|
-
await execPromise(
|
|
121
|
-
|
|
152
|
+
await execPromise('sudo apt update -y');
|
|
153
|
+
await execPromise('sudo apt install -y chromium chromium-browser chromium-codecs-ffmpeg');
|
|
154
|
+
await execPromise('sudo apt install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2');
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (this.logError) this.emit('error', `Install package for Linux ARM error: ${error}`);
|
|
157
|
+
}
|
|
122
158
|
}
|
|
123
159
|
|
|
124
|
-
|
|
125
|
-
for (const p of armCandidates) {
|
|
160
|
+
for (const path of armCandidates) {
|
|
126
161
|
try {
|
|
127
|
-
await access(
|
|
128
|
-
|
|
162
|
+
await access(path, fs.constants.X_OK);
|
|
163
|
+
result.path = path;
|
|
164
|
+
result.system = 'Linux';
|
|
165
|
+
return result;
|
|
129
166
|
} catch { }
|
|
130
167
|
}
|
|
131
|
-
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// QNAP / Entware
|
|
136
|
-
let entwareExists = false;
|
|
137
|
-
try {
|
|
138
|
-
await access("/opt/bin/opkg", fs.constants.X_OK);
|
|
139
|
-
entwareExists = true;
|
|
140
|
-
} catch { }
|
|
141
|
-
|
|
142
|
-
if (entwareExists) {
|
|
143
|
-
try {
|
|
144
|
-
await execPromise("/opt/bin/opkg update");
|
|
145
|
-
await execPromise("/opt/bin/opkg install nspr nss libx11 libxcomposite libxdamage libxrandr atk libcups libdrm libgbm alsa-lib");
|
|
146
|
-
process.env.LD_LIBRARY_PATH = `/opt/lib:${process.env.LD_LIBRARY_PATH || ""}`;
|
|
147
|
-
} catch { }
|
|
168
|
+
return result;
|
|
148
169
|
}
|
|
149
170
|
|
|
150
|
-
|
|
151
|
-
const synoCandidates = [
|
|
152
|
-
"/var/packages/Chromium/target/usr/bin/chromium",
|
|
153
|
-
"/usr/local/chromium/bin/chromium"
|
|
154
|
-
];
|
|
155
|
-
for (const p of synoCandidates) {
|
|
156
|
-
try {
|
|
157
|
-
await access(p, fs.constants.X_OK);
|
|
158
|
-
return p;
|
|
159
|
-
} catch { }
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Linux x64
|
|
171
|
+
/* ===================== Linux x64 ===================== */
|
|
163
172
|
if (isLinux) {
|
|
164
|
-
const linuxCandidates = [
|
|
165
|
-
"/usr/bin/chromium",
|
|
166
|
-
"/usr/bin/chromium-browser",
|
|
167
|
-
"/usr/bin/google-chrome",
|
|
168
|
-
"/snap/bin/chromium",
|
|
169
|
-
"/usr/local/bin/chromium"
|
|
170
|
-
];
|
|
171
|
-
|
|
173
|
+
const linuxCandidates = ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome', '/snap/bin/chromium', '/usr/local/bin/chromium'];
|
|
172
174
|
try {
|
|
173
|
-
const { stdout } = await execPromise(
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
const { stdout } = await execPromise('which chromium || which chromium-browser || which google-chrome');
|
|
176
|
+
if (stdout.trim()) {
|
|
177
|
+
result.path = stdout.trim();
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
176
180
|
} catch { }
|
|
177
181
|
|
|
178
|
-
for (const
|
|
182
|
+
for (const path of linuxCandidates) {
|
|
179
183
|
try {
|
|
180
|
-
await access(
|
|
181
|
-
|
|
184
|
+
await access(path, fs.constants.X_OK);
|
|
185
|
+
result.path = path;
|
|
186
|
+
result.system = 'Linux';
|
|
187
|
+
return result;
|
|
182
188
|
} catch { }
|
|
183
189
|
}
|
|
184
190
|
|
|
185
|
-
// Docker: try installing chromium inside container (if allowed)
|
|
186
191
|
if (isDocker) {
|
|
187
192
|
try {
|
|
188
|
-
await execPromise(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
193
|
-
} catch {
|
|
193
|
+
await execPromise('apt update -y && apt install -y chromium');
|
|
194
|
+
await access('/usr/bin/chromium', fs.constants.X_OK);
|
|
195
|
+
result.path = '/usr/bin/chromium';
|
|
196
|
+
result.system = 'Linux Docker';
|
|
197
|
+
return result;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (this.logError) this.emit('error', `Install package for Linux Docker error: ${error}`);
|
|
200
|
+
}
|
|
194
201
|
}
|
|
195
|
-
|
|
196
|
-
// Install missing libraries
|
|
197
|
-
const depCommands = [
|
|
198
|
-
"apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2 || true",
|
|
199
|
-
"yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib || true",
|
|
200
|
-
"apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib || true"
|
|
201
|
-
];
|
|
202
|
-
for (const cmd of depCommands) {
|
|
203
|
-
try {
|
|
204
|
-
await execPromise(`sudo ${cmd}`);
|
|
205
|
-
} catch { }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return null;
|
|
209
202
|
}
|
|
210
203
|
|
|
211
|
-
return
|
|
204
|
+
return result;
|
|
212
205
|
} catch (err) {
|
|
213
|
-
if (this.logError) this.emit(
|
|
214
|
-
return null;
|
|
206
|
+
if (this.logError) this.emit('error', `Chromium detection error: ${err.message}`);
|
|
207
|
+
return { path: null, arch: 'unknown' };
|
|
215
208
|
}
|
|
216
209
|
}
|
|
217
210
|
|
|
@@ -280,7 +273,6 @@ class Functions extends EventEmitter {
|
|
|
280
273
|
|
|
281
274
|
return { min, max };
|
|
282
275
|
}
|
|
283
|
-
|
|
284
276
|
}
|
|
285
277
|
|
|
286
278
|
export default Functions
|
package/src/melcloud.js
CHANGED
|
@@ -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.client(ApiUrls.ListDevices, { method: 'GET', });
|
|
59
|
+
const listDevicesData = await this.client(ApiUrls.Get.ListDevices, { method: 'GET', });
|
|
60
60
|
|
|
61
61
|
if (!listDevicesData || !listDevicesData.data) {
|
|
62
62
|
devicesList.Info = 'Invalid or empty response from MELCloud API'
|
|
@@ -132,9 +132,9 @@ class MelCloud extends EventEmitter {
|
|
|
132
132
|
CaptchaResponse: '',
|
|
133
133
|
Persist: true
|
|
134
134
|
};
|
|
135
|
-
const accountData = await axios(ApiUrls.ClientLogin, {
|
|
135
|
+
const accountData = await axios(ApiUrls.Post.ClientLogin, {
|
|
136
136
|
method: 'POST',
|
|
137
|
-
baseURL: ApiUrls.
|
|
137
|
+
baseURL: ApiUrls.Base,
|
|
138
138
|
timeout: 15000,
|
|
139
139
|
data: payload
|
|
140
140
|
});
|
|
@@ -164,7 +164,7 @@ class MelCloud extends EventEmitter {
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
this.client = axios.create({
|
|
167
|
-
baseURL: ApiUrls.
|
|
167
|
+
baseURL: ApiUrls.Base,
|
|
168
168
|
timeout: 30000,
|
|
169
169
|
headers: headers
|
|
170
170
|
});
|
package/src/melcloudata.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import Functions from './functions.js';
|
|
3
|
-
import { ApiUrls,
|
|
3
|
+
import { ApiUrls, AirConditioner } from './constants.js';
|
|
4
4
|
|
|
5
5
|
class MelCloudAta extends EventEmitter {
|
|
6
6
|
constructor(account, device, defaultTempsFile, accountFile, melcloud) {
|
|
@@ -204,7 +204,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
204
204
|
case 'account':
|
|
205
205
|
flagData.Account.LoginData.UseFahrenheit = flagData.UseFahrenheit;
|
|
206
206
|
payload = { data: flagData.LoginData };
|
|
207
|
-
path = ApiUrls.UpdateApplicationOptions;
|
|
207
|
+
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
208
208
|
await this.functions.saveData(this.accountFile, flagData);
|
|
209
209
|
break;
|
|
210
210
|
default:
|
|
@@ -230,7 +230,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
230
230
|
HideDryModeControl: deviceData.HideDryModeControl,
|
|
231
231
|
HasPendingCommand: true
|
|
232
232
|
};
|
|
233
|
-
path = ApiUrls.
|
|
233
|
+
path = ApiUrls.Post.Ata;
|
|
234
234
|
update = true;
|
|
235
235
|
break;
|
|
236
236
|
}
|
|
@@ -254,7 +254,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
254
254
|
units: { ATA: [deviceData.DeviceID] }
|
|
255
255
|
};
|
|
256
256
|
method = 'POST';
|
|
257
|
-
path =
|
|
257
|
+
path = ApiUrls.Home.Post.ProtectionFrost;
|
|
258
258
|
update = true;
|
|
259
259
|
break;
|
|
260
260
|
case 'overheatprotection':
|
|
@@ -265,7 +265,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
265
265
|
units: { ATA: [deviceData.DeviceID] }
|
|
266
266
|
};
|
|
267
267
|
method = 'POST';
|
|
268
|
-
path =
|
|
268
|
+
path = ApiUrls.Home.Post.ProtectionOverheat;
|
|
269
269
|
update = true;
|
|
270
270
|
break;
|
|
271
271
|
case 'holidaymode':
|
|
@@ -276,17 +276,17 @@ class MelCloudAta extends EventEmitter {
|
|
|
276
276
|
units: { ATA: [deviceData.DeviceID] }
|
|
277
277
|
};
|
|
278
278
|
method = 'POST';
|
|
279
|
-
path =
|
|
279
|
+
path = ApiUrls.Home.Post.HolidayMode;
|
|
280
280
|
break;
|
|
281
281
|
case 'schedule':
|
|
282
282
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
283
283
|
method = 'PUT';
|
|
284
|
-
path =
|
|
284
|
+
path = ApiUrls.Home.Put.ScheduleEnableDisable.Home.replace('deviceid', deviceData.DeviceID);
|
|
285
285
|
update = true;
|
|
286
286
|
break;
|
|
287
287
|
case 'scene':
|
|
288
288
|
method = 'PUT';
|
|
289
|
-
path =
|
|
289
|
+
path = `${ApiUrls.Home.Put.SceneEnableDisable.replace('sceneid', flagData.Id)}${flagData.Enabled ? 'enable' : 'disable'}`;
|
|
290
290
|
break;
|
|
291
291
|
default:
|
|
292
292
|
if (displayType === 1 && deviceData.Device.OperationMode === 8) {
|
|
@@ -312,7 +312,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
312
312
|
inStandbyMode: null
|
|
313
313
|
};
|
|
314
314
|
method = 'PUT';
|
|
315
|
-
path =
|
|
315
|
+
path = ApiUrls.Home.Put.Ata.replace('deviceid', deviceData.DeviceID);
|
|
316
316
|
break;
|
|
317
317
|
}
|
|
318
318
|
|
package/src/melcloudatw.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import Functions from './functions.js';
|
|
3
|
-
import { ApiUrls,
|
|
3
|
+
import { ApiUrls, HeatPump } from './constants.js';
|
|
4
4
|
|
|
5
5
|
class MelCloudAtw extends EventEmitter {
|
|
6
6
|
constructor(account, device, defaultTempsFile, accountFile, melcloud) {
|
|
@@ -204,7 +204,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
204
204
|
case 'account':
|
|
205
205
|
flagData.Account.LoginData.UseFahrenheit = flagData.UseFahrenheit;
|
|
206
206
|
payload = { data: flagData.LoginData };
|
|
207
|
-
path = ApiUrls.UpdateApplicationOptions;
|
|
207
|
+
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
208
208
|
await this.functions.saveData(this.accountFile, flagData);
|
|
209
209
|
break;
|
|
210
210
|
default:
|
|
@@ -231,7 +231,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
231
231
|
ProhibitHotWater: deviceData.Device.ProhibitHotWater,
|
|
232
232
|
HasPendingCommand: true
|
|
233
233
|
}
|
|
234
|
-
path = ApiUrls.
|
|
234
|
+
path = ApiUrls.Post.Atw;
|
|
235
235
|
update = true;
|
|
236
236
|
break;
|
|
237
237
|
}
|
|
@@ -255,7 +255,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
255
255
|
units: { ATA: [deviceData.DeviceID] }
|
|
256
256
|
};
|
|
257
257
|
method = 'POST';
|
|
258
|
-
path =
|
|
258
|
+
path = ApiUrls.Home.Post.ProtectionFrost;
|
|
259
259
|
update = true;
|
|
260
260
|
break;
|
|
261
261
|
case 'holidaymode':
|
|
@@ -266,17 +266,17 @@ class MelCloudAtw extends EventEmitter {
|
|
|
266
266
|
units: { ATW: [deviceData.DeviceID] }
|
|
267
267
|
};
|
|
268
268
|
method = 'POST';
|
|
269
|
-
path =
|
|
269
|
+
path = ApiUrls.Home.Post.HolidayMode;
|
|
270
270
|
break;
|
|
271
271
|
case 'schedule':
|
|
272
272
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
273
273
|
method = 'PUT';
|
|
274
|
-
path =
|
|
274
|
+
path = ApiUrls.Home.Put.ScheduleEnableDisable.Home.replace('deviceid', deviceData.DeviceID);
|
|
275
275
|
update = true;
|
|
276
276
|
break;
|
|
277
277
|
case 'scene':
|
|
278
278
|
method = 'PUT';
|
|
279
|
-
path =
|
|
279
|
+
path = `${ApiUrls.Home.Put.SceneEnableDisable.replace('sceneid', flagData.Id)}${flagData.Enabled ? 'enable' : 'disable'}`;
|
|
280
280
|
break;
|
|
281
281
|
default:
|
|
282
282
|
payload = {
|
|
@@ -295,7 +295,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
295
295
|
ecoHotWater: deviceData.Device.EcoHotWater,
|
|
296
296
|
};
|
|
297
297
|
method = 'PUT';
|
|
298
|
-
path =
|
|
298
|
+
path = ApiUrls.Home.Put.Atw.replace('deviceid', deviceData.DeviceID);
|
|
299
299
|
break
|
|
300
300
|
}
|
|
301
301
|
|
package/src/melclouderv.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import Functions from './functions.js';
|
|
3
|
-
import { ApiUrls,
|
|
3
|
+
import { ApiUrls, Ventilation } from './constants.js';
|
|
4
4
|
|
|
5
5
|
class MelCloudErv extends EventEmitter {
|
|
6
6
|
constructor(account, device, defaultTempsFile, accountFile, melcloud) {
|
|
@@ -189,7 +189,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
189
189
|
case 'account':
|
|
190
190
|
flagData.Account.LoginData.UseFahrenheit = flagData.UseFahrenheit;
|
|
191
191
|
payload = { data: flagData.LoginData };
|
|
192
|
-
path = ApiUrls.UpdateApplicationOptions;
|
|
192
|
+
path = ApiUrls.Post.UpdateApplicationOptions;
|
|
193
193
|
await this.functions.saveData(this.accountFile, flagData);
|
|
194
194
|
break;
|
|
195
195
|
default:
|
|
@@ -231,7 +231,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
231
231
|
NightPurgeMode: deviceData.Device.NightPurgeMode,
|
|
232
232
|
HasPendingCommand: true
|
|
233
233
|
}
|
|
234
|
-
path = ApiUrls.
|
|
234
|
+
path = ApiUrls.Post.Erv;
|
|
235
235
|
update = true;
|
|
236
236
|
break;
|
|
237
237
|
}
|
|
@@ -255,17 +255,17 @@ class MelCloudErv extends EventEmitter {
|
|
|
255
255
|
units: { ERV: [deviceData.DeviceID] }
|
|
256
256
|
};
|
|
257
257
|
method = 'POST';
|
|
258
|
-
path =
|
|
258
|
+
path = ApiUrls.Home.Post.HolidayMode;
|
|
259
259
|
break;
|
|
260
260
|
case 'schedule':
|
|
261
261
|
payload = { enabled: deviceData.ScheduleEnabled };
|
|
262
262
|
method = 'PUT';
|
|
263
|
-
path =
|
|
263
|
+
path = ApiUrls.Home.Put.ScheduleEnableDisable.Home.replace('deviceid', deviceData.DeviceID);
|
|
264
264
|
update = true;
|
|
265
265
|
break;
|
|
266
266
|
case 'scene':
|
|
267
267
|
method = 'PUT';
|
|
268
|
-
path =
|
|
268
|
+
path = `${ApiUrls.Home.Put.SceneEnableDisable.replace('sceneid', flagData.Id)}${flagData.Enabled ? 'enable' : 'disable'}`;
|
|
269
269
|
break;
|
|
270
270
|
default:
|
|
271
271
|
if (displayType === 1 && deviceData.Device.VentilationMode === 2) {
|
|
@@ -288,7 +288,7 @@ class MelCloudErv extends EventEmitter {
|
|
|
288
288
|
ventilationMode: Ventilation.VentilationModeMapEnumToString[deviceData.Device.VentilationMode],
|
|
289
289
|
};
|
|
290
290
|
method = 'PUT';
|
|
291
|
-
path =
|
|
291
|
+
path = ApiUrls.Home.Put.Erv.replace('deviceid', deviceData.DeviceID);
|
|
292
292
|
break
|
|
293
293
|
}
|
|
294
294
|
|
package/src/melcloudhome.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import axios from 'axios';
|
|
3
2
|
import WebSocket from 'ws';
|
|
4
3
|
import { exec } from 'child_process';
|
|
@@ -7,7 +6,7 @@ import EventEmitter from 'events';
|
|
|
7
6
|
import puppeteer from 'puppeteer';
|
|
8
7
|
import ImpulseGenerator from './impulsegenerator.js';
|
|
9
8
|
import Functions from './functions.js';
|
|
10
|
-
import {
|
|
9
|
+
import { ApiUrls, LanguageLocaleMap } from './constants.js';
|
|
11
10
|
const execPromise = promisify(exec);
|
|
12
11
|
|
|
13
12
|
class MelCloudHome extends EventEmitter {
|
|
@@ -17,7 +16,6 @@ class MelCloudHome extends EventEmitter {
|
|
|
17
16
|
this.user = account.user;
|
|
18
17
|
this.passwd = account.passwd;
|
|
19
18
|
this.language = account.language;
|
|
20
|
-
this.logSuccess = account.log?.success;
|
|
21
19
|
this.logWarn = account.log?.warn;
|
|
22
20
|
this.logError = account.log?.error;
|
|
23
21
|
this.logDebug = account.log?.debug;
|
|
@@ -79,7 +77,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
79
77
|
async checkScenesList() {
|
|
80
78
|
try {
|
|
81
79
|
if (this.logDebug) this.emit('debug', `Scanning for scenes`);
|
|
82
|
-
const listScenesData = await this.client(
|
|
80
|
+
const listScenesData = await this.client(ApiUrls.Home.Get.Scenes, { method: 'GET', });
|
|
83
81
|
|
|
84
82
|
const scenesList = listScenesData.data;
|
|
85
83
|
if (this.logDebug) this.emit('debug', `Scenes: ${JSON.stringify(scenesList, null, 2)}`);
|
|
@@ -111,7 +109,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
111
109
|
try {
|
|
112
110
|
const devicesList = { State: false, Info: null, Devices: [], Scenes: [] }
|
|
113
111
|
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
114
|
-
const listDevicesData = await this.client(
|
|
112
|
+
const listDevicesData = await this.client(ApiUrls.Home.Get.ListDevices, { method: 'GET' });
|
|
115
113
|
|
|
116
114
|
const userContext = listDevicesData.data;
|
|
117
115
|
const buildings = userContext.buildings ?? [];
|
|
@@ -225,32 +223,36 @@ class MelCloudHome extends EventEmitter {
|
|
|
225
223
|
try {
|
|
226
224
|
const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false };
|
|
227
225
|
|
|
228
|
-
// Get Chromium path
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
226
|
+
// Get Chromium path from resolver
|
|
227
|
+
const chromiumInfo = await this.functions.ensureChromiumInstalled();
|
|
228
|
+
let chromiumPath = chromiumInfo.path;
|
|
229
|
+
const arch = chromiumInfo.arch;
|
|
230
|
+
const system = chromiumInfo.system;
|
|
231
|
+
|
|
232
|
+
// If path is found, use it
|
|
233
|
+
if (chromiumPath) {
|
|
234
|
+
if (!this.logDebug) this.emit('debug', `Using Chromium for ${system} (${arch}) at ${chromiumPath}`);
|
|
235
|
+
} else {
|
|
236
|
+
if (arch === 'arm') {
|
|
237
|
+
accountInfo.Info = `No Chromium found for ${system} (${arch}). Please install it manually and try again.`;
|
|
238
|
+
return accountInfo;
|
|
239
|
+
} else {
|
|
240
|
+
try {
|
|
241
|
+
chromiumPath = puppeteer.executablePath();
|
|
242
|
+
if (!this.logDebug) this.emit('debug', `Using Puppeteer Chromium for ${system} (${arch}) at ${chromiumPath}`);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
accountInfo.Info = `No Chromium available for ${system} (${arch})`;
|
|
240
245
|
return accountInfo;
|
|
241
246
|
}
|
|
242
|
-
} catch (error) {
|
|
243
|
-
accountInfo.Info = `Failed to get Puppeteer Chromium path: ${error.message}`;
|
|
244
|
-
return accountInfo;
|
|
245
247
|
}
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
// Verify Chromium executable
|
|
249
251
|
try {
|
|
250
252
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
251
|
-
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
253
|
+
if (!this.logDebug) this.emit('debug', `Chromium for ${system} (${arch}) detected: ${stdout.trim()}`);
|
|
252
254
|
} catch (error) {
|
|
253
|
-
accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
255
|
+
accountInfo.Info = `Chromium for ${system} (${arch}) found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
254
256
|
return accountInfo;
|
|
255
257
|
}
|
|
256
258
|
|
|
@@ -282,22 +284,22 @@ class MelCloudHome extends EventEmitter {
|
|
|
282
284
|
await client.send('Network.enable')
|
|
283
285
|
client.on('Network.webSocketCreated', ({ url }) => {
|
|
284
286
|
try {
|
|
285
|
-
if (url.startsWith(`${
|
|
287
|
+
if (url.startsWith(`${ApiUrls.Home.WebSocket}`)) {
|
|
286
288
|
const params = new URL(url).searchParams;
|
|
287
289
|
const hash = params.get('hash');
|
|
288
290
|
if (this.logDebug) this.emit('debug', `Web socket hash detected: ${hash}`);
|
|
289
291
|
|
|
290
|
-
//
|
|
292
|
+
// Web socket connection
|
|
291
293
|
if (!this.connecting && !this.socketConnected) {
|
|
292
294
|
this.connecting = true;
|
|
293
295
|
|
|
294
296
|
try {
|
|
295
297
|
const headers = {
|
|
296
|
-
'Origin':
|
|
298
|
+
'Origin': ApiUrls.Home.Base,
|
|
297
299
|
'Pragma': 'no-cache',
|
|
298
300
|
'Cache-Control': 'no-cache'
|
|
299
301
|
};
|
|
300
|
-
const webSocket = new WebSocket(`${
|
|
302
|
+
const webSocket = new WebSocket(`${ApiUrls.Home.WebSocket}${hash}`, { headers: headers })
|
|
301
303
|
.on('error', (error) => {
|
|
302
304
|
if (this.logError) this.emit('error', `Web socket error: ${error}`);
|
|
303
305
|
try {
|
|
@@ -326,7 +328,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
326
328
|
})
|
|
327
329
|
.on('message', (message) => {
|
|
328
330
|
const parsedMessage = JSON.parse(message);
|
|
329
|
-
if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
331
|
+
if (!this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
330
332
|
if (parsedMessage.message === 'Forbidden') return;
|
|
331
333
|
|
|
332
334
|
this.emit('webSocket', parsedMessage);
|
|
@@ -343,9 +345,9 @@ class MelCloudHome extends EventEmitter {
|
|
|
343
345
|
});
|
|
344
346
|
|
|
345
347
|
try {
|
|
346
|
-
await page.goto(
|
|
348
|
+
await page.goto(ApiUrls.Home.Base, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
347
349
|
} catch (error) {
|
|
348
|
-
accountInfo.Info = `Navigation to ${
|
|
350
|
+
accountInfo.Info = `Navigation to ${ApiUrls.Home.Base} failed: ${error.message}`;
|
|
349
351
|
return accountInfo;
|
|
350
352
|
}
|
|
351
353
|
|
|
@@ -407,7 +409,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
407
409
|
'Accept-Language': LanguageLocaleMap[this.language],
|
|
408
410
|
'Cookie': cookies,
|
|
409
411
|
'Priority': 'u=3, i',
|
|
410
|
-
'Referer':
|
|
412
|
+
'Referer': ApiUrls.Home.Dashboard,
|
|
411
413
|
'Sec-Fetch-Dest': 'empty',
|
|
412
414
|
'Sec-Fetch-Mode': 'cors',
|
|
413
415
|
'Sec-Fetch-Site': 'same-origin',
|
|
@@ -416,7 +418,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
416
418
|
};
|
|
417
419
|
|
|
418
420
|
this.client = axios.create({
|
|
419
|
-
baseURL:
|
|
421
|
+
baseURL: ApiUrls.Home.Base,
|
|
420
422
|
timeout: 30000,
|
|
421
423
|
headers: headers
|
|
422
424
|
})
|