iobroker.smartfriends 1.0.1 → 1.1.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/README.md +5 -0
- package/io-package.json +27 -1
- package/lib/SchellenbergBridge.js +145 -52
- package/lib/comunication/CommandFactory.js +15 -50
- package/lib/comunication/HashHelper.js +10 -21
- package/lib/comunication/SmartSocket.js +6 -6
- package/lib/comunication/comModel/JSONCommand.js +2 -2
- package/lib/comunication/comModel/responseBody/NewCompatibilityConfiguration.js +7 -2
- package/lib/devices/DeviceManager.js +125 -0
- package/lib/{SchellenbergDevice.js → devices/SchellenbergDevice.js} +67 -90
- package/lib/devices/SchellenbergMasterDevice.js +26 -0
- package/lib/helpers/CommonDefines.js +1 -19
- package/lib/helpers/{Deffered.js → Deferred.js} +5 -4
- package/main.js +1 -1
- package/package.json +2 -3
- package/lib/DeviceManager.js +0 -121
- package/lib/comunication/DataDelegateInterface.js +0 -7
package/README.md
CHANGED
|
@@ -33,6 +33,11 @@ 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.1.0-alpha.1 (2025-12-28)
|
|
37
|
+
|
|
38
|
+
- (Black-Thunder) Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID
|
|
39
|
+
- (Black-Thunder) Handle device value updates now correctly
|
|
40
|
+
|
|
36
41
|
### 1.0.1 (2025-12-20)
|
|
37
42
|
|
|
38
43
|
- (Black-Thunder) Increased robustness when communicating with the gateway
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "smartfriends",
|
|
4
|
-
"version": "1.0.1",
|
|
4
|
+
"version": "1.1.0-alpha.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.1.0-alpha.1": {
|
|
7
|
+
"en": "Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID\nHandle device value updates now correctly",
|
|
8
|
+
"de": "Refactored Device Handling: dynamische Zustände, entfernte Typ Whitelist, gruppierte Geräte unter Master ID\nGerätewert-Updates jetzt korrekt ausschalten",
|
|
9
|
+
"ru": "Рефакторированная обработка устройств: динамические состояния, удаленный белый список типов, сгруппированные устройства под идентификатором Master ID\nОбновления стоимости устройства теперь правильно",
|
|
10
|
+
"pt": "Manipulação do dispositivo refatorado: estados dinâmicos, lista branca do tipo removido, dispositivos agrupados sob ID mestre\nGerenciar as atualizações de valor do dispositivo agora corretamente",
|
|
11
|
+
"nl": "Refactored device handling: dynamische toestanden, verwijderd type whitelist, gegroepeerde apparaten onder master ID\nHandle apparaat waarde updates nu correct",
|
|
12
|
+
"fr": "Manipulation de l'appareil refacturé : états dynamiques, liste blanche de type enlevé, dispositifs groupés sous Master ID\nGérer les mises à jour de la valeur du périphérique maintenant correctement",
|
|
13
|
+
"it": "Movimentazione del dispositivo refattore: stati dinamici, tipo rimosso whitelist, dispositivi raggruppati sotto master ID\nMantenere gli aggiornamenti del valore del dispositivo ora correttamente",
|
|
14
|
+
"es": "Manejo de dispositivo refactorizado: estados dinámicos, lista blanca de tipo eliminado, dispositivos agrupados bajo ID maestro\nActualizaciones de valor del dispositivo manual ahora correctamente",
|
|
15
|
+
"pl": "Przekształcona obsługa urządzenia: stany dynamiczne, usunięty biały typ, zgrupowane urządzenia pod master ID\nUchwyt aktualizacji wartości urządzenia teraz poprawnie",
|
|
16
|
+
"uk": "Рефакторний пристрій обробки: динамічні стани, видалений тип білий список, вбудовані пристрої під магістр ID\nОновлення значення ручного пристрою тепер правильно",
|
|
17
|
+
"zh-cn": "重构设备处理: 动态状态, 删除类型白名单, 主 ID 下分组设备\n现在正确处理设备值更新"
|
|
18
|
+
},
|
|
19
|
+
"1.1.0-alpha.0": {
|
|
20
|
+
"en": "Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID",
|
|
21
|
+
"de": "Überarbeitete Geräteverwaltung: dynamische Zustände, entfernte Typ-Whitelist, gruppierte Geräte unter Master-ID",
|
|
22
|
+
"ru": "Рефакторированная обработка устройств: динамические состояния, удаленный белый список типов, сгруппированные устройства под идентификатором Master ID",
|
|
23
|
+
"pt": "Manipulação do dispositivo refatorado: estados dinâmicos, lista branca do tipo removido, dispositivos agrupados sob ID mestre",
|
|
24
|
+
"nl": "Refactored device handling: dynamische toestanden, verwijderd type whitelist, gegroepeerde apparaten onder master ID",
|
|
25
|
+
"fr": "Manipulation de l'appareil refacturé : états dynamiques, liste blanche de type enlevé, dispositifs groupés sous Master ID",
|
|
26
|
+
"it": "Movimentazione del dispositivo refattore: stati dinamici, tipo rimosso whitelist, dispositivi raggruppati sotto master ID",
|
|
27
|
+
"es": "Manejo de dispositivo refactorizado: estados dinámicos, lista blanca de tipo eliminado, dispositivos agrupados bajo ID maestro",
|
|
28
|
+
"pl": "Przekształcona obsługa urządzenia: stany dynamiczne, usunięty biały typ, zgrupowane urządzenia pod master ID",
|
|
29
|
+
"uk": "Рефакторний пристрій обробки: динамічні стани, видалений тип білий список, вбудовані пристрої під магістр ID",
|
|
30
|
+
"zh-cn": "重构设备处理: 动态状态, 删除类型白名单, 主 ID 下分组设备"
|
|
31
|
+
},
|
|
6
32
|
"1.0.1": {
|
|
7
33
|
"en": "Increased robustness when communicating with the gateway\nAdded new option to ignore certificate errors",
|
|
8
34
|
"de": "Erhöhte Robustheit bei der Kommunikation mit dem Gateway\nNeue Option hinzugefügt, um Zertifikatsfehler zu ignorieren",
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
//--------------------------------------------------
|
|
4
|
-
//Copyright 2020 Pascâl Hartmann
|
|
5
|
-
//See LICENSE File
|
|
6
|
-
//--------------------------------------------------
|
|
7
|
-
//Handles incoming messages from a SmartSocket and
|
|
8
|
-
//emits Events defined in helpers/Events
|
|
9
|
-
//This is an exmaple Class that implements the DataDelegateInterface which is required by the Socket
|
|
10
|
-
//Implement an App in this Fashion
|
|
11
|
-
//--------------------------------------------------
|
|
12
|
-
|
|
13
3
|
const maxRetries = 3; // number of connection retries when connection was lost
|
|
14
4
|
const reconnectInterval = 10 * 1000; // time (in ms) after which a single new reconnection try should be made
|
|
15
5
|
const retryInterval = 30 * 60 * 1000; // time (in ms) after which new reconnection tries should be made
|
|
16
6
|
|
|
7
|
+
const commonDefines = require("./helpers/CommonDefines");
|
|
17
8
|
const CommandFactory = require("./comunication/CommandFactory");
|
|
18
9
|
const AllNewDeviceInfos = require("./comunication/comModel/responseBody/AllNewDeviceInfos");
|
|
19
10
|
const JSONResponse = require("./comunication/comModel/JSONResponse");
|
|
20
11
|
const SmartSocketFactory = require("./comunication/SmartSocketFactory");
|
|
21
12
|
const CommonDefines = require("./helpers/CommonDefines");
|
|
22
|
-
const DeviceManager = require("./DeviceManager");
|
|
13
|
+
const DeviceManager = require("./devices/DeviceManager");
|
|
14
|
+
const { SchellenbergMasterDevice } = require("./devices/SchellenbergMasterDevice");
|
|
23
15
|
|
|
24
16
|
class SchellenbergBridge {
|
|
25
17
|
constructor(adapter) {
|
|
@@ -39,6 +31,7 @@ class SchellenbergBridge {
|
|
|
39
31
|
this.retryCounter = 0;
|
|
40
32
|
this.stopRenewal = false;
|
|
41
33
|
this.deviceManager = new DeviceManager(this.adapter);
|
|
34
|
+
this.deviceDefinitions = [];
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
async Connect() {
|
|
@@ -72,48 +65,148 @@ class SchellenbergBridge {
|
|
|
72
65
|
);
|
|
73
66
|
const response = await this.socket.sendAndRecieveCommand(command, this.loginResponse.sessionID);
|
|
74
67
|
|
|
75
|
-
if (response?.response) {
|
|
76
|
-
|
|
77
|
-
await this.processAllNewDeviceInfos(parsedResponse);
|
|
68
|
+
if (!response?.response) {
|
|
69
|
+
return;
|
|
78
70
|
}
|
|
71
|
+
|
|
72
|
+
const allNewDeviceInfosParsed = AllNewDeviceInfos.default.fromObject(response.response);
|
|
73
|
+
this.processCompatibilityConfiguration(allNewDeviceInfosParsed);
|
|
74
|
+
await this.processAllNewDeviceInfos(allNewDeviceInfosParsed);
|
|
75
|
+
this.processInitialDeviceValues(allNewDeviceInfosParsed);
|
|
79
76
|
} catch (err) {
|
|
80
|
-
|
|
81
|
-
this.
|
|
77
|
+
const msg = err?.responseMessage || err.message || JSON.stringify(err);
|
|
78
|
+
this.adapter.log.error(`Connection failed: ${msg}`);
|
|
79
|
+
|
|
80
|
+
if (err?.response?.errorCode === 50 && err?.response?.remainingBlockDuration) {
|
|
81
|
+
{
|
|
82
|
+
this.adapter.log.error(
|
|
83
|
+
`Login to the gateway is currently blocked. Please try again in ${err?.response?.remainingBlockDuration} second(s)`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.handleDisconnect();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updateTimestamp(parsed) {
|
|
93
|
+
if (parsed.currentTimestamp) {
|
|
94
|
+
this.lastTimestamp = parsed.currentTimestamp;
|
|
95
|
+
this.adapter.log.debug(`Updated timestamp to ${this.lastTimestamp}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
processCompatibilityConfiguration(parsed) {
|
|
100
|
+
if (this.deviceDefinitions?.length) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const standards = parsed.newCompatibilityConfiguration?.compatibleRadioStandards ?? [];
|
|
105
|
+
this.deviceDefinitions = [];
|
|
106
|
+
|
|
107
|
+
for (const standard of standards) {
|
|
108
|
+
for (const def of standard.compatibleDevices ?? []) {
|
|
109
|
+
if (def.deviceDesignation) {
|
|
110
|
+
this.deviceDefinitions.push(def);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.adapter.log.debug(`Loaded ${this.deviceDefinitions.length} device definitions`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
processInitialDeviceValues(parsed) {
|
|
119
|
+
const values = parsed.newDeviceValues?.values ?? [];
|
|
120
|
+
|
|
121
|
+
for (const value of values) {
|
|
122
|
+
this.handleDeviceValue(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
handleDeviceValue(deviceValue) {
|
|
127
|
+
if (!deviceValue || deviceValue.deviceID == null) {
|
|
128
|
+
this.adapter.log.debug("Invalid device value received, ignoring.");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.adapter.log.debug(`Device value update: deviceID=${deviceValue.deviceID}, value=${deviceValue.value}`);
|
|
133
|
+
|
|
134
|
+
this.deviceManager.updateDeviceValue(deviceValue);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
handleDeviceInfo(deviceInfo) {
|
|
138
|
+
if (!deviceInfo || deviceInfo.deviceName || !deviceInfo.deviceDesignation) {
|
|
139
|
+
this.adapter.log.debug("Invalid device info received, ignoring.");
|
|
140
|
+
return;
|
|
82
141
|
}
|
|
142
|
+
|
|
143
|
+
this.adapter.log.debug(`New device info received for deviceID=${deviceInfo.deviceID}`);
|
|
144
|
+
// TODO: implement updates of device info if needed
|
|
83
145
|
}
|
|
84
146
|
|
|
85
147
|
async processAllNewDeviceInfos(response) {
|
|
86
148
|
try {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for (const device of
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
device.deviceTypClient.includes(CommonDefines.KnownDeviceTypes.RollingShutter.type))
|
|
97
|
-
) {
|
|
98
|
-
knownDevices++;
|
|
99
|
-
await this.deviceManager.createDevice({
|
|
100
|
-
id: device.deviceID,
|
|
101
|
-
name: device.deviceName,
|
|
102
|
-
deviceType: device.deviceTypClient,
|
|
103
|
-
designation: device.deviceDesignation,
|
|
104
|
-
});
|
|
105
|
-
this.adapter.log.info(
|
|
106
|
-
`Device created: ${device.deviceName} (Type: ${device.deviceTypClient}, Device ID: ${device.deviceID})`,
|
|
107
|
-
);
|
|
108
|
-
} else {
|
|
149
|
+
const deviceInfos = response.newDeviceInfos?.values ?? [];
|
|
150
|
+
const createdMasters = [];
|
|
151
|
+
|
|
152
|
+
// 1️⃣ Child-Devices nach MasterID gruppieren
|
|
153
|
+
const devicesByMaster = {};
|
|
154
|
+
for (const device of deviceInfos) {
|
|
155
|
+
// Definition dynamisch zuweisen
|
|
156
|
+
device.definition = this.deviceDefinitions.find(d => d.deviceDesignation === device.deviceDesignation);
|
|
157
|
+
if (!device.definition) {
|
|
109
158
|
this.adapter.log.debug(
|
|
110
|
-
`
|
|
159
|
+
`Skipping device ${device.deviceName} (${device.deviceDesignation}) – no definition found`,
|
|
111
160
|
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const masterId = device.masterDeviceID || device.deviceID;
|
|
165
|
+
if (!devicesByMaster[masterId]) {
|
|
166
|
+
devicesByMaster[masterId] = [];
|
|
167
|
+
}
|
|
168
|
+
devicesByMaster[masterId].push(device);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2️⃣ MasterDevices anlegen
|
|
172
|
+
for (const [masterId, childDevices] of Object.entries(devicesByMaster)) {
|
|
173
|
+
const masterName = childDevices[0].masterDeviceName || childDevices[0].deviceName;
|
|
174
|
+
const masterDevice = new SchellenbergMasterDevice(this.adapter, masterId, masterName, []);
|
|
175
|
+
|
|
176
|
+
// 3️⃣ Child-Devices unter Master anlegen
|
|
177
|
+
for (const child of childDevices) {
|
|
178
|
+
// Nur Devices mit definierbaren Control-States
|
|
179
|
+
const hasSwitching = child.definition.deviceType?.switchingValues?.length;
|
|
180
|
+
const isPosition = child.definition.deviceType?.kind === commonDefines.AdapterStateIDs.Position;
|
|
181
|
+
|
|
182
|
+
if (!hasSwitching && !isPosition) {
|
|
183
|
+
this.adapter.log.debug(
|
|
184
|
+
`Skipping device ${child.deviceName} (${child.deviceDesignation}) – no definable control states found`,
|
|
185
|
+
);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const schellenbergDevice = await this.deviceManager.createDevice({
|
|
190
|
+
id: child.deviceID,
|
|
191
|
+
name: child.deviceName,
|
|
192
|
+
deviceType: child.deviceTypClient ?? child.definition.deviceType?.kind ?? "unknown",
|
|
193
|
+
designation: child.deviceDesignation,
|
|
194
|
+
definition: child.definition,
|
|
195
|
+
masterPrefix: `${commonDefines.AdapterDatapointIDs.Devices}.${masterId}`, // States unter Master-Ordner
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
masterDevice.childDevices.push(schellenbergDevice);
|
|
199
|
+
this.adapter.log.info(`Device created: ${child.deviceName} (ID: ${child.deviceID})`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (masterDevice.childDevices?.length !== 0) {
|
|
203
|
+
await masterDevice.createMasterFolder();
|
|
204
|
+
createdMasters.push(masterDevice);
|
|
112
205
|
}
|
|
113
206
|
}
|
|
114
207
|
|
|
115
208
|
this.adapter.log.info(
|
|
116
|
-
`Processed ${
|
|
209
|
+
`Processed ${deviceInfos.length} device(s), created ${createdMasters.length} master device(s).`,
|
|
117
210
|
);
|
|
118
211
|
} catch (error) {
|
|
119
212
|
this.adapter.log.error(`Error processing devices: ${error.message}`);
|
|
@@ -180,6 +273,7 @@ class SchellenbergBridge {
|
|
|
180
273
|
[1]: r => {
|
|
181
274
|
this.retryCounter = 0;
|
|
182
275
|
this.resolveNextPromise(r);
|
|
276
|
+
this.updateTimestamp(r);
|
|
183
277
|
},
|
|
184
278
|
[2]: r => this.handleUpdate(r),
|
|
185
279
|
[5]: r => this.handleSpecificResponseCode(r, 5),
|
|
@@ -239,12 +333,12 @@ class SchellenbergBridge {
|
|
|
239
333
|
if (this.retryCounter < maxRetries) {
|
|
240
334
|
this.retryCounter++;
|
|
241
335
|
this.adapter.log.warn(`Reconnecting (try ${this.retryCounter} of ${maxRetries})...`);
|
|
242
|
-
setTimeout(() => this.Connect(), reconnectInterval);
|
|
336
|
+
this.adapter.setTimeout(() => this.Connect(), reconnectInterval);
|
|
243
337
|
} else {
|
|
244
338
|
this.adapter.log.warn(
|
|
245
339
|
`Connection to gateway lost, connection temporarily disabled! Trying again in ${retryInterval / (60 * 1000)} minutes.`,
|
|
246
340
|
);
|
|
247
|
-
setTimeout(() => this.Connect(), retryInterval);
|
|
341
|
+
this.adapter.setTimeout(() => this.Connect(), retryInterval);
|
|
248
342
|
}
|
|
249
343
|
}
|
|
250
344
|
|
|
@@ -272,7 +366,7 @@ class SchellenbergBridge {
|
|
|
272
366
|
|
|
273
367
|
async handleLoginMessage(response) {
|
|
274
368
|
if (response.sessionID && response.hardware && response.macAddress && response.shsVersion) {
|
|
275
|
-
this.adapter.log.debug(
|
|
369
|
+
this.adapter.log.debug(`Connection and login to gateway ${response.hardware} successful.`);
|
|
276
370
|
this.loginResponse = response;
|
|
277
371
|
this.retryCounter = 0;
|
|
278
372
|
|
|
@@ -309,14 +403,13 @@ class SchellenbergBridge {
|
|
|
309
403
|
}
|
|
310
404
|
|
|
311
405
|
handleUpdate(response) {
|
|
312
|
-
if (response.responseMessage) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
break;
|
|
406
|
+
if (response && response.response && response.responseMessage) {
|
|
407
|
+
if (response.responseMessage === "newDeviceInfo") {
|
|
408
|
+
this.updateTimestamp(response);
|
|
409
|
+
this.handleDeviceInfo(response.response);
|
|
410
|
+
} else if (response.responseMessage === "newDeviceValue") {
|
|
411
|
+
this.updateTimestamp(response);
|
|
412
|
+
this.handleDeviceValue(response.response);
|
|
320
413
|
}
|
|
321
414
|
}
|
|
322
415
|
}
|
|
@@ -327,7 +420,7 @@ class SchellenbergBridge {
|
|
|
327
420
|
}
|
|
328
421
|
|
|
329
422
|
this.adapter.log.warn(`Socket renewal was requested with reason: ${reason}`);
|
|
330
|
-
setTimeout(() => {
|
|
423
|
+
this.adapter.setTimeout(() => {
|
|
331
424
|
this.Connect();
|
|
332
425
|
}, 1000);
|
|
333
426
|
}
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
//--------------------------------------------------
|
|
7
7
|
//Uses Command Wrapper to generate JSON-Commands
|
|
8
8
|
|
|
9
|
-
//exports.setDeviceValueCommand = exports.allNewInfoCommand = exports.loginCommand = exports.heloCommand = void 0;
|
|
10
9
|
const JSONCommand = require("./comModel/JSONCommand.js");
|
|
11
10
|
const JSONHelper = require("./comModel/JSONHelper.js");
|
|
11
|
+
|
|
12
12
|
class CommandFactory {
|
|
13
13
|
static createLoginCommand(username, digest, cSymbol, shcVersion, shApiVersion) {
|
|
14
|
-
return new
|
|
14
|
+
return new LoginCommand(username, digest, cSymbol, shcVersion, shApiVersion);
|
|
15
15
|
}
|
|
16
|
+
|
|
16
17
|
static createAllNewInfoCmd(timestamp, compatibilityConfigurationVersion, languageTranslationVersion) {
|
|
17
18
|
return new allNewInfoCommand(
|
|
18
19
|
JSONHelper.default.dateToString(timestamp),
|
|
@@ -20,26 +21,33 @@ class CommandFactory {
|
|
|
20
21
|
languageTranslationVersion,
|
|
21
22
|
);
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
static createSetDeviceValueCmd(inDeviceId, inValue) {
|
|
24
26
|
return new setDeviceValueCommand(inDeviceId, inValue);
|
|
25
27
|
}
|
|
28
|
+
|
|
26
29
|
static createupdateAllDeviceValues() {
|
|
27
30
|
return new JSONCommand.default("keepalive");
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
static createGetComptibilityConfigurationCmd() {
|
|
30
34
|
return new JSONCommand.default("getCompatibilityConfiguration");
|
|
31
35
|
}
|
|
36
|
+
|
|
32
37
|
static createLogoutCmd() {
|
|
33
38
|
return new JSONCommand.default("logout");
|
|
34
39
|
}
|
|
40
|
+
|
|
35
41
|
static createKeepAliveCmd() {
|
|
36
42
|
return new JSONCommand.default("keepalive");
|
|
37
43
|
}
|
|
44
|
+
|
|
38
45
|
static createHeloCmd(username) {
|
|
39
46
|
return new heloCommand(username);
|
|
40
47
|
}
|
|
41
48
|
}
|
|
42
49
|
exports.default = CommandFactory;
|
|
50
|
+
|
|
43
51
|
class heloCommand extends JSONCommand.default {
|
|
44
52
|
constructor(username) {
|
|
45
53
|
super("helo");
|
|
@@ -47,7 +55,8 @@ class heloCommand extends JSONCommand.default {
|
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
exports.heloCommand = heloCommand;
|
|
50
|
-
|
|
58
|
+
|
|
59
|
+
class LoginCommand extends JSONCommand.default {
|
|
51
60
|
constructor(username, digest, cSymbol, shcVersion, shApiVersion) {
|
|
52
61
|
super("login");
|
|
53
62
|
this.username = username;
|
|
@@ -57,7 +66,8 @@ class loginCommand extends JSONCommand.default {
|
|
|
57
66
|
this.shApiVersion = shApiVersion;
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
|
-
exports.
|
|
69
|
+
exports.LoginCommand = LoginCommand;
|
|
70
|
+
|
|
61
71
|
class allNewInfoCommand extends JSONCommand.default {
|
|
62
72
|
constructor(timestamp, compatibilityConfigurationVersion, languageTranslationVersion) {
|
|
63
73
|
super("getAllNewInfos");
|
|
@@ -67,6 +77,7 @@ class allNewInfoCommand extends JSONCommand.default {
|
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
79
|
exports.allNewInfoCommand = allNewInfoCommand;
|
|
80
|
+
|
|
70
81
|
class setDeviceValueCommand extends JSONCommand.default {
|
|
71
82
|
constructor(deviceID, value) {
|
|
72
83
|
super("setDeviceValue");
|
|
@@ -75,49 +86,3 @@ class setDeviceValueCommand extends JSONCommand.default {
|
|
|
75
86
|
}
|
|
76
87
|
}
|
|
77
88
|
exports.setDeviceValueCommand = setDeviceValueCommand;
|
|
78
|
-
//TODO: implementation nedded
|
|
79
|
-
//class executeDeviceCmdCmd extends Command{
|
|
80
|
-
// constructor(){
|
|
81
|
-
// super('')
|
|
82
|
-
// }
|
|
83
|
-
//}
|
|
84
|
-
//class newDeviceCmd extends Command{
|
|
85
|
-
// constructor(){
|
|
86
|
-
// super('')
|
|
87
|
-
// }
|
|
88
|
-
//}
|
|
89
|
-
//class changeDeviceCmd extends Command{
|
|
90
|
-
// constructor(){
|
|
91
|
-
// super('')
|
|
92
|
-
// }
|
|
93
|
-
//}
|
|
94
|
-
//class changeRoomCmd extends Command{
|
|
95
|
-
// constructor(){
|
|
96
|
-
// super('')
|
|
97
|
-
// }
|
|
98
|
-
//}
|
|
99
|
-
//class deleteRoomCmd extends Command{
|
|
100
|
-
// constructor(){
|
|
101
|
-
// super('')
|
|
102
|
-
// }
|
|
103
|
-
//}
|
|
104
|
-
//class newSwitchingSequenceCmd extends Command{
|
|
105
|
-
// constructor(){
|
|
106
|
-
// super('')
|
|
107
|
-
// }
|
|
108
|
-
//}
|
|
109
|
-
//class changeSwitchingSequenceCmd extends Command{
|
|
110
|
-
// constructor(){
|
|
111
|
-
// super('')
|
|
112
|
-
// }
|
|
113
|
-
//}
|
|
114
|
-
//class deleteSwitchingSequenceCmd extends Command{
|
|
115
|
-
// constructor(){
|
|
116
|
-
// super('')
|
|
117
|
-
// }
|
|
118
|
-
//}
|
|
119
|
-
//class activateSwitchingSequenceCmd extends Command{
|
|
120
|
-
// constructor(){
|
|
121
|
-
// super('')
|
|
122
|
-
// }
|
|
123
|
-
//}
|
|
@@ -10,32 +10,21 @@
|
|
|
10
10
|
//--------------------------------------------------
|
|
11
11
|
|
|
12
12
|
const crypto = require("crypto");
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
class HashHelper {
|
|
15
15
|
static calculateDigest(password, salt, sessionSalt) {
|
|
16
16
|
const hashedPassword = this.getHash("sha256", password, salt);
|
|
17
17
|
return this.getHash("sha1", hashedPassword, sessionSalt);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
static
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
result.push(p);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
static getHash(method, password, salt) {
|
|
33
|
-
const decode = base_64.decode(salt);
|
|
34
|
-
const saltArray = this.string2Bin(decode);
|
|
35
|
-
const passwordArray = this.string2Bin(password);
|
|
36
|
-
const pasConSalt = passwordArray.concat(saltArray);
|
|
37
|
-
const cryptHash = crypto.createHash(method).update(new Uint8Array(pasConSalt));
|
|
38
|
-
return cryptHash.digest("base64");
|
|
19
|
+
|
|
20
|
+
static getHash(method, password, saltBase64) {
|
|
21
|
+
const saltBuffer = Buffer.from(saltBase64, "base64");
|
|
22
|
+
const passwordBuffer = Buffer.from(password, "utf8");
|
|
23
|
+
|
|
24
|
+
const combined = Buffer.concat([passwordBuffer, saltBuffer]);
|
|
25
|
+
|
|
26
|
+
return crypto.createHash(method).update(combined).digest("base64");
|
|
39
27
|
}
|
|
40
28
|
}
|
|
29
|
+
|
|
41
30
|
exports.default = HashHelper;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//events occurring and passing on received data
|
|
9
9
|
//--------------------------------------------------
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const Deferred = require("../helpers/Deferred.js");
|
|
12
12
|
const tls = require("tls");
|
|
13
13
|
const CommandFactory = require("./CommandFactory.js");
|
|
14
14
|
|
|
@@ -110,7 +110,7 @@ class SmartSocket {
|
|
|
110
110
|
});
|
|
111
111
|
this.internalSocket.on("timeout", () => {
|
|
112
112
|
this.adapter.log.warn("Socket timed out");
|
|
113
|
-
setTimeout(() => {
|
|
113
|
+
this.adapter.setTimeout(() => {
|
|
114
114
|
if (this.dataDelegate) {
|
|
115
115
|
this.dataDelegate.renewSocket("Timeout");
|
|
116
116
|
}
|
|
@@ -136,14 +136,14 @@ class SmartSocket {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
startKeepAlive() {
|
|
139
|
-
this.keepAliveHandler = setInterval(() => {
|
|
139
|
+
this.keepAliveHandler = this.adapter.setInterval(() => {
|
|
140
140
|
this.sendJSONCommand(CommandFactory.default.createKeepAliveCmd());
|
|
141
141
|
}, 5000);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
stopKeepAlive() {
|
|
145
145
|
if (this.keepAliveHandler) {
|
|
146
|
-
clearInterval(this.keepAliveHandler);
|
|
146
|
+
this.adapter.clearInterval(this.keepAliveHandler);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -152,7 +152,7 @@ class SmartSocket {
|
|
|
152
152
|
//be advised, that a may occurring response is discarded and may result in an error
|
|
153
153
|
sendJSONCommand(command) {
|
|
154
154
|
this.adapter.log.debug(`Sending command with method: ${command.command}`);
|
|
155
|
-
const localPromise = new
|
|
155
|
+
const localPromise = new Deferred.default(this.adapter, (resolve, reject) => {
|
|
156
156
|
if (this.internalSocket && command) {
|
|
157
157
|
this.adapter.log.debug(`Send JSON: ${command.toString()}`);
|
|
158
158
|
this.internalSocket.write(command.toString());
|
|
@@ -170,7 +170,7 @@ class SmartSocket {
|
|
|
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
172
|
sendAndRecieveCommand(command, sessionkey) {
|
|
173
|
-
const localPromise = new
|
|
173
|
+
const localPromise = new Deferred.default(this.adapter, (resolve, reject) => {
|
|
174
174
|
if (this.internalSocket && command) {
|
|
175
175
|
this.adapter.log.debug(`Send and receive JSON: ${command.toString(sessionkey)}`);
|
|
176
176
|
this.internalSocket.write(command.toString(sessionkey));
|
|
@@ -6,11 +6,16 @@
|
|
|
6
6
|
//--------------------------------------------------
|
|
7
7
|
|
|
8
8
|
class NewCompatibilityConfiguration {
|
|
9
|
-
constructor(compatibilityConfigurationVersion) {
|
|
9
|
+
constructor(compatibilityConfigurationVersion, compatibleRadioStandards = []) {
|
|
10
10
|
this.compatibilityConfigurationVersion = compatibilityConfigurationVersion;
|
|
11
|
+
this.compatibleRadioStandards = compatibleRadioStandards;
|
|
11
12
|
}
|
|
13
|
+
|
|
12
14
|
static fromObject(object) {
|
|
13
|
-
return new NewCompatibilityConfiguration(
|
|
15
|
+
return new NewCompatibilityConfiguration(
|
|
16
|
+
object.compatibilityConfigurationVersion,
|
|
17
|
+
object.compatibleRadioStandards ?? [],
|
|
18
|
+
);
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
exports.default = NewCompatibilityConfiguration;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const { SchellenbergDevice } = require("./SchellenbergDevice");
|
|
2
|
+
const commonDefines = require("../helpers/CommonDefines");
|
|
3
|
+
const commandFactory = require("../comunication/CommandFactory");
|
|
4
|
+
|
|
5
|
+
class DeviceManager {
|
|
6
|
+
constructor(adapter) {
|
|
7
|
+
this.adapter = adapter;
|
|
8
|
+
this.devices = new Map();
|
|
9
|
+
this.log = adapter.log;
|
|
10
|
+
this.bridge = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
setBridge(bridge) {
|
|
14
|
+
this.bridge = bridge;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async createDevice(deviceInfo) {
|
|
18
|
+
try {
|
|
19
|
+
const device = new SchellenbergDevice(this.adapter);
|
|
20
|
+
Object.assign(device, deviceInfo);
|
|
21
|
+
await device.CreateAndSave(deviceInfo.masterPrefix);
|
|
22
|
+
this.devices.set(deviceInfo.id, device);
|
|
23
|
+
return device;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
this.log.error(`Failed to create device ${deviceInfo.id}: ${error.message}`);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getDevice(id) {
|
|
31
|
+
return this.devices.get(id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getAllDevices() {
|
|
35
|
+
return Array.from(this.devices.values());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async updateDevice(id, data) {
|
|
39
|
+
const device = this.getDevice(id);
|
|
40
|
+
if (device) {
|
|
41
|
+
Object.assign(device, data);
|
|
42
|
+
await device.Update();
|
|
43
|
+
this.log.debug(`Device updated: ${id}`);
|
|
44
|
+
} else {
|
|
45
|
+
this.log.warn(`Device not found for update: ${id}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async deleteDevice(id) {
|
|
50
|
+
const device = this.getDevice(id);
|
|
51
|
+
if (device) {
|
|
52
|
+
await device.Delete();
|
|
53
|
+
this.devices.delete(id);
|
|
54
|
+
this.log.debug(`Device deleted: ${id}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async handleStateChange(id, state) {
|
|
59
|
+
try {
|
|
60
|
+
if (!state || state.ack) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const devicePrefix = `${this.adapter.namespace}.${commonDefines.AdapterDatapointIDs.Devices}.`;
|
|
65
|
+
const path = id.replace(devicePrefix, "");
|
|
66
|
+
const parts = path.split(".");
|
|
67
|
+
|
|
68
|
+
let childId;
|
|
69
|
+
if (parts.length === 1) {
|
|
70
|
+
// nur ein Device, kein Master
|
|
71
|
+
childId = parts[0];
|
|
72
|
+
} else if (parts.length >= 2) {
|
|
73
|
+
// Master + Child
|
|
74
|
+
childId = parts[1];
|
|
75
|
+
} else {
|
|
76
|
+
this.adapter.log.warn(`Invalid state path: ${id}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
81
|
+
|
|
82
|
+
const commandValue = typeof state.val === "number" ? state.val : obj?.native?.commandValue;
|
|
83
|
+
|
|
84
|
+
if (commandValue == null) {
|
|
85
|
+
this.adapter.log.warn(`No commandValue defined for ${id}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.adapter.log.debug(`Control triggered: device=${childId}, command=${commandValue} (state=${id})`);
|
|
90
|
+
|
|
91
|
+
await this.sendDeviceCommand(childId, commandValue);
|
|
92
|
+
|
|
93
|
+
await this.adapter.setStateAsync(id, false, true);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.log.error(`Error in handleStateChange: ${error.responseMessage}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async sendDeviceCommand(deviceId, value) {
|
|
100
|
+
try {
|
|
101
|
+
if (!this.bridge) {
|
|
102
|
+
throw new Error("Bridge not initialized");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.log.debug(`Sending value '${value}' to device ${deviceId}...`);
|
|
106
|
+
|
|
107
|
+
await this.bridge.sendAndReceiveCommand(commandFactory.default.createSetDeviceValueCmd(deviceId, value));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.log.error(`Error in sendDeviceCommand: ${error.responseMessage}`);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
updateDeviceValue(deviceValue) {
|
|
115
|
+
const device = this.devices?.get(deviceValue.deviceID);
|
|
116
|
+
if (!device) {
|
|
117
|
+
this.adapter.log.debug(`Received value for unknown device ${deviceValue.deviceID}, ignoring.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
device.updateValue(deviceValue);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = DeviceManager;
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const commonDefines = require("
|
|
3
|
+
const commonDefines = require("../helpers/CommonDefines");
|
|
4
4
|
|
|
5
5
|
class SchellenbergDevice {
|
|
6
6
|
constructor(adapter) {
|
|
7
7
|
this.adapter = adapter;
|
|
8
|
-
this.
|
|
8
|
+
this.definition = null;
|
|
9
9
|
|
|
10
10
|
// Info
|
|
11
11
|
this.id = -1;
|
|
12
12
|
this.name = "";
|
|
13
13
|
this.deviceType = "";
|
|
14
14
|
this.designation = "";
|
|
15
|
-
|
|
16
|
-
// Control
|
|
17
|
-
this.power = false;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
// Creates all necessery states and channels and writes the values into the DB
|
|
21
|
-
async CreateAndSave() {
|
|
22
|
-
|
|
18
|
+
async CreateAndSave(masterPrefix) {
|
|
19
|
+
let devicePrefix = masterPrefix
|
|
20
|
+
? `${masterPrefix}.${this.id}`
|
|
21
|
+
: `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}`;
|
|
22
|
+
|
|
23
|
+
devicePrefix = devicePrefix.replace(this.adapter.FORBIDDEN_CHARS, "");
|
|
24
|
+
|
|
23
25
|
await this.adapter.setObjectNotExistsAsync(devicePrefix, {
|
|
24
26
|
type: "channel",
|
|
25
27
|
common: {
|
|
26
|
-
name:
|
|
28
|
+
name: this.name,
|
|
27
29
|
},
|
|
28
30
|
native: {},
|
|
29
31
|
});
|
|
@@ -85,6 +87,7 @@ class SchellenbergDevice {
|
|
|
85
87
|
|
|
86
88
|
//#region CONTROL
|
|
87
89
|
let controlPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Control}`;
|
|
90
|
+
|
|
88
91
|
await this.adapter.setObjectNotExistsAsync(controlPrefix, {
|
|
89
92
|
type: "channel",
|
|
90
93
|
common: {
|
|
@@ -95,98 +98,46 @@ class SchellenbergDevice {
|
|
|
95
98
|
|
|
96
99
|
controlPrefix += ".";
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
role: "button.start",
|
|
106
|
-
read: false,
|
|
107
|
-
write: true,
|
|
108
|
-
def: false,
|
|
109
|
-
desc: "Move downwards, lower sunblind",
|
|
110
|
-
},
|
|
111
|
-
native: {},
|
|
112
|
-
});
|
|
101
|
+
// Dynamisch aus device.definition
|
|
102
|
+
if (this.definition?.deviceType?.switchingValues) {
|
|
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();
|
|
113
108
|
|
|
114
|
-
await this.adapter.setObjectNotExistsAsync(controlPrefix
|
|
109
|
+
await this.adapter.setObjectNotExistsAsync(`${controlPrefix}${stateId}`, {
|
|
115
110
|
type: "state",
|
|
116
111
|
common: {
|
|
117
|
-
name:
|
|
112
|
+
name: stateId,
|
|
118
113
|
type: "boolean",
|
|
119
|
-
role: "button
|
|
114
|
+
role: "button",
|
|
120
115
|
read: false,
|
|
121
116
|
write: true,
|
|
122
117
|
def: false,
|
|
123
|
-
desc: "Move upwards, retract sunblind",
|
|
124
118
|
},
|
|
125
|
-
native: {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
await this.adapter.setObjectNotExistsAsync(controlPrefix + commonDefines.AdapterStateIDs.MoveStop, {
|
|
129
|
-
type: "state",
|
|
130
|
-
common: {
|
|
131
|
-
name: "Stop movement",
|
|
132
|
-
type: "boolean",
|
|
133
|
-
role: "button.stop",
|
|
134
|
-
read: false,
|
|
135
|
-
write: true,
|
|
136
|
-
def: false,
|
|
137
|
-
desc: "Stop movement, leave sunblind at current position",
|
|
119
|
+
native: {
|
|
120
|
+
commandValue: stateDef.value,
|
|
138
121
|
},
|
|
139
|
-
native: {},
|
|
140
122
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
common: {
|
|
160
|
-
name: "Open shutter",
|
|
161
|
-
type: "boolean",
|
|
162
|
-
role: "button.start",
|
|
163
|
-
read: false,
|
|
164
|
-
write: true,
|
|
165
|
-
def: false,
|
|
166
|
-
desc: "Open shutter",
|
|
167
|
-
},
|
|
168
|
-
native: {},
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
await this.adapter.setObjectNotExistsAsync(controlPrefix + commonDefines.AdapterStateIDs.MoveStop, {
|
|
172
|
-
type: "state",
|
|
173
|
-
common: {
|
|
174
|
-
name: "Stop movement",
|
|
175
|
-
type: "boolean",
|
|
176
|
-
role: "button.stop",
|
|
177
|
-
read: false,
|
|
178
|
-
write: true,
|
|
179
|
-
def: false,
|
|
180
|
-
desc: "Stop shutter movement",
|
|
181
|
-
},
|
|
182
|
-
native: {},
|
|
183
|
-
});
|
|
184
|
-
break;
|
|
185
|
-
default:
|
|
186
|
-
this.adapter.log.error(
|
|
187
|
-
`No controls known for device type '${this.deviceType}' - Report this to the developer!`,
|
|
188
|
-
);
|
|
189
|
-
break;
|
|
123
|
+
}
|
|
124
|
+
} else if (this.definition.deviceType.kind === commonDefines.AdapterStateIDs.Position) {
|
|
125
|
+
await this.adapter.setObjectNotExistsAsync(`${controlPrefix}${commonDefines.AdapterStateIDs.Position}`, {
|
|
126
|
+
type: "state",
|
|
127
|
+
common: {
|
|
128
|
+
name: commonDefines.AdapterStateIDs.Position,
|
|
129
|
+
type: "number",
|
|
130
|
+
role: "level.blind",
|
|
131
|
+
read: true,
|
|
132
|
+
write: true,
|
|
133
|
+
min: this.definition.deviceType.min ?? 0,
|
|
134
|
+
max: this.definition.deviceType.max ?? 100,
|
|
135
|
+
step: this.definition.deviceType.step ?? 1,
|
|
136
|
+
unit: "%",
|
|
137
|
+
def: 0,
|
|
138
|
+
},
|
|
139
|
+
native: {},
|
|
140
|
+
});
|
|
190
141
|
}
|
|
191
142
|
//#endregion
|
|
192
143
|
|
|
@@ -213,6 +164,32 @@ class SchellenbergDevice {
|
|
|
213
164
|
|
|
214
165
|
this.adapter.log.debug(`Updated device data for device ${this.id} (${this.name})`);
|
|
215
166
|
}
|
|
167
|
+
|
|
168
|
+
async updateValue(value) {
|
|
169
|
+
// value: Instanz von DeviceValue
|
|
170
|
+
if (value.deviceID !== this.id) {
|
|
171
|
+
// Diese Instanz betrifft nicht dieses Device
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let controlPrefix = "";
|
|
176
|
+
if (value.masterDeviceID != this.id) {
|
|
177
|
+
// Child-Device unter einem Master-Device
|
|
178
|
+
controlPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${value.masterDeviceID}.${this.id}.${commonDefines.AdapterDatapointIDs.Control}.`;
|
|
179
|
+
} else {
|
|
180
|
+
// Einzelnes Device
|
|
181
|
+
controlPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Control}.`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.definition?.deviceType?.switchingValues) {
|
|
185
|
+
// SwitchingValues ignorieren, liefern keine Updates zurück
|
|
186
|
+
} else if (this.definition?.deviceType?.kind === commonDefines.AdapterStateIDs.Position) {
|
|
187
|
+
// Für Position → direkt setzen
|
|
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}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
216
193
|
}
|
|
217
194
|
|
|
218
195
|
exports.SchellenbergDevice = SchellenbergDevice;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const commonDefines = require("../helpers/CommonDefines");
|
|
2
|
+
|
|
3
|
+
class SchellenbergMasterDevice {
|
|
4
|
+
constructor(adapter, masterId, name, devices) {
|
|
5
|
+
this.adapter = adapter;
|
|
6
|
+
this.id = masterId;
|
|
7
|
+
this.name = name;
|
|
8
|
+
this.childDevices = devices; // Array von SchellenbergDevice
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async createMasterFolder() {
|
|
12
|
+
const masterPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id.replace(this.adapter.FORBIDDEN_CHARS, "")}`;
|
|
13
|
+
await this.adapter.setObjectNotExistsAsync(masterPrefix, {
|
|
14
|
+
type: "channel",
|
|
15
|
+
common: { name: this.name },
|
|
16
|
+
native: {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Child Devices anlegen
|
|
20
|
+
for (const child of this.childDevices) {
|
|
21
|
+
await child.CreateAndSave(masterPrefix); // MasterPrefix übergeben
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.SchellenbergMasterDevice = SchellenbergMasterDevice;
|
|
@@ -18,23 +18,5 @@ exports.AdapterStateIDs = Object.freeze({
|
|
|
18
18
|
TypeClient: "typeClient",
|
|
19
19
|
Designation: "designation",
|
|
20
20
|
// devices.XXX.control
|
|
21
|
-
|
|
22
|
-
Open: "open",
|
|
23
|
-
MoveDown: "moveDown",
|
|
24
|
-
Close: "close",
|
|
25
|
-
MoveStop: "moveStop",
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
exports.KnownDeviceTypes = Object.freeze({
|
|
29
|
-
AwningEngine: { name: "Markisenantrieb Plus", type: "${Awning}" },
|
|
30
|
-
RollingShutter: { name: "Gurtantrieb", type: "${RollingShutter}" },
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
exports.DeviceCommands = Object.freeze({
|
|
34
|
-
MoveDown: { name: "MoveDown", value: 1 },
|
|
35
|
-
Open: { name: "Open", value: 1 },
|
|
36
|
-
MoveUp: { name: "MoveUp", value: 2 },
|
|
37
|
-
Close: { name: "Close", value: 2 },
|
|
38
|
-
MoveStop: { name: "MoveStop", value: 0 },
|
|
39
|
-
UNDEF: { name: "UndefinedCommand", value: -1 },
|
|
21
|
+
Position: "position",
|
|
40
22
|
});
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
//--------------------------------------------------
|
|
11
11
|
|
|
12
12
|
class Deferred {
|
|
13
|
-
constructor(executor) {
|
|
14
|
-
this.
|
|
13
|
+
constructor(adapter, executor) {
|
|
14
|
+
this.adapter = adapter;
|
|
15
|
+
this.timeout = this.adapter.setTimeout(() => {
|
|
15
16
|
this.reject("timeout");
|
|
16
17
|
}, 5000);
|
|
17
18
|
this.promise = new Promise((resolve, reject) => {
|
|
@@ -30,11 +31,11 @@ class Deferred {
|
|
|
30
31
|
return this.promise.finally(onfinally);
|
|
31
32
|
}
|
|
32
33
|
resolve(val) {
|
|
33
|
-
clearTimeout(this.timeout);
|
|
34
|
+
this.adapter.clearTimeout(this.timeout);
|
|
34
35
|
this._resolveSelf(val);
|
|
35
36
|
}
|
|
36
37
|
reject(reason) {
|
|
37
|
-
clearTimeout(this.timeout);
|
|
38
|
+
this.adapter.clearTimeout(this.timeout);
|
|
38
39
|
this._rejectSelf(reason);
|
|
39
40
|
}
|
|
40
41
|
}
|
package/main.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const utils = require("@iobroker/adapter-core");
|
|
9
9
|
const schellenbergBridge = require("./lib/SchellenbergBridge");
|
|
10
10
|
const commonDefines = require("./lib/helpers/CommonDefines");
|
|
11
|
-
const DeviceManager = require("./lib/DeviceManager");
|
|
11
|
+
const DeviceManager = require("./lib/devices/DeviceManager");
|
|
12
12
|
|
|
13
13
|
let SchellenbergBridge = null;
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.smartfriends",
|
|
3
|
-
"version": "1.0.1",
|
|
3
|
+
"version": "1.1.0-alpha.1",
|
|
4
4
|
"description": "smartfriends",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Black-Thunder",
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"node": ">= 20"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@iobroker/adapter-core": "^3.3.2"
|
|
27
|
-
"base-64": "^1.0.0"
|
|
26
|
+
"@iobroker/adapter-core": "^3.3.2"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {
|
|
30
29
|
"@alcalzone/release-script": "^5.0.0",
|
package/lib/DeviceManager.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
const { SchellenbergDevice } = require("./SchellenbergDevice");
|
|
2
|
-
const commonDefines = require("./helpers/CommonDefines");
|
|
3
|
-
const commandFactory = require("./comunication/CommandFactory");
|
|
4
|
-
|
|
5
|
-
class DeviceManager {
|
|
6
|
-
constructor(adapter) {
|
|
7
|
-
this.adapter = adapter;
|
|
8
|
-
this.devices = new Map();
|
|
9
|
-
this.log = adapter.log;
|
|
10
|
-
this.bridge = null;
|
|
11
|
-
|
|
12
|
-
// Bind all methods that use 'this' context
|
|
13
|
-
this.handleStateChange = this.handleStateChange.bind(this);
|
|
14
|
-
this.sendDeviceCommand = this.sendDeviceCommand.bind(this);
|
|
15
|
-
this.mapControlCommand = this.mapControlCommand.bind(this);
|
|
16
|
-
this.createDevice = this.createDevice.bind(this);
|
|
17
|
-
this.updateDevice = this.updateDevice.bind(this);
|
|
18
|
-
this.deleteDevice = this.deleteDevice.bind(this);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
setBridge(bridge) {
|
|
22
|
-
this.bridge = bridge;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async createDevice(deviceInfo) {
|
|
26
|
-
try {
|
|
27
|
-
const device = new SchellenbergDevice(this.adapter);
|
|
28
|
-
Object.assign(device, deviceInfo);
|
|
29
|
-
await device.CreateAndSave();
|
|
30
|
-
this.devices.set(deviceInfo.id, device);
|
|
31
|
-
return device;
|
|
32
|
-
} catch (error) {
|
|
33
|
-
this.log.error(`Failed to create device ${deviceInfo.id}: ${error.message}`);
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getDevice(id) {
|
|
39
|
-
return this.devices.get(id);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getAllDevices() {
|
|
43
|
-
return Array.from(this.devices.values());
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async updateDevice(id, data) {
|
|
47
|
-
const device = this.getDevice(id);
|
|
48
|
-
if (device) {
|
|
49
|
-
Object.assign(device, data);
|
|
50
|
-
await device.Update();
|
|
51
|
-
this.log.debug(`Device updated: ${id}`);
|
|
52
|
-
} else {
|
|
53
|
-
this.log.warn(`Device not found for update: ${id}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async deleteDevice(id) {
|
|
58
|
-
const device = this.getDevice(id);
|
|
59
|
-
if (device) {
|
|
60
|
-
await device.Delete();
|
|
61
|
-
this.devices.delete(id);
|
|
62
|
-
this.log.debug(`Device deleted: ${id}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async handleStateChange(id, state) {
|
|
67
|
-
try {
|
|
68
|
-
if (!state || state.ack || state.val === false) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let deviceId = id.replace(`${this.adapter.namespace}.${commonDefines.AdapterDatapointIDs.Devices}.`, "");
|
|
73
|
-
deviceId = deviceId.substring(0, deviceId.indexOf("."));
|
|
74
|
-
|
|
75
|
-
const controlOption = id.substring(id.lastIndexOf(".") + 1, id.length);
|
|
76
|
-
const controlCommand = this.mapControlCommand(controlOption);
|
|
77
|
-
|
|
78
|
-
if (deviceId && controlCommand !== commonDefines.DeviceCommands.UNDEF) {
|
|
79
|
-
await this.sendDeviceCommand(deviceId, controlCommand, id);
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
this.log.error(`Error in handleStateChange: ${error.message}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
mapControlCommand(controlOption) {
|
|
87
|
-
const commandMap = {
|
|
88
|
-
[commonDefines.AdapterStateIDs.MoveDown]: commonDefines.DeviceCommands.MoveDown,
|
|
89
|
-
[commonDefines.AdapterStateIDs.Close]: commonDefines.DeviceCommands.Close,
|
|
90
|
-
[commonDefines.AdapterStateIDs.MoveUp]: commonDefines.DeviceCommands.MoveUp,
|
|
91
|
-
[commonDefines.AdapterStateIDs.Open]: commonDefines.DeviceCommands.Open,
|
|
92
|
-
[commonDefines.AdapterStateIDs.MoveStop]: commonDefines.DeviceCommands.MoveStop,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const command = commandMap[controlOption];
|
|
96
|
-
if (!command) {
|
|
97
|
-
this.log.error(`Unsupported control option: ${controlOption} - Please report this to the developer!`);
|
|
98
|
-
return commonDefines.DeviceCommands.UNDEF;
|
|
99
|
-
}
|
|
100
|
-
return command;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async sendDeviceCommand(deviceId, controlCommand, stateId) {
|
|
104
|
-
try {
|
|
105
|
-
if (!this.bridge) {
|
|
106
|
-
throw new Error("Bridge not initialized");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.log.debug(`Sending command '${controlCommand.name}' to device ${deviceId}...`);
|
|
110
|
-
await this.bridge.sendAndReceiveCommand(
|
|
111
|
-
commandFactory.default.createSetDeviceValueCmd(deviceId, controlCommand.value),
|
|
112
|
-
);
|
|
113
|
-
await this.adapter.setState(stateId, false, true);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
this.log.error(`Error in sendDeviceCommand: ${error.message}`);
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = DeviceManager;
|