iobroker.smartfriends 1.1.0 → 1.2.0-alpha.1
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/LICENSE +2 -3
- package/README.md +10 -1
- package/io-package.json +27 -1
- package/lib/SchellenbergBridge.js +19 -9
- package/lib/comunication/CommandFactory.js +1 -1
- package/lib/comunication/SmartSocket.js +18 -10
- package/lib/comunication/SmartSocketFactory.js +2 -2
- package/lib/comunication/comModel/JSONCommand.js +4 -1
- package/lib/devices/DeviceCapabilityRegistry.js +70 -0
- package/lib/devices/DeviceKindCapabilities.js +90 -0
- package/lib/devices/DeviceManager.js +1 -1
- package/lib/devices/SchellenbergDevice.js +133 -52
- package/lib/helpers/Deferred.js +10 -2
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025 Black-Thunder <glwars@aol.de>
|
|
3
|
+
Copyright (c) 2025-2026 Black-Thunder <glwars@aol.de>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -20,8 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
-------------------------------------------------------------------------------
|
|
23
|
+
---
|
|
25
24
|
|
|
26
25
|
LoPablo/SchellenbergApi (https://github.com/LoPablo/SchellenbergApi)
|
|
27
26
|
|
package/README.md
CHANGED
|
@@ -33,6 +33,15 @@ The adapter establishes a direct connection to the gateway to control and query
|
|
|
33
33
|
Placeholder for the next version (at the beginning of the line):
|
|
34
34
|
### __WORK IN PROGRESS__
|
|
35
35
|
-->
|
|
36
|
+
### 1.2.0-alpha.1 (2026-01-08)
|
|
37
|
+
|
|
38
|
+
- (Black-Thunder) Timeout for initial device request was increased
|
|
39
|
+
- (Black-Thunder) Devices without defined device type are ignored
|
|
40
|
+
|
|
41
|
+
### 1.2.0-alpha.0 (2026-01-02)
|
|
42
|
+
|
|
43
|
+
- (Black-Thunder) Refactored device handling and added support for further device types
|
|
44
|
+
|
|
36
45
|
### 1.1.0 (2025-12-28)
|
|
37
46
|
|
|
38
47
|
- (Black-Thunder) Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID
|
|
@@ -55,7 +64,7 @@ Special thanks und credits to [LoPablo](https://github.com/LoPablo/SchellenbergA
|
|
|
55
64
|
|
|
56
65
|
MIT License
|
|
57
66
|
|
|
58
|
-
Copyright (c) 2025 Black-Thunder <glwars@aol.de>
|
|
67
|
+
Copyright (c) 2025-2026 Black-Thunder <glwars@aol.de>
|
|
59
68
|
|
|
60
69
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
61
70
|
of this software and associated documentation files (the "Software"), to deal
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "smartfriends",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0-alpha.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.2.0-alpha.1": {
|
|
7
|
+
"en": "Timeout for initial device request was increased\nDevices without defined device type are ignored",
|
|
8
|
+
"de": "Timeout für erste Geräteanfrage wurde erhöht\nGeräte ohne definierten Gerätetyp werden ignoriert",
|
|
9
|
+
"ru": "Время ожидания первоначального запроса устройства было увеличено\nУстройства без определенного типа устройства игнорируются",
|
|
10
|
+
"pt": "O tempo limite para a solicitação inicial do dispositivo foi aumentado\nOs dispositivos sem tipo de dispositivo definido são ignorados",
|
|
11
|
+
"nl": "Tijdslimiet voor het eerste apparaatverzoek werd verhoogd\nApparaten zonder gedefinieerd apparaattype worden genegeerd",
|
|
12
|
+
"fr": "Le délai pour la demande initiale d'appareil a été augmenté\nLes dispositifs sans type défini sont ignorés",
|
|
13
|
+
"it": "Timeout per la richiesta iniziale del dispositivo è stato aumentato\nI dispositivi senza tipo di dispositivo definito vengono ignorati",
|
|
14
|
+
"es": "Se aumentó el tiempo para la solicitud inicial del dispositivo\nLos dispositivos sin tipo de dispositivo definido son ignorados",
|
|
15
|
+
"pl": "Zwiększono czas na początkowe żądanie urządzenia\nUrządzenia bez określonego typu urządzenia są ignorowane",
|
|
16
|
+
"uk": "Підвищено запит на початковий пристрій\nПристрої без визначеного типу пристрою ігноруються",
|
|
17
|
+
"zh-cn": "初始设备请求的超时增加\n没有定义设备类型的设备会被忽略"
|
|
18
|
+
},
|
|
19
|
+
"1.2.0-alpha.0": {
|
|
20
|
+
"en": "Refactored device handling and added support for further device types",
|
|
21
|
+
"de": "Refactored Device Handling und zusätzliche Unterstützung für weitere Gerätetypen",
|
|
22
|
+
"ru": "Рефакторированная обработка устройств и дополнительная поддержка для других типов устройств",
|
|
23
|
+
"pt": "Manipulação do dispositivo refatorizado e suporte adicional para outros tipos de dispositivo",
|
|
24
|
+
"nl": "Refactored device handling en toegevoegde ondersteuning voor andere apparaattypes",
|
|
25
|
+
"fr": "Manipulation de l'appareil refactoré et support supplémentaire pour d'autres types d'appareil",
|
|
26
|
+
"it": "Gestione del dispositivo e supporto aggiunto per ulteriori tipi di dispositivo",
|
|
27
|
+
"es": "Manejo de dispositivo refactorizado y soporte añadido para nuevos tipos de dispositivos",
|
|
28
|
+
"pl": "Zmieniona obsługa urządzenia i dodana obsługa dla innych typów urządzeń",
|
|
29
|
+
"uk": "Рефакторний пристрій обробки та додано підтримку для подальших типів пристроїв",
|
|
30
|
+
"zh-cn": "对设备类型进行重构处理并添加支持"
|
|
31
|
+
},
|
|
6
32
|
"1.1.0": {
|
|
7
33
|
"en": "Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID\nHandle device value updates now correctly",
|
|
8
34
|
"de": "Refactored Device Handling: dynamische Zustände, entfernte Typ Whitelist, gruppierte Geräte unter Master ID\nGerätewert-Updates jetzt korrekt ausschalten",
|
|
@@ -12,6 +12,7 @@ const SmartSocketFactory = require("./comunication/SmartSocketFactory");
|
|
|
12
12
|
const CommonDefines = require("./helpers/CommonDefines");
|
|
13
13
|
const DeviceManager = require("./devices/DeviceManager");
|
|
14
14
|
const { SchellenbergMasterDevice } = require("./devices/SchellenbergMasterDevice");
|
|
15
|
+
const { getDeviceCapabilities } = require("./devices/DeviceKindCapabilities");
|
|
15
16
|
|
|
16
17
|
class SchellenbergBridge {
|
|
17
18
|
constructor(adapter) {
|
|
@@ -63,7 +64,7 @@ class SchellenbergBridge {
|
|
|
63
64
|
this.compatibilityConfigurationVersion,
|
|
64
65
|
this.languageTranslationVersion,
|
|
65
66
|
);
|
|
66
|
-
const response = await this.socket.
|
|
67
|
+
const response = await this.socket.sendAndReceiveCommand(command, this.loginResponse.sessionID);
|
|
67
68
|
|
|
68
69
|
if (!response?.response) {
|
|
69
70
|
return;
|
|
@@ -170,25 +171,34 @@ class SchellenbergBridge {
|
|
|
170
171
|
|
|
171
172
|
// 2️⃣ MasterDevices anlegen
|
|
172
173
|
for (const [masterId, childDevices] of Object.entries(devicesByMaster)) {
|
|
173
|
-
const masterName = childDevices[0].masterDeviceName || childDevices[0].deviceName
|
|
174
|
+
const masterName = (childDevices[0].masterDeviceName || childDevices[0].deviceName).replace(
|
|
175
|
+
/\${|}/g,
|
|
176
|
+
"",
|
|
177
|
+
);
|
|
174
178
|
const masterDevice = new SchellenbergMasterDevice(this.adapter, masterId, masterName, []);
|
|
175
179
|
|
|
176
180
|
// 3️⃣ Child-Devices unter Master anlegen
|
|
177
181
|
for (const child of childDevices) {
|
|
178
|
-
// Nur Devices mit definierbaren
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
// Nur Devices mit definierbaren States
|
|
183
|
+
this.adapter.log.debug(
|
|
184
|
+
`Getting device capabilites from definition: ${JSON.stringify(child.definition)}`,
|
|
185
|
+
);
|
|
186
|
+
const capabilities = getDeviceCapabilities(child.definition);
|
|
181
187
|
|
|
182
|
-
if (!
|
|
188
|
+
if (!capabilities.shouldBeCreated) {
|
|
183
189
|
this.adapter.log.debug(
|
|
184
|
-
`Skipping device ${child.deviceName} (${child.deviceDesignation}) – no definable
|
|
190
|
+
`Skipping device ${child.deviceName} (${child.deviceDesignation}) – no definable states found`,
|
|
185
191
|
);
|
|
186
192
|
continue;
|
|
193
|
+
} else {
|
|
194
|
+
this.adapter.log.debug(
|
|
195
|
+
`Device ${child.deviceID}: kind=${child.definition.deviceType.kind}, capabilities=${JSON.stringify(capabilities)}`,
|
|
196
|
+
);
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
const schellenbergDevice = await this.deviceManager.createDevice({
|
|
190
200
|
id: child.deviceID,
|
|
191
|
-
name: child.deviceName,
|
|
201
|
+
name: child.deviceName.replace(/\${|}/g, ""),
|
|
192
202
|
deviceType: child.deviceTypClient ?? child.definition.deviceType?.kind ?? "unknown",
|
|
193
203
|
designation: child.deviceDesignation,
|
|
194
204
|
definition: child.definition,
|
|
@@ -391,7 +401,7 @@ class SchellenbergBridge {
|
|
|
391
401
|
|
|
392
402
|
sendAndReceiveCommand(command) {
|
|
393
403
|
if (this.socket && this.loginResponse && this.loginResponse.sessionID) {
|
|
394
|
-
return this.socket.
|
|
404
|
+
return this.socket.sendAndReceiveCommand(command, this.loginResponse.sessionID);
|
|
395
405
|
}
|
|
396
406
|
|
|
397
407
|
this.adapter.log.error("Login to the gateway was not successful yet. Ignoring command.");
|
|
@@ -70,7 +70,7 @@ exports.LoginCommand = LoginCommand;
|
|
|
70
70
|
|
|
71
71
|
class allNewInfoCommand extends JSONCommand.default {
|
|
72
72
|
constructor(timestamp, compatibilityConfigurationVersion, languageTranslationVersion) {
|
|
73
|
-
super("getAllNewInfos");
|
|
73
|
+
super("getAllNewInfos", 15000); // higher timeout as this command might take longer to receive a response
|
|
74
74
|
this.timestamp = timestamp;
|
|
75
75
|
this.compatibilityConfigurationVersion = compatibilityConfigurationVersion;
|
|
76
76
|
this.languageTranslationVersion = languageTranslationVersion;
|
|
@@ -169,16 +169,24 @@ class SmartSocket {
|
|
|
169
169
|
//sends command and waits for receive in the promise queue of the message handler
|
|
170
170
|
//don't know if the output socket of the gateway is in-order, so maybe there needs
|
|
171
171
|
//to be some checking
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
sendAndReceiveCommand(command, sessionkey) {
|
|
173
|
+
const timeout = command.timeout ?? 5000; // default timeout is 5 sec
|
|
174
|
+
|
|
175
|
+
const localPromise = new Deferred.default(
|
|
176
|
+
this.adapter,
|
|
177
|
+
(resolve, reject) => {
|
|
178
|
+
if (this.internalSocket && command) {
|
|
179
|
+
this.adapter.log.debug(
|
|
180
|
+
`Send and receive JSON (timeout: ${timeout}ms): ${command.toString(sessionkey)}`,
|
|
181
|
+
);
|
|
182
|
+
this.internalSocket.write(command.toString(sessionkey));
|
|
183
|
+
this.internalSocket.write("\n");
|
|
184
|
+
} else {
|
|
185
|
+
reject();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
timeout,
|
|
189
|
+
);
|
|
182
190
|
if (this.dataDelegate) {
|
|
183
191
|
this.dataDelegate.queueUpPromise(localPromise);
|
|
184
192
|
}
|
|
@@ -35,7 +35,7 @@ class SmartSocketFactory {
|
|
|
35
35
|
.then(socket => {
|
|
36
36
|
if (socket) {
|
|
37
37
|
socket
|
|
38
|
-
.
|
|
38
|
+
.sendAndReceiveCommand(CommandFactory.default.createHeloCmd(username))
|
|
39
39
|
.then(responseHelo => {
|
|
40
40
|
if (responseHelo.response) {
|
|
41
41
|
const parsedResponseHelo = HeloResponse.default.fromObject(
|
|
@@ -52,7 +52,7 @@ class SmartSocketFactory {
|
|
|
52
52
|
parsedResponseHelo.sessionSalt,
|
|
53
53
|
);
|
|
54
54
|
socket
|
|
55
|
-
.
|
|
55
|
+
.sendAndReceiveCommand(
|
|
56
56
|
CommandFactory.default.createLoginCommand(
|
|
57
57
|
username,
|
|
58
58
|
digest,
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
//--------------------------------------------------
|
|
9
9
|
|
|
10
10
|
class JSONCommand {
|
|
11
|
-
constructor(method) {
|
|
11
|
+
constructor(method, timeoutMs = 5000) {
|
|
12
12
|
this.command = method;
|
|
13
|
+
this.timeout = timeoutMs;
|
|
13
14
|
}
|
|
15
|
+
|
|
14
16
|
toString(sessionID) {
|
|
15
17
|
if (sessionID) {
|
|
16
18
|
this.sessionID = sessionID;
|
|
17
19
|
}
|
|
20
|
+
|
|
18
21
|
return JSON.stringify(this);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Declarative registry for device kinds that provide
|
|
5
|
+
* a level-like control (position, brightness, etc.)
|
|
6
|
+
*/
|
|
7
|
+
const LevelKinds = {
|
|
8
|
+
position: {
|
|
9
|
+
role: "level.blind",
|
|
10
|
+
desc: "Position of the device",
|
|
11
|
+
unit: "%",
|
|
12
|
+
defaultMin: 0,
|
|
13
|
+
defaultMax: 100,
|
|
14
|
+
defaultStep: 1,
|
|
15
|
+
},
|
|
16
|
+
brightness: {
|
|
17
|
+
role: "level.dimmer",
|
|
18
|
+
desc: "Brightness of the device",
|
|
19
|
+
unit: "%",
|
|
20
|
+
defaultMin: 0,
|
|
21
|
+
defaultMax: 100,
|
|
22
|
+
defaultStep: 1,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Declarative registry for device kinds that provide
|
|
28
|
+
* a sensor state information (temperature, rain, etc.)
|
|
29
|
+
*/
|
|
30
|
+
const SensorKinds = {
|
|
31
|
+
thermometer: {
|
|
32
|
+
role: "level.temperature",
|
|
33
|
+
desc: "Measured temperature",
|
|
34
|
+
unit: "°C",
|
|
35
|
+
defaultMin: -90,
|
|
36
|
+
defaultMax: 60,
|
|
37
|
+
defaultStep: 0.5,
|
|
38
|
+
},
|
|
39
|
+
volume: {
|
|
40
|
+
role: "value.rain",
|
|
41
|
+
desc: "Measured rain volume",
|
|
42
|
+
unit: "mm",
|
|
43
|
+
defaultMin: 0,
|
|
44
|
+
defaultMax: 350,
|
|
45
|
+
defaultStep: 1,
|
|
46
|
+
},
|
|
47
|
+
// generic sensor kind for different types of weather sensors (e.g. atmospheric pressure, wind speed, etc.)
|
|
48
|
+
weather: {
|
|
49
|
+
role: "value",
|
|
50
|
+
desc: "Measured value",
|
|
51
|
+
unit: "",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Declarative registry for device kinds that provide
|
|
57
|
+
* an alarm information
|
|
58
|
+
*/
|
|
59
|
+
const AlarmKinds = {
|
|
60
|
+
failureStatus: {
|
|
61
|
+
role: "value",
|
|
62
|
+
desc: "Error code of the device",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
AlarmKinds,
|
|
68
|
+
LevelKinds,
|
|
69
|
+
SensorKinds,
|
|
70
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { LevelKinds, SensorKinds, AlarmKinds } = require("./DeviceCapabilityRegistry");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves capabilities from a SmartFriends device definition
|
|
7
|
+
*/
|
|
8
|
+
function getDeviceCapabilities(definition) {
|
|
9
|
+
if (!definition?.deviceType) {
|
|
10
|
+
return {
|
|
11
|
+
shouldBeCreated: false,
|
|
12
|
+
hasWritableStates: false,
|
|
13
|
+
hasSwitchingValues: false,
|
|
14
|
+
hasLevel: false,
|
|
15
|
+
hasSensor: false,
|
|
16
|
+
hasAlarm: false,
|
|
17
|
+
configObj: null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const deviceType = definition?.deviceType ?? {};
|
|
22
|
+
const kind = deviceType.kind ?? "unknown";
|
|
23
|
+
const deviceDesignation = definition?.deviceDesignation?.replace(/\${|}/g, "") ?? kind;
|
|
24
|
+
const model = deviceType.model ?? "unknown";
|
|
25
|
+
|
|
26
|
+
const switchingValues = Array.isArray(deviceType.switchingValues) ? deviceType.switchingValues : [];
|
|
27
|
+
|
|
28
|
+
const hasSwitchingValues = switchingValues.length > 0;
|
|
29
|
+
let configObj = {
|
|
30
|
+
kind,
|
|
31
|
+
name: deviceDesignation,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const levelConfig = LevelKinds[kind] ?? null;
|
|
35
|
+
const sensorConfig = (SensorKinds[kind] && model == "analog") ?? null; // only sensor type "analog" contains measurable values
|
|
36
|
+
const alarmConfig = AlarmKinds[kind] ?? null;
|
|
37
|
+
|
|
38
|
+
if (levelConfig) {
|
|
39
|
+
configObj = {
|
|
40
|
+
...configObj,
|
|
41
|
+
role: levelConfig.role,
|
|
42
|
+
desc: levelConfig.desc,
|
|
43
|
+
unit: levelConfig.unit,
|
|
44
|
+
min: deviceType.min ?? levelConfig.defaultMin,
|
|
45
|
+
max: deviceType.max ?? levelConfig.defaultMax,
|
|
46
|
+
step: deviceType.step ?? levelConfig.defaultStep,
|
|
47
|
+
};
|
|
48
|
+
} else if (sensorConfig) {
|
|
49
|
+
configObj = {
|
|
50
|
+
...configObj,
|
|
51
|
+
role: sensorConfig.role,
|
|
52
|
+
desc: sensorConfig.desc,
|
|
53
|
+
unit: sensorConfig.unit,
|
|
54
|
+
min: deviceType.min ?? sensorConfig.defaultMin,
|
|
55
|
+
max: deviceType.max ?? sensorConfig.defaultMax,
|
|
56
|
+
step: deviceType.step ?? sensorConfig.defaultStep,
|
|
57
|
+
};
|
|
58
|
+
} else if (alarmConfig) {
|
|
59
|
+
configObj = {
|
|
60
|
+
...configObj,
|
|
61
|
+
role: alarmConfig.role,
|
|
62
|
+
desc: alarmConfig.desc,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// map "textOptions" (if present) into configObj as enumerated states
|
|
66
|
+
if (deviceType.textOptions && Array.isArray(deviceType.textOptions) && deviceType.textOptions.length > 0) {
|
|
67
|
+
configObj.states = deviceType.textOptions.reduce((acc, opt) => {
|
|
68
|
+
acc[opt.value] = `${opt.state} (${opt.name.replace(/\${|}/g, "")})`;
|
|
69
|
+
return acc;
|
|
70
|
+
}, {});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
// state creation decision
|
|
76
|
+
shouldBeCreated: hasSwitchingValues || !!levelConfig || !!sensorConfig || !!alarmConfig,
|
|
77
|
+
hasWritableStates: hasSwitchingValues || !!levelConfig,
|
|
78
|
+
// metadata for builders
|
|
79
|
+
hasSwitchingValues,
|
|
80
|
+
hasLevel: !!levelConfig,
|
|
81
|
+
hasSensor: !!sensorConfig,
|
|
82
|
+
hasAlarm: !!alarmConfig,
|
|
83
|
+
// config object for state creation
|
|
84
|
+
configObj: configObj,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
getDeviceCapabilities,
|
|
90
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
const { getDeviceCapabilities } = require("./DeviceKindCapabilities");
|
|
3
4
|
const commonDefines = require("../helpers/CommonDefines");
|
|
4
5
|
|
|
5
6
|
class SchellenbergDevice {
|
|
@@ -16,6 +17,9 @@ class SchellenbergDevice {
|
|
|
16
17
|
|
|
17
18
|
// Creates all necessery states and channels and writes the values into the DB
|
|
18
19
|
async CreateAndSave(masterPrefix) {
|
|
20
|
+
// Dynamisch aus device.definition
|
|
21
|
+
const capabilities = getDeviceCapabilities(this.definition);
|
|
22
|
+
|
|
19
23
|
let devicePrefix = masterPrefix
|
|
20
24
|
? `${masterPrefix}.${this.id}`
|
|
21
25
|
: `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}`;
|
|
@@ -83,65 +87,121 @@ class SchellenbergDevice {
|
|
|
83
87
|
},
|
|
84
88
|
native: {},
|
|
85
89
|
});
|
|
90
|
+
|
|
91
|
+
if (capabilities.hasSensor) {
|
|
92
|
+
await this.createSensorState(infoPrefix, capabilities.configObj);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (capabilities.hasAlarm) {
|
|
96
|
+
await this.createAlarmState(infoPrefix, capabilities.configObj);
|
|
97
|
+
}
|
|
86
98
|
//#endregion
|
|
87
99
|
|
|
88
100
|
//#region CONTROL
|
|
89
|
-
|
|
101
|
+
if (capabilities.hasWritableStates) {
|
|
102
|
+
let controlPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Control}`;
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
await this.adapter.setObjectNotExistsAsync(controlPrefix, {
|
|
105
|
+
type: "channel",
|
|
106
|
+
common: {
|
|
107
|
+
name: "Device control",
|
|
108
|
+
},
|
|
109
|
+
native: {},
|
|
110
|
+
});
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
controlPrefix += ".";
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for (const stateDef of this.definition.deviceType.switchingValues) {
|
|
104
|
-
const stateId = stateDef.name
|
|
105
|
-
.replace(/\${|}/g, "") // ${On} → On
|
|
106
|
-
.replace(this.adapter.FORBIDDEN_CHARS, "")
|
|
107
|
-
.toLowerCase();
|
|
108
|
-
|
|
109
|
-
await this.adapter.setObjectNotExistsAsync(`${controlPrefix}${stateId}`, {
|
|
110
|
-
type: "state",
|
|
111
|
-
common: {
|
|
112
|
-
name: stateId,
|
|
113
|
-
type: "boolean",
|
|
114
|
-
role: "button",
|
|
115
|
-
read: false,
|
|
116
|
-
write: true,
|
|
117
|
-
def: false,
|
|
118
|
-
},
|
|
119
|
-
native: {
|
|
120
|
-
commandValue: stateDef.value,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
114
|
+
if (capabilities.hasSwitchingValues) {
|
|
115
|
+
await this.createSwitchStates(controlPrefix);
|
|
123
116
|
}
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
|
|
118
|
+
if (capabilities.hasLevel) {
|
|
119
|
+
await this.createLevelState(controlPrefix, capabilities.configObj);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
|
|
124
|
+
this.adapter.log.debug(`Created and saved device ${this.id} (${this.name})`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async createSwitchStates(statePrefix) {
|
|
128
|
+
for (const stateDef of this.definition.deviceType.switchingValues) {
|
|
129
|
+
const stateId = stateDef.name
|
|
130
|
+
.replace(/\${|}/g, "") // ${On} → On
|
|
131
|
+
.replace(this.adapter.FORBIDDEN_CHARS, "")
|
|
132
|
+
.toLowerCase();
|
|
133
|
+
|
|
134
|
+
await this.adapter.setObjectNotExistsAsync(`${statePrefix}${stateId}`, {
|
|
126
135
|
type: "state",
|
|
127
136
|
common: {
|
|
128
|
-
name:
|
|
129
|
-
type: "
|
|
130
|
-
role: "
|
|
131
|
-
read:
|
|
137
|
+
name: stateId,
|
|
138
|
+
type: "boolean",
|
|
139
|
+
role: "button",
|
|
140
|
+
read: false,
|
|
132
141
|
write: true,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def: 0,
|
|
142
|
+
def: false,
|
|
143
|
+
},
|
|
144
|
+
native: {
|
|
145
|
+
commandValue: stateDef.value,
|
|
138
146
|
},
|
|
139
|
-
native: {},
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
|
-
|
|
149
|
+
}
|
|
143
150
|
|
|
144
|
-
|
|
151
|
+
async createLevelState(statePrefix, levelConfigObj) {
|
|
152
|
+
await this.adapter.setObjectNotExistsAsync(`${statePrefix}${levelConfigObj.kind}`, {
|
|
153
|
+
type: "state",
|
|
154
|
+
common: {
|
|
155
|
+
name: levelConfigObj.kind,
|
|
156
|
+
desc: levelConfigObj.desc,
|
|
157
|
+
type: "number",
|
|
158
|
+
role: levelConfigObj.role,
|
|
159
|
+
read: true,
|
|
160
|
+
write: true,
|
|
161
|
+
min: levelConfigObj.min,
|
|
162
|
+
max: levelConfigObj.max,
|
|
163
|
+
step: levelConfigObj.step,
|
|
164
|
+
unit: levelConfigObj.unit,
|
|
165
|
+
def: 0,
|
|
166
|
+
},
|
|
167
|
+
native: {},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createSensorState(statePrefix, sensorConfigObj) {
|
|
172
|
+
await this.adapter.setObjectNotExistsAsync(`${statePrefix}${sensorConfigObj.kind}`, {
|
|
173
|
+
type: "state",
|
|
174
|
+
common: {
|
|
175
|
+
name: sensorConfigObj.kind,
|
|
176
|
+
desc: sensorConfigObj.desc,
|
|
177
|
+
type: "number",
|
|
178
|
+
role: sensorConfigObj.role,
|
|
179
|
+
read: true,
|
|
180
|
+
write: false,
|
|
181
|
+
min: sensorConfigObj.min,
|
|
182
|
+
max: sensorConfigObj.max,
|
|
183
|
+
step: sensorConfigObj.step,
|
|
184
|
+
unit: sensorConfigObj.unit,
|
|
185
|
+
def: 0,
|
|
186
|
+
},
|
|
187
|
+
native: {},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async createAlarmState(statePrefix, alarmConfigObj) {
|
|
192
|
+
await this.adapter.setObjectNotExistsAsync(`${statePrefix}${alarmConfigObj.kind}`, {
|
|
193
|
+
type: "state",
|
|
194
|
+
common: {
|
|
195
|
+
name: alarmConfigObj.kind,
|
|
196
|
+
desc: alarmConfigObj.desc,
|
|
197
|
+
type: "number",
|
|
198
|
+
role: alarmConfigObj.role,
|
|
199
|
+
read: true,
|
|
200
|
+
write: false,
|
|
201
|
+
states: alarmConfigObj.states,
|
|
202
|
+
},
|
|
203
|
+
native: {},
|
|
204
|
+
});
|
|
145
205
|
}
|
|
146
206
|
|
|
147
207
|
// Only writes changed data into the DB
|
|
@@ -173,23 +233,44 @@ class SchellenbergDevice {
|
|
|
173
233
|
}
|
|
174
234
|
|
|
175
235
|
let controlPrefix = "";
|
|
236
|
+
let infoPrefix = "";
|
|
176
237
|
if (value.masterDeviceID != this.id) {
|
|
177
238
|
// Child-Device unter einem Master-Device
|
|
178
239
|
controlPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${value.masterDeviceID}.${this.id}.${commonDefines.AdapterDatapointIDs.Control}.`;
|
|
240
|
+
infoPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${value.masterDeviceID}.${this.id}.${commonDefines.AdapterDatapointIDs.Info}.`;
|
|
179
241
|
} else {
|
|
180
242
|
// Einzelnes Device
|
|
181
243
|
controlPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Control}.`;
|
|
244
|
+
infoPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Info}.`;
|
|
182
245
|
}
|
|
183
246
|
|
|
184
|
-
|
|
247
|
+
// Dynamisch aus device.definition
|
|
248
|
+
const capabilities = getDeviceCapabilities(this.definition);
|
|
249
|
+
|
|
250
|
+
if (capabilities.hasSwitchingValues) {
|
|
185
251
|
// SwitchingValues ignorieren, liefern keine Updates zurück
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const stateId = `${controlPrefix}${commonDefines.AdapterStateIDs.Position}`;
|
|
189
|
-
await this.adapter.setStateAsync(stateId, value.value, true);
|
|
190
|
-
this.adapter.log.debug(`Device ${this.id}: Updated position = ${value.value}`);
|
|
252
|
+
this.adapter.log.debug(`Device ${this.id}: switchingValue update ignored (value=${value.value})`);
|
|
253
|
+
return;
|
|
191
254
|
}
|
|
255
|
+
|
|
256
|
+
if (capabilities.hasLevel) {
|
|
257
|
+
await this.setDeviceStateValue(controlPrefix, capabilities.configObj, value);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (capabilities.hasSensor) {
|
|
261
|
+
await this.setDeviceStateValue(infoPrefix, capabilities.configObj, value);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (capabilities.hasAlarm) {
|
|
265
|
+
await this.setDeviceStateValue(infoPrefix, capabilities.configObj, value);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async setDeviceStateValue(statePrefix, configObj, value) {
|
|
270
|
+
const stateId = `${statePrefix}${configObj.kind}`;
|
|
271
|
+
await this.adapter.setStateAsync(stateId, value.value, true);
|
|
272
|
+
this.adapter.log.debug(`Device ${this.id}: Updated ${configObj.kind} = ${value.value}`);
|
|
192
273
|
}
|
|
193
274
|
}
|
|
194
275
|
|
|
195
|
-
exports
|
|
276
|
+
module.exports = SchellenbergDevice;
|
package/lib/helpers/Deferred.js
CHANGED
|
@@ -10,30 +10,38 @@
|
|
|
10
10
|
//--------------------------------------------------
|
|
11
11
|
|
|
12
12
|
class Deferred {
|
|
13
|
-
constructor(adapter, executor) {
|
|
13
|
+
constructor(adapter, executor, timeoutMs = 5000) {
|
|
14
14
|
this.adapter = adapter;
|
|
15
|
+
|
|
15
16
|
this.timeout = this.adapter.setTimeout(() => {
|
|
16
17
|
this.reject("timeout");
|
|
17
|
-
},
|
|
18
|
+
}, timeoutMs);
|
|
19
|
+
|
|
18
20
|
this.promise = new Promise((resolve, reject) => {
|
|
19
21
|
this._resolveSelf = resolve;
|
|
20
22
|
this._rejectSelf = reject;
|
|
21
23
|
});
|
|
24
|
+
|
|
22
25
|
executor.call(this, this._resolveSelf, this._rejectSelf);
|
|
23
26
|
}
|
|
27
|
+
|
|
24
28
|
then(onfulfilled, onrejected) {
|
|
25
29
|
return this.promise.then(onfulfilled, onrejected);
|
|
26
30
|
}
|
|
31
|
+
|
|
27
32
|
catch(onrejected) {
|
|
28
33
|
return this.promise.then(onrejected);
|
|
29
34
|
}
|
|
35
|
+
|
|
30
36
|
finally(onfinally) {
|
|
31
37
|
return this.promise.finally(onfinally);
|
|
32
38
|
}
|
|
39
|
+
|
|
33
40
|
resolve(val) {
|
|
34
41
|
this.adapter.clearTimeout(this.timeout);
|
|
35
42
|
this._resolveSelf(val);
|
|
36
43
|
}
|
|
44
|
+
|
|
37
45
|
reject(reason) {
|
|
38
46
|
this.adapter.clearTimeout(this.timeout);
|
|
39
47
|
this._rejectSelf(reason);
|