]> git.basschouten.com Git - openhab-addons.git/blob
cf6989436f5bfe8d08a5dde5ee50c939bbe9cfb5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.lib.manager.impl;
14
15 import java.util.Arrays;
16 import java.util.Calendar;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.concurrent.CopyOnWriteArrayList;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.openhab.binding.digitalstrom.internal.lib.GeneralLibConstance;
28 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
29 import org.openhab.binding.digitalstrom.internal.lib.event.EventListener;
30 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventNames;
31 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventResponseEnum;
32 import org.openhab.binding.digitalstrom.internal.lib.event.types.EventItem;
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.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.sensorjobexecutor.SceneReadingJobExecutor;
45 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.SensorJobExecutor;
46 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.SensorJob;
47 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.DeviceConsumptionSensorJob;
48 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.DeviceOutputValueSensorJob;
49 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.SceneConfigReadingJob;
50 import org.openhab.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.SceneOutputValueReadingJob;
51 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
52 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.constants.JSONApiResponseKeysEnum;
53 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Circuit;
54 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Device;
55 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.CachedMeteringValue;
56 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceConstants;
57 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceStateUpdate;
58 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
59 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringTypeEnum;
60 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringUnitsEnum;
61 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
62 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
63 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DSID;
64 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceStateUpdateImpl;
65 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.impl.DeviceImpl;
66 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
67 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.ApartmentSceneEnum;
68 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
69 import org.openhab.core.common.ThreadPoolManager;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73 import com.google.gson.JsonElement;
74 import com.google.gson.JsonObject;
75
76 /**
77  * The {@link DeviceStatusManagerImpl} is the implementation of the {@link DeviceStatusManager}.
78  *
79  * @author Michael Ochel - Initial contribution
80  * @author Matthias Siegele - Initial contribution
81  */
82 public class DeviceStatusManagerImpl implements DeviceStatusManager {
83
84     private final Logger logger = LoggerFactory.getLogger(DeviceStatusManagerImpl.class);
85
86     /**
87      * Contains all supported event-types.
88      */
89     public static final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.DEVICE_SENSOR_VALUE,
90             EventNames.DEVICE_BINARY_INPUT_EVENT);
91
92     private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(Config.THREADPOOL_NAME);
93     private ScheduledFuture<?> pollingScheduler;
94
95     /**
96      * Query to get all {@link Device}'s with more informations than {@link DsAPI#getApartmentDevices(String)}. Can be
97      * executed with {@link DsAPI#query(String, String)} or {@link DsAPI#query2(String, String)}.
98      */
99     public static final String GET_DETAILD_DEVICES = "/apartment/zones/zone0(*)/devices/*(*)/*(*)/*(*)";
100     /**
101      * Query to get the last called scenes of all groups in digitalSTROM. Can be executed with
102      * {@link DsAPI#query(String, String)} or
103      * {@link DsAPI#query2(String, String)}.
104      */
105     public static final String LAST_CALL_SCENE_QUERY = "/apartment/zones/*(*)/groups/*(*)/*(*)";
106
107     private ConnectionManager connMan;
108     private StructureManager strucMan;
109     private SceneManager sceneMan;
110     private DsAPI digitalSTROMClient;
111     private Config config;
112
113     private SensorJobExecutor sensorJobExecutor;
114     private SceneReadingJobExecutor sceneJobExecutor;
115     private EventListener eventListener;
116
117     private final List<TrashDevice> trashDevices = new CopyOnWriteArrayList<>();
118
119     private long lastBinCheck = 0;
120     private ManagerStates state = ManagerStates.STOPPED;
121
122     private int tempConsumption = 0;
123     private int tempEnergyMeter = 0;
124     private int tempEnergyMeterWs = 0;
125
126     private DeviceStatusListener deviceDiscovery;
127     private TotalPowerConsumptionListener totalPowerConsumptionListener;
128     private ManagerStatusListener statusListener;
129
130     /**
131      * Creates a new {@link DeviceStatusManagerImpl} through the given {@link Config} object, which has to be contains
132      * all needed parameters like host address, authentication data and so on. This constructor the
133      * {@link DeviceStatusManagerImpl} will be create all needed managers itself.
134      *
135      * @param config (must not be null)
136      */
137     public DeviceStatusManagerImpl(Config config) {
138         init(new ConnectionManagerImpl(config), null, null, null, null);
139     }
140
141     /**
142      * Creates a new {@link DeviceStatusManagerImpl}. The given fields needed to create {@link ConnectionManager}
143      * through the constructor {@link ConnectionManagerImpl#ConnectionManagerImpl(String, String, String, String)}. All
144      * other needed manager will be automatically created, too.
145      *
146      * @param hostAddress (must not be null)
147      * @param user (can be null, if appToken is set)
148      * @param password (can be null, if appToken is set)
149      * @param appToken (can be null, if user and password is set)
150      */
151     public DeviceStatusManagerImpl(String hostAddress, String user, String password, String appToken) {
152         init(new ConnectionManagerImpl(hostAddress, user, password, false), null, null, null, null);
153     }
154
155     /**
156      * Creates a new {@link DeviceStatusManagerImpl} with the given managers. If the {@link StructureManager} or
157      * {@link SceneManager} is null, they will be automatically created.
158      *
159      * @param connMan (must not be null)
160      * @param strucMan (can be null)
161      * @param sceneMan (can be null)
162      */
163     public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan) {
164         init(connMan, strucMan, sceneMan, null, null);
165     }
166
167     /**
168      * Same constructor like {@link #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager)}, but a
169      * {@link ManagerStatusListener} can be set, too.
170      *
171      * @param connMan (must not be null)
172      * @param strucMan (can be null)
173      * @param sceneMan (can be null)
174      * @param statusListener (can be null)
175      * @see #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager)
176      */
177     public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
178             ManagerStatusListener statusListener) {
179         init(connMan, strucMan, sceneMan, statusListener, null);
180     }
181
182     /**
183      * Same constructor like
184      * {@link #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager, ManagerStatusListener)}, but a
185      * {@link EventListener} can be set, too.
186      *
187      * @param connMan (must not be null)
188      * @param strucMan (can be null)
189      * @param sceneMan (can be null)
190      * @param statusListener (can be null)
191      * @param eventListener (can be null)
192      * @see #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager, ManagerStatusListener)
193      */
194     public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
195             ManagerStatusListener statusListener, EventListener eventListener) {
196         init(connMan, strucMan, sceneMan, statusListener, eventListener);
197     }
198
199     /**
200      * Creates a new {@link DeviceStatusManagerImpl} with the given {@link ConnectionManager}. The
201      * {@link StructureManager} and
202      * {@link SceneManager} will be automatically created.
203      *
204      * @param connMan (must not be null)
205      */
206     public DeviceStatusManagerImpl(ConnectionManager connMan) {
207         init(connMan, null, null, null, null);
208     }
209
210     private void init(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
211             ManagerStatusListener statusListener, EventListener eventListener) {
212         this.connMan = connMan;
213         this.digitalSTROMClient = connMan.getDigitalSTROMAPI();
214         this.config = connMan.getConfig();
215         if (strucMan != null) {
216             this.strucMan = strucMan;
217         } else {
218             this.strucMan = new StructureManagerImpl();
219         }
220         if (sceneMan != null) {
221             this.sceneMan = sceneMan;
222         } else {
223             this.sceneMan = new SceneManagerImpl(connMan, strucMan, statusListener);
224         }
225         this.statusListener = statusListener;
226         this.eventListener = eventListener;
227     }
228
229     /**
230      * Check and updates the {@link Device} structure, configurations and status.
231      *
232      * @author Michael Ochel - initial contributer
233      * @author Matthias Siegele - initial contributer
234      */
235     private class PollingRunnable implements Runnable {
236         private boolean devicesLoaded = false;
237         private long nextSensorUpdate = 0;
238
239         @Override
240         public void run() {
241             if (!getManagerState().equals(ManagerStates.RUNNING)) {
242                 logger.debug("Thread started");
243                 if (devicesLoaded) {
244                     stateChanged(ManagerStates.RUNNING);
245                 } else {
246                     stateChanged(ManagerStates.INITIALIZING);
247                 }
248             }
249             Map<DSID, Device> tempDeviceMap;
250             if (strucMan.getDeviceMap() != null) {
251                 tempDeviceMap = strucMan.getDeviceMap();
252             } else {
253                 tempDeviceMap = new HashMap<>();
254             }
255
256             List<Device> currentDeviceList = getDetailedDevices();
257
258             // update the current total power consumption
259             if (nextSensorUpdate <= System.currentTimeMillis()) {
260                 // check circuits
261                 List<Circuit> circuits = digitalSTROMClient.getApartmentCircuits(connMan.getSessionToken());
262                 for (Circuit circuit : circuits) {
263                     if (strucMan.getCircuitByDSID(circuit.getDSID()) != null) {
264                         if (!circuit.equals(strucMan.getCircuitByDSID(circuit.getDSID()))) {
265                             strucMan.updateCircuitConfig(circuit);
266                         }
267                     } else {
268                         strucMan.addCircuit(circuit);
269                         if (deviceDiscovery != null) {
270                             deviceDiscovery.onDeviceAdded(circuit);
271                         }
272                     }
273                 }
274                 getMeterData();
275                 nextSensorUpdate = System.currentTimeMillis() + config.getTotalPowerUpdateInterval();
276             }
277
278             while (!currentDeviceList.isEmpty()) {
279                 Device currentDevice = currentDeviceList.remove(0);
280                 DSID currentDeviceDSID = currentDevice.getDSID();
281                 Device device = tempDeviceMap.remove(currentDeviceDSID);
282
283                 if (device != null) {
284                     checkDeviceConfig(currentDevice, device);
285
286                     if (device.isPresent()) {
287                         // check device state updates
288                         while (!device.isDeviceUpToDate()) {
289                             DeviceStateUpdate deviceStateUpdate = device.getNextDeviceUpdateState();
290                             if (deviceStateUpdate != null) {
291                                 switch (deviceStateUpdate.getType()) {
292                                     case DeviceStateUpdate.OUTPUT:
293                                     case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
294                                     case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
295                                         filterCommand(deviceStateUpdate, device);
296                                         break;
297                                     case DeviceStateUpdate.UPDATE_SCENE_CONFIG:
298                                     case DeviceStateUpdate.UPDATE_SCENE_OUTPUT:
299                                         updateSceneData(device, deviceStateUpdate);
300                                         break;
301                                     case DeviceStateUpdate.UPDATE_OUTPUT_VALUE:
302                                         if (deviceStateUpdate.getValueAsInteger() > -1) {
303                                             readOutputValue(device);
304                                         } else {
305                                             removeSensorJob(device, deviceStateUpdate);
306                                         }
307                                         break;
308                                     default:
309                                         sendComandsToDSS(device, deviceStateUpdate);
310                                 }
311                             }
312                         }
313                     }
314                 } else {
315                     logger.debug("Found new device!");
316                     if (trashDevices.isEmpty()) {
317                         currentDevice.setConfig(config);
318                         strucMan.addDeviceToStructure(currentDevice);
319                         logger.debug("trashDevices are empty, add Device with dSID {} to the deviceMap!",
320                                 currentDevice.getDSID());
321                     } else {
322                         logger.debug("Search device in trashDevices.");
323                         boolean found = trashDevices.removeIf(trashDevice -> {
324                             if (trashDevice.getDevice().equals(currentDevice)) {
325                                 logger.debug(
326                                         "Found device in trashDevices, add TrashDevice with dSID {} to the StructureManager!",
327                                         currentDeviceDSID);
328                                 strucMan.addDeviceToStructure(trashDevice.getDevice());
329                                 return true;
330                             } else {
331                                 return false;
332                             }
333                         });
334                         if (!found) {
335                             strucMan.addDeviceToStructure(currentDevice);
336                             logger.debug(
337                                     "Can't find device in trashDevices, add Device with dSID: {} to the StructureManager!",
338                                     currentDeviceDSID);
339                         }
340                     }
341                     if (deviceDiscovery != null) {
342                         // only informs discovery, if the device is a output or a sensor device
343                         deviceDiscovery.onDeviceAdded(currentDevice);
344                         logger.debug("inform DeviceStatusListener: {} about added device with dSID {}",
345                                 DeviceStatusListener.DEVICE_DISCOVERY, currentDevice.getDSID().getValue());
346                     } else {
347                         logger.debug(
348                                 "The device discovery is not registrated, can't inform device discovery about found device.");
349                     }
350                 }
351             }
352
353             if (!devicesLoaded && strucMan.getDeviceMap() != null) {
354                 if (!strucMan.getDeviceMap().values().isEmpty()) {
355                     logger.debug("Devices loaded");
356                     devicesLoaded = true;
357                     setInizialStateWithLastCallScenes();
358                     stateChanged(ManagerStates.RUNNING);
359                 } else {
360                     logger.debug("No devices found");
361                 }
362             }
363
364             if (!sceneMan.scenesGenerated() && devicesLoaded
365                     && !sceneMan.getManagerState().equals(ManagerStates.GENERATING_SCENES)) {
366                 logger.debug("{}", sceneMan.getManagerState());
367                 sceneMan.generateScenes();
368             }
369
370             for (Device device : tempDeviceMap.values()) {
371                 logger.debug("Found removed devices.");
372
373                 trashDevices.add(new TrashDevice(device));
374                 DeviceStatusListener listener = device.unregisterDeviceStatusListener();
375                 if (listener != null) {
376                     listener.onDeviceRemoved(null);
377                 }
378                 strucMan.deleteDevice(device);
379                 logger.debug("Add device with dSID {} to trashDevices", device.getDSID().getValue());
380
381                 if (deviceDiscovery != null) {
382                     deviceDiscovery.onDeviceRemoved(device);
383                     logger.debug("inform DeviceStatusListener: {} about removed device with dSID {}",
384                             DeviceStatusListener.DEVICE_DISCOVERY, device.getDSID().getValue());
385                 } else {
386                     logger.debug(
387                             "The device-Discovery is not registered, can't inform device discovery about removed device.");
388                 }
389             }
390
391             if (!trashDevices.isEmpty() && (lastBinCheck + config.getBinCheckTime() < System.currentTimeMillis())) {
392                 trashDevices.removeIf(trashDevice -> {
393                     if (trashDevice.isTimeToDelete(Calendar.getInstance().get(Calendar.DAY_OF_YEAR))) {
394                         logger.debug("Deleted trashDevice: {}", trashDevice.getDevice().getDSID().getValue());
395                         return true;
396                     } else {
397                         return false;
398                     }
399                 });
400                 lastBinCheck = System.currentTimeMillis();
401             }
402         }
403
404         private List<Device> getDetailedDevices() {
405             List<Device> deviceList = new LinkedList<>();
406             JsonObject result = connMan.getDigitalSTROMAPI().query2(connMan.getSessionToken(), GET_DETAILD_DEVICES);
407             if (result != null && result.isJsonObject()) {
408                 if (result.getAsJsonObject().get(GeneralLibConstance.QUERY_BROADCAST_ZONE_STRING).isJsonObject()) {
409                     result = result.getAsJsonObject().get(GeneralLibConstance.QUERY_BROADCAST_ZONE_STRING)
410                             .getAsJsonObject();
411                     for (Entry<String, JsonElement> entry : result.entrySet()) {
412                         if (!(entry.getKey().equals(JSONApiResponseKeysEnum.ZONE_ID.getKey())
413                                 && entry.getKey().equals(JSONApiResponseKeysEnum.NAME.getKey()))
414                                 && entry.getValue().isJsonObject()) {
415                             deviceList.add(new DeviceImpl(entry.getValue().getAsJsonObject()));
416                         }
417                     }
418                 }
419             }
420             return deviceList;
421         }
422
423         private void filterCommand(DeviceStateUpdate deviceStateUpdate, Device device) {
424             DeviceStateUpdate intDeviceStateUpdate = deviceStateUpdate;
425             String stateUpdateType = intDeviceStateUpdate.getType();
426             short newAngle = 0;
427             if (stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE)
428                     || stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)) {
429                 newAngle = device.getAnglePosition();
430             }
431             DeviceStateUpdate nextDeviceStateUpdate = device.getNextDeviceUpdateState();
432             while (nextDeviceStateUpdate != null && nextDeviceStateUpdate.getType().equals(stateUpdateType)) {
433                 switch (stateUpdateType) {
434                     case DeviceStateUpdate.OUTPUT:
435                         intDeviceStateUpdate = nextDeviceStateUpdate;
436                         nextDeviceStateUpdate = device.getNextDeviceUpdateState();
437                         break;
438                     case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
439                         if (intDeviceStateUpdate.getValueAsInteger() == 1) {
440                             newAngle = (short) (newAngle + DeviceConstants.ANGLE_STEP_SLAT);
441                         }
442                         break;
443                     case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
444                         if (intDeviceStateUpdate.getValueAsInteger() == 1) {
445                             newAngle = (short) (newAngle - DeviceConstants.ANGLE_STEP_SLAT);
446                         }
447                         break;
448                 }
449             }
450             if (stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE)
451                     || stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)) {
452                 if (newAngle > device.getMaxSlatAngle()) {
453                     newAngle = (short) device.getMaxSlatAngle();
454                 }
455                 if (newAngle < device.getMinSlatAngle()) {
456                     newAngle = (short) device.getMinSlatAngle();
457                 }
458                 if (!(stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE) && checkAngleIsMinMax(device) == 1)
459                         || !(stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)
460                                 && checkAngleIsMinMax(device) == 0)) {
461                     intDeviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, newAngle);
462                 }
463             }
464             sendComandsToDSS(device, intDeviceStateUpdate);
465             if (nextDeviceStateUpdate != null) {
466                 if (intDeviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
467                         || intDeviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
468                     updateSceneData(device, intDeviceStateUpdate);
469                 } else {
470                     sendComandsToDSS(device, intDeviceStateUpdate);
471                 }
472             }
473         }
474     }
475
476     private void removeSensorJob(Device device, DeviceStateUpdate deviceStateUpdate) {
477         switch (deviceStateUpdate.getType()) {
478             case DeviceStateUpdate.UPDATE_SCENE_CONFIG:
479                 if (sceneJobExecutor != null) {
480                     sceneJobExecutor.removeSensorJob(device,
481                             SceneConfigReadingJob.getID(device, deviceStateUpdate.getSceneId()));
482                 }
483                 break;
484             case DeviceStateUpdate.UPDATE_SCENE_OUTPUT:
485                 if (sceneJobExecutor != null) {
486                     sceneJobExecutor.removeSensorJob(device,
487                             SceneOutputValueReadingJob.getID(device, deviceStateUpdate.getSceneId()));
488                 }
489                 break;
490             case DeviceStateUpdate.UPDATE_OUTPUT_VALUE:
491                 if (sensorJobExecutor != null) {
492                     sensorJobExecutor.removeSensorJob(device, DeviceOutputValueSensorJob.getID(device));
493                 }
494                 break;
495         }
496         if (deviceStateUpdate.isSensorUpdateType()) {
497             if (sensorJobExecutor != null) {
498                 logger.debug("remove SensorJob with ID: {}",
499                         DeviceConsumptionSensorJob.getID(device, deviceStateUpdate.getTypeAsSensorEnum()));
500                 sensorJobExecutor.removeSensorJob(device,
501                         DeviceConsumptionSensorJob.getID(device, deviceStateUpdate.getTypeAsSensorEnum()));
502             }
503         }
504     }
505
506     @Override
507     public ManagerTypes getManagerType() {
508         return ManagerTypes.DEVICE_STATUS_MANAGER;
509     }
510
511     @Override
512     public synchronized ManagerStates getManagerState() {
513         return state;
514     }
515
516     private synchronized void stateChanged(ManagerStates state) {
517         if (statusListener != null) {
518             this.state = state;
519             statusListener.onStatusChanged(ManagerTypes.DEVICE_STATUS_MANAGER, state);
520         }
521     }
522
523     @Override
524     public synchronized void start() {
525         logger.debug("start DeviceStatusManager");
526         if (pollingScheduler == null || pollingScheduler.isCancelled()) {
527             pollingScheduler = scheduler.scheduleWithFixedDelay(new PollingRunnable(), 0, config.getPollingFrequency(),
528                     TimeUnit.MILLISECONDS);
529             logger.debug("start pollingScheduler");
530         }
531         sceneMan.start();
532         if (sceneJobExecutor != null) {
533             this.sceneJobExecutor.startExecutor();
534         }
535
536         if (sensorJobExecutor != null) {
537             this.sensorJobExecutor.startExecutor();
538         }
539         if (eventListener != null) {
540             eventListener.addEventHandler(this);
541         } else {
542             eventListener = new EventListener(connMan, this);
543             eventListener.start();
544         }
545     }
546
547     @Override
548     public synchronized void stop() {
549         logger.debug("stop DeviceStatusManager");
550         stateChanged(ManagerStates.STOPPED);
551         if (sceneMan != null) {
552             sceneMan.stop();
553         }
554         if (pollingScheduler != null && !pollingScheduler.isCancelled()) {
555             pollingScheduler.cancel(true);
556             pollingScheduler = null;
557             logger.debug("stop pollingScheduler");
558         }
559         if (sceneJobExecutor != null) {
560             this.sceneJobExecutor.shutdown();
561         }
562         if (sensorJobExecutor != null) {
563             this.sensorJobExecutor.shutdown();
564         }
565         if (eventListener != null) {
566             eventListener.removeEventHandler(this);
567         }
568     }
569
570     /**
571      * The {@link TrashDevice} saves not present {@link Device}'s, but at this point not deleted from the
572      * digitalSTROM-System, temporary to get back the configuration of the {@link Device}'s faster.
573      *
574      * @author Michael Ochel - Initial contribution
575      * @author Matthias Siegele - Initial contribution
576      */
577     private class TrashDevice {
578         private final Device device;
579         private final int timestamp;
580
581         /**
582          * Creates a new {@link TrashDevice}.
583          *
584          * @param device to put in {@link TrashDevice}
585          */
586         public TrashDevice(Device device) {
587             this.device = device;
588             this.timestamp = Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
589         }
590
591         /**
592          * Returns the saved {@link Device}.
593          *
594          * @return device
595          */
596         public Device getDevice() {
597             return device;
598         }
599
600         /**
601          * Returns true if the time for the {@link TrashDevice} is over and it can be deleted.
602          *
603          * @param dayOfYear day of the current year
604          * @return true = time to delete | false = not time to delete
605          */
606         public boolean isTimeToDelete(int dayOfYear) {
607             return this.timestamp + config.getTrashDeviceDeleteTime() <= dayOfYear;
608         }
609
610         @Override
611         public boolean equals(Object object) {
612             return object instanceof TrashDevice
613                     ? this.device.getDSID().equals(((TrashDevice) object).getDevice().getDSID())
614                     : false;
615         }
616     }
617
618     private void checkDeviceConfig(Device newDevice, Device internalDevice) {
619         if (newDevice == null || internalDevice == null) {
620             return;
621         }
622         // check device availability has changed and informs the deviceStatusListener about the change.
623         // NOTE:
624         // The device is not availability for the digitalSTROM-Server, it has not been deleted and therefore it is set
625         // to
626         // OFFLINE.
627         // An alternate algorithm is responsible for deletion.
628         if (newDevice.isPresent() != internalDevice.isPresent()) {
629             internalDevice.setIsPresent(newDevice.isPresent());
630         }
631         if (newDevice.getMeterDSID() != null && !newDevice.getMeterDSID().equals(internalDevice.getMeterDSID())) {
632             internalDevice.setMeterDSID(newDevice.getMeterDSID().getValue());
633         }
634         if (newDevice.getFunctionalColorGroup() != null
635                 && !newDevice.getFunctionalColorGroup().equals(internalDevice.getFunctionalColorGroup())) {
636             internalDevice.setFunctionalColorGroup(newDevice.getFunctionalColorGroup());
637         }
638         if (newDevice.getName() != null && !newDevice.getName().equals(internalDevice.getName())) {
639             internalDevice.setName(newDevice.getName());
640         }
641         if (newDevice.getOutputMode() != null && !newDevice.getOutputMode().equals(internalDevice.getOutputMode())) {
642             if (deviceDiscovery != null) {
643                 if (OutputModeEnum.DISABLED.equals(internalDevice.getOutputMode())
644                         || OutputModeEnum.outputModeIsTemperationControlled(internalDevice.getOutputMode())) {
645                     deviceDiscovery.onDeviceAdded(newDevice);
646                 }
647                 if (OutputModeEnum.DISABLED.equals(newDevice.getOutputMode())
648                         || OutputModeEnum.outputModeIsTemperationControlled(newDevice.getOutputMode())) {
649                     deviceDiscovery.onDeviceRemoved(newDevice);
650                 }
651             }
652             internalDevice.setOutputMode(newDevice.getOutputMode());
653         }
654         if (!newDevice.getBinaryInputs().equals(internalDevice.getBinaryInputs())) {
655             internalDevice.setBinaryInputs(newDevice.getBinaryInputs());
656         }
657         strucMan.updateDevice(newDevice);
658     }
659
660     private long lastSceneCall = 0;
661     private long sleepTime = 0;
662
663     @Override
664     public synchronized void sendSceneComandsToDSS(InternalScene scene, boolean call_undo) {
665         if (scene != null) {
666             if (lastSceneCall + 1000 > System.currentTimeMillis()) {
667                 sleepTime = System.currentTimeMillis() - lastSceneCall;
668                 try {
669                     Thread.sleep(sleepTime);
670                 } catch (InterruptedException e) {
671                     logger.debug("An InterruptedException occurred", e);
672                 }
673             }
674             lastSceneCall = System.currentTimeMillis();
675             boolean requestSuccessful = false;
676             if (scene.getZoneID() == 0) {
677                 if (call_undo) {
678                     logger.debug("{} {} {}", scene.getGroupID(), scene.getSceneID(),
679                             ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
680                     requestSuccessful = this.digitalSTROMClient.callApartmentScene(connMan.getSessionToken(),
681                             scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()), false);
682                 } else {
683                     requestSuccessful = this.digitalSTROMClient.undoApartmentScene(connMan.getSessionToken(),
684                             scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
685                 }
686             } else {
687                 if (call_undo) {
688                     requestSuccessful = this.digitalSTROMClient.callZoneScene(connMan.getSessionToken(),
689                             scene.getZoneID(), null, scene.getGroupID(), null, SceneEnum.getScene(scene.getSceneID()),
690                             false);
691                 } else {
692                     requestSuccessful = this.digitalSTROMClient.undoZoneScene(connMan.getSessionToken(),
693                             scene.getZoneID(), null, scene.getGroupID(), null, SceneEnum.getScene(scene.getSceneID()));
694                 }
695             }
696
697             logger.debug("Was the scene call succsessful?: {}", requestSuccessful);
698             if (requestSuccessful) {
699                 this.sceneMan.addEcho(scene.getID());
700                 if (call_undo) {
701                     scene.activateScene();
702                 } else {
703                     scene.deactivateScene();
704                 }
705             }
706         }
707     }
708
709     @Override
710     public synchronized void sendStopComandsToDSS(final Device device) {
711         scheduler.execute(new Runnable() {
712
713             @Override
714             public void run() {
715                 if (digitalSTROMClient.callDeviceScene(connMan.getSessionToken(), device.getDSID(), null, null,
716                         SceneEnum.STOP, true)) {
717                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.STOP.getSceneNumber());
718                     readOutputValue(device);
719                 }
720             }
721         });
722     }
723
724     private void readOutputValue(Device device) {
725         short outputIndex = DeviceConstants.DEVICE_SENSOR_OUTPUT;
726         if (device.isShade()) {
727             outputIndex = DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT;
728         }
729
730         int outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(), device.getDSID(),
731                 null, null, outputIndex);
732         if (outputValue != -1) {
733             if (!device.isShade()) {
734                 device.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT, outputValue));
735             } else {
736                 device.updateInternalDeviceState(
737                         new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, outputValue));
738                 if (device.isBlind()) {
739                     outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(),
740                             device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT);
741                     device.updateInternalDeviceState(
742                             new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, outputValue));
743                 }
744             }
745         }
746     }
747
748     /**
749      * Updates the {@link Device} status of the given {@link Device} with handling outstanding commands, which are saved
750      * as {@link DeviceStateUpdate}'s.
751      *
752      * @param device to update
753      */
754     public synchronized void updateDevice(Device device) {
755         logger.debug("Check device updates");
756         // check device state updates
757         while (!device.isDeviceUpToDate()) {
758             DeviceStateUpdate deviceStateUpdate = device.getNextDeviceUpdateState();
759             if (deviceStateUpdate != null) {
760                 if (deviceStateUpdate.getType() != DeviceStateUpdate.OUTPUT) {
761                     if (deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
762                             || deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
763                         updateSceneData(device, deviceStateUpdate);
764                     } else {
765                         sendComandsToDSS(device, deviceStateUpdate);
766                     }
767                 } else {
768                     DeviceStateUpdate nextDeviceStateUpdate = device.getNextDeviceUpdateState();
769                     while (nextDeviceStateUpdate != null
770                             && nextDeviceStateUpdate.getType() == DeviceStateUpdate.OUTPUT) {
771                         deviceStateUpdate = nextDeviceStateUpdate;
772                         nextDeviceStateUpdate = device.getNextDeviceUpdateState();
773                     }
774                     sendComandsToDSS(device, deviceStateUpdate);
775                     if (nextDeviceStateUpdate != null) {
776                         if (deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
777                                 || deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
778                             updateSceneData(device, deviceStateUpdate);
779                         } else {
780                             sendComandsToDSS(device, deviceStateUpdate);
781                         }
782                     }
783                 }
784             }
785         }
786     }
787
788     /**
789      * Checks the output value of a {@link Device} and return 0, if the output value or slat position is min and 1, if
790      * the output value or slat position is max, otherwise it returns -1.
791      *
792      * @param device
793      * @return 0 = output value is min, 1 device value is min, otherwise -1
794      */
795     private short checkIsAllreadyMinMax(Device device) {
796         if (device.isShade()) {
797             if (device.getSlatPosition() == device.getMaxSlatPosition()) {
798                 if (device.isBlind()) {
799                     if (device.getAnglePosition() == device.getMaxSlatAngle()) {
800                         return 1;
801                     } else {
802                         return -1;
803                     }
804                 }
805                 return 1;
806             }
807             if (device.getSlatPosition() == device.getMinSlatPosition()) {
808                 if (device.isBlind()) {
809                     if (device.getAnglePosition() == device.getMinSlatAngle()) {
810                         return 0;
811                     } else {
812                         return -1;
813                     }
814                 }
815                 return 0;
816             }
817         } else {
818             if (device.getOutputValue() == device.getMaxOutputValue()) {
819                 return 1;
820             }
821             if (device.getOutputValue() == device.getMinOutputValue() || device.getOutputValue() <= 0) {
822                 return 0;
823             }
824         }
825         return -1;
826     }
827
828     /**
829      * Checks the angle value of a {@link Device} and return 0, if the angle value is min and 1, if the angle value is
830      * max, otherwise it returns -1.
831      *
832      * @param device
833      * @return 0 = angle value is min, 1 angle value is min, otherwise -1
834      */
835     private short checkAngleIsMinMax(Device device) {
836         if (device.getAnglePosition() == device.getMaxSlatAngle()) {
837             return 1;
838         }
839         if (device.getAnglePosition() == device.getMinSlatAngle()) {
840             return 1;
841         }
842         return -1;
843     }
844
845     @Override
846     public synchronized void sendComandsToDSS(Device device, DeviceStateUpdate deviceStateUpdate) {
847         boolean requestSuccsessful = false;
848         boolean commandHasNoEffect = false;
849         if (deviceStateUpdate != null) {
850             if (deviceStateUpdate.isSensorUpdateType()) {
851                 SensorEnum sensorType = deviceStateUpdate.getTypeAsSensorEnum();
852                 if (deviceStateUpdate.getValueAsInteger() == 0) {
853                     updateSensorData(new DeviceConsumptionSensorJob(device, sensorType),
854                             device.getPowerSensorRefreshPriority(sensorType));
855                     return;
856                 } else if (deviceStateUpdate.getValueAsInteger() < 0) {
857                     removeSensorJob(device, deviceStateUpdate);
858                     return;
859                 } else {
860                     int consumption = this.digitalSTROMClient.getDeviceSensorValue(connMan.getSessionToken(),
861                             device.getDSID(), null, null, device.getSensorIndex(sensorType));
862                     if (consumption >= 0) {
863                         device.setDeviceSensorDsValueBySensorJob(sensorType, consumption);
864                         requestSuccsessful = true;
865                     }
866                 }
867             } else {
868                 switch (deviceStateUpdate.getType()) {
869                     case DeviceStateUpdate.OUTPUT_DECREASE:
870                     case DeviceStateUpdate.SLAT_DECREASE:
871                         if (checkIsAllreadyMinMax(device) != 0) {
872                             requestSuccsessful = digitalSTROMClient.decreaseValue(connMan.getSessionToken(),
873                                     device.getDSID(), null, null);
874                             if (requestSuccsessful) {
875                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.DECREMENT.getSceneNumber());
876                             }
877                         } else {
878                             commandHasNoEffect = true;
879                         }
880                         break;
881                     case DeviceStateUpdate.OUTPUT_INCREASE:
882                     case DeviceStateUpdate.SLAT_INCREASE:
883                         if (checkIsAllreadyMinMax(device) != 1) {
884                             requestSuccsessful = digitalSTROMClient.increaseValue(connMan.getSessionToken(),
885                                     device.getDSID(), null, null);
886                             if (requestSuccsessful) {
887                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.INCREMENT.getSceneNumber());
888                             }
889                         } else {
890                             commandHasNoEffect = true;
891                         }
892                         break;
893                     case DeviceStateUpdate.OUTPUT:
894                         if (device.getOutputValue() != deviceStateUpdate.getValueAsInteger()) {
895                             requestSuccsessful = digitalSTROMClient.setDeviceValue(connMan.getSessionToken(),
896                                     device.getDSID(), null, null, deviceStateUpdate.getValueAsInteger());
897                         } else {
898                             commandHasNoEffect = true;
899                         }
900                         break;
901                     case DeviceStateUpdate.OPEN_CLOSE:
902                     case DeviceStateUpdate.ON_OFF:
903                         if (deviceStateUpdate.getValueAsInteger() > 0) {
904                             if (checkIsAllreadyMinMax(device) != 1) {
905                                 requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
906                                         device.getDSID(), null, null);
907                                 if (requestSuccsessful) {
908                                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
909                                 }
910                             } else {
911                                 commandHasNoEffect = true;
912                             }
913                         } else {
914                             if (checkIsAllreadyMinMax(device) != 0) {
915                                 requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
916                                         device.getDSID(), null, null);
917                                 if (requestSuccsessful) {
918                                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
919                                 }
920                             } else {
921                                 commandHasNoEffect = true;
922                             }
923                         }
924                         break;
925                     case DeviceStateUpdate.SLATPOSITION:
926                         if (device.getSlatPosition() != deviceStateUpdate.getValueAsInteger()) {
927                             requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
928                                     device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT,
929                                     deviceStateUpdate.getValueAsInteger());
930                         } else {
931                             commandHasNoEffect = true;
932                         }
933                         break;
934                     case DeviceStateUpdate.SLAT_STOP:
935                         this.sendStopComandsToDSS(device);
936                         break;
937                     case DeviceStateUpdate.SLAT_MOVE:
938                         if (deviceStateUpdate.getValueAsInteger() > 0) {
939                             requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
940                                     device.getDSID(), null, null);
941                             if (requestSuccsessful) {
942                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
943                             }
944                         } else {
945                             requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
946                                     device.getDSID(), null, null);
947                             if (requestSuccsessful) {
948                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
949                             }
950                             if (sensorJobExecutor != null) {
951                                 sensorJobExecutor.removeSensorJobs(device);
952                             }
953                         }
954                         break;
955                     case DeviceStateUpdate.UPDATE_CALL_SCENE:
956                         if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
957                             requestSuccsessful = digitalSTROMClient.callDeviceScene(connMan.getSessionToken(),
958                                     device.getDSID(), null, null,
959                                     SceneEnum.getScene((short) deviceStateUpdate.getValue()), true);
960                         }
961                         break;
962                     case DeviceStateUpdate.UPDATE_UNDO_SCENE:
963                         if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
964                             requestSuccsessful = digitalSTROMClient.undoDeviceScene(connMan.getSessionToken(),
965                                     device.getDSID(), null, null,
966                                     SceneEnum.getScene((short) deviceStateUpdate.getValue()));
967                         }
968                         break;
969                     case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
970                         // By UPDATE_SLAT_ANGLE_DECREASE, UPDATE_SLAT_ANGLE_INCREASE with value unequal 1 which will
971                         // handled in the pollingRunnable and UPDATE_OPEN_CLOSE_ANGLE the value will be set without
972                         // checking, because it was triggered by setting the slat position.
973                         requestSuccsessful = true;
974                         break;
975                     case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
976                         requestSuccsessful = true;
977                         break;
978                     case DeviceStateUpdate.OPEN_CLOSE_ANGLE:
979                         requestSuccsessful = true;
980                         break;
981                     case DeviceStateUpdate.SLAT_ANGLE:
982                         if (device.getAnglePosition() != deviceStateUpdate.getValueAsInteger()) {
983                             requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
984                                     device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT,
985                                     deviceStateUpdate.getValueAsInteger());
986                         } else {
987                             commandHasNoEffect = true;
988                         }
989                         break;
990                     case DeviceStateUpdate.REFRESH_OUTPUT:
991                         readOutputValue(device);
992                         logger.debug("Inizalize output value reading for device with dSID {}.",
993                                 device.getDSID().getValue());
994                         return;
995                     default:
996                         return;
997                 }
998             }
999             if (commandHasNoEffect) {
1000                 logger.debug("Command {} for device with dSID {} not send to dSS, because it has no effect!",
1001                         deviceStateUpdate.getType(), device.getDSID().getValue());
1002                 return;
1003             }
1004             if (requestSuccsessful) {
1005                 logger.debug("Send {} command to dSS and updateInternalDeviceState for device with dSID {}.",
1006                         deviceStateUpdate.getType(), device.getDSID().getValue());
1007                 device.updateInternalDeviceState(deviceStateUpdate);
1008             } else {
1009                 logger.debug("Can't send {} command for device with dSID {} to dSS!", deviceStateUpdate.getType(),
1010                         device.getDSID().getValue());
1011             }
1012         }
1013     }
1014
1015     @Override
1016     public void updateSensorData(SensorJob sensorJob, String priority) {
1017         if (sensorJobExecutor == null) {
1018             sensorJobExecutor = new SensorJobExecutor(connMan);
1019             this.sensorJobExecutor.startExecutor();
1020         }
1021         if (sensorJob != null && priority != null) {
1022             switch (priority) {
1023                 case Config.REFRESH_PRIORITY_HIGH:
1024                     sensorJobExecutor.addHighPriorityJob(sensorJob);
1025                     break;
1026                 case Config.REFRESH_PRIORITY_MEDIUM:
1027                     sensorJobExecutor.addMediumPriorityJob(sensorJob);
1028                     break;
1029                 case Config.REFRESH_PRIORITY_LOW:
1030                     sensorJobExecutor.addLowPriorityJob(sensorJob);
1031                     break;
1032                 default:
1033                     try {
1034                         long prio = Long.parseLong(priority);
1035                         sensorJobExecutor.addPriorityJob(sensorJob, prio);
1036                     } catch (NumberFormatException e) {
1037                         logger.debug("Sensor data update priority do not exist! Please check the input!");
1038                         return;
1039                     }
1040             }
1041             logger.debug("Add new sensorJob {} with priority: {} to sensorJobExecuter", sensorJob.toString(), priority);
1042         }
1043     }
1044
1045     @Override
1046     public void updateSceneData(Device device, DeviceStateUpdate deviceStateUpdate) {
1047         if (sceneJobExecutor == null) {
1048             sceneJobExecutor = new SceneReadingJobExecutor(connMan);
1049             this.sceneJobExecutor.startExecutor();
1050         }
1051
1052         if (deviceStateUpdate != null) {
1053             if (deviceStateUpdate.getScenePriority() > -1) {
1054                 if (deviceStateUpdate.getType().equals(DeviceStateUpdate.UPDATE_SCENE_OUTPUT)) {
1055                     sceneJobExecutor.addPriorityJob(
1056                             new SceneOutputValueReadingJob(device, deviceStateUpdate.getSceneId()),
1057                             deviceStateUpdate.getScenePriority().longValue());
1058                 } else {
1059                     sceneJobExecutor.addPriorityJob(new SceneConfigReadingJob(device, deviceStateUpdate.getSceneId()),
1060                             deviceStateUpdate.getScenePriority().longValue());
1061                 }
1062                 if (deviceStateUpdate.getScenePriority() == 0) {
1063                     updateSensorData(new DeviceOutputValueSensorJob(device), "0");
1064                 }
1065                 logger.debug("Add new sceneReadingJob with priority: {} to SceneReadingJobExecuter",
1066                         deviceStateUpdate.getScenePriority());
1067             } else {
1068                 removeSensorJob(device, deviceStateUpdate);
1069             }
1070         }
1071     }
1072
1073     @Override
1074     public void registerDeviceListener(DeviceStatusListener deviceListener) {
1075         if (deviceListener != null) {
1076             String id = deviceListener.getDeviceStatusListenerID();
1077             if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
1078                 this.deviceDiscovery = deviceListener;
1079                 logger.debug("register Device-Discovery ");
1080                 for (Device device : strucMan.getDeviceMap().values()) {
1081                     deviceDiscovery.onDeviceAdded(device);
1082                 }
1083                 for (Circuit circuit : strucMan.getCircuitMap().values()) {
1084                     deviceDiscovery.onDeviceAdded(circuit);
1085                 }
1086             } else {
1087                 Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
1088                 if (intDevice != null) {
1089                     logger.debug("register DeviceListener with id: {} to Device ", id);
1090                     intDevice.registerDeviceStatusListener(deviceListener);
1091                 } else {
1092                     Circuit intCircuit = strucMan
1093                             .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
1094                     if (intCircuit != null) {
1095                         logger.debug("register DeviceListener with id: {} to Circuit ", id);
1096                         intCircuit.registerDeviceStatusListener(deviceListener);
1097                     } else {
1098                         deviceListener.onDeviceRemoved(null);
1099                     }
1100                 }
1101             }
1102         }
1103     }
1104
1105     @Override
1106     public void unregisterDeviceListener(DeviceStatusListener deviceListener) {
1107         if (deviceListener != null) {
1108             String id = deviceListener.getDeviceStatusListenerID();
1109             logger.debug("unregister DeviceListener with id: {}", id);
1110             if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
1111                 this.deviceDiscovery = null;
1112             } else {
1113                 Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
1114                 if (intDevice != null) {
1115                     intDevice.unregisterDeviceStatusListener();
1116                 } else {
1117                     Circuit intCircuit = strucMan
1118                             .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
1119                     if (intCircuit != null) {
1120                         intCircuit.unregisterDeviceStatusListener();
1121                         if (deviceDiscovery != null) {
1122                             deviceDiscovery.onDeviceAdded(intCircuit);
1123                         }
1124                     }
1125                 }
1126             }
1127         }
1128     }
1129
1130     @Override
1131     public void removeDevice(String dSID) {
1132         Device intDevice = strucMan.getDeviceByDSID(dSID);
1133         if (intDevice != null) {
1134             strucMan.deleteDevice(intDevice);
1135             trashDevices.add(new TrashDevice(intDevice));
1136         }
1137     }
1138
1139     @Override
1140     public void registerTotalPowerConsumptionListener(TotalPowerConsumptionListener totalPowerConsumptionListener) {
1141         this.totalPowerConsumptionListener = totalPowerConsumptionListener;
1142     }
1143
1144     @Override
1145     public void unregisterTotalPowerConsumptionListener() {
1146         this.totalPowerConsumptionListener = null;
1147     }
1148
1149     @Override
1150     public void registerSceneListener(SceneStatusListener sceneListener) {
1151         this.sceneMan.registerSceneListener(sceneListener);
1152     }
1153
1154     @Override
1155     public void unregisterSceneListener(SceneStatusListener sceneListener) {
1156         this.sceneMan.unregisterSceneListener(sceneListener);
1157     }
1158
1159     @Override
1160     public void registerStatusListener(ManagerStatusListener statusListener) {
1161         this.statusListener = statusListener;
1162         this.sceneMan.registerStatusListener(statusListener);
1163     }
1164
1165     @Override
1166     public void unregisterStatusListener() {
1167         this.statusListener = null;
1168         this.sceneMan.unregisterStatusListener();
1169     }
1170
1171     @Override
1172     public void registerConnectionListener(ConnectionListener connectionListener) {
1173         this.connMan.registerConnectionListener(connectionListener);
1174     }
1175
1176     @Override
1177     public void unregisterConnectionListener() {
1178         this.connMan.unregisterConnectionListener();
1179     }
1180
1181     @Override
1182     public int getTotalPowerConsumption() {
1183         List<CachedMeteringValue> cachedConsumptionMeteringValues = digitalSTROMClient
1184                 .getLatest(connMan.getSessionToken(), MeteringTypeEnum.CONSUMPTION, DsAPI.ALL_METERS, null);
1185         if (cachedConsumptionMeteringValues != null) {
1186             tempConsumption = 0;
1187             for (CachedMeteringValue value : cachedConsumptionMeteringValues) {
1188                 tempConsumption += value.getValue();
1189                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1190                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1191                 }
1192             }
1193         }
1194         return tempConsumption;
1195     }
1196
1197     private void getMeterData() {
1198         int val = getTotalPowerConsumption();
1199         if (totalPowerConsumptionListener != null) {
1200             totalPowerConsumptionListener.onTotalPowerConsumptionChanged(val);
1201         }
1202         val = getTotalEnergyMeterValue();
1203         if (totalPowerConsumptionListener != null) {
1204             totalPowerConsumptionListener.onEnergyMeterValueChanged(val);
1205         }
1206     }
1207
1208     @Override
1209     public int getTotalEnergyMeterValue() {
1210         List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient.getLatest(connMan.getSessionToken(),
1211                 MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, null);
1212         if (cachedEnergyMeteringValues != null) {
1213             tempEnergyMeter = 0;
1214             for (CachedMeteringValue value : cachedEnergyMeteringValues) {
1215                 tempEnergyMeter += value.getValue();
1216                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1217                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1218                 }
1219             }
1220         }
1221         return tempEnergyMeter;
1222     }
1223
1224     @Override
1225     public int getTotalEnergyMeterWsValue() {
1226         List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient.getLatest(connMan.getSessionToken(),
1227                 MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, MeteringUnitsEnum.WS);
1228         if (cachedEnergyMeteringValues != null) {
1229             tempEnergyMeterWs = 0;
1230             for (CachedMeteringValue value : cachedEnergyMeteringValues) {
1231                 tempEnergyMeterWs += value.getValue();
1232                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1233                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1234                 }
1235             }
1236         }
1237         return tempEnergyMeterWs;
1238     }
1239
1240     private void setInizialStateWithLastCallScenes() {
1241         if (sceneMan == null) {
1242             return;
1243         }
1244         JsonObject response = connMan.getDigitalSTROMAPI().query2(connMan.getSessionToken(), LAST_CALL_SCENE_QUERY);
1245         if (!response.isJsonObject()) {
1246             return;
1247         }
1248         for (Entry<String, JsonElement> entry : response.entrySet()) {
1249             if (!entry.getValue().isJsonObject()) {
1250                 continue;
1251             }
1252             JsonObject zone = entry.getValue().getAsJsonObject();
1253             int zoneID = -1;
1254             short groupID = -1;
1255             short sceneID = -1;
1256             if (zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()) != null) {
1257                 zoneID = zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()).getAsInt();
1258             }
1259             for (Entry<String, JsonElement> groupEntry : zone.entrySet()) {
1260                 if (groupEntry.getKey().startsWith("group") && groupEntry.getValue().isJsonObject()) {
1261                     JsonObject group = groupEntry.getValue().getAsJsonObject();
1262                     if (group.get(JSONApiResponseKeysEnum.DEVICES.getKey()) != null) {
1263                         if (group.get(JSONApiResponseKeysEnum.GROUP.getKey()) != null) {
1264                             groupID = group.get(JSONApiResponseKeysEnum.GROUP.getKey()).getAsShort();
1265                         }
1266                         if (group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()) != null) {
1267                             sceneID = group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()).getAsShort();
1268                         }
1269                         if (zoneID > -1 && groupID > -1 && sceneID > -1) {
1270                             logger.debug("initial state, call scene {}-{}-{}", zoneID, groupID, sceneID);
1271                             sceneMan.callInternalSceneWithoutDiscovery(zoneID, groupID, sceneID);
1272                         }
1273                     }
1274                 }
1275             }
1276         }
1277     }
1278
1279     @Override
1280     public void handleEvent(EventItem eventItem) {
1281         try {
1282             if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())
1283                     || EventNames.DEVICE_BINARY_INPUT_EVENT.equals(eventItem.getName())) {
1284                 logger.debug("Detect {} eventItem = {}", eventItem.getName(), eventItem.toString());
1285                 Device dev = getDeviceOfEvent(eventItem);
1286                 if (dev != null) {
1287                     if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())) {
1288                         dev.setDeviceSensorByEvent(eventItem);
1289                     } else {
1290                         DeviceBinarayInputEnum binaryInputType = DeviceBinarayInputEnum.getdeviceBinarayInput(Short
1291                                 .parseShort(eventItem.getProperties().getOrDefault(EventResponseEnum.INPUT_TYPE, "")));
1292                         Short newState = Short
1293                                 .parseShort(eventItem.getProperties().getOrDefault(EventResponseEnum.INPUT_STATE, ""));
1294                         if (binaryInputType != null) {
1295                             dev.setBinaryInputState(binaryInputType, newState);
1296                         }
1297                     }
1298                 }
1299             }
1300         } catch (NumberFormatException e) {
1301             logger.debug("Unexpected missing or invalid number while handling event", e);
1302         }
1303     }
1304
1305     private Device getDeviceOfEvent(EventItem eventItem) {
1306         if (eventItem.getSource().get(EventResponseEnum.DSID) != null) {
1307             String dSID = eventItem.getSource().get(EventResponseEnum.DSID);
1308             Device dev = strucMan.getDeviceByDSID(dSID);
1309             if (dev == null) {
1310                 dev = strucMan.getDeviceByDSUID(dSID);
1311             }
1312             return dev;
1313         }
1314         return null;
1315     }
1316
1317     @Override
1318     public List<String> getSupportedEvents() {
1319         return SUPPORTED_EVENTS;
1320     }
1321
1322     @Override
1323     public boolean supportsEvent(String eventName) {
1324         return SUPPORTED_EVENTS.contains(eventName);
1325     }
1326
1327     @Override
1328     public String getUID() {
1329         return getClass().getName();
1330     }
1331
1332     @Override
1333     public void setEventListener(EventListener eventListener) {
1334         if (this.eventListener != null) {
1335             this.eventListener.removeEventHandler(this);
1336         }
1337         this.eventListener = eventListener;
1338     }
1339
1340     @Override
1341     public void unsetEventListener(EventListener eventListener) {
1342         if (this.eventListener != null) {
1343             this.eventListener.removeEventHandler(this);
1344         }
1345         this.eventListener = null;
1346     }
1347 }