iobroker.smartfriends 1.0.0 → 1.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,34 +1,34 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Black-Thunder <glwars@aol.de>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
23
-
24
- -------------------------------------------------------------------------------
25
-
26
- LoPablo/SchellenbergApi (https://github.com/LoPablo/SchellenbergApi)
27
-
28
- ISC License
29
-
30
- Copyright 2020 Pascâl Hartmann
31
-
32
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
33
-
34
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Black-Thunder <glwars@aol.de>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
24
+ -------------------------------------------------------------------------------
25
+
26
+ LoPablo/SchellenbergApi (https://github.com/LoPablo/SchellenbergApi)
27
+
28
+ ISC License
29
+
30
+ Copyright 2020 Pascâl Hartmann
31
+
32
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,67 +1,75 @@
1
- ![Logo](admin/smartfriends.png)
2
-
3
- # ioBroker.smartfriends
4
-
5
- [![NPM version](http://img.shields.io/npm/v/iobroker.smartfriends.svg)](https://www.npmjs.com/package/iobroker.smartfriends)
6
- [![Downloads](https://img.shields.io/npm/dm/iobroker.smartfriends.svg)](https://www.npmjs.com/package/iobroker.smartfriends)
7
- ![Number of Installations (latest)](http://iobroker.live/badges/smartfriends-installed.svg)
8
- ![Number of Installations (stable)](http://iobroker.live/badges/smartfriends-stable.svg)
9
- [![Known Vulnerabilities](https://snyk.io/test/github/Black-Thunder/ioBroker.smartfriends/badge.svg)](https://snyk.io/test/github/Black-Thunder/ioBroker.smartfriends)
10
-
11
- [![NPM](https://nodei.co/npm/iobroker.smartfriends.png?downloads=true)](https://nodei.co/npm/iobroker.smartfriends/)
12
-
13
- [![Test and Release](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/test-and-release.yml/badge.svg)](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/test-and-release.yml) [![Reviewdog](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/code-quality.yml/badge.svg)](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/code-quality.yml)
14
-
15
- ## smartfriends adapter for ioBroker
16
-
17
- This adapter enables a direct **local integration** of the **SmartFriends Box** (e.g. Smart Friends Box by Schellenberg, ABUS, Paulmann, STEINEL, etc.) into ioBroker – **without using the official cloud**.
18
-
19
- The adapter establishes a direct connection to the gateway to control and query devices locally.
20
-
21
- ## Documentation:
22
-
23
- - [English description](https://github.com/Black-Thunder/ioBroker.smartfriends/tree/master/docs/en/smartfriends.md)
24
- - [Deutsche Beschreibung](https://github.com/Black-Thunder/ioBroker.smartfriends/tree/master/docs/de/smartfriends.md)
25
-
26
- ## Discussion:
27
-
28
- - [ioBroker Forum](https://forum.iobroker.net/topic/83202)
29
-
30
- ## Changelog
31
-
32
- <!--
33
- Placeholder for the next version (at the beginning of the line):
34
- ### __WORK IN PROGRESS__
35
- -->
36
-
37
- ### 1.0.0 (2025-12-18)
38
-
39
- - (Black-Thunder) initial release
40
-
41
- ## Acknowledgements
42
-
43
- Special thanks und credits to [LoPablo](https://github.com/LoPablo/SchellenbergApi) for reverse engineering the API!
44
-
45
- ## License
46
-
47
- MIT License
48
-
49
- Copyright (c) 2025 Black-Thunder <glwars@aol.de>
50
-
51
- Permission is hereby granted, free of charge, to any person obtaining a copy
52
- of this software and associated documentation files (the "Software"), to deal
53
- in the Software without restriction, including without limitation the rights
54
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
55
- copies of the Software, and to permit persons to whom the Software is
56
- furnished to do so, subject to the following conditions:
57
-
58
- The above copyright notice and this permission notice shall be included in all
59
- copies or substantial portions of the Software.
60
-
61
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
64
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
65
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
66
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
67
- SOFTWARE.
1
+ ![Logo](admin/smartfriends.png)
2
+
3
+ # ioBroker.smartfriends
4
+
5
+ [![NPM version](http://img.shields.io/npm/v/iobroker.smartfriends.svg)](https://www.npmjs.com/package/iobroker.smartfriends)
6
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.smartfriends.svg)](https://www.npmjs.com/package/iobroker.smartfriends)
7
+ ![Number of Installations (latest)](http://iobroker.live/badges/smartfriends-installed.svg)
8
+ ![Number of Installations (stable)](http://iobroker.live/badges/smartfriends-stable.svg)
9
+ [![Known Vulnerabilities](https://snyk.io/test/github/Black-Thunder/ioBroker.smartfriends/badge.svg)](https://snyk.io/test/github/Black-Thunder/ioBroker.smartfriends)
10
+
11
+ [![NPM](https://nodei.co/npm/iobroker.smartfriends.png?downloads=true)](https://nodei.co/npm/iobroker.smartfriends/)
12
+
13
+ [![Test and Release](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/test-and-release.yml/badge.svg)](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/test-and-release.yml) [![Reviewdog](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/code-quality.yml/badge.svg)](https://github.com/Black-Thunder/ioBroker.smartfriends/actions/workflows/code-quality.yml)
14
+
15
+ ## smartfriends adapter for ioBroker
16
+
17
+ This adapter enables a direct **local integration** of the **SmartFriends Box** (e.g. Smart Friends Box by Schellenberg, ABUS, Paulmann, STEINEL, etc.) into ioBroker – **without using the official cloud**.
18
+
19
+ The adapter establishes a direct connection to the gateway to control and query devices locally.
20
+
21
+ ## Documentation:
22
+
23
+ - [English description](https://github.com/Black-Thunder/ioBroker.smartfriends/tree/master/docs/en/smartfriends.md)
24
+ - [Deutsche Beschreibung](https://github.com/Black-Thunder/ioBroker.smartfriends/tree/master/docs/de/smartfriends.md)
25
+
26
+ ## Discussion:
27
+
28
+ - [ioBroker Forum](https://forum.iobroker.net/topic/83202)
29
+
30
+ ## Changelog
31
+
32
+ <!--
33
+ Placeholder for the next version (at the beginning of the line):
34
+ ### __WORK IN PROGRESS__
35
+ -->
36
+ ### 1.1.0-alpha.0 (2025-12-23)
37
+
38
+ - (Black-Thunder) Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID
39
+
40
+ ### 1.0.1 (2025-12-20)
41
+
42
+ - (Black-Thunder) Increased robustness when communicating with the gateway
43
+ - (Black-Thunder) Added new option to ignore certificate errors
44
+
45
+ ### 1.0.0 (2025-12-18)
46
+
47
+ - (Black-Thunder) initial release
48
+
49
+ ## Acknowledgements
50
+
51
+ Special thanks und credits to [LoPablo](https://github.com/LoPablo/SchellenbergApi) for reverse engineering the API!
52
+
53
+ ## License
54
+
55
+ MIT License
56
+
57
+ Copyright (c) 2025 Black-Thunder <glwars@aol.de>
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining a copy
60
+ of this software and associated documentation files (the "Software"), to deal
61
+ in the Software without restriction, including without limitation the rights
62
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63
+ copies of the Software, and to permit persons to whom the Software is
64
+ furnished to do so, subject to the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be included in all
67
+ copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75
+ SOFTWARE.
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "smartfriends",
4
- "version": "1.0.0",
4
+ "version": "1.1.0-alpha.0",
5
5
  "news": {
6
+ "1.1.0-alpha.0": {
7
+ "en": "Refactored device handling: dynamic states, removed type whitelist, grouped devices under master ID",
8
+ "de": "Überarbeitete Geräteverwaltung: dynamische Zustände, entfernte Typ-Whitelist, gruppierte Geräte unter Master-ID",
9
+ "ru": "Рефакторированная обработка устройств: динамические состояния, удаленный белый список типов, сгруппированные устройства под идентификатором Master ID",
10
+ "pt": "Manipulação do dispositivo refatorado: estados dinâmicos, lista branca do tipo removido, dispositivos agrupados sob ID mestre",
11
+ "nl": "Refactored device handling: dynamische toestanden, verwijderd type whitelist, gegroepeerde apparaten onder master ID",
12
+ "fr": "Manipulation de l'appareil refacturé : états dynamiques, liste blanche de type enlevé, dispositifs groupés sous Master ID",
13
+ "it": "Movimentazione del dispositivo refattore: stati dinamici, tipo rimosso whitelist, dispositivi raggruppati sotto master ID",
14
+ "es": "Manejo de dispositivo refactorizado: estados dinámicos, lista blanca de tipo eliminado, dispositivos agrupados bajo ID maestro",
15
+ "pl": "Przekształcona obsługa urządzenia: stany dynamiczne, usunięty biały typ, zgrupowane urządzenia pod master ID",
16
+ "uk": "Рефакторний пристрій обробки: динамічні стани, видалений тип білий список, вбудовані пристрої під магістр ID",
17
+ "zh-cn": "重构设备处理: 动态状态, 删除类型白名单, 主 ID 下分组设备"
18
+ },
19
+ "1.0.1": {
20
+ "en": "Increased robustness when communicating with the gateway\nAdded new option to ignore certificate errors",
21
+ "de": "Erhöhte Robustheit bei der Kommunikation mit dem Gateway\nNeue Option hinzugefügt, um Zertifikatsfehler zu ignorieren",
22
+ "ru": "Повышенная надежность при общении с шлюзом\nДобавлена новая возможность игнорировать ошибки сертификата",
23
+ "pt": "Maior robustez ao se comunicar com o gateway\nAdicionada nova opção para ignorar erros de certificado",
24
+ "nl": "Verhoogde robuustheid bij het communiceren met de gateway\nNieuwe optie toegevoegd om certificaatfouten te negeren",
25
+ "fr": "Une robustesse accrue lors de la communication avec la passerelle\nAjout d'une nouvelle option pour ignorer les erreurs de certificat",
26
+ "it": "Maggiore robustezza quando si comunica con il gateway\nAggiunta nuova opzione per ignorare gli errori del certificato",
27
+ "es": "Mayor robustez al comunicarse con la puerta de entrada\nNueva opción para ignorar errores de certificado",
28
+ "pl": "Większa odporność podczas komunikacji z bramą\nDodano nową opcję do ignorowania błędów certyfikatu",
29
+ "uk": "Підвищена надійність при спілкуванні з шлюзом\nДодано новий варіант ігнорувати помилки сертифіката",
30
+ "zh-cn": "与网关沟通时的强度提高\n添加新选项以忽略证书错误"
31
+ },
6
32
  "1.0.0": {
7
33
  "en": "initial release",
8
34
  "de": "Erstveröffentlichung",
@@ -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,128 @@ class SchellenbergBridge {
72
65
  );
73
66
  const response = await this.socket.sendAndRecieveCommand(command, this.loginResponse.sessionID);
74
67
 
75
- if (response?.response) {
76
- const parsedResponse = AllNewDeviceInfos.default.fromObject(response.response);
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
77
  this.adapter.log.error(`Connection failed: ${err.message}`);
81
78
  this.handleDisconnect();
82
79
  }
83
80
  }
84
81
 
82
+ updateTimestamp(parsed) {
83
+ if (parsed.currentTimestamp) {
84
+ this.lastTimestamp = parsed.currentTimestamp;
85
+ this.adapter.log.debug(`Updated timestamp to ${this.lastTimestamp}`);
86
+ }
87
+ }
88
+
89
+ processCompatibilityConfiguration(parsed) {
90
+ if (this.deviceDefinitions?.length) {
91
+ return;
92
+ }
93
+
94
+ const standards = parsed.newCompatibilityConfiguration?.compatibleRadioStandards ?? [];
95
+ this.deviceDefinitions = [];
96
+
97
+ for (const standard of standards) {
98
+ for (const def of standard.compatibleDevices ?? []) {
99
+ if (def.deviceDesignation) {
100
+ this.deviceDefinitions.push(def);
101
+ }
102
+ }
103
+ }
104
+
105
+ this.adapter.log.debug(`Loaded ${this.deviceDefinitions.length} device definitions`);
106
+ }
107
+
108
+ processInitialDeviceValues(parsed) {
109
+ const values = parsed.newDeviceValues?.values ?? [];
110
+
111
+ for (const value of values) {
112
+ this.handleDeviceValue(value);
113
+ }
114
+ }
115
+
116
+ handleDeviceValue(deviceValue) {
117
+ if (!deviceValue || deviceValue.deviceID == null) {
118
+ this.adapter.log.debug("Invalid device value received, ignoring.");
119
+ return;
120
+ }
121
+
122
+ this.adapter.log.debug(`Device value update: deviceID=${deviceValue.deviceID}, value=${deviceValue.value}`);
123
+
124
+ this.deviceManager.updateDeviceValue(deviceValue);
125
+ }
126
+
85
127
  async processAllNewDeviceInfos(response) {
86
128
  try {
87
- const newDeviceInfos = response.newDeviceInfos.values;
88
- let knownDevices = 0;
129
+ const deviceInfos = response.newDeviceInfos?.values ?? [];
130
+ const createdMasters = [];
131
+
132
+ // 1️⃣ Child-Devices nach MasterID gruppieren
133
+ const devicesByMaster = {};
134
+ for (const device of deviceInfos) {
135
+ // Definition dynamisch zuweisen
136
+ device.definition = this.deviceDefinitions.find(d => d.deviceDesignation === device.deviceDesignation);
137
+ if (!device.definition) {
138
+ this.adapter.log.debug(
139
+ `Skipping device ${device.deviceName} (${device.deviceDesignation}) – no definition found`,
140
+ );
141
+ continue;
142
+ }
143
+
144
+ const masterId = device.masterDeviceID || device.deviceID;
145
+ if (!devicesByMaster[masterId]) {
146
+ devicesByMaster[masterId] = [];
147
+ }
148
+ devicesByMaster[masterId].push(device);
149
+ }
89
150
 
90
- this.adapter.log.debug(`Received all devices:\r\n${JSON.stringify(newDeviceInfos)}`);
151
+ // 2️⃣ MasterDevices anlegen
152
+ for (const [masterId, childDevices] of Object.entries(devicesByMaster)) {
153
+ const masterName = childDevices[0].masterDeviceName || childDevices[0].deviceName;
154
+ const masterDevice = new SchellenbergMasterDevice(this.adapter, masterId, masterName, []);
155
+
156
+ // 3️⃣ Child-Devices unter Master anlegen
157
+ for (const child of childDevices) {
158
+ // Nur Devices mit definierbaren Control-States
159
+ const hasSwitching = child.definition.deviceType?.switchingValues?.length;
160
+ const isPosition = child.definition.deviceType?.kind === commonDefines.AdapterStateIDs.Position;
161
+
162
+ if (!hasSwitching && !isPosition) {
163
+ this.adapter.log.debug(
164
+ `Skipping device ${child.deviceName} (${child.deviceDesignation}) – no definable control states found`,
165
+ );
166
+ continue;
167
+ }
91
168
 
92
- for (const device of response.newDeviceInfos.values) {
93
- if (
94
- device.deviceTypClient &&
95
- (device.deviceTypClient.includes(CommonDefines.KnownDeviceTypes.AwningEngine.type) ||
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,
169
+ const schellenbergDevice = await this.deviceManager.createDevice({
170
+ id: child.deviceID,
171
+ name: child.deviceName,
172
+ deviceType: child.deviceTypClient ?? child.definition.deviceType?.kind ?? "unknown",
173
+ designation: child.deviceDesignation,
174
+ definition: child.definition,
175
+ masterPrefix: `${commonDefines.AdapterDatapointIDs.Devices}.${masterId}`, // States unter Master-Ordner
104
176
  });
105
- this.adapter.log.info(
106
- `Device created: ${device.deviceName} (Type: ${device.deviceTypClient}, Device ID: ${device.deviceID})`,
107
- );
108
- } else {
109
- this.adapter.log.debug(
110
- `Unsupported device type received: ${device.deviceName} (Type: ${device.deviceTypClient}, Device ID: ${device.deviceID}). Ignoring...`,
111
- );
177
+
178
+ masterDevice.childDevices.push(schellenbergDevice);
179
+ this.adapter.log.info(`Device created: ${child.deviceName} (ID: ${child.deviceID})`);
180
+ }
181
+
182
+ if (masterDevice.childDevices?.length !== 0) {
183
+ await masterDevice.createMasterFolder();
184
+ createdMasters.push(masterDevice);
112
185
  }
113
186
  }
114
187
 
115
188
  this.adapter.log.info(
116
- `Processed ${newDeviceInfos.length} device(s) and added ${knownDevices} supported device(s). Adapter successfully started.`,
189
+ `Processed ${deviceInfos.length} device(s), created ${createdMasters.length} master device(s).`,
117
190
  );
118
191
  } catch (error) {
119
192
  this.adapter.log.error(`Error processing devices: ${error.message}`);
@@ -143,76 +216,96 @@ class SchellenbergBridge {
143
216
  }
144
217
 
145
218
  handleMessage(data) {
146
- if (data) {
147
- const parsedResponse = JSONResponse.default.fromJSONString(data);
219
+ if (!data) {
220
+ return;
221
+ }
148
222
 
149
- if (parsedResponse == null || parsedResponse.responseCode == null) {
150
- this.adapter.log.debug("Invalid message received. Skipping...");
151
- return;
152
- }
223
+ let parsedResponse;
153
224
 
154
- this.adapter.log.debug(
155
- `Message received: ${parsedResponse.responseMessage} (code: ${parsedResponse.responseCode})`,
225
+ try {
226
+ parsedResponse = JSONResponse.default.fromJSONString(data);
227
+ } catch (e) {
228
+ this.adapter.log.warn(`Invalid JSON message ignored: ${e.message}`);
229
+ return;
230
+ }
231
+
232
+ if (parsedResponse == null || parsedResponse.responseCode == null) {
233
+ this.adapter.log.debug("Invalid message received. Skipping...");
234
+ return;
235
+ }
236
+
237
+ this.adapter.log.debug(
238
+ `Message received: ${parsedResponse.responseMessage} (code: ${parsedResponse.responseCode})`,
239
+ );
240
+
241
+ if (parsedResponse.currentTimestamp) {
242
+ this.adapter.log.debug(`Updated timestamp to ${parsedResponse.currentTimestamp}`);
243
+ this.lastTimestamp = parsedResponse.currentTimestamp;
244
+ }
245
+
246
+ this.handleResponseCode(parsedResponse);
247
+ }
248
+
249
+ handleResponseCode(parsedResponse) {
250
+ // Map mit allen Response-Code-Handlern
251
+ const responseHandlers = {
252
+ [-1]: r => this.rejectNextPromise(r),
253
+ [1]: r => {
254
+ this.retryCounter = 0;
255
+ this.resolveNextPromise(r);
256
+ },
257
+ [2]: r => this.handleUpdate(r),
258
+ [5]: r => this.handleSpecificResponseCode(r, 5),
259
+ [84]: _r => this.reconnectWithWarn("TLS/SSL connection to the gateway has been closed."),
260
+ [91]: _r => this.reconnectWithWarn("Connection to the gateway timed out."),
261
+ [4]: _r => {}, // Ignored (newCConfig)
262
+ [7]: _r => {}, // Ignored (newLicense)
263
+ [8]: _r => {}, // Ignored as it's just an empty message after disconnect because of responseCode=5
264
+ [15]: _r => {}, // Ignored (showModuleInfo)
265
+ [16]: _r => {}, // Ignored ('Die Smart Friends Box kann sich mit dem RemoteHome Server nicht verbinden. Überprüfen Sie bitte, ob die Smart Friends Box mit dem Internet verbunden ist.')
266
+ [20]: _r => {}, // Ignored as it's just an empty message after successful login (message: 'loginFinished')
267
+ [87]: _r => {}, // Ignored as it's just an empty message after successful login (message: 'loginFinished')
268
+ [203]: _r => {}, // Ignored time-outs ('Connection timed out')
269
+ };
270
+
271
+ const handler = responseHandlers[parsedResponse.responseCode];
272
+ if (handler) {
273
+ handler(parsedResponse);
274
+ } else {
275
+ this.adapter.log.error(
276
+ `Unknown message response code: ${parsedResponse.responseCode} (message: '${parsedResponse.responseMessage}') - Please report this to the developer!`,
156
277
  );
278
+ }
279
+ }
157
280
 
158
- if (parsedResponse.currentTimestamp) {
159
- this.adapter.log.debug(`Updated timestamp to ${parsedResponse.currentTimestamp}`);
160
- this.lastTimestamp = parsedResponse.currentTimestamp;
161
- }
281
+ handleSpecificResponseCode(parsedResponse, codeNumber) {
282
+ switch (codeNumber) {
283
+ case 5: {
284
+ // Ignore warnings when licenses are expiring soon and update messages
285
+ const message = String(parsedResponse.responseMessage || "").toLowerCase();
286
+ const isInformationalPopup = message.includes("folgende lizenzen") || message.includes("update");
162
287
 
163
- switch (parsedResponse.responseCode) {
164
- case -1:
165
- this.rejectNextPromise(parsedResponse);
166
- break;
167
- case 1:
168
- this.retryCounter = 0;
169
- this.resolveNextPromise(parsedResponse);
170
- break;
171
- case 2:
172
- this.handleUpdate(parsedResponse);
173
- break;
174
- // Message boxes and disconnections because of multiple login attempts
175
- case 5:
176
- // Ignore warnings when licenses are expiring soon and update messages
177
- if (
178
- parsedResponse.responseMessage.toLowerCase().includes("folgende lizenzen") ||
179
- parsedResponse.responseMessage.toLowerCase().includes("update")
180
- ) {
181
- break;
182
- }
288
+ if (isInformationalPopup) {
289
+ return;
290
+ }
183
291
 
184
- // Disconnect because of login
185
- this.adapter.log.warn(
186
- "Connection was closed, because credentials were used to login on a different device or the official app.",
187
- );
188
- this.reconnect();
189
- break;
190
- case 84:
191
- this.adapter.log.warn("The TLS/SSL connection to the gateway has been closed.");
192
- this.reconnect();
193
- break;
194
- case 91:
195
- this.adapter.log.warn("Connection to the gateway timed out.");
196
- this.reconnect();
197
- break;
198
- case 4: // Ignore (newCConfig)
199
- case 7: // Ignore (newLicense)
200
- case 8: // Ignore as it's just an empty message after disconnect because of responseCode=5
201
- case 15: // Ignore (showModuleInfo)
202
- case 16: // Ignore ('Die Smart Friends Box kann sich mit dem RemoteHome Server nicht verbinden. Überprüfen Sie bitte, ob die Smart Friends Box mit dem Internet verbunden ist.')
203
- case 20: // Ignore as it's just an empty message after successful login (message: 'loginFinished')
204
- case 87: // Ignore as it's just an empty message after successful login (message: 'loginFinished')
205
- case 203: // Ignore time-outs ('Connection timed out')
206
- break;
207
- default:
208
- this.adapter.log.error(
209
- `Unknown message response code: ${parsedResponse.responseCode} (message: '${parsedResponse.responseMessage}') - Please report this to the developer!`,
210
- );
211
- break;
292
+ // Disconnect because of login
293
+ this.adapter.log.warn(
294
+ "Connection was closed, because credentials were used to login on a different device or the official app.",
295
+ );
296
+ this.reconnect();
297
+ break;
212
298
  }
299
+ default:
300
+ break;
213
301
  }
214
302
  }
215
303
 
304
+ reconnectWithWarn(message) {
305
+ this.adapter.log.warn(message);
306
+ this.reconnect();
307
+ }
308
+
216
309
  reconnect() {
217
310
  this.adapter.setAdapterConnectionState(false);
218
311
 
@@ -252,7 +345,7 @@ class SchellenbergBridge {
252
345
 
253
346
  async handleLoginMessage(response) {
254
347
  if (response.sessionID && response.hardware && response.macAddress && response.shsVersion) {
255
- this.adapter.log.debug("Connection and login to gateway successful.");
348
+ this.adapter.log.debug(`Connection and login to gateway ${response.hardware} successful.`);
256
349
  this.loginResponse = response;
257
350
  this.retryCounter = 0;
258
351
 
@@ -289,14 +382,17 @@ class SchellenbergBridge {
289
382
  }
290
383
 
291
384
  handleUpdate(response) {
292
- if (response.responseMessage) {
293
- switch (response.responseMessage) {
294
- case "newDeviceValue":
295
- break;
296
- case "newDeviceInfo":
297
- break;
298
- default:
299
- break;
385
+ // new device values
386
+ if (response.newDeviceValues?.values?.length) {
387
+ for (const value of response.newDeviceValues.values) {
388
+ this.handleDeviceValue(value);
389
+ }
390
+ }
391
+
392
+ // new device infos (optional, für später)
393
+ if (response.newDeviceInfos?.values?.length) {
394
+ for (const device of response.newDeviceInfos.values) {
395
+ this.adapter.log.debug(`New device info received for deviceID=${device.deviceID}`);
300
396
  }
301
397
  }
302
398
  }
@@ -30,14 +30,11 @@ class JSONResponse {
30
30
  }
31
31
 
32
32
  // e.g.: { "currentTimestamp" : 20201210140021, "response" : { "remoteHomeActivated" : true, "remoteHomeStatus" : "notConnected" }, "responseCode" : 1, "responseMessage" : "success" }
33
- return new JSONResponse(
34
- JSONHelper.default.stringToDate(
35
- object.currentTimestamp ? object.currentTimestamp : object.response.currentTimestamp,
36
- ),
37
- object.response,
38
- object.responseCode,
39
- object.responseMessage,
40
- );
33
+ // also different formats possible, e.g.: { "programmingProcess" : { "context" : "system-update", "items" : [ { "button" : { "kind" : "OK_BUTTON", "requestID" : 12, "text" : "Ja" } }, { "button" : { "kind" : "CANCEL_BUTTON", "requestID" : -4, "text" : "Nein" } } ], "nextCommand" : "changeServerSettings", "target" : "ServerSettingsHandler", "text" : "Die verwendeten App erfordert eine neuere Version der Zentrale.\n\nBitte installieren sie umgehend das neueste Update\nSoll nach updates gesucht werden?", "title" : "${Note}" }, "responseCode" : 6 }
34
+ const timestampRaw = object?.currentTimestamp ?? object?.response?.currentTimestamp ?? null;
35
+ const timestamp = timestampRaw ? JSONHelper.default.stringToDate(timestampRaw) : null;
36
+
37
+ return new JSONResponse(timestamp, object.response, object.responseCode, object.responseMessage);
41
38
  }
42
39
  }
43
40
  exports.default = JSONResponse;