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