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