]> git.basschouten.com Git - openhab-addons.git/blob
5bdc50e7edbbcb07318964fea1fc2c826935962e
[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 td ? this.device.getDSID().equals(td.getDevice().getDSID()) : false;
614         }
615     }
616
617     private void checkDeviceConfig(Device newDevice, Device internalDevice) {
618         if (newDevice == null || internalDevice == null) {
619             return;
620         }
621         // check device availability has changed and informs the deviceStatusListener about the change.
622         // NOTE:
623         // The device is not availability for the digitalSTROM-Server, it has not been deleted and therefore it is set
624         // to
625         // OFFLINE.
626         // An alternate algorithm is responsible for deletion.
627         if (!Objects.equals(newDevice.isPresent(), internalDevice.isPresent())) {
628             internalDevice.setIsPresent(newDevice.isPresent());
629         }
630         if (newDevice.getMeterDSID() != null && !newDevice.getMeterDSID().equals(internalDevice.getMeterDSID())) {
631             internalDevice.setMeterDSID(newDevice.getMeterDSID().getValue());
632         }
633         if (newDevice.getFunctionalColorGroup() != null
634                 && !newDevice.getFunctionalColorGroup().equals(internalDevice.getFunctionalColorGroup())) {
635             internalDevice.setFunctionalColorGroup(newDevice.getFunctionalColorGroup());
636         }
637         if (newDevice.getName() != null && !newDevice.getName().equals(internalDevice.getName())) {
638             internalDevice.setName(newDevice.getName());
639         }
640         if (newDevice.getOutputMode() != null && !newDevice.getOutputMode().equals(internalDevice.getOutputMode())) {
641             if (deviceDiscovery != null) {
642                 if (OutputModeEnum.DISABLED.equals(internalDevice.getOutputMode())
643                         || OutputModeEnum.outputModeIsTemperationControlled(internalDevice.getOutputMode())) {
644                     deviceDiscovery.onDeviceAdded(newDevice);
645                 }
646                 if (OutputModeEnum.DISABLED.equals(newDevice.getOutputMode())
647                         || OutputModeEnum.outputModeIsTemperationControlled(newDevice.getOutputMode())) {
648                     deviceDiscovery.onDeviceRemoved(newDevice);
649                 }
650             }
651             internalDevice.setOutputMode(newDevice.getOutputMode());
652         }
653         if (!newDevice.getBinaryInputs().equals(internalDevice.getBinaryInputs())) {
654             internalDevice.setBinaryInputs(newDevice.getBinaryInputs());
655         }
656         strucMan.updateDevice(newDevice);
657     }
658
659     private long lastSceneCall = 0;
660     private long sleepTime = 0;
661
662     @Override
663     public synchronized void sendSceneComandsToDSS(InternalScene scene, boolean call_undo) {
664         if (scene != null) {
665             if (lastSceneCall + 1000 > System.currentTimeMillis()) {
666                 sleepTime = System.currentTimeMillis() - lastSceneCall;
667                 try {
668                     Thread.sleep(sleepTime);
669                 } catch (InterruptedException e) {
670                     logger.debug("An InterruptedException occurred", e);
671                 }
672             }
673             lastSceneCall = System.currentTimeMillis();
674             boolean requestSuccessful = false;
675             if (scene.getZoneID() == 0) {
676                 if (call_undo) {
677                     logger.debug("{} {} {}", scene.getGroupID(), scene.getSceneID(),
678                             ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
679                     requestSuccessful = this.digitalSTROMClient.callApartmentScene(connMan.getSessionToken(),
680                             scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()), false);
681                 } else {
682                     requestSuccessful = this.digitalSTROMClient.undoApartmentScene(connMan.getSessionToken(),
683                             scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
684                 }
685             } else {
686                 if (call_undo) {
687                     requestSuccessful = this.digitalSTROMClient.callZoneScene(connMan.getSessionToken(),
688                             scene.getZoneID(), null, scene.getGroupID(), null, SceneEnum.getScene(scene.getSceneID()),
689                             false);
690                 } else {
691                     requestSuccessful = this.digitalSTROMClient.undoZoneScene(connMan.getSessionToken(),
692                             scene.getZoneID(), null, scene.getGroupID(), null, SceneEnum.getScene(scene.getSceneID()));
693                 }
694             }
695
696             logger.debug("Was the scene call succsessful?: {}", requestSuccessful);
697             if (requestSuccessful) {
698                 this.sceneMan.addEcho(scene.getID());
699                 if (call_undo) {
700                     scene.activateScene();
701                 } else {
702                     scene.deactivateScene();
703                 }
704             }
705         }
706     }
707
708     @Override
709     public synchronized void sendStopComandsToDSS(final Device device) {
710         scheduler.execute(new Runnable() {
711
712             @Override
713             public void run() {
714                 if (digitalSTROMClient.callDeviceScene(connMan.getSessionToken(), device.getDSID(), null, null,
715                         SceneEnum.STOP, true)) {
716                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.STOP.getSceneNumber());
717                     readOutputValue(device);
718                 }
719             }
720         });
721     }
722
723     private void readOutputValue(Device device) {
724         short outputIndex = DeviceConstants.DEVICE_SENSOR_OUTPUT;
725         if (device.isShade()) {
726             outputIndex = DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT;
727         }
728
729         int outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(), device.getDSID(),
730                 null, null, outputIndex);
731         if (outputValue != -1) {
732             if (!device.isShade()) {
733                 device.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT, outputValue));
734             } else {
735                 device.updateInternalDeviceState(
736                         new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, outputValue));
737                 if (device.isBlind()) {
738                     outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(),
739                             device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT);
740                     device.updateInternalDeviceState(
741                             new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, outputValue));
742                 }
743             }
744         }
745     }
746
747     /**
748      * Updates the {@link Device} status of the given {@link Device} with handling outstanding commands, which are saved
749      * as {@link DeviceStateUpdate}'s.
750      *
751      * @param device to update
752      */
753     public synchronized void updateDevice(Device device) {
754         logger.debug("Check device updates");
755         // check device state updates
756         while (!device.isDeviceUpToDate()) {
757             DeviceStateUpdate deviceStateUpdate = device.getNextDeviceUpdateState();
758             if (deviceStateUpdate != null) {
759                 if (!DeviceStateUpdate.OUTPUT.equals(deviceStateUpdate.getType())) {
760                     if (DeviceStateUpdate.UPDATE_SCENE_CONFIG.equals(deviceStateUpdate.getType())
761                             || DeviceStateUpdate.UPDATE_SCENE_OUTPUT.equals(deviceStateUpdate.getType())) {
762                         updateSceneData(device, deviceStateUpdate);
763                     } else {
764                         sendComandsToDSS(device, deviceStateUpdate);
765                     }
766                 } else {
767                     DeviceStateUpdate nextDeviceStateUpdate = device.getNextDeviceUpdateState();
768                     while (nextDeviceStateUpdate != null
769                             && DeviceStateUpdate.OUTPUT.equals(nextDeviceStateUpdate.getType())) {
770                         deviceStateUpdate = nextDeviceStateUpdate;
771                         nextDeviceStateUpdate = device.getNextDeviceUpdateState();
772                     }
773                     sendComandsToDSS(device, deviceStateUpdate);
774                     if (nextDeviceStateUpdate != null) {
775                         if (DeviceStateUpdate.UPDATE_SCENE_CONFIG.equals(deviceStateUpdate.getType())
776                                 || DeviceStateUpdate.UPDATE_SCENE_OUTPUT.equals(deviceStateUpdate.getType())) {
777                             updateSceneData(device, deviceStateUpdate);
778                         } else {
779                             sendComandsToDSS(device, deviceStateUpdate);
780                         }
781                     }
782                 }
783             }
784         }
785     }
786
787     /**
788      * Checks the output value of a {@link Device} and return 0, if the output value or slat position is min and 1, if
789      * the output value or slat position is max, otherwise it returns -1.
790      *
791      * @param device
792      * @return 0 = output value is min, 1 device value is min, otherwise -1
793      */
794     private short checkIsAllreadyMinMax(Device device) {
795         if (device.isShade()) {
796             if (device.getSlatPosition() == device.getMaxSlatPosition()) {
797                 if (device.isBlind()) {
798                     if (device.getAnglePosition() == device.getMaxSlatAngle()) {
799                         return 1;
800                     } else {
801                         return -1;
802                     }
803                 }
804                 return 1;
805             }
806             if (device.getSlatPosition() == device.getMinSlatPosition()) {
807                 if (device.isBlind()) {
808                     if (device.getAnglePosition() == device.getMinSlatAngle()) {
809                         return 0;
810                     } else {
811                         return -1;
812                     }
813                 }
814                 return 0;
815             }
816         } else {
817             if (device.getOutputValue() == device.getMaxOutputValue()) {
818                 return 1;
819             }
820             if (device.getOutputValue() == device.getMinOutputValue() || device.getOutputValue() <= 0) {
821                 return 0;
822             }
823         }
824         return -1;
825     }
826
827     /**
828      * Checks the angle value of a {@link Device} and return 0, if the angle value is min and 1, if the angle value is
829      * max, otherwise it returns -1.
830      *
831      * @param device
832      * @return 0 = angle value is min, 1 angle value is min, otherwise -1
833      */
834     private short checkAngleIsMinMax(Device device) {
835         if (device.getAnglePosition() == device.getMaxSlatAngle()) {
836             return 1;
837         }
838         if (device.getAnglePosition() == device.getMinSlatAngle()) {
839             return 1;
840         }
841         return -1;
842     }
843
844     @Override
845     public synchronized void sendComandsToDSS(Device device, DeviceStateUpdate deviceStateUpdate) {
846         boolean requestSuccsessful = false;
847         boolean commandHasNoEffect = false;
848         if (deviceStateUpdate != null) {
849             if (deviceStateUpdate.isSensorUpdateType()) {
850                 SensorEnum sensorType = deviceStateUpdate.getTypeAsSensorEnum();
851                 if (deviceStateUpdate.getValueAsInteger() == 0) {
852                     updateSensorData(new DeviceConsumptionSensorJob(device, sensorType),
853                             device.getPowerSensorRefreshPriority(sensorType));
854                     return;
855                 } else if (deviceStateUpdate.getValueAsInteger() < 0) {
856                     removeSensorJob(device, deviceStateUpdate);
857                     return;
858                 } else {
859                     int consumption = this.digitalSTROMClient.getDeviceSensorValue(connMan.getSessionToken(),
860                             device.getDSID(), null, null, device.getSensorIndex(sensorType));
861                     if (consumption >= 0) {
862                         device.setDeviceSensorDsValueBySensorJob(sensorType, consumption);
863                         requestSuccsessful = true;
864                     }
865                 }
866             } else {
867                 switch (deviceStateUpdate.getType()) {
868                     case DeviceStateUpdate.OUTPUT_DECREASE:
869                     case DeviceStateUpdate.SLAT_DECREASE:
870                         if (checkIsAllreadyMinMax(device) != 0) {
871                             requestSuccsessful = digitalSTROMClient.decreaseValue(connMan.getSessionToken(),
872                                     device.getDSID(), null, null);
873                             if (requestSuccsessful) {
874                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.DECREMENT.getSceneNumber());
875                             }
876                         } else {
877                             commandHasNoEffect = true;
878                         }
879                         break;
880                     case DeviceStateUpdate.OUTPUT_INCREASE:
881                     case DeviceStateUpdate.SLAT_INCREASE:
882                         if (checkIsAllreadyMinMax(device) != 1) {
883                             requestSuccsessful = digitalSTROMClient.increaseValue(connMan.getSessionToken(),
884                                     device.getDSID(), null, null);
885                             if (requestSuccsessful) {
886                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.INCREMENT.getSceneNumber());
887                             }
888                         } else {
889                             commandHasNoEffect = true;
890                         }
891                         break;
892                     case DeviceStateUpdate.OUTPUT:
893                         if (device.getOutputValue() != deviceStateUpdate.getValueAsInteger()) {
894                             requestSuccsessful = digitalSTROMClient.setDeviceValue(connMan.getSessionToken(),
895                                     device.getDSID(), null, null, deviceStateUpdate.getValueAsInteger());
896                         } else {
897                             commandHasNoEffect = true;
898                         }
899                         break;
900                     case DeviceStateUpdate.OPEN_CLOSE:
901                     case DeviceStateUpdate.ON_OFF:
902                         if (deviceStateUpdate.getValueAsInteger() > 0) {
903                             if (checkIsAllreadyMinMax(device) != 1) {
904                                 requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
905                                         device.getDSID(), null, null);
906                                 if (requestSuccsessful) {
907                                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
908                                 }
909                             } else {
910                                 commandHasNoEffect = true;
911                             }
912                         } else {
913                             if (checkIsAllreadyMinMax(device) != 0) {
914                                 requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
915                                         device.getDSID(), null, null);
916                                 if (requestSuccsessful) {
917                                     sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
918                                 }
919                             } else {
920                                 commandHasNoEffect = true;
921                             }
922                         }
923                         break;
924                     case DeviceStateUpdate.SLATPOSITION:
925                         if (device.getSlatPosition() != deviceStateUpdate.getValueAsInteger()) {
926                             requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
927                                     device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT,
928                                     deviceStateUpdate.getValueAsInteger());
929                         } else {
930                             commandHasNoEffect = true;
931                         }
932                         break;
933                     case DeviceStateUpdate.SLAT_STOP:
934                         this.sendStopComandsToDSS(device);
935                         break;
936                     case DeviceStateUpdate.SLAT_MOVE:
937                         if (deviceStateUpdate.getValueAsInteger() > 0) {
938                             requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
939                                     device.getDSID(), null, null);
940                             if (requestSuccsessful) {
941                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
942                             }
943                         } else {
944                             requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
945                                     device.getDSID(), null, null);
946                             if (requestSuccsessful) {
947                                 sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
948                             }
949                             if (sensorJobExecutor != null) {
950                                 sensorJobExecutor.removeSensorJobs(device);
951                             }
952                         }
953                         break;
954                     case DeviceStateUpdate.UPDATE_CALL_SCENE:
955                         if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
956                             requestSuccsessful = digitalSTROMClient.callDeviceScene(connMan.getSessionToken(),
957                                     device.getDSID(), null, null,
958                                     SceneEnum.getScene((short) deviceStateUpdate.getValue()), true);
959                         }
960                         break;
961                     case DeviceStateUpdate.UPDATE_UNDO_SCENE:
962                         if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
963                             requestSuccsessful = digitalSTROMClient.undoDeviceScene(connMan.getSessionToken(),
964                                     device.getDSID(), null, null,
965                                     SceneEnum.getScene((short) deviceStateUpdate.getValue()));
966                         }
967                         break;
968                     case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
969                         // By UPDATE_SLAT_ANGLE_DECREASE, UPDATE_SLAT_ANGLE_INCREASE with value unequal 1 which will
970                         // handled in the pollingRunnable and UPDATE_OPEN_CLOSE_ANGLE the value will be set without
971                         // checking, because it was triggered by setting the slat position.
972                         requestSuccsessful = true;
973                         break;
974                     case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
975                         requestSuccsessful = true;
976                         break;
977                     case DeviceStateUpdate.OPEN_CLOSE_ANGLE:
978                         requestSuccsessful = true;
979                         break;
980                     case DeviceStateUpdate.SLAT_ANGLE:
981                         if (device.getAnglePosition() != deviceStateUpdate.getValueAsInteger()) {
982                             requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
983                                     device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT,
984                                     deviceStateUpdate.getValueAsInteger());
985                         } else {
986                             commandHasNoEffect = true;
987                         }
988                         break;
989                     case DeviceStateUpdate.REFRESH_OUTPUT:
990                         readOutputValue(device);
991                         logger.debug("Inizalize output value reading for device with dSID {}.",
992                                 device.getDSID().getValue());
993                         return;
994                     default:
995                         return;
996                 }
997             }
998             if (commandHasNoEffect) {
999                 logger.debug("Command {} for device with dSID {} not send to dSS, because it has no effect!",
1000                         deviceStateUpdate.getType(), device.getDSID().getValue());
1001                 return;
1002             }
1003             if (requestSuccsessful) {
1004                 logger.debug("Send {} command to dSS and updateInternalDeviceState for device with dSID {}.",
1005                         deviceStateUpdate.getType(), device.getDSID().getValue());
1006                 device.updateInternalDeviceState(deviceStateUpdate);
1007             } else {
1008                 logger.debug("Can't send {} command for device with dSID {} to dSS!", deviceStateUpdate.getType(),
1009                         device.getDSID().getValue());
1010             }
1011         }
1012     }
1013
1014     @Override
1015     public void updateSensorData(SensorJob sensorJob, String priority) {
1016         if (sensorJobExecutor == null) {
1017             sensorJobExecutor = new SensorJobExecutor(connMan);
1018             this.sensorJobExecutor.startExecutor();
1019         }
1020         if (sensorJob != null && priority != null) {
1021             switch (priority) {
1022                 case Config.REFRESH_PRIORITY_HIGH:
1023                     sensorJobExecutor.addHighPriorityJob(sensorJob);
1024                     break;
1025                 case Config.REFRESH_PRIORITY_MEDIUM:
1026                     sensorJobExecutor.addMediumPriorityJob(sensorJob);
1027                     break;
1028                 case Config.REFRESH_PRIORITY_LOW:
1029                     sensorJobExecutor.addLowPriorityJob(sensorJob);
1030                     break;
1031                 default:
1032                     try {
1033                         long prio = Long.parseLong(priority);
1034                         sensorJobExecutor.addPriorityJob(sensorJob, prio);
1035                     } catch (NumberFormatException e) {
1036                         logger.debug("Sensor data update priority do not exist! Please check the input!");
1037                         return;
1038                     }
1039             }
1040             logger.debug("Add new sensorJob {} with priority: {} to sensorJobExecuter", sensorJob.toString(), priority);
1041         }
1042     }
1043
1044     @Override
1045     public void updateSceneData(Device device, DeviceStateUpdate deviceStateUpdate) {
1046         if (sceneJobExecutor == null) {
1047             sceneJobExecutor = new SceneReadingJobExecutor(connMan);
1048             this.sceneJobExecutor.startExecutor();
1049         }
1050
1051         if (deviceStateUpdate != null) {
1052             if (deviceStateUpdate.getScenePriority() > -1) {
1053                 if (deviceStateUpdate.getType().equals(DeviceStateUpdate.UPDATE_SCENE_OUTPUT)) {
1054                     sceneJobExecutor.addPriorityJob(
1055                             new SceneOutputValueReadingJob(device, deviceStateUpdate.getSceneId()),
1056                             deviceStateUpdate.getScenePriority().longValue());
1057                 } else {
1058                     sceneJobExecutor.addPriorityJob(new SceneConfigReadingJob(device, deviceStateUpdate.getSceneId()),
1059                             deviceStateUpdate.getScenePriority().longValue());
1060                 }
1061                 if (deviceStateUpdate.getScenePriority() == 0) {
1062                     updateSensorData(new DeviceOutputValueSensorJob(device), "0");
1063                 }
1064                 logger.debug("Add new sceneReadingJob with priority: {} to SceneReadingJobExecuter",
1065                         deviceStateUpdate.getScenePriority());
1066             } else {
1067                 removeSensorJob(device, deviceStateUpdate);
1068             }
1069         }
1070     }
1071
1072     @Override
1073     public void registerDeviceListener(DeviceStatusListener deviceListener) {
1074         if (deviceListener != null) {
1075             String id = deviceListener.getDeviceStatusListenerID();
1076             if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
1077                 this.deviceDiscovery = deviceListener;
1078                 logger.debug("register Device-Discovery ");
1079                 for (Device device : strucMan.getDeviceMap().values()) {
1080                     deviceDiscovery.onDeviceAdded(device);
1081                 }
1082                 for (Circuit circuit : strucMan.getCircuitMap().values()) {
1083                     deviceDiscovery.onDeviceAdded(circuit);
1084                 }
1085             } else {
1086                 Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
1087                 if (intDevice != null) {
1088                     logger.debug("register DeviceListener with id: {} to Device ", id);
1089                     intDevice.registerDeviceStatusListener(deviceListener);
1090                 } else {
1091                     Circuit intCircuit = strucMan
1092                             .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
1093                     if (intCircuit != null) {
1094                         logger.debug("register DeviceListener with id: {} to Circuit ", id);
1095                         intCircuit.registerDeviceStatusListener(deviceListener);
1096                     } else {
1097                         deviceListener.onDeviceRemoved(null);
1098                     }
1099                 }
1100             }
1101         }
1102     }
1103
1104     @Override
1105     public void unregisterDeviceListener(DeviceStatusListener deviceListener) {
1106         if (deviceListener != null) {
1107             String id = deviceListener.getDeviceStatusListenerID();
1108             logger.debug("unregister DeviceListener with id: {}", id);
1109             if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
1110                 this.deviceDiscovery = null;
1111             } else {
1112                 Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
1113                 if (intDevice != null) {
1114                     intDevice.unregisterDeviceStatusListener();
1115                 } else {
1116                     Circuit intCircuit = strucMan
1117                             .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
1118                     if (intCircuit != null) {
1119                         intCircuit.unregisterDeviceStatusListener();
1120                         if (deviceDiscovery != null) {
1121                             deviceDiscovery.onDeviceAdded(intCircuit);
1122                         }
1123                     }
1124                 }
1125             }
1126         }
1127     }
1128
1129     @Override
1130     public void removeDevice(String dSID) {
1131         Device intDevice = strucMan.getDeviceByDSID(dSID);
1132         if (intDevice != null) {
1133             strucMan.deleteDevice(intDevice);
1134             trashDevices.add(new TrashDevice(intDevice));
1135         }
1136     }
1137
1138     @Override
1139     public void registerTotalPowerConsumptionListener(TotalPowerConsumptionListener totalPowerConsumptionListener) {
1140         this.totalPowerConsumptionListener = totalPowerConsumptionListener;
1141     }
1142
1143     @Override
1144     public void unregisterTotalPowerConsumptionListener() {
1145         this.totalPowerConsumptionListener = null;
1146     }
1147
1148     @Override
1149     public void registerSceneListener(SceneStatusListener sceneListener) {
1150         this.sceneMan.registerSceneListener(sceneListener);
1151     }
1152
1153     @Override
1154     public void unregisterSceneListener(SceneStatusListener sceneListener) {
1155         this.sceneMan.unregisterSceneListener(sceneListener);
1156     }
1157
1158     @Override
1159     public void registerStatusListener(ManagerStatusListener statusListener) {
1160         this.statusListener = statusListener;
1161         this.sceneMan.registerStatusListener(statusListener);
1162     }
1163
1164     @Override
1165     public void unregisterStatusListener() {
1166         this.statusListener = null;
1167         this.sceneMan.unregisterStatusListener();
1168     }
1169
1170     @Override
1171     public void registerConnectionListener(ConnectionListener connectionListener) {
1172         this.connMan.registerConnectionListener(connectionListener);
1173     }
1174
1175     @Override
1176     public void unregisterConnectionListener() {
1177         this.connMan.unregisterConnectionListener();
1178     }
1179
1180     @Override
1181     public int getTotalPowerConsumption() {
1182         List<CachedMeteringValue> cachedConsumptionMeteringValues = digitalSTROMClient
1183                 .getLatest(connMan.getSessionToken(), MeteringTypeEnum.CONSUMPTION, DsAPI.ALL_METERS, null);
1184         if (cachedConsumptionMeteringValues != null) {
1185             tempConsumption = 0;
1186             for (CachedMeteringValue value : cachedConsumptionMeteringValues) {
1187                 tempConsumption += value.getValue();
1188                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1189                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1190                 } else if (strucMan.getCircuitByDSUID(value.getDsuid()) != null) {
1191                     strucMan.getCircuitByDSUID(value.getDsuid()).addMeteringValue(value);
1192                 }
1193             }
1194         }
1195         return tempConsumption;
1196     }
1197
1198     private void getMeterData() {
1199         int val = getTotalPowerConsumption();
1200         if (totalPowerConsumptionListener != null) {
1201             totalPowerConsumptionListener.onTotalPowerConsumptionChanged(val);
1202         }
1203         val = getTotalEnergyMeterValue();
1204         if (totalPowerConsumptionListener != null) {
1205             totalPowerConsumptionListener.onEnergyMeterValueChanged(val);
1206         }
1207     }
1208
1209     @Override
1210     public int getTotalEnergyMeterValue() {
1211         List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient.getLatest(connMan.getSessionToken(),
1212                 MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, null);
1213         if (cachedEnergyMeteringValues != null) {
1214             tempEnergyMeter = 0;
1215             for (CachedMeteringValue value : cachedEnergyMeteringValues) {
1216                 tempEnergyMeter += value.getValue();
1217                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1218                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1219                 } else if (strucMan.getCircuitByDSUID(value.getDsuid()) != null) {
1220                     strucMan.getCircuitByDSUID(value.getDsuid()).addMeteringValue(value);
1221                 }
1222             }
1223         }
1224         return tempEnergyMeter;
1225     }
1226
1227     @Override
1228     public int getTotalEnergyMeterWsValue() {
1229         List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient.getLatest(connMan.getSessionToken(),
1230                 MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, MeteringUnitsEnum.WS);
1231         if (cachedEnergyMeteringValues != null) {
1232             tempEnergyMeterWs = 0;
1233             for (CachedMeteringValue value : cachedEnergyMeteringValues) {
1234                 tempEnergyMeterWs += value.getValue();
1235                 if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
1236                     strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
1237                 } else if (strucMan.getCircuitByDSUID(value.getDsuid()) != null) {
1238                     strucMan.getCircuitByDSUID(value.getDsuid()).addMeteringValue(value);
1239                 }
1240             }
1241         }
1242         return tempEnergyMeterWs;
1243     }
1244
1245     private void setInizialStateWithLastCallScenes() {
1246         if (sceneMan == null) {
1247             return;
1248         }
1249         JsonObject response = connMan.getDigitalSTROMAPI().query2(connMan.getSessionToken(), LAST_CALL_SCENE_QUERY);
1250         if (!response.isJsonObject()) {
1251             return;
1252         }
1253         for (Entry<String, JsonElement> entry : response.entrySet()) {
1254             if (!entry.getValue().isJsonObject()) {
1255                 continue;
1256             }
1257             JsonObject zone = entry.getValue().getAsJsonObject();
1258             int zoneID = -1;
1259             short groupID = -1;
1260             short sceneID = -1;
1261             if (zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()) != null) {
1262                 zoneID = zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()).getAsInt();
1263             }
1264             for (Entry<String, JsonElement> groupEntry : zone.entrySet()) {
1265                 if (groupEntry.getKey().startsWith("group") && groupEntry.getValue().isJsonObject()) {
1266                     JsonObject group = groupEntry.getValue().getAsJsonObject();
1267                     if (group.get(JSONApiResponseKeysEnum.DEVICES.getKey()) != null) {
1268                         if (group.get(JSONApiResponseKeysEnum.GROUP.getKey()) != null) {
1269                             groupID = group.get(JSONApiResponseKeysEnum.GROUP.getKey()).getAsShort();
1270                         }
1271                         if (group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()) != null) {
1272                             sceneID = group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()).getAsShort();
1273                         }
1274                         if (zoneID > -1 && groupID > -1 && sceneID > -1) {
1275                             logger.debug("initial state, call scene {}-{}-{}", zoneID, groupID, sceneID);
1276                             sceneMan.callInternalSceneWithoutDiscovery(zoneID, groupID, sceneID);
1277                         }
1278                     }
1279                 }
1280             }
1281         }
1282     }
1283
1284     @Override
1285     public void handleEvent(EventItem eventItem) {
1286         try {
1287             if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())
1288                     || EventNames.DEVICE_BINARY_INPUT_EVENT.equals(eventItem.getName())) {
1289                 logger.debug("Detect {} eventItem = {}", eventItem.getName(), eventItem.toString());
1290                 Device dev = getDeviceOfEvent(eventItem);
1291                 if (dev != null) {
1292                     if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())) {
1293                         dev.setDeviceSensorByEvent(eventItem);
1294                     } else {
1295                         DeviceBinarayInputEnum binaryInputType = DeviceBinarayInputEnum.getdeviceBinarayInput(Short
1296                                 .parseShort(eventItem.getProperties().getOrDefault(EventResponseEnum.INPUT_TYPE, "")));
1297                         Short newState = Short
1298                                 .parseShort(eventItem.getProperties().getOrDefault(EventResponseEnum.INPUT_STATE, ""));
1299                         if (binaryInputType != null) {
1300                             dev.setBinaryInputState(binaryInputType, newState);
1301                         }
1302                     }
1303                 }
1304             }
1305         } catch (NumberFormatException e) {
1306             logger.debug("Unexpected missing or invalid number while handling event", e);
1307         }
1308     }
1309
1310     private Device getDeviceOfEvent(EventItem eventItem) {
1311         if (eventItem.getSource().get(EventResponseEnum.DSID) != null) {
1312             String dSID = eventItem.getSource().get(EventResponseEnum.DSID);
1313             Device dev = strucMan.getDeviceByDSID(dSID);
1314             if (dev == null) {
1315                 dev = strucMan.getDeviceByDSUID(dSID);
1316             }
1317             return dev;
1318         }
1319         return null;
1320     }
1321
1322     @Override
1323     public List<String> getSupportedEvents() {
1324         return SUPPORTED_EVENTS;
1325     }
1326
1327     @Override
1328     public boolean supportsEvent(String eventName) {
1329         return SUPPORTED_EVENTS.contains(eventName);
1330     }
1331
1332     @Override
1333     public String getUID() {
1334         return getClass().getName();
1335     }
1336
1337     @Override
1338     public void setEventListener(EventListener eventListener) {
1339         if (this.eventListener != null) {
1340             this.eventListener.removeEventHandler(this);
1341         }
1342         this.eventListener = eventListener;
1343     }
1344
1345     @Override
1346     public void unsetEventListener(EventListener eventListener) {
1347         if (this.eventListener != null) {
1348             this.eventListener.removeEventHandler(this);
1349         }
1350         this.eventListener = null;
1351     }
1352 }