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