]> git.basschouten.com Git - openhab-addons.git/blob
20d1228f818920d4cd8025cb3b879eb943808aba
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.digitalstrom.internal.handler;
14
15 import static org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants;
28 import org.openhab.binding.digitalstrom.internal.lib.climate.jsonresponsecontainer.impl.TemperatureControlStatus;
29 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
30 import org.openhab.binding.digitalstrom.internal.lib.event.EventListener;
31 import org.openhab.binding.digitalstrom.internal.lib.listener.ConnectionListener;
32 import org.openhab.binding.digitalstrom.internal.lib.listener.DeviceStatusListener;
33 import org.openhab.binding.digitalstrom.internal.lib.listener.ManagerStatusListener;
34 import org.openhab.binding.digitalstrom.internal.lib.listener.SceneStatusListener;
35 import org.openhab.binding.digitalstrom.internal.lib.listener.TemperatureControlStatusListener;
36 import org.openhab.binding.digitalstrom.internal.lib.listener.TotalPowerConsumptionListener;
37 import org.openhab.binding.digitalstrom.internal.lib.listener.stateenums.ManagerStates;
38 import org.openhab.binding.digitalstrom.internal.lib.listener.stateenums.ManagerTypes;
39 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
40 import org.openhab.binding.digitalstrom.internal.lib.manager.DeviceStatusManager;
41 import org.openhab.binding.digitalstrom.internal.lib.manager.SceneManager;
42 import org.openhab.binding.digitalstrom.internal.lib.manager.StructureManager;
43 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.ConnectionManagerImpl;
44 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.DeviceStatusManagerImpl;
45 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.SceneManagerImpl;
46 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.StructureManagerImpl;
47 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.TemperatureControlManager;
48 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Circuit;
49 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Device;
50 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceStateUpdate;
51 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringTypeEnum;
52 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringUnitsEnum;
53 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
54 import org.openhab.binding.digitalstrom.internal.providers.DsChannelTypeProvider;
55 import org.openhab.core.config.core.Configuration;
56 import org.openhab.core.library.types.DecimalType;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.ThingTypeUID;
63 import org.openhab.core.thing.binding.BaseBridgeHandler;
64 import org.openhab.core.thing.binding.ThingHandler;
65 import org.openhab.core.types.Command;
66 import org.openhab.core.types.RefreshType;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 /**
71  * The {@link BridgeHandler} is the handler for a digitalSTROM-Server and connects it to
72  * the framework.<br>
73  * All {@link DeviceHandler}s and {@link SceneHandler}s use the {@link BridgeHandler} to execute the actual
74  * commands.<br>
75  * <br>
76  * The {@link BridgeHandler} also:
77  * <ul>
78  * <li>manages the {@link DeviceStatusManager} (starts, stops, register {@link DeviceStatusListener},
79  * register {@link SceneStatusListener} and so on)</li>
80  * <li>creates and load the configurations in the {@link Config}.</li>
81  * <li>implements {@link ManagerStatusListener} to manage the expiration of the Thing initializations</li>
82  * <li>implements the {@link ConnectionListener} to manage the {@link ThingStatus} of this {@link BridgeHandler}</li>
83  * <li>and implements the {@link TotalPowerConsumptionListener} to update his Channels.</li>
84  * </ul>
85  *
86  * @author Michael Ochel - Initial contribution
87  * @author Matthias Siegele - Initial contribution
88  */
89 public class BridgeHandler extends BaseBridgeHandler
90         implements ConnectionListener, TotalPowerConsumptionListener, ManagerStatusListener {
91
92     private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class);
93
94     /**
95      * Contains all supported thing types of this handler
96      */
97     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DSS_BRIDGE);
98
99     private static final long RECONNECT_TRACKER_INTERVAL = 15;
100
101     /* DS-Manager */
102     private ConnectionManager connMan;
103     private StructureManager structMan;
104     private SceneManager sceneMan;
105     private DeviceStatusManager devStatMan;
106     private TemperatureControlManager tempContMan;
107
108     private EventListener eventListener;
109     private ScheduledFuture<?> reconnectTracker;
110
111     private DeviceStatusListener deviceDiscovery;
112     private SceneStatusListener sceneDiscovery;
113     private TemperatureControlStatusListener temperatureControlDiscovery;
114     private Config config;
115
116     List<SceneStatusListener> unregisterSceneStatusListeners;
117     private short connectionTimeoutCounter = 0;
118     private final short ignoredTimeouts = 5;
119
120     private class Initializer implements Runnable {
121
122         BridgeHandler bridge;
123         Config config;
124
125         public Initializer(BridgeHandler bridge, Config config) {
126             this.bridge = bridge;
127             this.config = config;
128         }
129
130         @Override
131         public void run() {
132             logger.debug("Checking connection");
133             if (connMan == null) {
134                 connMan = new ConnectionManagerImpl(config, bridge, true);
135             } else {
136                 connMan.registerConnectionListener(bridge);
137                 connMan.configHasBeenUpdated();
138             }
139
140             logger.debug("Initializing digitalSTROM Manager ");
141             if (eventListener == null) {
142                 eventListener = new EventListener(connMan);
143             }
144             if (structMan == null) {
145                 structMan = new StructureManagerImpl();
146             }
147             if (sceneMan == null) {
148                 sceneMan = new SceneManagerImpl(connMan, structMan, bridge, eventListener);
149             }
150             if (devStatMan == null) {
151                 devStatMan = new DeviceStatusManagerImpl(connMan, structMan, sceneMan, bridge, eventListener);
152             } else {
153                 devStatMan.registerStatusListener(bridge);
154             }
155
156             devStatMan.registerTotalPowerConsumptionListener(bridge);
157
158             if (connMan.checkConnection()) {
159                 logger.debug("connection established, start services");
160                 if (TemperatureControlManager.isHeatingControllerInstallated(connMan)) {
161                     if (tempContMan == null) {
162                         tempContMan = new TemperatureControlManager(connMan, eventListener,
163                                 temperatureControlDiscovery);
164                         temperatureControlDiscovery = null;
165                     } else {
166                         if (temperatureControlDiscovery != null) {
167                             tempContMan.registerTemperatureControlStatusListener(temperatureControlDiscovery);
168                         }
169                     }
170                 }
171                 structMan.generateZoneGroupNames(connMan);
172                 devStatMan.start();
173                 eventListener.start();
174             }
175
176             boolean configChanged = false;
177             Configuration configuration = bridge.getConfig();
178             if (connMan.getApplicationToken() != null) {
179                 configuration.remove(USER_NAME);
180                 configuration.remove(PASSWORD);
181                 logger.debug("Application-Token is: {}", connMan.getApplicationToken());
182                 configuration.put(APPLICATION_TOKEN, connMan.getApplicationToken());
183                 configChanged = true;
184             }
185             Map<String, String> properties = editProperties();
186             String dSSname = connMan.getDigitalSTROMAPI().getInstallationName(connMan.getSessionToken());
187             if (dSSname != null) {
188                 properties.put(DS_NAME, dSSname);
189             }
190             Map<String, String> dsidMap = connMan.getDigitalSTROMAPI().getDSID(connMan.getSessionToken());
191             if (dsidMap != null) {
192                 logger.debug("{}", dsidMap);
193                 properties.putAll(dsidMap);
194             }
195             Map<String, String> versions = connMan.getDigitalSTROMAPI().getSystemVersion();
196             if (versions != null) {
197                 properties.putAll(versions);
198             }
199             String certProperty = getThing().getProperties().get(DigitalSTROMBindingConstants.SERVER_CERT);
200             String certConfig = config.getCert();
201             if ((certProperty == null || certProperty.isBlank()) && (certConfig != null && !certConfig.isBlank())) {
202                 properties.put(DigitalSTROMBindingConstants.SERVER_CERT, certConfig);
203             }
204             logger.debug("update properties");
205             updateProperties(properties);
206
207             if (configChanged) {
208                 updateConfiguration(configuration);
209             }
210         }
211     }
212
213     /**
214      * Creates a new {@link BridgeHandler}.
215      *
216      * @param bridge must not be null
217      */
218     public BridgeHandler(Bridge bridge) {
219         super(bridge);
220     }
221
222     @Override
223     public void initialize() {
224         logger.debug("Initializing digitalSTROM-BridgeHandler");
225         updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking configuration...");
226         // Start an extra thread to readout the configuration and check the connection, because it takes sometimes more
227         // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
228         Config config = loadAndCheckConfig();
229
230         if (config != null) {
231             logger.debug("{}", config.toString());
232             scheduler.execute(new Initializer(this, config));
233         }
234     }
235
236     private boolean checkLoginConfig(Config config) {
237         String userName = config.getUserName();
238         String password = config.getPassword();
239         String appToken = config.getAppToken();
240
241         if (((userName != null && !userName.isBlank()) && (password != null && !password.isBlank()))
242                 || (appToken != null && !appToken.isBlank())) {
243             return true;
244         }
245         onConnectionStateChange(CONNECTION_LOST, NO_USER_PASSWORD);
246         return false;
247     }
248
249     private Config loadAndCheckConfig() {
250         Configuration thingConfig = super.getConfig();
251         Config config = loadAndCheckConnectionData(thingConfig);
252         if (config == null) {
253             return null;
254         }
255         logger.debug("Loading configuration");
256         List<String> numberExc = new ArrayList<>();
257         // Parameters can't be null, because of an existing default value.
258         if (thingConfig.get(DigitalSTROMBindingConstants.SENSOR_DATA_UPDATE_INTERVAL) instanceof BigDecimal) {
259             config.setSensordataRefreshInterval(
260                     ((BigDecimal) thingConfig.get(DigitalSTROMBindingConstants.SENSOR_DATA_UPDATE_INTERVAL)).intValue()
261                             * 1000);
262         } else {
263             numberExc.add("\"Sensor update interval\" ( "
264                     + thingConfig.get(DigitalSTROMBindingConstants.SENSOR_DATA_UPDATE_INTERVAL) + ")");
265         }
266         if (thingConfig.get(DigitalSTROMBindingConstants.TOTAL_POWER_UPDATE_INTERVAL) instanceof BigDecimal) {
267             config.setTotalPowerUpdateInterval(
268                     ((BigDecimal) thingConfig.get(DigitalSTROMBindingConstants.TOTAL_POWER_UPDATE_INTERVAL)).intValue()
269                             * 1000);
270         } else {
271             numberExc.add("\"Total power update interval\" ("
272                     + thingConfig.get(DigitalSTROMBindingConstants.TOTAL_POWER_UPDATE_INTERVAL) + ")");
273         }
274         if (thingConfig.get(DigitalSTROMBindingConstants.SENSOR_WAIT_TIME) instanceof BigDecimal) {
275             config.setSensorReadingWaitTime(
276                     ((BigDecimal) thingConfig.get(DigitalSTROMBindingConstants.SENSOR_WAIT_TIME)).intValue() * 1000);
277         } else {
278             numberExc.add("\"Wait time sensor reading\" ("
279                     + thingConfig.get(DigitalSTROMBindingConstants.SENSOR_WAIT_TIME) + ")");
280         }
281         if (thingConfig.get(DigitalSTROMBindingConstants.DEFAULT_TRASH_DEVICE_DELETE_TIME_KEY) instanceof BigDecimal) {
282             config.setTrashDeviceDeleteTime(
283                     ((BigDecimal) thingConfig.get(DigitalSTROMBindingConstants.DEFAULT_TRASH_DEVICE_DELETE_TIME_KEY))
284                             .intValue());
285         } else {
286             numberExc.add("\"Days to be slaked trash bin devices\" ("
287                     + thingConfig.get(DigitalSTROMBindingConstants.DEFAULT_TRASH_DEVICE_DELETE_TIME_KEY) + ")");
288         }
289         if (!numberExc.isEmpty()) {
290             String excText = "The field ";
291             for (int i = 0; i < numberExc.size(); i++) {
292                 excText = excText + numberExc.get(i);
293                 if (i < numberExc.size() - 2) {
294                     excText = excText + ", ";
295                 } else if (i < numberExc.size() - 1) {
296                     excText = excText + " and ";
297                 }
298             }
299             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, excText + " have to be a number.");
300             return null;
301         }
302         String servertCert = getThing().getProperties().get(DigitalSTROMBindingConstants.SERVER_CERT);
303         if (servertCert != null && !servertCert.isBlank()) {
304             config.setCert(servertCert);
305         }
306         return config;
307     }
308
309     private Config loadAndCheckConnectionData(Configuration thingConfig) {
310         if (this.config == null) {
311             this.config = new Config();
312         }
313         // load and check connection and authorization data
314         String host = (String) thingConfig.get(HOST);
315         if (host != null && !host.isBlank()) {
316             config.setHost(host);
317         } else {
318             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
319                     "The connection to the digitalSTROM-Server can't established, because the host address is missing. Please set the host address.");
320             return null;
321         }
322         if (thingConfig.get(USER_NAME) != null) {
323             config.setUserName(thingConfig.get(USER_NAME).toString());
324         } else {
325             config.setUserName(null);
326         }
327         if (thingConfig.get(PASSWORD) != null) {
328             config.setPassword(thingConfig.get(PASSWORD).toString());
329         } else {
330             config.setPassword(null);
331         }
332         if (thingConfig.get(APPLICATION_TOKEN) != null) {
333             config.setAppToken(thingConfig.get(APPLICATION_TOKEN).toString());
334         } else {
335             config.setAppToken(null);
336         }
337
338         if (!checkLoginConfig(config)) {
339             return null;
340         }
341         return config;
342     }
343
344     @Override
345     public void dispose() {
346         logger.debug("Handler disposed");
347         if (reconnectTracker != null && !reconnectTracker.isCancelled()) {
348             reconnectTracker.cancel(true);
349         }
350         if (eventListener != null) {
351             eventListener.stop();
352         }
353         if (devStatMan != null) {
354             devStatMan.unregisterTotalPowerConsumptionListener();
355             devStatMan.unregisterStatusListener();
356             this.devStatMan.stop();
357         }
358         if (connMan != null) {
359             connMan.unregisterConnectionListener();
360         }
361     }
362
363     @Override
364     public void handleCommand(ChannelUID channelUID, Command command) {
365         if (command instanceof RefreshType) {
366             channelLinked(channelUID);
367         } else {
368             logger.debug("Command {} is not supported for channel: {}", command, channelUID.getId());
369         }
370     }
371
372     @Override
373     public void handleRemoval() {
374         String applicationToken = (String) super.getConfig().get(APPLICATION_TOKEN);
375         if (applicationToken != null && !applicationToken.isEmpty()) {
376             if (connMan == null) {
377                 Config config = loadAndCheckConnectionData(this.getConfig());
378                 if (config != null) {
379                     this.connMan = new ConnectionManagerImpl(config, null, false);
380                 } else {
381                     updateStatus(ThingStatus.REMOVED);
382                     return;
383                 }
384             }
385             if (connMan.removeApplicationToken()) {
386                 logger.debug("Application-Token deleted");
387             }
388         }
389         updateStatus(ThingStatus.REMOVED);
390     }
391
392     /* methods to store listener */
393
394     /**
395      * Registers a new {@link DeviceStatusListener} on the {@link DeviceStatusManager}.
396      *
397      * @param deviceStatusListener (must not be null)
398      */
399     public synchronized void registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
400         if (this.devStatMan != null) {
401             if (deviceStatusListener == null) {
402                 throw new IllegalArgumentException("It's not allowed to pass null.");
403             }
404
405             if (deviceStatusListener.getDeviceStatusListenerID() != null) {
406                 if (devStatMan.getManagerState().equals(ManagerStates.RUNNING)) {
407                     devStatMan.registerDeviceListener(deviceStatusListener);
408                 } else if (deviceStatusListener.getDeviceStatusListenerID()
409                         .equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
410                     devStatMan.registerDeviceListener(deviceStatusListener);
411                 }
412             } else {
413                 throw new IllegalArgumentException("It's not allowed to pass a DeviceStatusListener with ID = null.");
414             }
415         } else {
416             if (deviceStatusListener.getDeviceStatusListenerID().equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
417                 deviceDiscovery = deviceStatusListener;
418             }
419         }
420     }
421
422     /**
423      * Unregisters a new {@link DeviceStatusListener} on the {@link BridgeHandler}.
424      *
425      * @param deviceStatusListener (must not be null)
426      */
427     public void unregisterDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
428         if (this.devStatMan != null) {
429             if (deviceStatusListener.getDeviceStatusListenerID() != null) {
430                 this.devStatMan.unregisterDeviceListener(deviceStatusListener);
431             } else {
432                 throw new IllegalArgumentException("It's not allowed to pass a DeviceStatusListener with ID = null.");
433             }
434         }
435     }
436
437     /**
438      * Registers a new {@link SceneStatusListener} on the {@link BridgeHandler}.
439      *
440      * @param sceneStatusListener (must not be null)
441      */
442     public synchronized void registerSceneStatusListener(SceneStatusListener sceneStatusListener) {
443         if (this.sceneMan != null) {
444             if (sceneStatusListener == null) {
445                 throw new IllegalArgumentException("It's not allowed to pass null.");
446             }
447
448             if (sceneStatusListener.getSceneStatusListenerID() != null) {
449                 this.sceneMan.registerSceneListener(sceneStatusListener);
450             } else {
451                 throw new IllegalArgumentException("It's not allowed to pass a SceneStatusListener with ID = null.");
452             }
453         } else {
454             if (sceneStatusListener.getSceneStatusListenerID().equals(SceneStatusListener.SCENE_DISCOVERY)) {
455                 sceneDiscovery = sceneStatusListener;
456             }
457         }
458     }
459
460     /**
461      * Unregisters a new {@link SceneStatusListener} on the {@link DeviceStatusManager}.
462      *
463      * @param sceneStatusListener (must not be null)
464      */
465     public void unregisterSceneStatusListener(SceneStatusListener sceneStatusListener) {
466         if (this.sceneMan != null) {
467             if (sceneStatusListener.getSceneStatusListenerID() != null) {
468                 this.sceneMan.unregisterSceneListener(sceneStatusListener);
469             } else {
470                 throw new IllegalArgumentException("It's not allowed to pass a SceneStatusListener with ID = null..");
471             }
472         }
473     }
474
475     /**
476      * Has to be called from a removed Thing-Child to rediscovers the Thing.
477      *
478      * @param id = scene or device id (must not be null)
479      */
480     public void childThingRemoved(String id) {
481         if (id != null && id.split("-").length == 3) {
482             InternalScene scene = sceneMan.getInternalScene(id);
483             if (scene != null) {
484                 sceneMan.removeInternalScene(id);
485                 sceneMan.addInternalScene(scene);
486             }
487         } else {
488             devStatMan.removeDevice(id);
489         }
490     }
491
492     /**
493      * Delegate a stop command from a Thing to the {@link DeviceStatusManager#sendStopComandsToDSS(Device)}.
494      *
495      * @param device can be null
496      */
497     public void stopOutputValue(Device device) {
498         this.devStatMan.sendStopComandsToDSS(device);
499     }
500
501     @Override
502     public void channelLinked(ChannelUID channelUID) {
503         if (devStatMan != null) {
504             MeteringTypeEnum meteringType = DsChannelTypeProvider.getMeteringType(channelUID.getId());
505             if (meteringType != null) {
506                 if (meteringType.equals(MeteringTypeEnum.ENERGY)) {
507                     onEnergyMeterValueChanged(devStatMan.getTotalEnergyMeterValue());
508                 } else {
509                     onTotalPowerConsumptionChanged(devStatMan.getTotalPowerConsumption());
510                 }
511             } else {
512                 logger.warn("Channel with id {} is not known for the thing with id {}.", channelUID.getId(),
513                         getThing().getUID());
514             }
515         }
516     }
517
518     @Override
519     public void onTotalPowerConsumptionChanged(int newPowerConsumption) {
520         updateChannelState(MeteringTypeEnum.CONSUMPTION, MeteringUnitsEnum.WH, newPowerConsumption);
521     }
522
523     @Override
524     public void onEnergyMeterValueChanged(int newEnergyMeterValue) {
525         updateChannelState(MeteringTypeEnum.ENERGY, MeteringUnitsEnum.WH, newEnergyMeterValue * 0.001);
526     }
527
528     @Override
529     public void onEnergyMeterWsValueChanged(int newEnergyMeterValue) {
530         // not needed
531     }
532
533     private void updateChannelState(MeteringTypeEnum meteringType, MeteringUnitsEnum meteringUnit, double value) {
534         String channelID = DsChannelTypeProvider.getMeteringChannelID(meteringType, meteringUnit, true);
535         if (getThing().getChannel(channelID) != null) {
536             updateState(channelID, new DecimalType(value));
537         }
538     }
539
540     @Override
541     public void onConnectionStateChange(String newConnectionState) {
542         switch (newConnectionState) {
543             case CONNECTION_LOST:
544                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
545                         "The connection to the digitalSTROM-Server cannot be established.");
546                 startReconnectTracker();
547                 return;
548             case CONNECTION_RESUMED:
549                 if (connectionTimeoutCounter > 0) {
550                     // reset connection timeout counter
551                     connectionTimeoutCounter = 0;
552                     if (connMan.checkConnection()) {
553                         restartServices();
554                         setStatus(ThingStatus.ONLINE);
555                     }
556                 }
557                 return;
558             case APPLICATION_TOKEN_GENERATED:
559                 if (connMan != null) {
560                     Configuration config = this.getConfig();
561                     config.remove(USER_NAME);
562                     config.remove(PASSWORD);
563                     config.put(APPLICATION_TOKEN, connMan.getApplicationToken());
564                     this.updateConfiguration(config);
565                 }
566                 return;
567             default:
568                 return;
569         }
570     }
571
572     private void setStatus(ThingStatus status) {
573         logger.debug("set status to: {}", status);
574         updateStatus(status);
575         for (Thing thing : getThing().getThings()) {
576             ThingHandler handler = thing.getHandler();
577             if (handler != null) {
578                 handler.bridgeStatusChanged(getThing().getStatusInfo());
579             }
580         }
581     }
582
583     private void startReconnectTracker() {
584         if (reconnectTracker == null || reconnectTracker.isCancelled()) {
585             logger.debug("Connection lost, stop all services and start reconnectTracker.");
586             stopServices();
587             reconnectTracker = scheduler.scheduleWithFixedDelay(new Runnable() {
588
589                 @Override
590                 public void run() {
591                     if (connMan != null) {
592                         boolean conStat = connMan.checkConnection();
593                         logger.debug("check connection = {}", conStat);
594                         if (conStat) {
595                             restartServices();
596                             reconnectTracker.cancel(false);
597                         }
598                     }
599                 }
600             }, RECONNECT_TRACKER_INTERVAL, RECONNECT_TRACKER_INTERVAL, TimeUnit.SECONDS);
601         }
602     }
603
604     private void stopServices() {
605         if (devStatMan != null && !devStatMan.getManagerState().equals(ManagerStates.STOPPED)) {
606             devStatMan.stop();
607         }
608         if (eventListener != null && eventListener.isStarted()) {
609             eventListener.stop();
610         }
611     }
612
613     private void restartServices() {
614         logger.debug("reconnect, stop reconnection tracker and restart services");
615         if (reconnectTracker != null && !reconnectTracker.isCancelled()) {
616             reconnectTracker.cancel(true);
617         }
618         stopServices();
619         if (devStatMan != null) {
620             devStatMan.start();
621         }
622         if (eventListener != null) {
623             eventListener.start();
624         }
625     }
626
627     @Override
628     public void onConnectionStateChange(String newConnectionState, String reason) {
629         if (newConnectionState.equals(NOT_AUTHENTICATED) || newConnectionState.equals(CONNECTION_LOST)) {
630             switch (reason) {
631                 case WRONG_APP_TOKEN:
632                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
633                             """
634                                     User defined Application-Token is wrong. \
635                                     Please set user name and password to generate an Application-Token or set a valid Application-Token.\
636                                     """);
637                     stopServices();
638                     return;
639                 case WRONG_USER_OR_PASSWORD:
640                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
641                             "The set username or password is wrong.");
642                     stopServices();
643                     return;
644                 case NO_USER_PASSWORD:
645                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
646                             "No username or password is set to generate Application-Token. Please set user name and password or Application-Token.");
647                     stopServices();
648                     return;
649                 case CONNECTON_TIMEOUT:
650                     // ignore the first connection timeout
651                     if (connectionTimeoutCounter++ > ignoredTimeouts) {
652                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
653                                 "Connection lost because connection timeout to Server.");
654                         break;
655                     } else {
656                         return;
657                     }
658                 case HOST_NOT_FOUND:
659                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, """
660                             Server not found! Please check these points:
661                              - Is digitalSTROM-Server turned on?
662                              - Is the host address correct?
663                              - Is the ethernet cable connection established?\
664                             """);
665                     break;
666                 case UNKNOWN_HOST:
667                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
668                             "Unknown host name, please check the set host name!");
669                     break;
670                 case INVALID_URL:
671                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid URL is set.");
672                     break;
673                 case CONNECTION_LOST:
674                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
675                             "IOException / Connection lost.");
676                     break;
677                 case SSL_HANDSHAKE_ERROR:
678                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
679                             "SSL Handshake error / Connection lost.");
680                     break;
681                 default:
682                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
683             }
684             // reset connection timeout counter
685             connectionTimeoutCounter = 0;
686             startReconnectTracker();
687         }
688     }
689
690     /**
691      * Returns a list of all {@link Device}'s.
692      *
693      * @return device list (cannot be null)
694      */
695     public List<Device> getDevices() {
696         return this.structMan != null && this.structMan.getDeviceMap() != null
697                 ? new LinkedList<>(this.structMan.getDeviceMap().values())
698                 : null;
699     }
700
701     /**
702      * Returns the {@link StructureManager}.
703      *
704      * @return StructureManager
705      */
706     public StructureManager getStructureManager() {
707         return this.structMan;
708     }
709
710     /**
711      * Delegates a scene command of a Thing to the
712      * {@link DeviceStatusManager#sendSceneComandsToDSS(InternalScene, boolean)}
713      *
714      * @param scene the called scene
715      * @param call_undo (true = call scene | false = undo scene)
716      */
717     public void sendSceneComandToDSS(InternalScene scene, boolean call_undo) {
718         if (devStatMan != null) {
719             devStatMan.sendSceneComandsToDSS(scene, call_undo);
720         }
721     }
722
723     /**
724      * Delegates a device command of a Thing to the
725      * {@link DeviceStatusManager#sendComandsToDSS(Device, DeviceStateUpdate)}
726      *
727      * @param device can be null
728      * @param deviceStateUpdate can be null
729      */
730     public void sendComandsToDSS(Device device, DeviceStateUpdate deviceStateUpdate) {
731         if (devStatMan != null) {
732             devStatMan.sendComandsToDSS(device, deviceStateUpdate);
733         }
734     }
735
736     /**
737      * Returns a list of all {@link InternalScene}'s.
738      *
739      * @return Scene list (cannot be null)
740      */
741     public List<InternalScene> getScenes() {
742         return sceneMan != null ? sceneMan.getScenes() : new LinkedList<>();
743     }
744
745     /**
746      * Returns the {@link ConnectionManager}.
747      *
748      * @return ConnectionManager
749      */
750     public ConnectionManager getConnectionManager() {
751         return this.connMan;
752     }
753
754     @Override
755     public void onStatusChanged(ManagerTypes managerType, ManagerStates state) {
756         if (managerType.equals(ManagerTypes.DEVICE_STATUS_MANAGER)) {
757             switch (state) {
758                 case INITIALIZING:
759                     if (deviceDiscovery != null) {
760                         devStatMan.registerDeviceListener(deviceDiscovery);
761                         deviceDiscovery = null;
762                     }
763                     logger.debug("Building digitalSTROM model");
764                     break;
765                 case RUNNING:
766                     updateStatus(ThingStatus.ONLINE);
767                     break;
768                 case STOPPED:
769                     if (!getThing().getStatusInfo().getStatusDetail().equals(ThingStatusDetail.COMMUNICATION_ERROR)
770                             && !getThing().getStatusInfo().getStatusDetail()
771                                     .equals(ThingStatusDetail.CONFIGURATION_ERROR)) {
772                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "DeviceStatusManager is stopped.");
773                         devStatMan.start();
774                     }
775                     break;
776                 default:
777                     break;
778             }
779         }
780         if (managerType.equals(ManagerTypes.SCENE_MANAGER)) {
781             switch (state) {
782                 case GENERATING_SCENES:
783                     logger.debug("SceneManager reports that he is generating scenes");
784                     if (sceneDiscovery != null) {
785                         sceneMan.registerSceneListener(sceneDiscovery);
786                         sceneDiscovery = null;
787                     }
788                     break;
789                 case RUNNING:
790                     logger.debug("SceneManager reports that he is running");
791                     break;
792                 default:
793                     break;
794             }
795         }
796     }
797
798     /**
799      * Returns a {@link List} of all {@link Circuit}'s.
800      *
801      * @return circuit list
802      */
803     public List<Circuit> getCircuits() {
804         logger.debug("circuits: {}", structMan.getCircuitMap().values().toString());
805         return structMan != null && structMan.getCircuitMap() != null
806                 ? new LinkedList<>(structMan.getCircuitMap().values())
807                 : null;
808     }
809
810     /**
811      * Returns the {@link TemperatureControlManager} or null if no one exist.
812      *
813      * @return {@link TemperatureControlManager}
814      */
815     public TemperatureControlManager getTemperatureControlManager() {
816         return tempContMan;
817     }
818
819     /**
820      * Registers the given {@link TemperatureControlStatusListener} to the {@link TemperatureControlManager}.
821      *
822      * @param temperatureControlStatusListener can be null
823      */
824     public void registerTemperatureControlStatusListener(
825             TemperatureControlStatusListener temperatureControlStatusListener) {
826         if (tempContMan != null) {
827             tempContMan.registerTemperatureControlStatusListener(temperatureControlStatusListener);
828         } else if (TemperatureControlStatusListener.DISCOVERY
829                 .equals(temperatureControlStatusListener.getTemperationControlStatusListenrID())) {
830             this.temperatureControlDiscovery = temperatureControlStatusListener;
831         }
832     }
833
834     /**
835      * Unregisters the given {@link TemperatureControlStatusListener} from the {@link TemperatureControlManager}.
836      *
837      * @param temperatureControlStatusListener can be null
838      */
839     public void unregisterTemperatureControlStatusListener(
840             TemperatureControlStatusListener temperatureControlStatusListener) {
841         if (tempContMan != null) {
842             tempContMan.unregisterTemperatureControlStatusListener(temperatureControlStatusListener);
843         }
844     }
845
846     /**
847      * see {@link TemperatureControlManager#getTemperatureControlStatusFromAllZones()}
848      *
849      * @return all temperature control status objects
850      */
851     public Collection<TemperatureControlStatus> getTemperatureControlStatusFromAllZones() {
852         return tempContMan != null ? tempContMan.getTemperatureControlStatusFromAllZones() : new LinkedList<>();
853     }
854 }