homebridge-melcloud-control 4.1.2-beta.9 → 4.1.2
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 +10 -0
- package/README.md +16 -3
- package/config.schema.json +445 -115
- package/homebridge-ui/public/index.html +90 -46
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/deviceata.js +68 -12
- package/src/deviceatw.js +64 -3
- package/src/deviceerv.js +68 -7
- package/src/functions.js +41 -57
- package/src/melcloud.js +64 -47
- package/src/melcloudata.js +17 -8
- package/src/melcloudatw.js +14 -8
- package/src/melclouderv.js +14 -8
package/src/deviceerv.js
CHANGED
|
@@ -31,7 +31,8 @@ class DeviceErv extends EventEmitter {
|
|
|
31
31
|
this.temperatureOutdoorSensor = device.temperatureOutdoorSensor || false;
|
|
32
32
|
this.temperatureSupplySensor = device.temperatureSupplySensor || false;
|
|
33
33
|
this.errorSensor = device.errorSensor || false;
|
|
34
|
-
this.presets = (device.presets || []).filter(preset => (preset.displayType ?? 0) > 0);
|
|
34
|
+
this.presets = this.accountType === 'melcloud' ? (device.presets || []).filter(preset => (preset.displayType ?? 0) > 0 && preset.id !== '0') : [];
|
|
35
|
+
this.schedules = this.accountType === 'melcloudhome' ? (device.schedules || []).filter(schedule => (schedule.displayType ?? 0) > 0 && schedule.id !== '0') : [];
|
|
35
36
|
this.buttons = (device.buttonsSensors || []).filter(button => (button.displayType ?? 0) > 0);
|
|
36
37
|
this.deviceId = device.id;
|
|
37
38
|
this.deviceName = device.name;
|
|
@@ -55,6 +56,14 @@ class DeviceErv extends EventEmitter {
|
|
|
55
56
|
preset.previousSettings = {};
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
//schedules configured
|
|
60
|
+
for (const schedule of this.schedules) {
|
|
61
|
+
schedule.name = schedule.name || 'Schedule'
|
|
62
|
+
schedule.serviceType = [null, Service.Outlet, Service.Switch, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][schedule.displayType];
|
|
63
|
+
schedule.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][schedule.displayType];
|
|
64
|
+
schedule.state = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
//buttons configured
|
|
59
68
|
for (const button of this.buttons) {
|
|
60
69
|
button.name = button.name || 'Button'
|
|
@@ -230,6 +239,7 @@ class DeviceErv extends EventEmitter {
|
|
|
230
239
|
const deviceName = this.deviceName;
|
|
231
240
|
const accountName = this.accountName;
|
|
232
241
|
const presetsOnServer = this.accessory.presets;
|
|
242
|
+
const schedulesOnServer = this.accessory.schedules;
|
|
233
243
|
const hasRoomTemperature = this.accessory.hasRoomTemperature;
|
|
234
244
|
const hasSupplyTemperature = this.accessory.hasSupplyTemperature;
|
|
235
245
|
const hasOutdoorTemperature = this.accessory.hasOutdoorTemperature;
|
|
@@ -703,14 +713,14 @@ class DeviceErv extends EventEmitter {
|
|
|
703
713
|
deviceData.Device.Power = presetData.Power;
|
|
704
714
|
deviceData.Device.OperationMode = presetData.OperationMode;
|
|
705
715
|
deviceData.Device.VentilationMode = presetData.VentilationMode;
|
|
706
|
-
deviceData.Device.SetFanSpeed = presetData.
|
|
716
|
+
deviceData.Device.SetFanSpeed = presetData.SetFanSpeed;
|
|
707
717
|
break;
|
|
708
718
|
case false:
|
|
709
719
|
deviceData.Device.SetTemperature = preset.previousSettings.SetTemperature;
|
|
710
720
|
deviceData.Device.Power = preset.previousSettings.Power;
|
|
711
721
|
deviceData.Device.OperationMode = preset.previousSettings.OperationMode;
|
|
712
722
|
deviceData.Device.VentilationMode = preset.previousSettings.VentilationMode;
|
|
713
|
-
deviceData.Device.SetFanSpeed = preset.previousSettings.
|
|
723
|
+
deviceData.Device.SetFanSpeed = preset.previousSettings.SetFanSpeed;
|
|
714
724
|
break;
|
|
715
725
|
};
|
|
716
726
|
|
|
@@ -725,6 +735,42 @@ class DeviceErv extends EventEmitter {
|
|
|
725
735
|
accessory.addService(presetService);
|
|
726
736
|
};
|
|
727
737
|
|
|
738
|
+
//schedules services
|
|
739
|
+
if (this.schedules.length > 0) {
|
|
740
|
+
if (this.logDebug) this.emit('debug', `Prepare schedules services`);
|
|
741
|
+
this.schedulesServices = [];
|
|
742
|
+
this.schedules.forEach((schedule, i) => {
|
|
743
|
+
//get preset name
|
|
744
|
+
const name = schedule.name;
|
|
745
|
+
|
|
746
|
+
//get preset name prefix
|
|
747
|
+
const namePrefix = schedule.namePrefix;
|
|
748
|
+
|
|
749
|
+
const serviceName = namePrefix ? `${accessoryName} ${name}` : name;
|
|
750
|
+
const serviceType = schedule.serviceType;
|
|
751
|
+
const characteristicType = schedule.characteristicType;
|
|
752
|
+
const scheduleService = new serviceType(serviceName, `Schedule ${deviceId} ${i}`);
|
|
753
|
+
scheduleService.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
754
|
+
scheduleService.setCharacteristic(Characteristic.ConfiguredName, serviceName);
|
|
755
|
+
scheduleService.getCharacteristic(characteristicType)
|
|
756
|
+
.onGet(async () => {
|
|
757
|
+
const state = schedule.state;
|
|
758
|
+
return state;
|
|
759
|
+
})
|
|
760
|
+
.onSet(async (state) => {
|
|
761
|
+
try {
|
|
762
|
+
deviceData.ScheduleEnabled = state;
|
|
763
|
+
await this.melCloudAta.send(this.accountType, this.displayType, deviceData, 'scheduleset');
|
|
764
|
+
if (this.logInfo) this.emit('info', `${state ? 'Set:' : 'Unset:'} ${name}`);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
if (this.logWarn) this.emit('warn', `Set schedule error: ${error}`);
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
this.schedulesServices.push(scheduleService);
|
|
770
|
+
accessory.addService(scheduleService);
|
|
771
|
+
});
|
|
772
|
+
};
|
|
773
|
+
|
|
728
774
|
//buttons services
|
|
729
775
|
if (this.buttons.length > 0) {
|
|
730
776
|
if (this.logDebug) this.emit('debug', `Prepare buttons services`);
|
|
@@ -882,8 +928,10 @@ class DeviceErv extends EventEmitter {
|
|
|
882
928
|
const tempStepKey = this.accountType === 'melcloud' ? 'TemperatureIncrement' : 'HasHalfDegreeIncrements';
|
|
883
929
|
const errorKey = this.accountType === 'melcloud' ? 'HasError' : 'IsInError';
|
|
884
930
|
|
|
885
|
-
//presets
|
|
886
|
-
const
|
|
931
|
+
//presets schedule
|
|
932
|
+
const scheduleEnabled = deviceData.ScheduleEnabled;
|
|
933
|
+
const schedulesOnServer = deviceData.Schedule ?? [];
|
|
934
|
+
const presetsOnServer = deviceData.Presets ?? [];
|
|
887
935
|
|
|
888
936
|
//device control
|
|
889
937
|
const hideRoomTemperature = deviceData.HideRoomTemperature;
|
|
@@ -934,6 +982,7 @@ class DeviceErv extends EventEmitter {
|
|
|
934
982
|
//accessory
|
|
935
983
|
const obj = {
|
|
936
984
|
presets: presetsOnServer,
|
|
985
|
+
schedules: schedulesOnServer,
|
|
937
986
|
hasRoomTemperature: hasRoomTemperature,
|
|
938
987
|
hasSupplyTemperature: hasSupplyTemperature,
|
|
939
988
|
hasOutdoorTemperature: hasOutdoorTemperature,
|
|
@@ -972,7 +1021,8 @@ class DeviceErv extends EventEmitter {
|
|
|
972
1021
|
maxTempCoolDry: maxTempCoolDry,
|
|
973
1022
|
useFahrenheit: this.useFahrenheit,
|
|
974
1023
|
temperatureUnit: TemperatureDisplayUnits[this.useFahrenheit],
|
|
975
|
-
isInError: isInError
|
|
1024
|
+
isInError: isInError,
|
|
1025
|
+
scheduleEnabled: scheduleEnabled
|
|
976
1026
|
};
|
|
977
1027
|
|
|
978
1028
|
//ventilation mode - 0, HEAT, 2, COOL, 4, 5, 6, FAN, AUTO
|
|
@@ -1121,13 +1171,24 @@ class DeviceErv extends EventEmitter {
|
|
|
1121
1171
|
&& presetData.SetTemperature === setTemperature
|
|
1122
1172
|
&& presetData.OperationMode === operationMode
|
|
1123
1173
|
&& presetData.VentilationMode === ventilationMode
|
|
1124
|
-
&& presetData.
|
|
1174
|
+
&& presetData.SetFanSpeed === setFanSpeed) : false;
|
|
1125
1175
|
|
|
1126
1176
|
const characteristicType = preset.characteristicType;
|
|
1127
1177
|
this.presetsServices?.[i]?.updateCharacteristic(characteristicType, preset.state);
|
|
1128
1178
|
});
|
|
1129
1179
|
};
|
|
1130
1180
|
|
|
1181
|
+
//update schedules state
|
|
1182
|
+
if (this.schedules.length > 0) {
|
|
1183
|
+
this.schedules.forEach((schedule, i) => {
|
|
1184
|
+
const scheduleData = schedulesOnServer.find(s => s[presetsIdKey] === schedule.id);
|
|
1185
|
+
schedule.state = scheduleEnabled; //scheduleData.Enabled : false;
|
|
1186
|
+
|
|
1187
|
+
const characteristicType = schedule.characteristicType;
|
|
1188
|
+
this.schedulesServices?.[i]?.updateCharacteristic(characteristicType, schedule.state);
|
|
1189
|
+
});
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1131
1192
|
//update buttons state
|
|
1132
1193
|
if (this.buttons.length > 0) {
|
|
1133
1194
|
this.buttons.forEach((button, i) => {
|
package/src/functions.js
CHANGED
|
@@ -68,18 +68,16 @@ class Functions extends EventEmitter {
|
|
|
68
68
|
const arch = archOut.trim();
|
|
69
69
|
if (this.logDebug) this.emit('debug', `Detected architecture: ${arch}`);
|
|
70
70
|
|
|
71
|
-
// --- Detect Docker
|
|
71
|
+
// --- Detect Docker ---
|
|
72
72
|
let isDocker = false;
|
|
73
73
|
try {
|
|
74
|
-
await access('/.dockerenv', fs.constants.F_OK);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
} catch { }
|
|
82
|
-
}
|
|
74
|
+
await access('/.dockerenv', fs.constants.F_OK); isDocker = true;
|
|
75
|
+
} catch { }
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await execPromise('cat /proc/1/cgroup || true');
|
|
79
|
+
if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
|
|
80
|
+
} catch { }
|
|
83
81
|
|
|
84
82
|
if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container.');
|
|
85
83
|
|
|
@@ -88,83 +86,68 @@ class Functions extends EventEmitter {
|
|
|
88
86
|
chromiumPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
89
87
|
try {
|
|
90
88
|
await access(chromiumPath, fs.constants.X_OK);
|
|
91
|
-
if (this.logDebug) this.emit('debug', `Using system Chrome at ${chromiumPath}`);
|
|
92
89
|
return chromiumPath;
|
|
93
90
|
} catch {
|
|
94
91
|
return null;
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
// === ARM
|
|
95
|
+
// === ARM ===
|
|
99
96
|
if (arch.startsWith('arm')) {
|
|
100
97
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
98
|
+
chromiumPath = '/usr/bin/chromium-browser';
|
|
99
|
+
await access(chromiumPath, fs.constants.X_OK);
|
|
100
|
+
return chromiumPath;
|
|
104
101
|
} catch {
|
|
105
|
-
if (this.logWarn) this.emit('warn', 'System Chromium not found on ARM. Attempting installation...');
|
|
106
102
|
try {
|
|
107
103
|
await execPromise('sudo apt-get update -y && sudo apt-get install -y chromium-browser chromium-codecs-ffmpeg');
|
|
108
|
-
|
|
109
|
-
return '/usr/bin/chromium-browser';
|
|
104
|
+
return chromiumPath;
|
|
110
105
|
} catch {
|
|
111
106
|
return null;
|
|
112
107
|
}
|
|
113
108
|
}
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
// === Linux
|
|
111
|
+
// === Linux x64 ===
|
|
117
112
|
if (osName === 'Linux') {
|
|
113
|
+
let systemChromium = null;
|
|
118
114
|
try {
|
|
119
|
-
// --- Try detect common Chromium binaries ---
|
|
120
115
|
const { stdout: checkOut } = await execPromise('which chromium || which chromium-browser || true');
|
|
121
|
-
|
|
122
|
-
if (chromiumPath) {
|
|
123
|
-
if (this.logDebug) this.emit('debug', `Found system Chromium: ${chromiumPath}`);
|
|
124
|
-
return chromiumPath;
|
|
125
|
-
}
|
|
116
|
+
systemChromium = checkOut.trim() || null;
|
|
126
117
|
} catch { }
|
|
127
118
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'yum install -y chromium chromium-codecs-ffmpeg'
|
|
135
|
-
];
|
|
119
|
+
// --- Detect Entware (QNAP) ---
|
|
120
|
+
let entwareExists = false;
|
|
121
|
+
try {
|
|
122
|
+
await access('/opt/bin/opkg', fs.constants.X_OK);
|
|
123
|
+
entwareExists = true;
|
|
124
|
+
} catch { }
|
|
136
125
|
|
|
137
|
-
|
|
126
|
+
if (entwareExists) {
|
|
138
127
|
try {
|
|
139
|
-
|
|
140
|
-
await execPromise(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
chromiumPath = checkOut.trim() || '/usr/bin/chromium';
|
|
144
|
-
if (chromiumPath) {
|
|
145
|
-
if (this.logDebug) this.emit('debug', `Chromium installed successfully at ${chromiumPath}`);
|
|
146
|
-
return chromiumPath;
|
|
147
|
-
}
|
|
148
|
-
} catch (error) {
|
|
149
|
-
if (this.logDebug) this.emit('debug', `Install attempt failed: ${cmd} → ${error.message}`);
|
|
150
|
-
}
|
|
128
|
+
await execPromise('/opt/bin/opkg update');
|
|
129
|
+
await execPromise('/opt/bin/opkg install nspr nss libx11 libxcomposite libxdamage libxrandr libatk libatk-bridge libcups libdrm libgbm libasound');
|
|
130
|
+
process.env.LD_LIBRARY_PATH = `/opt/lib:${process.env.LD_LIBRARY_PATH || ''}`;
|
|
131
|
+
} catch { }
|
|
151
132
|
}
|
|
152
133
|
|
|
153
|
-
|
|
154
|
-
|
|
134
|
+
// --- Generic Linux installs missing libs for Puppeteer ---
|
|
135
|
+
const depInstall = [
|
|
136
|
+
'apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2',
|
|
137
|
+
'apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib',
|
|
138
|
+
'yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib'
|
|
139
|
+
];
|
|
140
|
+
for (const cmd of depInstall) {
|
|
155
141
|
try {
|
|
156
|
-
await execPromise(
|
|
157
|
-
|
|
158
|
-
if (this.logDebug) this.emit('debug', 'Chromium installed successfully inside Docker at /usr/bin/chromium');
|
|
159
|
-
return '/usr/bin/chromium';
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
142
|
+
await execPromise(`sudo ${cmd}`);
|
|
143
|
+
} catch { }
|
|
163
144
|
}
|
|
164
|
-
|
|
145
|
+
|
|
146
|
+
// Set LD_LIBRARY_PATH so Puppeteer's Chromium can find libs
|
|
147
|
+
process.env.LD_LIBRARY_PATH = `/usr/lib:/usr/lib64:${process.env.LD_LIBRARY_PATH || ''}`;
|
|
148
|
+
return systemChromium;
|
|
165
149
|
}
|
|
166
150
|
|
|
167
|
-
// Unknown OS
|
|
168
151
|
if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}.`);
|
|
169
152
|
return null;
|
|
170
153
|
} catch (error) {
|
|
@@ -172,5 +155,6 @@ class Functions extends EventEmitter {
|
|
|
172
155
|
return null;
|
|
173
156
|
}
|
|
174
157
|
}
|
|
158
|
+
|
|
175
159
|
}
|
|
176
160
|
export default Functions
|
package/src/melcloud.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
1
2
|
import axios from 'axios';
|
|
2
3
|
import { exec } from 'child_process';
|
|
3
4
|
import { promisify } from 'util';
|
|
4
5
|
import EventEmitter from 'events';
|
|
5
|
-
import puppeteer from 'puppeteer
|
|
6
|
+
import puppeteer from 'puppeteer';
|
|
6
7
|
import ImpulseGenerator from './impulsegenerator.js';
|
|
7
8
|
import Functions from './functions.js';
|
|
8
9
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
@@ -235,61 +236,67 @@ class MelCloud extends EventEmitter {
|
|
|
235
236
|
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
236
237
|
|
|
237
238
|
const devices = buildingsList.flatMap(building => {
|
|
238
|
-
//
|
|
239
|
+
// Funkcja kapitalizująca klucze obiektu
|
|
240
|
+
const capitalizeKeys = obj =>
|
|
241
|
+
Object.fromEntries(
|
|
242
|
+
Object.entries(obj).map(([key, value]) => [
|
|
243
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
244
|
+
value
|
|
245
|
+
])
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Rekurencyjna kapitalizacja kluczy w obiekcie lub tablicy
|
|
239
249
|
const capitalizeKeysDeep = obj => {
|
|
240
|
-
if (Array.isArray(obj))
|
|
241
|
-
|
|
242
|
-
} else if (obj && typeof obj === 'object' && obj.constructor === Object) {
|
|
250
|
+
if (Array.isArray(obj)) return obj.map(capitalizeKeysDeep);
|
|
251
|
+
if (obj && typeof obj === 'object') {
|
|
243
252
|
return Object.fromEntries(
|
|
244
|
-
Object.entries(obj).map(([key, value]) =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
: key;
|
|
249
|
-
return [safeKey, capitalizeKeysDeep(value)];
|
|
250
|
-
})
|
|
253
|
+
Object.entries(obj).map(([key, value]) => [
|
|
254
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
255
|
+
capitalizeKeysDeep(value)
|
|
256
|
+
])
|
|
251
257
|
);
|
|
252
258
|
}
|
|
253
259
|
return obj;
|
|
254
260
|
};
|
|
255
261
|
|
|
256
262
|
// Funkcja tworząca finalny obiekt Device
|
|
257
|
-
const createDevice = (device, type
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
settingsArray
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
value.trim() !== ''
|
|
272
|
-
) parsedValue = Number(value);
|
|
273
|
-
|
|
274
|
-
const key =
|
|
275
|
-
name.charAt(0).toUpperCase() + name.slice(1);
|
|
276
|
-
return [key, parsedValue];
|
|
277
|
-
})
|
|
263
|
+
const createDevice = (device, type) => {
|
|
264
|
+
// Settings już kapitalizowane w nazwach
|
|
265
|
+
const settingsArray = device.Settings || [];
|
|
266
|
+
|
|
267
|
+
const settingsObject = Object.fromEntries(
|
|
268
|
+
settingsArray.map(({ name, value }) => {
|
|
269
|
+
let parsedValue = value;
|
|
270
|
+
if (value === "True") parsedValue = true;
|
|
271
|
+
else if (value === "False") parsedValue = false;
|
|
272
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
273
|
+
|
|
274
|
+
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
275
|
+
return [key, parsedValue];
|
|
276
|
+
})
|
|
278
277
|
);
|
|
279
278
|
|
|
280
|
-
//
|
|
279
|
+
// Scal Capabilities + Settings + DeviceType w Device
|
|
281
280
|
const deviceObject = {
|
|
282
|
-
...
|
|
283
|
-
...
|
|
281
|
+
...capitalizeKeys(device.Capabilities || {}),
|
|
282
|
+
...settingsObject,
|
|
284
283
|
DeviceType: type
|
|
285
284
|
};
|
|
286
285
|
|
|
287
|
-
//
|
|
286
|
+
// Kapitalizacja brakujących obiektów/tablic
|
|
287
|
+
if (device.FrostProtection) device.FrostProtection = { ...capitalizeKeys(device.FrostProtection || {}) };
|
|
288
|
+
if (device.OverheatProtection) device.OverheatProtection = { ...capitalizeKeys(device.OverheatProtection || {}) };
|
|
289
|
+
if (device.HolidayMode) device.HolidayMode = { ...capitalizeKeys(device.HolidayMode || {}) };
|
|
290
|
+
|
|
291
|
+
if (Array.isArray(device.Schedule)) {
|
|
292
|
+
device.Schedule = device.Schedule.map(capitalizeKeysDeep);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Usuń stare pola Settings i Capabilities
|
|
288
296
|
const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
|
|
289
297
|
|
|
290
|
-
// Zwróć gotowy obiekt urządzenia
|
|
291
298
|
return {
|
|
292
|
-
...
|
|
299
|
+
...rest,
|
|
293
300
|
Type: type,
|
|
294
301
|
DeviceID: Id,
|
|
295
302
|
DeviceName: GivenDisplayName,
|
|
@@ -298,14 +305,11 @@ class MelCloud extends EventEmitter {
|
|
|
298
305
|
};
|
|
299
306
|
};
|
|
300
307
|
|
|
301
|
-
// Mapowanie urządzeń w budynku
|
|
302
308
|
return [
|
|
303
|
-
...(building.airToAirUnits || []).map(d => createDevice(
|
|
304
|
-
...(building.airToWaterUnits || []).map(d => createDevice(
|
|
305
|
-
...(building.airToVentilationUnits || []).map(d => createDevice(
|
|
309
|
+
...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
|
|
310
|
+
...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
|
|
311
|
+
...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3))
|
|
306
312
|
];
|
|
307
|
-
|
|
308
|
-
|
|
309
313
|
});
|
|
310
314
|
|
|
311
315
|
const devicesCount = devices.length;
|
|
@@ -333,7 +337,21 @@ class MelCloud extends EventEmitter {
|
|
|
333
337
|
let browser;
|
|
334
338
|
try {
|
|
335
339
|
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
336
|
-
|
|
340
|
+
let chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
341
|
+
|
|
342
|
+
// === Fallback to Puppeteer's built-in Chromium ===
|
|
343
|
+
if (!chromiumPath) {
|
|
344
|
+
try {
|
|
345
|
+
const puppeteerPath = puppeteer.executablePath();
|
|
346
|
+
if (puppeteerPath && fs.existsSync(puppeteerPath)) {
|
|
347
|
+
chromiumPath = puppeteerPath;
|
|
348
|
+
if (this.logDebug) this.emit('debug', `Using puppeteer Chromium at ${chromiumPath}`);
|
|
349
|
+
}
|
|
350
|
+
} catch { }
|
|
351
|
+
} else {
|
|
352
|
+
if (this.logDebug) this.emit('debug', `Using system Chromium at ${chromiumPath}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
337
355
|
if (!chromiumPath) {
|
|
338
356
|
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
|
|
339
357
|
return accountInfo;
|
|
@@ -343,7 +361,6 @@ class MelCloud extends EventEmitter {
|
|
|
343
361
|
try {
|
|
344
362
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
345
363
|
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
346
|
-
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
347
364
|
} catch (error) {
|
|
348
365
|
accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
349
366
|
return accountInfo;
|
package/src/melcloudata.js
CHANGED
|
@@ -23,7 +23,6 @@ class MelCloudAta extends EventEmitter {
|
|
|
23
23
|
|
|
24
24
|
//set default values
|
|
25
25
|
this.deviceData = {};
|
|
26
|
-
this.headers = {};
|
|
27
26
|
|
|
28
27
|
//lock flags
|
|
29
28
|
this.locks = {
|
|
@@ -60,7 +59,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
60
59
|
return null;
|
|
61
60
|
}
|
|
62
61
|
const deviceData = devicesData.find(device => device.DeviceID === this.deviceId);
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
if (this.accountType === 'melcloudhome') {
|
|
65
64
|
deviceData.SerialNumber = deviceData.DeviceID || '4.0.0';
|
|
66
65
|
deviceData.Device.FirmwareAppVersion = deviceData.ConnectedInterfaceIdentifier || '4.0.0';
|
|
@@ -75,7 +74,12 @@ class MelCloudAta extends EventEmitter {
|
|
|
75
74
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
76
75
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
77
76
|
}
|
|
78
|
-
|
|
77
|
+
|
|
78
|
+
const safeConfig = {
|
|
79
|
+
...deviceData,
|
|
80
|
+
headers: 'removed',
|
|
81
|
+
};
|
|
82
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(safeConfig, null, 2)}`);
|
|
79
83
|
|
|
80
84
|
//device
|
|
81
85
|
const serialNumber = deviceData.SerialNumber;
|
|
@@ -144,7 +148,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
144
148
|
method: 'POST',
|
|
145
149
|
baseURL: ApiUrls.BaseURL,
|
|
146
150
|
timeout: 10000,
|
|
147
|
-
headers:
|
|
151
|
+
headers: deviceData.Headers,
|
|
148
152
|
withCredentials: true
|
|
149
153
|
});
|
|
150
154
|
|
|
@@ -183,7 +187,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
183
187
|
method: 'PUT',
|
|
184
188
|
baseURL: ApiUrlsHome.BaseURL,
|
|
185
189
|
timeout: 10000,
|
|
186
|
-
headers:
|
|
190
|
+
headers: deviceData.Headers,
|
|
187
191
|
withCredentials: true
|
|
188
192
|
});
|
|
189
193
|
|
|
@@ -199,19 +203,19 @@ class MelCloudAta extends EventEmitter {
|
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
|
|
202
|
-
const settings = {
|
|
206
|
+
const settings = effectiveFlags === 'scheduleset' ? { data: { enabled: deviceData.ScheduleEnabled } } : {
|
|
203
207
|
data: {
|
|
204
208
|
Power: deviceData.Device.Power,
|
|
205
209
|
SetTemperature: deviceData.Device.SetTemperature,
|
|
206
210
|
SetFanSpeed: String(deviceData.Device.SetFanSpeed),
|
|
207
211
|
OperationMode: AirConditioner.OperationModeMapEnumToString[deviceData.Device.OperationMode],
|
|
208
212
|
VaneHorizontalDirection: AirConditioner.VaneHorizontalDirectionMapEnumToString[deviceData.Device.VaneHorizontalDirection],
|
|
209
|
-
VaneVerticalDirection: AirConditioner.VaneVerticalDirectionMapEnumToString[deviceData.Device.VaneVerticalDirection]
|
|
213
|
+
VaneVerticalDirection: AirConditioner.VaneVerticalDirectionMapEnumToString[deviceData.Device.VaneVerticalDirection],
|
|
210
214
|
}
|
|
211
215
|
};
|
|
212
216
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(settings.data, null, 2)}`);
|
|
213
217
|
|
|
214
|
-
const path = ApiUrlsHome.SetAta.replace('deviceid', deviceData.DeviceID);
|
|
218
|
+
const path = effectiveFlags === 'scheduleset' ? ApiUrlsHome.SetSchedule.replace('deviceid', deviceData.DeviceID) : ApiUrlsHome.SetAta.replace('deviceid', deviceData.DeviceID);
|
|
215
219
|
await axiosInstancePut(path, settings);
|
|
216
220
|
this.updateData(deviceData);
|
|
217
221
|
return true;
|
|
@@ -219,6 +223,11 @@ class MelCloudAta extends EventEmitter {
|
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
221
225
|
} catch (error) {
|
|
226
|
+
// Return 500 for schedule hovewer working correct
|
|
227
|
+
if (error?.response?.status === 500) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
throw new Error(`Send data error: ${error.message}`);
|
|
223
232
|
}
|
|
224
233
|
}
|
package/src/melcloudatw.js
CHANGED
|
@@ -23,7 +23,6 @@ class MelCloudAtw extends EventEmitter {
|
|
|
23
23
|
|
|
24
24
|
//set default values
|
|
25
25
|
this.devicesData = {};
|
|
26
|
-
this.headers = {};
|
|
27
26
|
|
|
28
27
|
//lock flags
|
|
29
28
|
this.locks = {
|
|
@@ -55,19 +54,21 @@ class MelCloudAtw extends EventEmitter {
|
|
|
55
54
|
try {
|
|
56
55
|
//read device info from file
|
|
57
56
|
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
58
|
-
|
|
59
57
|
if (!Array.isArray(devicesData)) {
|
|
60
58
|
if (this.logWarn) this.emit('warn', `Device data not found`);
|
|
61
59
|
return null;
|
|
62
60
|
}
|
|
63
61
|
const deviceData = devicesData.find(device => device.DeviceID === this.deviceId);
|
|
64
|
-
this.headers = deviceData.Headers;
|
|
65
62
|
|
|
66
63
|
if (this.accountType === 'melcloudhome') {
|
|
67
64
|
deviceData.SerialNumber = deviceData.DeviceID || '4.0.0';
|
|
68
65
|
deviceData.Device.FirmwareAppVersion = deviceData.ConnectedInterfaceIdentifier || '4.0.0';
|
|
69
66
|
}
|
|
70
|
-
|
|
67
|
+
const safeConfig = {
|
|
68
|
+
...deviceData,
|
|
69
|
+
headers: 'removed',
|
|
70
|
+
};
|
|
71
|
+
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(safeConfig, null, 2)}`);
|
|
71
72
|
|
|
72
73
|
//device
|
|
73
74
|
const serialNumber = deviceData.SerialNumber;
|
|
@@ -152,7 +153,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
152
153
|
method: 'POST',
|
|
153
154
|
baseURL: ApiUrls.BaseURL,
|
|
154
155
|
timeout: 10000,
|
|
155
|
-
headers:
|
|
156
|
+
headers: deviceData.Headers,
|
|
156
157
|
withCredentials: true
|
|
157
158
|
});
|
|
158
159
|
|
|
@@ -190,11 +191,11 @@ class MelCloudAtw extends EventEmitter {
|
|
|
190
191
|
method: 'PUT',
|
|
191
192
|
baseURL: ApiUrlsHome.BaseURL,
|
|
192
193
|
timeout: 10000,
|
|
193
|
-
headers:
|
|
194
|
+
headers: deviceData.Headers,
|
|
194
195
|
withCredentials: true
|
|
195
196
|
});
|
|
196
197
|
|
|
197
|
-
const settings = {
|
|
198
|
+
const settings = effectiveFlags === 'scheduleset' ? { data: { enabled: deviceData.ScheduleEnabled } } : {
|
|
198
199
|
data: {
|
|
199
200
|
Power: deviceData.Device.Power,
|
|
200
201
|
SetTemperatureZone1: deviceData.Device.SetTemperatureZone1,
|
|
@@ -213,7 +214,7 @@ class MelCloudAtw extends EventEmitter {
|
|
|
213
214
|
};
|
|
214
215
|
if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(settings.data, null, 2)}`);
|
|
215
216
|
|
|
216
|
-
const path = ApiUrlsHome.SetAtw.replace('deviceid', deviceData.DeviceID);
|
|
217
|
+
const path = effectiveFlags === 'scheduleset' ? ApiUrlsHome.SetSchedule.replace('deviceid', deviceData.DeviceID) : ApiUrlsHome.SetAtw.replace('deviceid', deviceData.DeviceID);
|
|
217
218
|
await axiosInstancePut(path, settings);
|
|
218
219
|
this.updateData(deviceData);
|
|
219
220
|
return true;
|
|
@@ -221,6 +222,11 @@ class MelCloudAtw extends EventEmitter {
|
|
|
221
222
|
return;
|
|
222
223
|
}
|
|
223
224
|
} catch (error) {
|
|
225
|
+
// Return 500 for schedule hovewer working correct
|
|
226
|
+
if (error?.response?.status === 500) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
224
230
|
throw new Error(`Send data error: ${error.message}`);
|
|
225
231
|
}
|
|
226
232
|
}
|