homebridge-melcloud-control 4.4.1-beta.2 → 4.4.1-beta.20
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/README.md +29 -29
- 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 +96 -107
- 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 +34 -31
package/src/functions.js
CHANGED
|
@@ -56,162 +56,152 @@ 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 { }
|
|
123
|
+
|
|
124
|
+
for (const path of qnapCandidates) {
|
|
113
125
|
try {
|
|
114
|
-
await
|
|
126
|
+
await access(path, fs.constants.X_OK);
|
|
127
|
+
result.path = path;
|
|
128
|
+
result.system = 'QNAP';
|
|
129
|
+
return result;
|
|
115
130
|
} catch { }
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ===================== Linux ARM ===================== */
|
|
136
|
+
if (isLinux && isARM) {
|
|
137
|
+
const armCandidates = ['/usr/bin/chromium-browser', '/usr/bin/chromium', '/snap/bin/chromium'];
|
|
138
|
+
|
|
139
|
+
for (const path of armCandidates) {
|
|
116
140
|
try {
|
|
117
|
-
await
|
|
141
|
+
await access(path, fs.constants.X_OK);
|
|
142
|
+
result.path = path;
|
|
143
|
+
result.system = 'Linux';
|
|
144
|
+
return result;
|
|
118
145
|
} catch { }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!isDocker) {
|
|
119
149
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
150
|
+
this.emit('debug', `Try install chromium`);
|
|
151
|
+
await execPromise('sudo apt update -y');
|
|
152
|
+
await execPromise('sudo apt install -y chromium chromium-browser chromium-codecs-ffmpeg || true');
|
|
153
|
+
await execPromise('sudo apt install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2 || true');
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.emit('debug', `Install chromium failed ${error}`);
|
|
156
|
+
}
|
|
122
157
|
}
|
|
123
158
|
|
|
124
|
-
|
|
125
|
-
for (const p of armCandidates) {
|
|
159
|
+
for (const path of armCandidates) {
|
|
126
160
|
try {
|
|
127
|
-
await access(
|
|
128
|
-
|
|
161
|
+
await access(path, fs.constants.X_OK);
|
|
162
|
+
result.path = path;
|
|
163
|
+
result.system = 'Linux';
|
|
164
|
+
return result;
|
|
129
165
|
} catch { }
|
|
130
166
|
}
|
|
131
|
-
|
|
132
|
-
return null;
|
|
167
|
+
return result;
|
|
133
168
|
}
|
|
134
169
|
|
|
135
|
-
|
|
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 { }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Synology DSM 7
|
|
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
|
|
170
|
+
/* ===================== Linux x64 ===================== */
|
|
163
171
|
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
|
-
|
|
172
|
+
const linuxCandidates = ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome', '/snap/bin/chromium', '/usr/local/bin/chromium'];
|
|
172
173
|
try {
|
|
173
|
-
const { stdout } = await execPromise(
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
const { stdout } = await execPromise('which chromium || which chromium-browser || which google-chrome || true');
|
|
175
|
+
if (stdout.trim()) {
|
|
176
|
+
result.path = stdout.trim();
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
176
179
|
} catch { }
|
|
177
180
|
|
|
178
|
-
for (const
|
|
181
|
+
for (const path of linuxCandidates) {
|
|
179
182
|
try {
|
|
180
|
-
await access(
|
|
181
|
-
|
|
183
|
+
await access(path, fs.constants.X_OK);
|
|
184
|
+
result.path = path;
|
|
185
|
+
result.system = 'Linux';
|
|
186
|
+
return result;
|
|
182
187
|
} catch { }
|
|
183
188
|
}
|
|
184
189
|
|
|
185
|
-
// Docker: try installing chromium inside container (if allowed)
|
|
186
190
|
if (isDocker) {
|
|
187
191
|
try {
|
|
188
|
-
await execPromise(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
193
|
-
} catch { }
|
|
194
|
-
}
|
|
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}`);
|
|
192
|
+
await execPromise('apt update -y && apt install -y chromium || true');
|
|
193
|
+
await access('/usr/bin/chromium', fs.constants.X_OK);
|
|
194
|
+
result.path = '/usr/bin/chromium';
|
|
195
|
+
result.system = 'Linux Docker';
|
|
196
|
+
return result;
|
|
205
197
|
} catch { }
|
|
206
198
|
}
|
|
207
|
-
|
|
208
|
-
return null;
|
|
209
199
|
}
|
|
210
200
|
|
|
211
|
-
return
|
|
201
|
+
return result;
|
|
212
202
|
} catch (err) {
|
|
213
|
-
if (this.logError) this.emit(
|
|
214
|
-
return null;
|
|
203
|
+
if (this.logError) this.emit('error', `Chromium detection error: ${err.message}`);
|
|
204
|
+
return { path: null, arch: 'unknown' };
|
|
215
205
|
}
|
|
216
206
|
}
|
|
217
207
|
|
|
@@ -280,7 +270,6 @@ class Functions extends EventEmitter {
|
|
|
280
270
|
|
|
281
271
|
return { min, max };
|
|
282
272
|
}
|
|
283
|
-
|
|
284
273
|
}
|
|
285
274
|
|
|
286
275
|
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,37 @@ 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 ${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
|
|
|
250
|
+
|
|
248
251
|
// Verify Chromium executable
|
|
249
252
|
try {
|
|
250
253
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
251
|
-
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
254
|
+
if (!this.logDebug) this.emit('debug', `Chromium for ${system} (${arch}) detected: ${stdout.trim()}`);
|
|
252
255
|
} catch (error) {
|
|
253
|
-
accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
256
|
+
accountInfo.Info = `Chromium for ${system} (${arch}) found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
254
257
|
return accountInfo;
|
|
255
258
|
}
|
|
256
259
|
|
|
@@ -282,22 +285,22 @@ class MelCloudHome extends EventEmitter {
|
|
|
282
285
|
await client.send('Network.enable')
|
|
283
286
|
client.on('Network.webSocketCreated', ({ url }) => {
|
|
284
287
|
try {
|
|
285
|
-
if (url.startsWith(`${
|
|
288
|
+
if (url.startsWith(`${ApiUrls.Home.WebSocket}`)) {
|
|
286
289
|
const params = new URL(url).searchParams;
|
|
287
290
|
const hash = params.get('hash');
|
|
288
291
|
if (this.logDebug) this.emit('debug', `Web socket hash detected: ${hash}`);
|
|
289
292
|
|
|
290
|
-
//
|
|
293
|
+
// Web socket connection
|
|
291
294
|
if (!this.connecting && !this.socketConnected) {
|
|
292
295
|
this.connecting = true;
|
|
293
296
|
|
|
294
297
|
try {
|
|
295
298
|
const headers = {
|
|
296
|
-
'Origin':
|
|
299
|
+
'Origin': ApiUrls.Home.Base,
|
|
297
300
|
'Pragma': 'no-cache',
|
|
298
301
|
'Cache-Control': 'no-cache'
|
|
299
302
|
};
|
|
300
|
-
const webSocket = new WebSocket(`${
|
|
303
|
+
const webSocket = new WebSocket(`${ApiUrls.Home.WebSocket}${hash}`, { headers: headers })
|
|
301
304
|
.on('error', (error) => {
|
|
302
305
|
if (this.logError) this.emit('error', `Web socket error: ${error}`);
|
|
303
306
|
try {
|
|
@@ -326,7 +329,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
326
329
|
})
|
|
327
330
|
.on('message', (message) => {
|
|
328
331
|
const parsedMessage = JSON.parse(message);
|
|
329
|
-
if (this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
332
|
+
if (!this.logDebug) this.emit('debug', `Incoming message: ${JSON.stringify(parsedMessage, null, 2)}`);
|
|
330
333
|
if (parsedMessage.message === 'Forbidden') return;
|
|
331
334
|
|
|
332
335
|
this.emit('webSocket', parsedMessage);
|
|
@@ -343,9 +346,9 @@ class MelCloudHome extends EventEmitter {
|
|
|
343
346
|
});
|
|
344
347
|
|
|
345
348
|
try {
|
|
346
|
-
await page.goto(
|
|
349
|
+
await page.goto(ApiUrls.Home.Base, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
347
350
|
} catch (error) {
|
|
348
|
-
accountInfo.Info = `Navigation to ${
|
|
351
|
+
accountInfo.Info = `Navigation to ${ApiUrls.Home.Base} failed: ${error.message}`;
|
|
349
352
|
return accountInfo;
|
|
350
353
|
}
|
|
351
354
|
|
|
@@ -407,7 +410,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
407
410
|
'Accept-Language': LanguageLocaleMap[this.language],
|
|
408
411
|
'Cookie': cookies,
|
|
409
412
|
'Priority': 'u=3, i',
|
|
410
|
-
'Referer':
|
|
413
|
+
'Referer': ApiUrls.Home.Dashboard,
|
|
411
414
|
'Sec-Fetch-Dest': 'empty',
|
|
412
415
|
'Sec-Fetch-Mode': 'cors',
|
|
413
416
|
'Sec-Fetch-Site': 'same-origin',
|
|
@@ -416,7 +419,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
416
419
|
};
|
|
417
420
|
|
|
418
421
|
this.client = axios.create({
|
|
419
|
-
baseURL:
|
|
422
|
+
baseURL: ApiUrls.Home.Base,
|
|
420
423
|
timeout: 30000,
|
|
421
424
|
headers: headers
|
|
422
425
|
})
|