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