homebridge-melcloud-control 4.4.1-beta.9 → 4.5.0
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 +11 -0
- package/README.md +26 -26
- package/homebridge-ui/public/index.html +147 -134
- package/index.js +20 -5
- package/package.json +2 -2
- package/src/constants.js +10 -10
- package/src/deviceata.js +24 -13
- package/src/deviceatw.js +5 -5
- package/src/deviceerv.js +60 -55
- package/src/functions.js +105 -111
- package/src/melcloud.js +6 -4
- package/src/melcloudata.js +32 -1
- package/src/melcloudatw.js +17 -1
- package/src/melclouderv.js +17 -1
- package/src/melcloudhome.js +31 -25
package/src/deviceata.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, AirConditioner } from './constants.js';
|
|
|
7
7
|
let Accessory, Characteristic, Service, Categories, AccessoryUUID;
|
|
8
8
|
|
|
9
9
|
class DeviceAta extends EventEmitter {
|
|
10
|
-
constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
10
|
+
constructor(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
11
11
|
super();
|
|
12
12
|
|
|
13
13
|
Accessory = api.platformAccessory;
|
|
@@ -44,10 +44,10 @@ class DeviceAta extends EventEmitter {
|
|
|
44
44
|
this.frostProtectionSupport = device.frostProtectionSupport || false;
|
|
45
45
|
this.overheatProtectionSupport = device.overheatProtectionSupport || false;
|
|
46
46
|
this.holidayModeSupport = device.holidayModeSupport || false;
|
|
47
|
-
this.presets =
|
|
48
|
-
this.schedules =
|
|
49
|
-
this.scenes =
|
|
50
|
-
this.buttons =
|
|
47
|
+
this.presets = presets;
|
|
48
|
+
this.schedules = schedules;
|
|
49
|
+
this.scenes = scenes;
|
|
50
|
+
this.buttons = buttons;
|
|
51
51
|
|
|
52
52
|
//files
|
|
53
53
|
this.defaultTempsFile = defaultTempsFile;
|
|
@@ -693,7 +693,7 @@ class DeviceAta extends EventEmitter {
|
|
|
693
693
|
}
|
|
694
694
|
|
|
695
695
|
//frost protection
|
|
696
|
-
if (this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
|
|
696
|
+
if (supportsHeat && this.frostProtectionSupport && this.accessory.frostProtection.Enabled !== null) {
|
|
697
697
|
//control
|
|
698
698
|
if (this.logDebug) this.emit('debug', `Prepare frost protection control service`);
|
|
699
699
|
const frostProtectionControlService = new Service.HeaterCooler(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`);
|
|
@@ -1610,6 +1610,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1610
1610
|
|
|
1611
1611
|
//characteristics array
|
|
1612
1612
|
const characteristics = [];
|
|
1613
|
+
const operationModevalidValues = [];
|
|
1613
1614
|
|
|
1614
1615
|
//operating mode 0, HEAT, DRY, COOL, 4, 5, 6, FAN, AUTO, ISEE HEAT, ISEE DRY, ISEE COOL
|
|
1615
1616
|
switch (this.displayType) {
|
|
@@ -1655,11 +1656,15 @@ class DeviceAta extends EventEmitter {
|
|
|
1655
1656
|
default:
|
|
1656
1657
|
if (this.logWarn) this.emit('warn', `Unknown operating mode: ${operationMode}`);
|
|
1657
1658
|
}
|
|
1658
|
-
|
|
1659
1659
|
obj.currentOperationMode = !power ? 0 : (inStandbyMode ? 1 : obj.currentOperationMode);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1660
|
+
|
|
1661
|
+
if (supportsAuto) operationModevalidValues.push(0);
|
|
1662
|
+
if (supportsHeat) operationModevalidValues.push(1);
|
|
1663
|
+
if (supportsCool) operationModevalidValues.push(2);
|
|
1664
|
+
|
|
1665
|
+
obj.operationModeSetPropsMinValue = operationModevalidValues[0];
|
|
1666
|
+
obj.operationModeSetPropsMaxValue = operationModevalidValues.at(-1);
|
|
1667
|
+
obj.operationModeSetPropsValidValues = operationModevalidValues;
|
|
1663
1668
|
|
|
1664
1669
|
//fan speed mode
|
|
1665
1670
|
if (supportsFanSpeed) {
|
|
@@ -1736,9 +1741,15 @@ class DeviceAta extends EventEmitter {
|
|
|
1736
1741
|
|
|
1737
1742
|
obj.currentOperationMode = !power ? 0 : obj.currentOperationMode;
|
|
1738
1743
|
obj.targetOperationMode = !power ? 0 : obj.targetOperationMode;
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1744
|
+
|
|
1745
|
+
operationModevalidValues.push(0);
|
|
1746
|
+
if (supportsHeat) operationModevalidValues.push(1);
|
|
1747
|
+
if (supportsCool) operationModevalidValues.push(2);
|
|
1748
|
+
if (supportsAuto) operationModevalidValues.push(3);
|
|
1749
|
+
|
|
1750
|
+
obj.operationModeSetPropsMinValue = operationModevalidValues[0];
|
|
1751
|
+
obj.operationModeSetPropsMaxValue = operationModevalidValues.at(-1);
|
|
1752
|
+
obj.operationModeSetPropsValidValues = operationModevalidValues;
|
|
1742
1753
|
|
|
1743
1754
|
//create characteristics
|
|
1744
1755
|
characteristics.push(
|
package/src/deviceatw.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, HeatPump } from './constants.js';
|
|
|
7
7
|
let Accessory, Characteristic, Service, Categories, AccessoryUUID;
|
|
8
8
|
|
|
9
9
|
class DeviceAtw extends EventEmitter {
|
|
10
|
-
constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
10
|
+
constructor(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
11
11
|
super();
|
|
12
12
|
|
|
13
13
|
Accessory = api.platformAccessory;
|
|
@@ -48,10 +48,10 @@ class DeviceAtw extends EventEmitter {
|
|
|
48
48
|
this.errorSensor = device.errorSensor || false;
|
|
49
49
|
this.frostProtectionSupport = device.frostProtectionSupport || false;
|
|
50
50
|
this.holidayModeSupport = device.holidayModeSupport || false;
|
|
51
|
-
this.presets =
|
|
52
|
-
this.schedules =
|
|
53
|
-
this.scenes =
|
|
54
|
-
this.buttons =
|
|
51
|
+
this.presets = presets;
|
|
52
|
+
this.schedules = schedules;
|
|
53
|
+
this.scenes = scenes;
|
|
54
|
+
this.buttons = buttons;
|
|
55
55
|
|
|
56
56
|
//files
|
|
57
57
|
this.defaultTempsFile = defaultTempsFile;
|
package/src/deviceerv.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, Ventilation } from './constants.js';
|
|
|
7
7
|
let Accessory, Characteristic, Service, Categories, AccessoryUUID;
|
|
8
8
|
|
|
9
9
|
class DeviceErv extends EventEmitter {
|
|
10
|
-
constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
10
|
+
constructor(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList) {
|
|
11
11
|
super();
|
|
12
12
|
|
|
13
13
|
Accessory = api.platformAccessory;
|
|
@@ -40,10 +40,10 @@ class DeviceErv extends EventEmitter {
|
|
|
40
40
|
this.connectSensor = device.connectSensor || false;
|
|
41
41
|
this.errorSensor = device.errorSensor || false;
|
|
42
42
|
this.holidayModeSupport = device.holidayModeSupport || false;
|
|
43
|
-
this.presets =
|
|
44
|
-
this.schedules =
|
|
45
|
-
this.scenes =
|
|
46
|
-
this.buttons =
|
|
43
|
+
this.presets = presets;
|
|
44
|
+
this.schedules = schedules;
|
|
45
|
+
this.scenes = scenes;
|
|
46
|
+
this.buttons = buttons;
|
|
47
47
|
|
|
48
48
|
//files
|
|
49
49
|
this.defaultTempsFile = defaultTempsFile;
|
|
@@ -1257,43 +1257,44 @@ class DeviceErv extends EventEmitter {
|
|
|
1257
1257
|
|
|
1258
1258
|
//characteristics array
|
|
1259
1259
|
const characteristics = [];
|
|
1260
|
+
const operationModevalidValues = [];
|
|
1260
1261
|
|
|
1261
1262
|
//ventilation mode - 0, HEAT, 2, COOL, 4, 5, 6, FAN, AUTO
|
|
1262
1263
|
switch (this.displayType) {
|
|
1263
1264
|
case 1: //Heater Cooler
|
|
1264
1265
|
switch (ventilationMode) {
|
|
1265
|
-
case 0: //LOSSNAY
|
|
1266
|
-
obj.currentOperationMode = 2; //
|
|
1267
|
-
obj.targetOperationMode = 1;
|
|
1266
|
+
case 0: // LOSSNAY
|
|
1267
|
+
obj.currentOperationMode = 2; // heating
|
|
1268
|
+
obj.targetOperationMode = 1; // heat
|
|
1268
1269
|
break;
|
|
1269
|
-
case 1: //BYPASS
|
|
1270
|
-
obj.currentOperationMode = 3;
|
|
1271
|
-
obj.targetOperationMode = 2;
|
|
1270
|
+
case 1: // BYPASS
|
|
1271
|
+
obj.currentOperationMode = 3; // cooling
|
|
1272
|
+
obj.targetOperationMode = 2; // cool
|
|
1272
1273
|
break;
|
|
1273
|
-
case 2: //AUTO
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
default:
|
|
1282
|
-
if (this.logWarn) this.emit('warn', `Unknown actual ventilation mode: ${actualVentilationMode}`);
|
|
1283
|
-
break;
|
|
1284
|
-
};
|
|
1285
|
-
obj.targetOperationMode = 0;
|
|
1274
|
+
case 2: // AUTO
|
|
1275
|
+
if (actualVentilationMode === 0) {
|
|
1276
|
+
obj.currentOperationMode = 2; // heating
|
|
1277
|
+
} else if (actualVentilationMode === 1) {
|
|
1278
|
+
obj.currentOperationMode = 3; // cooling
|
|
1279
|
+
} else if (this.logWarn) this.emit('warn', `Unknown actual ventilation mode: ${actualVentilationMode}`);
|
|
1280
|
+
|
|
1281
|
+
obj.targetOperationMode = 0; // auto
|
|
1286
1282
|
break;
|
|
1287
1283
|
default:
|
|
1288
1284
|
if (this.logWarn) this.emit('warn', `Unknown ventilation mode: ${ventilationMode}`);
|
|
1289
1285
|
break;
|
|
1290
|
-
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// power override
|
|
1289
|
+
if (!power) obj.currentOperationMode = 0; // inactive
|
|
1291
1290
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
obj.
|
|
1291
|
+
if (supportsAutoVentilationMode) operationModevalidValues.push(0);
|
|
1292
|
+
if (supportsBypassVentilationMode) operationModevalidValues.push(1);
|
|
1293
|
+
operationModevalidValues.push(2); // manual zawsze dostępny
|
|
1294
|
+
|
|
1295
|
+
obj.operationModeSetPropsMinValue = operationModevalidValues[0];
|
|
1296
|
+
obj.operationModeSetPropsMaxValue = operationModevalidValues.at(-1);
|
|
1297
|
+
obj.operationModeSetPropsValidValues = operationModevalidValues;
|
|
1297
1298
|
|
|
1298
1299
|
//fan speed mode
|
|
1299
1300
|
obj.fanSpeedSetPropsMaxValue = 2;
|
|
@@ -1330,38 +1331,42 @@ class DeviceErv extends EventEmitter {
|
|
|
1330
1331
|
case 2: //Thermostat
|
|
1331
1332
|
//operation mode - 0, HEAT, 2, COOL, 4, 5, 6, FAN, AUTO
|
|
1332
1333
|
switch (ventilationMode) {
|
|
1333
|
-
case 0: //LOSSNAY
|
|
1334
|
-
obj.currentOperationMode = 1; //
|
|
1335
|
-
obj.targetOperationMode = 1;
|
|
1334
|
+
case 0: // LOSSNAY
|
|
1335
|
+
obj.currentOperationMode = 1; // HEAT
|
|
1336
|
+
obj.targetOperationMode = 1; // HEAT
|
|
1336
1337
|
break;
|
|
1337
|
-
case 1: //BYPASS
|
|
1338
|
-
obj.currentOperationMode = 2;
|
|
1339
|
-
obj.targetOperationMode = 2;
|
|
1338
|
+
case 1: // BYPASS
|
|
1339
|
+
obj.currentOperationMode = 2; // COOL
|
|
1340
|
+
obj.targetOperationMode = 2; // COOL
|
|
1340
1341
|
break;
|
|
1341
|
-
case 2: //AUTO
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
default:
|
|
1350
|
-
if (this.logWarn) this.emit('warn', `Unknown actual ventilation mode: ${actualVentilationMode}`);
|
|
1351
|
-
break;
|
|
1352
|
-
};
|
|
1353
|
-
obj.targetOperationMode = 3;
|
|
1342
|
+
case 2: // AUTO
|
|
1343
|
+
if (actualVentilationMode === 0) {
|
|
1344
|
+
obj.currentOperationMode = 1; // HEAT
|
|
1345
|
+
} else if (actualVentilationMode === 1) {
|
|
1346
|
+
obj.currentOperationMode = 2; // COOL
|
|
1347
|
+
} else if (this.logWarn) this.emit('warn', `Unknown actual ventilation mode: ${actualVentilationMode}`);
|
|
1348
|
+
|
|
1349
|
+
obj.targetOperationMode = 3; // AUTO
|
|
1354
1350
|
break;
|
|
1355
1351
|
default:
|
|
1356
1352
|
if (this.logWarn) this.emit('warn', `Unknown ventilation mode: ${ventilationMode}`);
|
|
1357
1353
|
break;
|
|
1358
|
-
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// power override (single source of truth)
|
|
1357
|
+
if (!power) {
|
|
1358
|
+
obj.currentOperationMode = 0;
|
|
1359
|
+
obj.targetOperationMode = 0;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
operationModevalidValues.push(0); // manual zawsze dostępny
|
|
1363
|
+
if (supportsBypassVentilationMode) operationModevalidValues.push(1);
|
|
1364
|
+
operationModevalidValues.push(2); // manual / normal
|
|
1365
|
+
if (supportsAutoVentilationMode) operationModevalidValues.push(3);
|
|
1359
1366
|
|
|
1360
|
-
obj.
|
|
1361
|
-
obj.
|
|
1362
|
-
obj.
|
|
1363
|
-
obj.operationModeSetPropsMaxValue = supportsAutoVentilationMode ? 3 : 2;
|
|
1364
|
-
obj.operationModeSetPropsValidValues = supportsAutoVentilationMode ? (supportsBypassVentilationMode ? [0, 1, 2, 3] : [0, 2, 3]) : (supportsBypassVentilationMode ? [0, 1, 2] : [0, 2]);
|
|
1367
|
+
obj.operationModeSetPropsMinValue = operationModevalidValues[0];
|
|
1368
|
+
obj.operationModeSetPropsMaxValue = operationModevalidValues.at(-1);
|
|
1369
|
+
obj.operationModeSetPropsValidValues = operationModevalidValues;
|
|
1365
1370
|
|
|
1366
1371
|
//create characteristics
|
|
1367
1372
|
characteristics.push(
|
package/src/functions.js
CHANGED
|
@@ -56,162 +56,157 @@ 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 { }
|
|
81
|
+
|
|
75
82
|
try {
|
|
76
|
-
const { stdout } = await execPromise(
|
|
77
|
-
if (stdout.includes(
|
|
83
|
+
const { stdout } = await execPromise('cat /proc/1/cgroup');
|
|
84
|
+
if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
|
|
78
85
|
} catch { }
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
const result = { path: null, arch, system: 'unknown' };
|
|
88
|
+
|
|
89
|
+
/* ===================== macOS ===================== */
|
|
81
90
|
if (isMac) {
|
|
82
|
-
const macCandidates = [
|
|
83
|
-
|
|
84
|
-
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
85
|
-
];
|
|
86
|
-
for (const p of macCandidates) {
|
|
91
|
+
const macCandidates = ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium'];
|
|
92
|
+
for (const path of macCandidates) {
|
|
87
93
|
try {
|
|
88
|
-
await access(
|
|
89
|
-
|
|
94
|
+
await access(path, fs.constants.X_OK);
|
|
95
|
+
result.path = path;
|
|
96
|
+
result.system = 'macOS';
|
|
97
|
+
return result;
|
|
90
98
|
} catch { }
|
|
91
99
|
}
|
|
92
|
-
return
|
|
100
|
+
return result;
|
|
93
101
|
}
|
|
94
102
|
|
|
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) {
|
|
103
|
+
/* ===================== QNAP ===================== */
|
|
104
|
+
if (isQnap) {
|
|
105
|
+
const qnapCandidates = ['/opt/bin/chromium', '/opt/bin/chromium-browser'];
|
|
106
|
+
for (const path of qnapCandidates) {
|
|
105
107
|
try {
|
|
106
|
-
await access(
|
|
107
|
-
|
|
108
|
+
await access(path, fs.constants.X_OK);
|
|
109
|
+
result.path = path;
|
|
110
|
+
result.system = 'Qnap';
|
|
111
|
+
return result;
|
|
108
112
|
} catch { }
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
try {
|
|
116
|
+
await access('/opt/bin/opkg', fs.constants.X_OK);
|
|
117
|
+
await execPromise('/opt/bin/opkg update');
|
|
118
|
+
await execPromise('opkg install chromium nspr nss libx11 libxcomposite libxdamage libxrandr atk libcups libdrm libgbm alsa-lib');
|
|
119
|
+
process.env.LD_LIBRARY_PATH = `/opt/lib:${process.env.LD_LIBRARY_PATH || ''}`;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (this.logError) this.emit('error', `Install package for Qnap error: ${error}`);
|
|
122
|
+
}
|
|
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
|
+
for (const path of armCandidates) {
|
|
116
139
|
try {
|
|
117
|
-
await
|
|
140
|
+
await access(path, fs.constants.X_OK);
|
|
141
|
+
result.path = path;
|
|
142
|
+
result.system = 'Linux';
|
|
143
|
+
return result;
|
|
118
144
|
} catch { }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!isDocker) {
|
|
119
148
|
try {
|
|
120
|
-
await execPromise(
|
|
121
|
-
|
|
149
|
+
await execPromise('sudo apt update -y');
|
|
150
|
+
await execPromise('sudo apt install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2');
|
|
151
|
+
await execPromise('sudo apt install -y chromium chromium-browser chromium-codecs-ffmpeg');
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (this.logError) this.emit('error', `Install package for Linux ARM error: ${error}`);
|
|
154
|
+
}
|
|
122
155
|
}
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
for (const p of armCandidates) {
|
|
157
|
+
for (const path of armCandidates) {
|
|
126
158
|
try {
|
|
127
|
-
await access(
|
|
128
|
-
|
|
159
|
+
await access(path, fs.constants.X_OK);
|
|
160
|
+
result.path = path;
|
|
161
|
+
result.system = 'Linux';
|
|
162
|
+
return result;
|
|
129
163
|
} catch { }
|
|
130
164
|
}
|
|
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 { }
|
|
165
|
+
return result;
|
|
148
166
|
}
|
|
149
167
|
|
|
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
|
|
168
|
+
/* ===================== Linux x64 ===================== */
|
|
163
169
|
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
|
-
|
|
170
|
+
const linuxCandidates = ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome', '/snap/bin/chromium', '/usr/local/bin/chromium'];
|
|
172
171
|
try {
|
|
173
|
-
const { stdout } = await execPromise(
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
const { stdout } = await execPromise('which chromium || which chromium-browser || which google-chrome');
|
|
173
|
+
if (stdout.trim()) {
|
|
174
|
+
result.path = stdout.trim();
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
176
177
|
} catch { }
|
|
177
178
|
|
|
178
|
-
for (const
|
|
179
|
+
for (const path of linuxCandidates) {
|
|
179
180
|
try {
|
|
180
|
-
await access(
|
|
181
|
-
|
|
181
|
+
await access(path, fs.constants.X_OK);
|
|
182
|
+
result.path = path;
|
|
183
|
+
result.system = 'Linux';
|
|
184
|
+
return result;
|
|
182
185
|
} catch { }
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
// Docker: try installing chromium inside container (if allowed)
|
|
186
188
|
if (isDocker) {
|
|
187
189
|
try {
|
|
188
|
-
await execPromise(
|
|
189
|
-
} catch {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
];
|
|
202
|
-
for (const cmd of depCommands) {
|
|
203
|
-
try {
|
|
204
|
-
await execPromise(`sudo ${cmd}`);
|
|
205
|
-
} catch { }
|
|
190
|
+
await execPromise('apt update -y && apt install -y chromium');
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (this.logError) this.emit('error', `Install package for Linux Docker error: ${error}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const path of linuxCandidates) {
|
|
196
|
+
try {
|
|
197
|
+
await access(path, fs.constants.X_OK);
|
|
198
|
+
result.path = path;
|
|
199
|
+
result.system = 'Linux Docker';
|
|
200
|
+
return result;
|
|
201
|
+
} catch { }
|
|
202
|
+
}
|
|
206
203
|
}
|
|
207
|
-
|
|
208
|
-
return null;
|
|
209
204
|
}
|
|
210
205
|
|
|
211
|
-
return
|
|
212
|
-
} catch (
|
|
213
|
-
if (this.logError) this.emit(
|
|
214
|
-
return null;
|
|
206
|
+
return result;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (this.logError) this.emit('error', `Chromium detection error: ${error.message}`);
|
|
209
|
+
return { path: null, arch: 'unknown', system: 'unknown' };
|
|
215
210
|
}
|
|
216
211
|
}
|
|
217
212
|
|
|
@@ -280,7 +275,6 @@ class Functions extends EventEmitter {
|
|
|
280
275
|
|
|
281
276
|
return { min, max };
|
|
282
277
|
}
|
|
283
|
-
|
|
284
278
|
}
|
|
285
279
|
|
|
286
280
|
export default Functions
|
package/src/melcloud.js
CHANGED
|
@@ -54,7 +54,7 @@ class MelCloud extends EventEmitter {
|
|
|
54
54
|
|
|
55
55
|
async checkDevicesList() {
|
|
56
56
|
try {
|
|
57
|
-
const devicesList = { State: false, Info: null, Devices: [], Scenes: [] }
|
|
57
|
+
const devicesList = { State: false, Info: null, Buildings: [], Devices: [], Scenes: [] }
|
|
58
58
|
if (this.logDebug) this.emit('debug', `Scanning for devices...`);
|
|
59
59
|
const listDevicesData = await this.client(ApiUrls.Get.ListDevices, { method: 'GET', });
|
|
60
60
|
|
|
@@ -71,9 +71,6 @@ class MelCloud extends EventEmitter {
|
|
|
71
71
|
return devicesList;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
75
|
-
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
76
|
-
|
|
77
74
|
const devices = [];
|
|
78
75
|
for (const building of buildingsList) {
|
|
79
76
|
if (!building.Structure) {
|
|
@@ -108,7 +105,12 @@ class MelCloud extends EventEmitter {
|
|
|
108
105
|
|
|
109
106
|
devicesList.State = true;
|
|
110
107
|
devicesList.Info = `Found ${devicesCount} devices`;
|
|
108
|
+
devicesList.Buildings = buildingsList;
|
|
111
109
|
devicesList.Devices = devices;
|
|
110
|
+
|
|
111
|
+
await this.functions.saveData(this.buildingsFile, devicesList);
|
|
112
|
+
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
113
|
+
|
|
112
114
|
this.emit('devicesList', devicesList);
|
|
113
115
|
|
|
114
116
|
return devicesList;
|
package/src/melcloudata.js
CHANGED
|
@@ -72,7 +72,34 @@ class MelCloudAta extends EventEmitter {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
updateState = true;
|
|
76
|
+
break;
|
|
77
|
+
case 'ataUnitFrostProtectionTriggered':
|
|
78
|
+
deviceData.FrostProtection.Active = messageData.active;
|
|
79
|
+
|
|
80
|
+
//update device settings
|
|
81
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
82
|
+
if (!this.functions.isValidValue(value)) continue;
|
|
83
|
+
|
|
84
|
+
if (key in deviceData.Device) {
|
|
85
|
+
deviceData.Device[key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
updateState = true;
|
|
90
|
+
break;
|
|
91
|
+
case 'ataUnitOverheatProtectionTriggered':
|
|
92
|
+
deviceData.OverheatProtection.Active = messageData.active;
|
|
93
|
+
|
|
94
|
+
//update device settings
|
|
95
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
96
|
+
if (!this.functions.isValidValue(value)) continue;
|
|
97
|
+
|
|
98
|
+
if (key in deviceData.Device) {
|
|
99
|
+
deviceData.Device[key] = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
updateState = true;
|
|
77
104
|
break;
|
|
78
105
|
case 'unitHolidayModeTriggered':
|
|
@@ -85,6 +112,10 @@ class MelCloudAta extends EventEmitter {
|
|
|
85
112
|
deviceData.Rssi = messageData.rssi;
|
|
86
113
|
updateState = true;
|
|
87
114
|
break;
|
|
115
|
+
case 'unitCommunicationRestored':
|
|
116
|
+
timestamp = messageData.timestamp;
|
|
117
|
+
deviceData.Device.IsConnected = true;
|
|
118
|
+
break;
|
|
88
119
|
default:
|
|
89
120
|
if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${parsedMessage}`);
|
|
90
121
|
return;
|