]> git.basschouten.com Git - openhab-addons.git/blob
ccec7c0a566b937b2b4cbd6521f01cad0272eabd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.digitalstrom.internal.lib.structure.devices.impl;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 import java.util.Set;
24
25 import org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants;
26 import org.openhab.binding.digitalstrom.internal.lib.GeneralLibConstance;
27 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
28 import org.openhab.binding.digitalstrom.internal.lib.event.types.EventItem;
29 import org.openhab.binding.digitalstrom.internal.lib.listener.DeviceStatusListener;
30 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.constants.JSONApiResponseKeysEnum;
31 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.AbstractGeneralDeviceInformations;
32 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Device;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceConstants;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceSceneSpec;
35 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceStateUpdate;
36 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ApplicationGroup;
37 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ChangeableDeviceConfigEnum;
38 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
39 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputChannelEnum;
40 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
41 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
42 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DSID;
43 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceBinaryInput;
44 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceSensorValue;
45 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceStateUpdateImpl;
46 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.JSONDeviceSceneSpecImpl;
47 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
48 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
49 import org.openhab.binding.digitalstrom.internal.lib.util.DSJsonParser;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import com.google.gson.JsonArray;
54 import com.google.gson.JsonElement;
55 import com.google.gson.JsonObject;
56
57 /**
58  * The {@link DeviceImpl} is the implementation of the {@link Device}.
59  *
60  * @author Michael Ochel - Initial contribution
61  * @author Matthias Siegele - Initial contribution
62  */
63 public class DeviceImpl extends AbstractGeneralDeviceInformations implements Device {
64
65     private final Logger logger = LoggerFactory.getLogger(DeviceImpl.class);
66
67     private Config config;
68
69     private DSID meterDSID;
70     private int zoneId = 0;
71     private List<ApplicationGroup> groupList = new LinkedList<>();
72
73     private String hwInfo;
74
75     private OutputModeEnum outputMode;
76
77     private boolean isOn = false;
78     private boolean isOpen = true;
79     private short outputValue = 0;
80     private short maxOutputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
81     private short minOutputValue = 0;
82
83     private short slatAngle = 0;
84     private final short maxSlatAngle = DeviceConstants.MAX_SLAT_ANGLE;
85     private final short minSlatAngle = DeviceConstants.MIN_SLAT_ANGLE;
86
87     private int slatPosition = 0;
88     private final int maxSlatPosition = DeviceConstants.MAX_ROLLERSHUTTER;
89     private final int minSlatPosition = DeviceConstants.MIN_ROLLERSHUTTER;
90
91     private final List<DeviceSensorValue> deviceSensorValues = Collections.synchronizedList(new ArrayList<>());
92     private final List<DeviceBinaryInput> deviceBinaryInputs = Collections.synchronizedList(new ArrayList<>());
93     private final List<SensorEnum> devicePowerSensorTypes = new ArrayList<>();
94     private final List<SensorEnum> deviceClimateSensorTypes = new ArrayList<>();
95     private final List<OutputChannelEnum> outputChannels = Collections.synchronizedList(new ArrayList<>());
96
97     // for scenes
98     private short activeSceneNumber = -1;
99     private InternalScene activeScene;
100     private InternalScene lastScene;
101     private int outputValueBeforeSceneCall = 0;
102     private short slatAngleBeforeSceneCall = 0;
103     private boolean lastCallWasUndo = false;
104
105     private final Map<Short, DeviceSceneSpec> sceneConfigMap = Collections.synchronizedMap(new HashMap<>());
106     private final Map<Short, Integer[]> sceneOutputMap = Collections.synchronizedMap(new HashMap<>());
107
108     // saves outstanding commands
109     private final List<DeviceStateUpdate> deviceStateUpdates = Collections.synchronizedList(new LinkedList<>());
110
111     /*
112      * Saves the refresh priorities and reading initialized flag of power sensors as
113      * an matrix. The first array fields are 0 = active power, 1 = output current, 2
114      * = electric meter, 3 = power consumption and in each field is a string array
115      * with the fields 0 = refresh priority 1 = reading initial flag (true = reading
116      * is initialized, otherwise false)
117      */
118     private final Object[] powerSensorRefresh = new Object[] { new String[] { Config.REFRESH_PRIORITY_NEVER, "false" },
119             new String[] { Config.REFRESH_PRIORITY_NEVER, "false" },
120             new String[] { Config.REFRESH_PRIORITY_NEVER, "false" },
121             new String[] { Config.REFRESH_PRIORITY_NEVER, "false" } };
122
123     public static final int REFRESH_PRIORITY_ARRAY_FIELD = 0;
124     public static final int READING_INITIALIZED_ARRAY_FIELD = 1;
125
126     public static final int REFRESH_ACTIVE_POWER_ARRAY_FIELD = 0;
127     public static final int REFRESH_OUTPUT_CURRENT_ARRAY_FIELD = 1;
128     public static final int REFRESH_ELECTRIC_METER_ARRAY_FIELD = 2;
129     public static final int REFRESH_POWER_CONSUMPTION_ARRAY_FIELD = 3;
130     /*
131      * Cache the last power sensor value to get power sensor value directly the key
132      * is the output value and the value is an Integer array for the sensor values
133      * (0 = active power, 1 = output current, 2 = power consumption, 3 = output
134      * current high)
135      */
136     private final Map<Short, Integer[]> cachedSensorPowerValues = Collections.synchronizedMap(new HashMap<>());
137
138     public static final int ACTIVE_POWER_ARRAY_FIELD = 0;
139     public static final int OUTPUT_CURRENT_ARRAY_FIELD = 1;
140     public static final int POWER_CONSUMPTION_ARRAY_FIELD = 2;
141     public static final int OUTPUT_CURRENT_HIGH_ARRAY_FIELD = 3;
142
143     // Preparing for the advance device property setting "Turn 'switched' output off
144     // if value below:", but the
145     // configuration currently not work in digitalSTROM, because of that the value
146     // is fix 1.
147     private final int switchPercentOff = 1;
148
149     /**
150      * Creates a new {@link DeviceImpl} from the given DigitalSTROM-Device
151      * {@link JsonObject}.
152      *
153      * @param deviceJsonObject json response of the digitalSTROM-Server, must not be
154      *            null
155      */
156     public DeviceImpl(JsonObject deviceJsonObject) {
157         super(deviceJsonObject);
158         if (deviceJsonObject.get(JSONApiResponseKeysEnum.METER_DSID.getKey()) != null) {
159             this.meterDSID = new DSID(deviceJsonObject.get(JSONApiResponseKeysEnum.METER_DSID.getKey()).getAsString());
160         } else if (deviceJsonObject.get(JSONApiResponseKeysEnum.DS_METER_DSID.getKey()) != null) {
161             this.meterDSID = new DSID(
162                     deviceJsonObject.get(JSONApiResponseKeysEnum.DS_METER_DSID.getKey()).getAsString());
163         }
164         if (deviceJsonObject.get(JSONApiResponseKeysEnum.HW_INFO.getKey()) != null) {
165             this.hwInfo = deviceJsonObject.get(JSONApiResponseKeysEnum.HW_INFO.getKey()).getAsString();
166         } else if (deviceJsonObject.get(JSONApiResponseKeysEnum.HW_INFO_UPPER_HW.getKey()) != null) {
167             this.hwInfo = deviceJsonObject.get(JSONApiResponseKeysEnum.HW_INFO_UPPER_HW.getKey()).getAsString();
168         }
169         if (deviceJsonObject.get(JSONApiResponseKeysEnum.ON.getKey()) != null) {
170             if (!isShade()) {
171                 this.isOn = deviceJsonObject.get(JSONApiResponseKeysEnum.ON.getKey()).getAsBoolean();
172             } else {
173                 this.isOpen = deviceJsonObject.get(JSONApiResponseKeysEnum.ON.getKey()).getAsBoolean();
174             }
175         }
176         if (deviceJsonObject.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()) != null) {
177             zoneId = deviceJsonObject.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()).getAsInt();
178         } else if (deviceJsonObject.get(JSONApiResponseKeysEnum.ZONE_ID_Lower_Z.getKey()) != null) {
179             zoneId = deviceJsonObject.get(JSONApiResponseKeysEnum.ZONE_ID_Lower_Z.getKey()).getAsInt();
180         }
181         JsonElement groups = deviceJsonObject.get(JSONApiResponseKeysEnum.GROUPS.getKey());
182         if (groups != null && groups.isJsonArray()) {
183             JsonArray array = deviceJsonObject.get(JSONApiResponseKeysEnum.GROUPS.getKey()).getAsJsonArray();
184             for (int i = 0; i < array.size(); i++) {
185                 if (array.get(i) != null) {
186                     addGroupToList(array.get(i).getAsShort());
187                 }
188             }
189         } else if (groups != null && groups.isJsonObject()) {
190             for (Entry<String, JsonElement> entry : deviceJsonObject.get(JSONApiResponseKeysEnum.GROUPS.getKey())
191                     .getAsJsonObject().entrySet()) {
192                 addGroupToList(
193                         entry.getValue().getAsJsonObject().get(JSONApiResponseKeysEnum.ID.getKey()).getAsShort());
194             }
195         }
196         if (deviceJsonObject.get(JSONApiResponseKeysEnum.OUTPUT_MODE.getKey()) != null) {
197             int tmp = deviceJsonObject.get(JSONApiResponseKeysEnum.OUTPUT_MODE.getKey()).getAsInt();
198             if (tmp != -1) {
199                 if (OutputModeEnum.containsMode(tmp)) {
200                     outputMode = OutputModeEnum.getMode(tmp);
201                 }
202             }
203         }
204         if (deviceJsonObject.get(JSONApiResponseKeysEnum.SENSOR_INPUTS.getKey()) != null
205                 && deviceJsonObject.get(JSONApiResponseKeysEnum.SENSOR_INPUTS.getKey()).isJsonObject()) {
206             JsonObject jObj = deviceJsonObject.get(JSONApiResponseKeysEnum.SENSOR_INPUTS.getKey()).getAsJsonObject();
207             for (Entry<String, JsonElement> entry : jObj.entrySet()) {
208                 if (entry.getValue().isJsonObject()) {
209                     JsonObject sensorType = entry.getValue().getAsJsonObject();
210                     if (sensorType.get(JSONApiResponseKeysEnum.TYPE.getKey()) != null) {
211                         if (SensorEnum
212                                 .containsSensor(sensorType.get(JSONApiResponseKeysEnum.TYPE.getKey()).getAsShort())) {
213                             setDeviceSensorValue(new DeviceSensorValue(entry.getValue().getAsJsonObject()));
214                         }
215                     }
216                 }
217             }
218         }
219         if (deviceJsonObject.get(JSONApiResponseKeysEnum.BINARY_INPUTS.getKey()) != null
220                 && deviceJsonObject.get(JSONApiResponseKeysEnum.BINARY_INPUTS.getKey()).isJsonObject()) {
221             JsonObject jObj = deviceJsonObject.get(JSONApiResponseKeysEnum.BINARY_INPUTS.getKey()).getAsJsonObject();
222             for (Entry<String, JsonElement> entry : jObj.entrySet()) {
223                 if (entry.getValue().isJsonObject()) {
224                     JsonObject binaryInput = entry.getValue().getAsJsonObject();
225                     deviceBinaryInputs.add(new DeviceBinaryInput(binaryInput));
226                 }
227             }
228         }
229
230         outputChannels.addAll(DSJsonParser.getOutputChannels(deviceJsonObject));
231
232         init();
233     }
234
235     private void init() {
236         if (groupList.contains(ApplicationGroup.LIGHTS)) {
237             maxOutputValue = DeviceConstants.MAX_OUTPUT_VALUE_LIGHT;
238             if (this.isDimmable()) {
239                 minOutputValue = DeviceConstants.MIN_DIM_VALUE;
240             }
241         } else {
242             maxOutputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
243             minOutputValue = 0;
244         }
245         if (isOn) {
246             outputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
247         }
248     }
249
250     @Override
251     public synchronized DSID getMeterDSID() {
252         return this.meterDSID;
253     }
254
255     @Override
256     public synchronized void setMeterDSID(String meterDSID) {
257         this.meterDSID = new DSID(meterDSID);
258         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.METER_DSID);
259     }
260
261     @Override
262     public String getHWinfo() {
263         return hwInfo;
264     }
265
266     @Override
267     public List<Short> getGroups() {
268         LinkedList<Short> linkedList = new LinkedList<>();
269         groupList.forEach(group -> linkedList.add(group.getId()));
270         return linkedList;
271     }
272
273     /**
274      * Adds the ApplicationGroup of the given groupId to the internal list
275      * 
276      * @param groupID
277      * @return true if the groupId is a valid ApplicationGroup id, false otherwise
278      */
279     private boolean addGroupToList(Short groupID) {
280         ApplicationGroup group = ApplicationGroup.getGroup(groupID);
281         if (!ApplicationGroup.UNDEFINED.equals(group)) {
282             if (!this.groupList.contains(group)) {
283                 this.groupList.add(group);
284             }
285         }
286
287         return group != null;
288     }
289
290     @Override
291     public void addGroup(Short groupID) {
292         addGroupToList(groupID);
293         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.GROUPS);
294     }
295
296     @Override
297     public void setGroups(List<Short> newGroupList) {
298         if (newGroupList != null) {
299             groupList.clear();
300             newGroupList.forEach(groupId -> groupList.add(ApplicationGroup.getGroup(groupId)));
301         }
302         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.GROUPS);
303     }
304
305     @Override
306     public synchronized int getZoneId() {
307         return zoneId;
308     }
309
310     @Override
311     public synchronized void setZoneId(int zoneID) {
312         this.zoneId = zoneID;
313         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.ZONE_ID);
314     }
315
316     @Override
317     public synchronized boolean isOn() {
318         return isOn;
319     }
320
321     @Override
322     public synchronized void setIsOn(boolean flag) {
323         if (flag) {
324             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, 1));
325         } else {
326             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, -1));
327         }
328     }
329
330     @Override
331     public synchronized boolean isOpen() {
332         return this.isOpen;
333     }
334
335     @Override
336     public synchronized void setIsOpen(boolean flag) {
337         if (flag) {
338             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE, 1));
339             if (isBlind()) {
340                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE_ANGLE, 1));
341             }
342         } else {
343             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE, -1));
344             if (isBlind()) {
345                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE_ANGLE, -1));
346             }
347         }
348     }
349
350     @Override
351     public synchronized void setOutputValue(short value) {
352         if (!isShade()) {
353             if (value <= 0) {
354                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, -1));
355
356             } else if (value > maxOutputValue) {
357                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, 1));
358             } else {
359                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT, value));
360             }
361         }
362     }
363
364     @Override
365     public synchronized boolean isDimmable() {
366         return OutputModeEnum.outputModeIsDimmable(outputMode);
367     }
368
369     @Override
370     public synchronized boolean isSwitch() {
371         return OutputModeEnum.outputModeIsSwitch(outputMode);
372     }
373
374     @Override
375     public synchronized boolean isDeviceWithOutput() {
376         return this.outputMode != null && !this.outputMode.equals(OutputModeEnum.DISABLED);
377     }
378
379     @Override
380     public boolean isSensorDevice() {
381         return !isDeviceWithOutput() && !deviceClimateSensorTypes.isEmpty();
382     }
383
384     @Override
385     public boolean isHeatingDevice() {
386         return groupList.contains(ApplicationGroup.HEATING);
387     }
388
389     @Override
390     public boolean isTemperatureControlledDevice() {
391         return groupList.contains(ApplicationGroup.TEMPERATURE_CONTROL);
392     }
393
394     @Override
395     public boolean isShade() {
396         return OutputModeEnum.outputModeIsShade(outputMode);
397     }
398
399     @Override
400     public boolean isBlind() {
401         return (outputMode == OutputModeEnum.POSITION_CON || outputMode == OutputModeEnum.POSITION_CON_US)
402                 && (outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_INDOOR)
403                         || outputChannels.contains(OutputChannelEnum.SHADE_OPENING_ANGLE_OUTSIDE));
404     }
405
406     @Override
407     public synchronized ApplicationGroup getFunctionalColorGroup() {
408         return groupList.stream().findFirst().get();
409     }
410
411     @Override
412     public synchronized void setFunctionalColorGroup(ApplicationGroup functionalColorGroup) {
413         groupList.add(functionalColorGroup);
414         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.FUNCTIONAL_GROUP);
415     }
416
417     @Override
418     public OutputModeEnum getOutputMode() {
419         return outputMode;
420     }
421
422     @Override
423     public List<OutputChannelEnum> getOutputChannels() {
424         return outputChannels;
425     }
426
427     @Override
428     public synchronized void setOutputMode(OutputModeEnum newOutputMode) {
429         this.outputMode = newOutputMode;
430         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.OUTPUT_MODE);
431     }
432
433     @Override
434     public synchronized void increase() {
435         if (isDimmable()) {
436             deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_INCREASE, 0));
437         }
438         if (isShade()) {
439             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_INCREASE, 0));
440             if (isBlind()) {
441                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_DECREASE, 0));
442             }
443         }
444     }
445
446     @Override
447     public synchronized void decrease() {
448         if (isDimmable()) {
449             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_DECREASE, 0));
450         }
451         if (isShade()) {
452             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_DECREASE, 0));
453             if (isBlind()) {
454                 this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_DECREASE, 0));
455             }
456         }
457     }
458
459     @Override
460     public synchronized void increaseSlatAngle() {
461         if (isBlind()) {
462             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_DECREASE, 1));
463         }
464     }
465
466     @Override
467     public synchronized void decreaseSlatAngle() {
468         if (isBlind()) {
469             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_DECREASE, 1));
470         }
471     }
472
473     @Override
474     public synchronized short getOutputValue() {
475         return outputValue;
476     }
477
478     @Override
479     public short getMaxOutputValue() {
480         return maxOutputValue;
481     }
482
483     @Override
484     public short getMinOutputValue() {
485         return minOutputValue;
486     }
487
488     @Override
489     public synchronized int getSlatPosition() {
490         return slatPosition;
491     }
492
493     @Override
494     public synchronized short getAnglePosition() {
495         return slatAngle;
496     }
497
498     @Override
499     public synchronized void setAnglePosition(int angle) {
500         if (angle == slatAngle) {
501             return;
502         }
503         if (angle < minSlatAngle) {
504             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, minSlatAngle));
505         } else if (angle > this.maxSlatPosition) {
506             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, maxSlatAngle));
507         } else {
508             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, angle));
509         }
510     }
511
512     @Override
513     public synchronized void setSlatPosition(int position) {
514         if (position == this.slatPosition) {
515             return;
516         }
517         if (position < minSlatPosition) {
518             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, minSlatPosition));
519         } else if (position > this.maxSlatPosition) {
520             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, maxSlatPosition));
521         } else {
522             this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, position));
523         }
524     }
525
526     private short getDimmStep() {
527         if (isDimmable()) {
528             return DeviceConstants.DIM_STEP_LIGHT;
529         } else if (isShade()) {
530             return DeviceConstants.MOVE_STEP_ROLLERSHUTTER;
531         } else {
532             return DeviceConstants.DEFAULT_MOVE_STEP;
533         }
534     }
535
536     @Override
537     public int getMaxSlatPosition() {
538         return maxSlatPosition;
539     }
540
541     @Override
542     public int getMinSlatPosition() {
543         return minSlatPosition;
544     }
545
546     @Override
547     public int getMaxSlatAngle() {
548         return maxSlatAngle;
549     }
550
551     @Override
552     public int getMinSlatAngle() {
553         return minSlatAngle;
554     }
555
556     /* Begin-Scenes */
557
558     @Override
559     public synchronized void callInternalScene(InternalScene scene) {
560         if (lastCallWasUndo) {
561             lastScene = null;
562             if (activeScene != null) {
563                 activeScene.deactivateSceneByDevice();
564             }
565             activeScene = null;
566         }
567         internalCallScene(scene.getSceneID());
568         activeScene = scene;
569         lastCallWasUndo = false;
570     }
571
572     @Override
573     public void checkSceneConfig(Short sceneNumber, short prio) {
574         if (isDeviceWithOutput()) {
575             if (!containsSceneConfig(sceneNumber)) {
576                 deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_CONFIG,
577                         new Short[] { sceneNumber, prio }));
578
579             }
580             if (sceneOutputMap.get(sceneNumber) == null) {
581                 deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_OUTPUT,
582                         new Short[] { sceneNumber, prio }));
583             }
584         }
585     }
586
587     @Override
588     public synchronized void undoInternalScene(InternalScene scene) {
589         logger.debug("undo Scene {} dSID {}", scene.getSceneID(), dsid.getValue());
590         if (activeScene != null && activeScene.equals(scene)) {
591             if (lastCallWasUndo) {
592                 lastScene = null;
593                 return;
594             }
595             if (this.lastScene != null && !lastScene.equals(activeScene)) {
596                 activeScene = lastScene;
597                 lastScene = null;
598                 activeScene.activateSceneByDevice();
599             } else {
600                 internalUndoScene();
601                 logger.debug("internalUndo Scene dSID {}", dsid.getValue());
602                 this.activeScene = null;
603             }
604             internalUndoScene();
605             lastCallWasUndo = true;
606         }
607     }
608
609     @Override
610     public synchronized void callScene(Short sceneNumber) {
611         this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_CALL_SCENE, sceneNumber));
612     }
613
614     @Override
615     public synchronized void internalCallScene(Short sceneNumber) {
616         logger.debug("call Scene id {} dSID {}", sceneNumber, dsid.getValue());
617         if (isDeviceWithOutput()) {
618             activeSceneNumber = sceneNumber;
619             informLastSceneAboutSceneCall(sceneNumber);
620             if (!isShade()) {
621                 outputValueBeforeSceneCall = this.outputValue;
622             } else {
623                 outputValueBeforeSceneCall = this.slatPosition;
624                 if (isBlind()) {
625                     slatAngleBeforeSceneCall = this.slatAngle;
626                 }
627             }
628             if (!checkSceneNumber(sceneNumber)) {
629                 if (containsSceneConfig(sceneNumber)) {
630                     if (doIgnoreScene(sceneNumber)) {
631                         return;
632                     }
633                 } else {
634                     this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_CONFIG,
635                             new Short[] { sceneNumber, GeneralLibConstance.HIGHEST_READ_OUT_PRIORITY }));
636                 }
637                 if (sceneOutputMap.get(sceneNumber) != null) {
638                     if (!isShade()) {
639                         updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT,
640                                 sceneOutputMap.get(sceneNumber)[GeneralLibConstance.SCENE_ARRAY_INDEX_VALUE]));
641                     } else {
642                         updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION,
643                                 sceneOutputMap.get(sceneNumber)[GeneralLibConstance.SCENE_ARRAY_INDEX_VALUE]));
644                         if (isBlind()) {
645                             updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE,
646                                     sceneOutputMap.get(sceneNumber)[GeneralLibConstance.SCENE_ARRAY_INDEX_ANGLE]));
647                         }
648                     }
649                 } else {
650                     this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_OUTPUT,
651                             new Short[] { sceneNumber, GeneralLibConstance.HIGHEST_READ_OUT_PRIORITY }));
652                 }
653             }
654
655         }
656     }
657
658     private boolean checkSceneNumber(Short sceneNumber) {
659         if (SceneEnum.containsScene(sceneNumber)) {
660             if (this.outputMode.equals(OutputModeEnum.POWERSAVE)) {
661                 switch (SceneEnum.getScene(sceneNumber)) {
662                     case ABSENT:
663                     case DEEP_OFF:
664                     case SLEEPING:
665                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, -1));
666                         return true;
667                     case AREA_1_OFF:
668                     case AREA_2_OFF:
669                     case AREA_3_OFF:
670                     case AREA_4_OFF:
671                     case PRESET_0:
672                     case PRESET_10:
673                     case PRESET_20:
674                     case PRESET_30:
675                     case PRESET_40:
676                         return true;
677                     default:
678                         break;
679                 }
680             }
681             if (this.outputMode.equals(OutputModeEnum.WIPE)) {
682                 switch (SceneEnum.getScene(sceneNumber)) {
683                     case STANDBY:
684                     case AUTO_STANDBY:
685                     case AREA_1_OFF:
686                     case AREA_2_OFF:
687                     case AREA_3_OFF:
688                     case AREA_4_OFF:
689                     case PRESET_0:
690                     case PRESET_10:
691                     case PRESET_20:
692                     case PRESET_30:
693                     case PRESET_40:
694                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, -1));
695                         return true;
696                     default:
697                         break;
698                 }
699             }
700             switch (SceneEnum.getScene(sceneNumber)) {
701                 // on scenes
702                 case DEVICE_ON:
703                 case MAXIMUM:
704                     if (!isShade()) {
705                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, 1));
706                     } else {
707                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE, 1));
708                         if (isBlind()) {
709                             this.updateInternalDeviceState(
710                                     new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE_ANGLE, 1));
711                         }
712                     }
713                     return true;
714                 // off scenes
715                 case MINIMUM:
716                 case DEVICE_OFF:
717                 case AUTO_OFF:
718                     if (!isShade()) {
719                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, -1));
720                     } else {
721                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE, -1));
722                         if (isBlind()) {
723                             this.updateInternalDeviceState(
724                                     new DeviceStateUpdateImpl(DeviceStateUpdate.OPEN_CLOSE_ANGLE, -1));
725                         }
726                     }
727                     return true;
728                 // increase scenes
729                 case INCREMENT:
730                 case AREA_1_INCREMENT:
731                 case AREA_2_INCREMENT:
732                 case AREA_3_INCREMENT:
733                 case AREA_4_INCREMENT:
734                     if (isDimmable()) {
735                         if (outputValue == maxOutputValue) {
736                             return true;
737                         }
738                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_INCREASE, 0));
739                     }
740                     if (isShade()) {
741                         if (slatPosition == maxSlatPosition) {
742                             return true;
743                         }
744                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_INCREASE, 0));
745                         if (isBlind()) {
746                             if (slatAngle == maxSlatAngle) {
747                                 return true;
748                             }
749                             updateInternalDeviceState(
750                                     new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_INCREASE, 0));
751                         }
752                     }
753                     return true;
754                 // decrease scenes
755                 case DECREMENT:
756                 case AREA_1_DECREMENT:
757                 case AREA_2_DECREMENT:
758                 case AREA_3_DECREMENT:
759                 case AREA_4_DECREMENT:
760                     if (isDimmable()) {
761                         if (outputValue == minOutputValue) {
762                             return true;
763                         }
764                         updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_DECREASE, 0));
765                     }
766                     if (isShade()) {
767                         if (slatPosition == minSlatPosition) {
768                             return true;
769                         }
770                         updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_DECREASE, 0));
771                         if (isBlind()) {
772                             if (slatAngle == minSlatAngle) {
773                                 return true;
774                             }
775                             this.updateInternalDeviceState(
776                                     new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_INCREASE, 0));
777                         }
778                     }
779                     return true;
780                 // Stop scenes
781                 case AREA_1_STOP:
782                 case AREA_2_STOP:
783                 case AREA_3_STOP:
784                 case AREA_4_STOP:
785                 case DEVICE_STOP:
786                 case STOP:
787                     this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OUTPUT_VALUE, 0));
788                     return true;
789                 // Area Stepping continue scenes
790                 case AREA_STEPPING_CONTINUE:
791                     return true;
792                 default:
793                     return false;
794             }
795         }
796         return false;
797     }
798
799     private Integer[] getStandartSceneOutput(short sceneNumber) {
800         if (SceneEnum.getScene(sceneNumber) != null) {
801             switch (SceneEnum.getScene(sceneNumber)) {
802                 case DEVICE_ON:
803                 case MAXIMUM:
804                     if (!isShade()) {
805                         return new Integer[] { (int) maxOutputValue, -1 };
806                     } else {
807                         if (isBlind()) {
808                             return new Integer[] { (int) maxSlatPosition, (int) maxSlatAngle };
809                         } else {
810                             return new Integer[] { (int) maxSlatPosition, -1 };
811                         }
812                     }
813                     // off scenes
814                 case MINIMUM:
815                 case DEVICE_OFF:
816                 case AUTO_OFF:
817                     if (!isShade()) {
818                         return new Integer[] { (int) 0, -1 };
819                     } else {
820                         if (isBlind()) {
821                             return new Integer[] { (int) 0, 0 };
822                         } else {
823                             return new Integer[] { (int) 0, -1 };
824                         }
825                     }
826                 default:
827                     break;
828             }
829         }
830         return null;
831     }
832
833     private void informLastSceneAboutSceneCall(short sceneNumber) {
834         if (this.activeScene != null && this.activeScene.getSceneID() != sceneNumber) {
835             this.activeScene.deactivateSceneByDevice();
836             this.lastScene = this.activeScene;
837             this.activeScene = null;
838         }
839     }
840
841     @Override
842     public synchronized void undoScene() {
843         this.deviceStateUpdates
844                 .add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_UNDO_SCENE, this.activeSceneNumber));
845     }
846
847     @Override
848     public synchronized void internalUndoScene() {
849         if (!isShade()) {
850             updateInternalDeviceState(
851                     new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT, this.outputValueBeforeSceneCall));
852         } else {
853             updateInternalDeviceState(
854                     new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, this.outputValueBeforeSceneCall));
855             if (isBlind()) {
856                 updateInternalDeviceState(
857                         new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, this.slatAngleBeforeSceneCall));
858             }
859         }
860
861         if (activeSceneNumber != -1) {
862             activeSceneNumber = -1;
863         }
864     }
865
866     @Override
867     public InternalScene getAcitiveScene() {
868         return this.activeScene;
869     }
870
871     @Override
872     public Integer[] getSceneOutputValue(short sceneId) {
873         synchronized (sceneOutputMap) {
874             if (sceneOutputMap.containsKey(sceneId)) {
875                 return sceneOutputMap.get(sceneId);
876             }
877         }
878         return new Integer[] { -1, -1 };
879     }
880
881     @Override
882     public void setSceneOutputValue(short sceneId, int value) {
883         internalSetSceneOutputValue(sceneId, value, -1);
884         if (listener != null) {
885             listener.onSceneConfigAdded(sceneId);
886         }
887     }
888
889     @Override
890     public void setSceneOutputValue(short sceneId, int value, int angle) {
891         internalSetSceneOutputValue(sceneId, value, angle);
892         if (listener != null) {
893             listener.onSceneConfigAdded(sceneId);
894         }
895     }
896
897     private void internalSetSceneOutputValue(short sceneId, int value, int angle) {
898         synchronized (sceneOutputMap) {
899             sceneOutputMap.put(sceneId, new Integer[] { value, angle });
900         }
901         if (activeSceneNumber == sceneId) {
902             internalCallScene(sceneId);
903         }
904     }
905
906     @Override
907     public List<Short> getSavedScenes() {
908         Set<Short> bothKeySet = new HashSet<>(sceneOutputMap.keySet());
909         bothKeySet.addAll(sceneConfigMap.keySet());
910         return new LinkedList<>(bothKeySet);
911     }
912
913     @Override
914     public void addSceneConfig(short sceneId, DeviceSceneSpec sceneSpec) {
915         if (sceneSpec != null) {
916             synchronized (sceneConfigMap) {
917                 sceneConfigMap.put(sceneId, sceneSpec);
918                 if (listener != null) {
919                     listener.onSceneConfigAdded(sceneId);
920                 }
921             }
922         }
923     }
924
925     @Override
926     public DeviceSceneSpec getSceneConfig(short sceneId) {
927         synchronized (sceneConfigMap) {
928             return sceneConfigMap.get(sceneId);
929         }
930     }
931
932     @Override
933     public boolean doIgnoreScene(short sceneId) {
934         synchronized (sceneConfigMap) {
935             if (this.sceneConfigMap.containsKey(sceneId)) {
936                 return this.sceneConfigMap.get(sceneId).isDontCare();
937             }
938         }
939         return false;
940     }
941
942     @Override
943     public boolean containsSceneConfig(short sceneId) {
944         synchronized (sceneConfigMap) {
945             return sceneConfigMap.containsKey(sceneId);
946         }
947     }
948
949     /* End-Scenes */
950
951     @Override
952     public boolean equals(Object obj) {
953         if (obj instanceof Device) {
954             Device device = (Device) obj;
955             return device.getDSID().equals(this.getDSID());
956         }
957         return false;
958     }
959
960     @Override
961     public int hashCode() {
962         return this.getDSID().hashCode();
963     }
964
965     @Override
966     public boolean isPowerSensorUpToDate(SensorEnum powerSensorType) {
967         if (powerSensorType != null && SensorEnum.isPowerSensor(powerSensorType)) {
968             boolean isUpToDate = true;
969             if (powerSensorType.equals(SensorEnum.ACTIVE_POWER)) {
970                 isUpToDate = (outputMode != null && outputMode.equals(OutputModeEnum.WIPE) && !isOn)
971                         || (isOn && !isShade()) && !checkPowerSensorRefreshPriorityNever(powerSensorType)
972                                 ? checkSensorRefreshTime(powerSensorType)
973                                 : true;
974             }
975             if (powerSensorType.equals(SensorEnum.ELECTRIC_METER)) {
976                 isUpToDate = (isOn || getDeviceSensorValue(powerSensorType).getDsValue() == 0) && !isShade()
977                         && !checkPowerSensorRefreshPriorityNever(powerSensorType)
978                                 ? checkSensorRefreshTime(powerSensorType)
979                                 : true;
980             }
981             isUpToDate = isOn && !isShade() && !checkPowerSensorRefreshPriorityNever(powerSensorType)
982                     ? checkSensorRefreshTime(powerSensorType)
983                     : true;
984             if (!isUpToDate) {
985                 if (!getSensorDataReadingInitialized(powerSensorType)) {
986                     deviceStateUpdates.add(new DeviceStateUpdateImpl(powerSensorType, 0));
987                     setSensorDataReadingInitialized(powerSensorType, true);
988                 }
989                 return false;
990             }
991             return true;
992         }
993         throw new IllegalArgumentException("powerSensorType is null or not a power sensor type.");
994     }
995
996     private boolean checkSensorRefreshTime(SensorEnum sensorType) {
997         if (sensorType != null) {
998             DeviceSensorValue devSenVal = getDeviceSensorValue(sensorType);
999             if (devSenVal.getValid()) {
1000                 int refresh = Config.DEFAULT_SENSORDATA_REFRESH_INTERVAL;
1001                 if (config != null) {
1002                     refresh = config.getSensordataRefreshInterval();
1003                 }
1004                 return (devSenVal.getTimestamp().getTime() + refresh) > System.currentTimeMillis();
1005             }
1006         }
1007         return false;
1008     }
1009
1010     @Override
1011     public boolean isSensorDataUpToDate() {
1012         boolean isUpToDate = true;
1013         for (SensorEnum sensorType : devicePowerSensorTypes) {
1014             isUpToDate = isPowerSensorUpToDate(sensorType);
1015         }
1016         return isUpToDate;
1017     }
1018
1019     @Override
1020     public void setSensorDataRefreshPriority(String activePowerRefreshPriority, String electricMeterRefreshPriority,
1021             String outputCurrentRefreshPriority) {
1022         if (checkPriority(activePowerRefreshPriority)) {
1023             ((String[]) powerSensorRefresh[REFRESH_ACTIVE_POWER_ARRAY_FIELD])[REFRESH_PRIORITY_ARRAY_FIELD] = activePowerRefreshPriority;
1024         }
1025         if (checkPriority(outputCurrentRefreshPriority)) {
1026             ((String[]) powerSensorRefresh[REFRESH_OUTPUT_CURRENT_ARRAY_FIELD])[REFRESH_PRIORITY_ARRAY_FIELD] = outputCurrentRefreshPriority;
1027         }
1028         if (checkPriority(electricMeterRefreshPriority)) {
1029             ((String[]) powerSensorRefresh[REFRESH_ELECTRIC_METER_ARRAY_FIELD])[REFRESH_PRIORITY_ARRAY_FIELD] = electricMeterRefreshPriority;
1030         }
1031     }
1032
1033     @Override
1034     public void setSensorDataRefreshPriority(SensorEnum powerSensorType, String refreshPriority) {
1035         if (checkPriority(refreshPriority)) {
1036             String[] powerSensorRefresh = getPowerSensorRefresh(powerSensorType);
1037             if (powerSensorRefresh != null) {
1038                 powerSensorRefresh[REFRESH_ACTIVE_POWER_ARRAY_FIELD] = refreshPriority;
1039             }
1040         }
1041     }
1042
1043     @Override
1044     public String getPowerSensorRefreshPriority(SensorEnum powerSensorType) {
1045         if (powerSensorType.equals(SensorEnum.ACTIVE_POWER) && outputMode.equals(OutputModeEnum.WIPE) && !isOn) {
1046             return Config.REFRESH_PRIORITY_LOW;
1047         }
1048         String[] powerSensorRefresh = getPowerSensorRefresh(powerSensorType);
1049         if (powerSensorRefresh != null) {
1050             return powerSensorRefresh[REFRESH_PRIORITY_ARRAY_FIELD];
1051         }
1052         return null;
1053     }
1054
1055     @Override
1056     public boolean checkPowerSensorRefreshPriorityNever(SensorEnum powerSensorType) {
1057         if (getPowerSensorRefreshPriority(powerSensorType) != null) {
1058             return getPowerSensorRefreshPriority(powerSensorType).equals(Config.REFRESH_PRIORITY_NEVER);
1059         }
1060         return true;
1061     }
1062
1063     private void setAllSensorDataRefreshPrioritiesToNever() {
1064         for (short i = 0; i < powerSensorRefresh.length; i++) {
1065             ((String[]) powerSensorRefresh[i])[REFRESH_PRIORITY_ARRAY_FIELD] = Config.REFRESH_PRIORITY_NEVER;
1066         }
1067     }
1068
1069     private void setSensorDataReadingInitialized(SensorEnum powerSensorType, Boolean flag) {
1070         String[] powerSensorRefresh = getPowerSensorRefresh(powerSensorType);
1071         if (powerSensorRefresh != null) {
1072             powerSensorRefresh[READING_INITIALIZED_ARRAY_FIELD] = flag.toString();
1073         }
1074     }
1075
1076     private boolean getSensorDataReadingInitialized(SensorEnum powerSensorType) {
1077         String[] powerSensorRefresh = getPowerSensorRefresh(powerSensorType);
1078         if (powerSensorRefresh != null) {
1079             return Boolean.valueOf(powerSensorRefresh[READING_INITIALIZED_ARRAY_FIELD]);
1080         }
1081         return false;
1082     }
1083
1084     private String[] getPowerSensorRefresh(SensorEnum powerSensorType) {
1085         switch (powerSensorType) {
1086             case ACTIVE_POWER:
1087                 return (String[]) powerSensorRefresh[REFRESH_ACTIVE_POWER_ARRAY_FIELD];
1088             case OUTPUT_CURRENT:
1089                 return (String[]) powerSensorRefresh[REFRESH_OUTPUT_CURRENT_ARRAY_FIELD];
1090             case ELECTRIC_METER:
1091                 return (String[]) powerSensorRefresh[REFRESH_ELECTRIC_METER_ARRAY_FIELD];
1092             case POWER_CONSUMPTION:
1093                 return (String[]) powerSensorRefresh[REFRESH_POWER_CONSUMPTION_ARRAY_FIELD];
1094             default:
1095                 return null;
1096         }
1097     }
1098
1099     private boolean checkPriority(String priority) {
1100         switch (priority) {
1101             case Config.REFRESH_PRIORITY_HIGH:
1102             case Config.REFRESH_PRIORITY_MEDIUM:
1103             case Config.REFRESH_PRIORITY_LOW:
1104             case Config.REFRESH_PRIORITY_NEVER:
1105                 return true;
1106             default:
1107                 logger.error("Sensor data update priority do not exist! Please check the input!");
1108                 return false;
1109         }
1110     }
1111
1112     @Override
1113     public boolean isDeviceUpToDate() {
1114         isSensorDataUpToDate();
1115         return this.deviceStateUpdates.isEmpty();
1116     }
1117
1118     @Override
1119     public DeviceStateUpdate getNextDeviceUpdateState() {
1120         return !this.deviceStateUpdates.isEmpty() ? this.deviceStateUpdates.remove(0) : null;
1121     }
1122
1123     private int internalSetOutputValue(int value) {
1124         if (isShade()) {
1125             slatPosition = value;
1126             if (slatPosition <= 0) {
1127                 slatPosition = 0;
1128                 isOpen = false;
1129             } else {
1130                 isOpen = true;
1131             }
1132             return slatPosition;
1133         } else {
1134             outputValue = (short) value;
1135             if (outputValue <= 0) {
1136                 internalSetOff();
1137             } else {
1138                 if (isSwitch()) {
1139                     if (outputValue < switchPercentOff) {
1140                         internalSetOff();
1141                         isOn = false;
1142                     } else {
1143                         isOn = true;
1144                         setCachedMeterData();
1145                     }
1146                 } else {
1147                     isOn = true;
1148                     setCachedMeterData();
1149                 }
1150             }
1151             return outputValue;
1152         }
1153     }
1154
1155     private void internalSetOff() {
1156         this.isOn = false;
1157         logger.debug("internal set OFF ");
1158         if (!checkPowerSensorRefreshPriorityNever(SensorEnum.ACTIVE_POWER)) {
1159             if (getSensorDataReadingInitialized(SensorEnum.ACTIVE_POWER)) {
1160                 deviceStateUpdates.add(new DeviceStateUpdateImpl(SensorEnum.ACTIVE_POWER, -1));
1161                 logger.debug("internal set sensor to 0");
1162             }
1163             setDsSensorValue(SensorEnum.ACTIVE_POWER, 0);
1164         }
1165         if (!checkPowerSensorRefreshPriorityNever(SensorEnum.OUTPUT_CURRENT)) {
1166             if (getSensorDataReadingInitialized(SensorEnum.OUTPUT_CURRENT)) {
1167                 deviceStateUpdates.add(new DeviceStateUpdateImpl(SensorEnum.OUTPUT_CURRENT, -1));
1168             }
1169             setDsSensorValue(SensorEnum.OUTPUT_CURRENT, 0);
1170         }
1171         if (!checkPowerSensorRefreshPriorityNever(SensorEnum.POWER_CONSUMPTION)) {
1172             if (getSensorDataReadingInitialized(SensorEnum.POWER_CONSUMPTION)) {
1173                 deviceStateUpdates.add(new DeviceStateUpdateImpl(SensorEnum.POWER_CONSUMPTION, -1));
1174             }
1175             setDsSensorValue(SensorEnum.POWER_CONSUMPTION, 0);
1176         }
1177     }
1178
1179     private short internalSetAngleValue(int value) {
1180         if (value < 0) {
1181             slatAngle = 0;
1182         }
1183         if (value > maxSlatAngle) {
1184             slatAngle = maxSlatAngle;
1185         } else {
1186             slatAngle = (short) value;
1187         }
1188         return slatAngle;
1189     }
1190
1191     // Device sensors
1192     @Override
1193     public List<SensorEnum> getSensorTypes() {
1194         List<SensorEnum> list = new ArrayList<>(devicePowerSensorTypes);
1195         list.addAll(deviceClimateSensorTypes);
1196         return list;
1197     }
1198
1199     @Override
1200     public List<SensorEnum> getPowerSensorTypes() {
1201         return devicePowerSensorTypes;
1202     }
1203
1204     @Override
1205     public List<SensorEnum> getClimateSensorTypes() {
1206         return deviceClimateSensorTypes;
1207     }
1208
1209     @Override
1210     public List<DeviceSensorValue> getDeviceSensorValues() {
1211         return deviceSensorValues;
1212     }
1213
1214     @Override
1215     public boolean supportsSensorType(SensorEnum sensorType) {
1216         if (sensorType != null) {
1217             return getSensorTypes().contains(sensorType);
1218         }
1219         return false;
1220     }
1221
1222     @Override
1223     public void setDeviceSensorValue(DeviceSensorValue deviceSensorValue) {
1224         if (deviceSensorValue != null) {
1225             int index = deviceSensorValues.indexOf(deviceSensorValue);
1226             if (index < 0) {
1227                 deviceSensorValues.add(deviceSensorValue);
1228                 if (SensorEnum.isPowerSensor(deviceSensorValue.getSensorType())) {
1229                     devicePowerSensorTypes.add(deviceSensorValue.getSensorType());
1230                 } else {
1231                     deviceClimateSensorTypes.add(deviceSensorValue.getSensorType());
1232                 }
1233             } else {
1234                 if (deviceSensorValue.getTimestamp().after(deviceSensorValues.get(index).getTimestamp())) {
1235                     logger.debug("set deviceSeneorValue, new deviceSensorValue is: {}", deviceSensorValue.toString());
1236                     deviceSensorValues.set(index, deviceSensorValue);
1237                     checkSensorValueSet(deviceSensorValue, true);
1238                 }
1239             }
1240         }
1241     }
1242
1243     @Override
1244     public void setDeviceSensorByEvent(EventItem event) {
1245         DeviceSensorValue devSenVal = new DeviceSensorValue(event.getProperties());
1246         SensorEnum sensorType = devSenVal.getSensorType();
1247         if (!isEchoSensor(sensorType)) {
1248             logger.debug("Event is no echo, set values {} for sensorType {}", devSenVal, devSenVal.getSensorType());
1249             if (SensorEnum.isPowerSensor(sensorType) && getSensorDataReadingInitialized(sensorType)) {
1250                 logger.debug("SensorJob was initialized, remove sensorjob for sensorType: {}",
1251                         devSenVal.getSensorType());
1252                 deviceStateUpdates.add(new DeviceStateUpdateImpl(sensorType, -1));
1253             }
1254             setDeviceSensorValue(devSenVal);
1255         } else {
1256             logger.debug("Event is echo remove sensorType {} from echoBox", devSenVal.getSensorType());
1257             sensorEchoBox.remove(devSenVal.getSensorType());
1258         }
1259     }
1260
1261     private boolean isEchoSensor(SensorEnum sensorType) {
1262         return sensorEchoBox != null ? sensorEchoBox.contains(sensorType) : false;
1263     }
1264
1265     private List<SensorEnum> sensorEchoBox = Collections.synchronizedList(new LinkedList<>());
1266
1267     @Override
1268     public void setDeviceSensorDsValueBySensorJob(SensorEnum sensorType, Integer value) {
1269         logger.debug("sensorJob for device {} is executet", dsid.getValue());
1270         if (isSensorEchoBoxEnabled()) {
1271             // temperature resolution is not correct, so waiting for device sensor-event
1272             if (!sensorType.toString().contains("TEMPERATURE")) {
1273                 logger.debug("echoBox is enabled, add sensorType {} to echoBox", sensorType);
1274                 sensorEchoBox.add(sensorType);
1275             } else {
1276                 logger.debug("echoBox is enabled, ignoring temperation update and wait for sensor Event");
1277                 return;
1278             }
1279         }
1280         setDsSensorValue(sensorType, value);
1281     }
1282
1283     @Override
1284     public void enableSensorEchoBox() {
1285         if (sensorEchoBox == null) {
1286             sensorEchoBox = Collections.synchronizedList(new LinkedList<>());
1287         }
1288     }
1289
1290     @Override
1291     public void disableSensorEchoBox() {
1292         sensorEchoBox = null;
1293     }
1294
1295     @Override
1296     public boolean isSensorEchoBoxEnabled() {
1297         return sensorEchoBox != null;
1298     }
1299
1300     @Override
1301     public DeviceSensorValue getDeviceSensorValue(SensorEnum sensorType) {
1302         if (sensorType != null) {
1303             for (DeviceSensorValue devSenVal : deviceSensorValues) {
1304                 if (devSenVal.getSensorType().equals(sensorType)) {
1305                     return devSenVal;
1306                 }
1307             }
1308         }
1309         return null;
1310     }
1311
1312     @Override
1313     public DeviceSensorValue getDeviceSensorValue(Short sensorIndex) {
1314         if (sensorIndex != null) {
1315             for (DeviceSensorValue devSenVal : deviceSensorValues) {
1316                 if (devSenVal.getSensorIndex().equals(sensorIndex)) {
1317                     return devSenVal;
1318                 }
1319             }
1320         }
1321         return null;
1322     }
1323
1324     @Override
1325     public Short getSensorIndex(SensorEnum sensorType) {
1326         if (sensorType != null) {
1327             DeviceSensorValue devSenVal = getDeviceSensorValue(sensorType);
1328             return devSenVal != null ? devSenVal.getSensorIndex() : null;
1329         }
1330         return null;
1331     }
1332
1333     @Override
1334     public SensorEnum getSensorType(Short sensorIndex) {
1335         if (sensorIndex != null) {
1336             DeviceSensorValue devSenVal = getDeviceSensorValue(sensorIndex);
1337             return devSenVal != null ? devSenVal.getSensorType() : null;
1338         }
1339         return null;
1340     }
1341
1342     @Override
1343     public Integer getDsSensorValue(SensorEnum sensorType) {
1344         return getDsSensorValue((Object) sensorType);
1345     }
1346
1347     @Override
1348     public Integer getDsSensorValue(Short sensorIndex) {
1349         return getDsSensorValue((Object) sensorIndex);
1350     }
1351
1352     @Override
1353     public Float getFloatSensorValue(Short sensorIndex) {
1354         return getFloatSensorValue((Object) sensorIndex);
1355     }
1356
1357     @Override
1358     public Float getFloatSensorValue(SensorEnum sensorType) {
1359         return getFloatSensorValue((Object) sensorType);
1360     }
1361
1362     @Override
1363     public boolean setFloatSensorValue(SensorEnum sensorType, Float floatSensorValue) {
1364         return checkAndSetSensorValue(sensorType, null, floatSensorValue);
1365     }
1366
1367     @Override
1368     public boolean setFloatSensorValue(Short sensorIndex, Float floatSensorValue) {
1369         return checkAndSetSensorValue(sensorIndex, null, floatSensorValue);
1370     }
1371
1372     @Override
1373     public boolean setDsSensorValue(Short sensorIndex, Integer dSSensorValue) {
1374         return checkAndSetSensorValue(sensorIndex, dSSensorValue, null);
1375     }
1376
1377     @Override
1378     public boolean setDsSensorValue(SensorEnum sensorType, Integer dSSensorValue) {
1379         return checkAndSetSensorValue(sensorType, dSSensorValue, null);
1380     }
1381
1382     @Override
1383     public boolean setDsSensorValue(Short sensorIndex, Integer dSSensorValue, Float floatSensorValue) {
1384         return checkAndSetSensorValue(sensorIndex, dSSensorValue, floatSensorValue);
1385     }
1386
1387     @Override
1388     public boolean setDsSensorValue(SensorEnum sensorType, Integer dSSensorValue, Float floatSensorValue) {
1389         return checkAndSetSensorValue(sensorType, dSSensorValue, floatSensorValue);
1390     }
1391
1392     @Override
1393     public boolean hasSensors() {
1394         return hasClimateSensors() || hasPowerSensors();
1395     }
1396
1397     @Override
1398     public boolean hasClimateSensors() {
1399         return !deviceClimateSensorTypes.isEmpty();
1400     }
1401
1402     @Override
1403     public boolean hasPowerSensors() {
1404         return !devicePowerSensorTypes.isEmpty();
1405     }
1406
1407     // Sensor get/set helper methods
1408     private DeviceSensorValue getDeviceSensorValueForGet(Object obj) {
1409         return checkHighOutputCurrent(getDeviceSensorValueForSet(obj));
1410     }
1411
1412     private DeviceSensorValue getDeviceSensorValueForSet(Object obj) {
1413         if (obj instanceof Short) {
1414             return getDeviceSensorValue((Short) obj);
1415         } else {
1416             return getDeviceSensorValue((SensorEnum) obj);
1417         }
1418     }
1419
1420     private Integer getDsSensorValue(Object obj) {
1421         if (obj != null) {
1422             DeviceSensorValue devSenVal = checkPowerSensor(getDeviceSensorValueForGet(obj));
1423             return devSenVal != null && devSenVal.getValid() ? devSenVal.getDsValue() : null;
1424         }
1425         return null;
1426     }
1427
1428     private Float getFloatSensorValue(Object obj) {
1429         if (obj != null) {
1430             DeviceSensorValue devSenVal = checkPowerSensor(getDeviceSensorValueForGet(obj));
1431             return devSenVal != null && devSenVal.getValid() ? devSenVal.getFloatValue() : null;
1432         }
1433         return null;
1434     }
1435
1436     private DeviceSensorValue checkPowerSensor(DeviceSensorValue devSenVal) {
1437         if (devSenVal != null && SensorEnum.isPowerSensor(devSenVal.getSensorType())) {
1438             if (!devSenVal.getSensorType().equals(SensorEnum.ELECTRIC_METER)
1439                     && !(SensorEnum.isPowerSensor(devSenVal.getSensorType()) && isOn)) {
1440                 devSenVal.setDsValue(0);
1441             }
1442         }
1443         return devSenVal;
1444     }
1445
1446     /**
1447      * Checks output current sensor to return automatically high output current
1448      * sensor, if the sensor exists.
1449      *
1450      * @param devSenVal
1451      * @return output current high DeviceSensorValue or the given DeviceSensorValue
1452      */
1453     private DeviceSensorValue checkHighOutputCurrent(DeviceSensorValue devSenVal) {
1454         if (devSenVal != null && devSenVal.getSensorType().equals(SensorEnum.OUTPUT_CURRENT)
1455                 && devSenVal.getDsValue() == SensorEnum.OUTPUT_CURRENT.getMax().intValue()
1456                 && devicePowerSensorTypes.contains(SensorEnum.OUTPUT_CURRENT_H)) {
1457             return getDeviceSensorValue(SensorEnum.OUTPUT_CURRENT_H);
1458         }
1459         return devSenVal;
1460     }
1461
1462     private boolean checkAndSetSensorValue(Object obj, Integer dsValue, Float floatValue) {
1463         boolean isSet = false;
1464         if (obj != null) {
1465             DeviceSensorValue devSenVal = getDeviceSensorValueForSet(obj);
1466             if (devSenVal != null) {
1467                 if (dsValue != null && floatValue != null) {
1468                     isSet = devSenVal.setValues(floatValue, dsValue);
1469                 } else if (dsValue != null) {
1470                     isSet = devSenVal.setDsValue(dsValue);
1471                 } else if (floatValue != null) {
1472                     isSet = devSenVal.setFloatValue(floatValue);
1473                 }
1474                 logger.debug("check devSenVal {} isSet={}", devSenVal.toString(), isSet);
1475                 checkSensorValueSet(devSenVal, isSet);
1476             }
1477         }
1478         return isSet;
1479     }
1480
1481     private void checkSensorValueSet(DeviceSensorValue devSenVal, boolean isSet) {
1482         if (devSenVal != null) {
1483             if (isSet) {
1484                 if (outputMode.equals(OutputModeEnum.WIPE) && !isOn
1485                         && devSenVal.getSensorType().equals(SensorEnum.ACTIVE_POWER)) {
1486                     int standby = Config.DEFAULT_STANDBY_ACTIVE_POWER;
1487                     if (config != null) {
1488                         standby = config.getStandbyActivePower();
1489                     }
1490                     if (devSenVal.getDsValue() > standby) {
1491                         this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, 1));
1492                     }
1493                 }
1494                 if (SensorEnum.isPowerSensor(devSenVal.getSensorType())) {
1495                     addPowerSensorCache(devSenVal);
1496                 }
1497                 informListenerAboutStateUpdate(
1498                         new DeviceStateUpdateImpl(devSenVal.getSensorType(), devSenVal.getFloatValue()));
1499             }
1500             setSensorDataReadingInitialized(devSenVal.getSensorType(), false);
1501         }
1502     }
1503
1504     private void addPowerSensorCache(DeviceSensorValue newDevSenVal) {
1505         Integer[] cachedPowerValues = cachedSensorPowerValues.get(outputValue);
1506         if (cachedPowerValues == null) {
1507             cachedPowerValues = new Integer[4];
1508         }
1509         switch (newDevSenVal.getSensorType()) {
1510             case ACTIVE_POWER:
1511                 cachedPowerValues[ACTIVE_POWER_ARRAY_FIELD] = newDevSenVal.getDsValue();
1512                 break;
1513             case OUTPUT_CURRENT:
1514                 cachedPowerValues[OUTPUT_CURRENT_ARRAY_FIELD] = newDevSenVal.getDsValue();
1515                 break;
1516             case OUTPUT_CURRENT_H:
1517                 cachedPowerValues[OUTPUT_CURRENT_HIGH_ARRAY_FIELD] = newDevSenVal.getDsValue();
1518                 break;
1519             case POWER_CONSUMPTION:
1520                 cachedPowerValues[POWER_CONSUMPTION_ARRAY_FIELD] = newDevSenVal.getDsValue();
1521                 break;
1522             default:
1523                 return;
1524         }
1525         this.cachedSensorPowerValues.put(outputValue, cachedPowerValues);
1526     }
1527
1528     @Override
1529     public synchronized void updateInternalDeviceState(DeviceStateUpdate deviceStateUpdate) {
1530         DeviceStateUpdate deviceStateUpdateInt = internalSetOutputValue(deviceStateUpdate);
1531         if (deviceStateUpdateInt != null) {
1532             validateActiveScene();
1533             informListenerAboutStateUpdate(deviceStateUpdate);
1534         }
1535     }
1536
1537     private DeviceStateUpdate internalSetOutputValue(DeviceStateUpdate deviceStateUpdate) {
1538         if (deviceStateUpdate == null) {
1539             return null;
1540         }
1541         logger.debug("internal set outputvalue");
1542         switch (deviceStateUpdate.getType()) {
1543             case DeviceStateUpdate.OUTPUT_DECREASE:
1544                 return new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_DECREASE,
1545                         internalSetOutputValue(outputValue - getDimmStep()));
1546             case DeviceStateUpdate.OUTPUT_INCREASE:
1547                 return new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT_INCREASE,
1548                         internalSetOutputValue(outputValue + getDimmStep()));
1549             case DeviceStateUpdate.OUTPUT:
1550                 internalSetOutputValue(deviceStateUpdate.getValueAsInteger());
1551                 break;
1552             case DeviceStateUpdate.ON_OFF:
1553                 if (deviceStateUpdate.getValueAsInteger() < 0) {
1554                     internalSetOutputValue(0);
1555                 } else {
1556                     internalSetOutputValue(maxOutputValue);
1557                 }
1558                 break;
1559             case DeviceStateUpdate.OPEN_CLOSE:
1560                 if (deviceStateUpdate.getValueAsInteger() < 0) {
1561                     internalSetOutputValue(0);
1562                 } else {
1563                     internalSetOutputValue(maxSlatPosition);
1564                 }
1565                 break;
1566             case DeviceStateUpdate.OPEN_CLOSE_ANGLE:
1567                 if (deviceStateUpdate.getValueAsInteger() < 0) {
1568                     internalSetAngleValue(0);
1569                 } else {
1570                     internalSetAngleValue(maxSlatAngle);
1571                 }
1572                 break;
1573             case DeviceStateUpdate.SLAT_DECREASE:
1574                 return new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_DECREASE,
1575                         internalSetOutputValue(slatPosition - getDimmStep()));
1576             case DeviceStateUpdate.SLAT_INCREASE:
1577                 return new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_INCREASE,
1578                         internalSetOutputValue(slatPosition + getDimmStep()));
1579             case DeviceStateUpdate.SLATPOSITION:
1580                 internalSetOutputValue(deviceStateUpdate.getValueAsInteger());
1581                 break;
1582             case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
1583                 return new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_DECREASE,
1584                         internalSetAngleValue(slatAngle - DeviceConstants.ANGLE_STEP_SLAT));
1585             case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
1586                 return new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE_INCREASE,
1587                         internalSetAngleValue(slatAngle + DeviceConstants.ANGLE_STEP_SLAT));
1588             case DeviceStateUpdate.SLAT_ANGLE:
1589                 internalSetAngleValue(deviceStateUpdate.getValueAsInteger());
1590                 break;
1591             case DeviceStateUpdate.UPDATE_CALL_SCENE:
1592                 this.internalCallScene(deviceStateUpdate.getValueAsShort());
1593                 return null;
1594             case DeviceStateUpdate.UPDATE_UNDO_SCENE:
1595                 this.internalUndoScene();
1596                 return null;
1597             default:
1598                 if (deviceStateUpdate.isSensorUpdateType()) {
1599                     SensorEnum sensorType = deviceStateUpdate.getTypeAsSensorEnum();
1600                     setFloatSensorValue(sensorType, deviceStateUpdate.getValueAsFloat());
1601                 }
1602                 return null;
1603         }
1604         return deviceStateUpdate;
1605     }
1606
1607     private void validateActiveScene() {
1608         if (activeScene == null) {
1609             return;
1610         }
1611         Integer[] sceneOutput = getStandartSceneOutput(activeScene.getSceneID());
1612         if (sceneOutput == null) {
1613             sceneOutput = sceneOutputMap.get(activeScene.getSceneID());
1614         }
1615         if (sceneOutput != null) {
1616             boolean outputChanged = false;
1617             if (isShade()) {
1618                 if (isBlind() && sceneOutput[1] != slatAngle) {
1619                     logger.debug("Scene output angle: {} setted output value {}", sceneOutput[1], slatAngle);
1620                     outputChanged = true;
1621                 }
1622                 if (sceneOutput[0] != slatPosition) {
1623                     logger.debug("Scene output value: {} setted output value {}", sceneOutput[0], slatPosition);
1624                     outputChanged = true;
1625                 }
1626             } else {
1627                 if (sceneOutput[0] != outputValue) {
1628                     logger.debug("Scene output value: {} setted output value {}", sceneOutput[0], outputValue);
1629                     outputChanged = true;
1630                 }
1631             }
1632             if (outputChanged) {
1633                 logger.debug("Device output from Device with dSID {} changed deactivate scene {}", dsid.getValue(),
1634                         activeScene.getID());
1635                 activeScene.deviceSceneChanged((short) -1);
1636                 lastScene = null;
1637                 activeScene = null;
1638             }
1639         } else {
1640             lastScene = null;
1641         }
1642     }
1643
1644     @Override
1645     public DeviceStatusListener unregisterDeviceStatusListener() {
1646         setAllSensorDataRefreshPrioritiesToNever();
1647         return super.unregisterDeviceStatusListener();
1648     }
1649
1650     private void setCachedMeterData() {
1651         logger.debug("load cached sensor data device with dsid {}", dsid.getValue());
1652         Integer[] cachedSensorData = this.cachedSensorPowerValues.get(this.getOutputValue());
1653         if (cachedSensorData != null) {
1654             if (cachedSensorData[ACTIVE_POWER_ARRAY_FIELD] != null
1655                     && !checkPowerSensorRefreshPriorityNever(SensorEnum.ACTIVE_POWER)) {
1656                 informListenerAboutStateUpdate(new DeviceStateUpdateImpl(SensorEnum.ACTIVE_POWER,
1657                         (float) cachedSensorData[ACTIVE_POWER_ARRAY_FIELD]));
1658
1659             }
1660             if (cachedSensorData[OUTPUT_CURRENT_ARRAY_FIELD] != null
1661                     && !checkPowerSensorRefreshPriorityNever(SensorEnum.OUTPUT_CURRENT)) {
1662                 if (cachedSensorData[OUTPUT_CURRENT_ARRAY_FIELD] == SensorEnum.OUTPUT_CURRENT.getMax().intValue()
1663                         && devicePowerSensorTypes.contains(SensorEnum.OUTPUT_CURRENT_H)) {
1664                     informListenerAboutStateUpdate(new DeviceStateUpdateImpl(SensorEnum.OUTPUT_CURRENT,
1665                             cachedSensorData[OUTPUT_CURRENT_HIGH_ARRAY_FIELD]));
1666                 } else {
1667                     informListenerAboutStateUpdate(new DeviceStateUpdateImpl(SensorEnum.OUTPUT_CURRENT,
1668                             cachedSensorData[OUTPUT_CURRENT_ARRAY_FIELD]));
1669                 }
1670             }
1671             if (cachedSensorData[ACTIVE_POWER_ARRAY_FIELD] != null
1672                     && !checkPowerSensorRefreshPriorityNever(SensorEnum.POWER_CONSUMPTION)) {
1673                 informListenerAboutStateUpdate(
1674                         new DeviceStateUpdateImpl(SensorEnum.ACTIVE_POWER, cachedSensorData[ACTIVE_POWER_ARRAY_FIELD]));
1675             }
1676         }
1677     }
1678
1679     /**
1680      * if an {@link DeviceStatusListener} is registered inform him about the new
1681      * state otherwise do nothing.
1682      *
1683      * @param deviceStateUpdate
1684      */
1685     private void informListenerAboutStateUpdate(DeviceStateUpdate deviceStateUpdate) {
1686         if (listener != null) {
1687             listener.onDeviceStateChanged(correctDeviceStatusUpdate(deviceStateUpdate));
1688         }
1689     }
1690
1691     private DeviceStateUpdate correctDeviceStatusUpdate(DeviceStateUpdate deviceStateUpdate) {
1692         if (isSwitch() && deviceStateUpdate.getType().equals(DeviceStateUpdate.OUTPUT)) {
1693             if (deviceStateUpdate.getValueAsInteger() >= switchPercentOff) {
1694                 return new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, DeviceStateUpdate.ON_VALUE);
1695             } else {
1696                 return new DeviceStateUpdateImpl(DeviceStateUpdate.ON_OFF, DeviceStateUpdate.OFF_VALUE);
1697             }
1698         }
1699         return deviceStateUpdate;
1700     }
1701
1702     private void informListenerAboutConfigChange(ChangeableDeviceConfigEnum changedConfig) {
1703         if (listener != null) {
1704             listener.onDeviceConfigChanged(changedConfig);
1705             logger.debug("Inform listener about device config {} changed", changedConfig.toString());
1706         }
1707     }
1708
1709     @SuppressWarnings("null")
1710     @Override
1711     public void saveConfigSceneSpecificationIntoDevice(Map<String, String> propertries) {
1712         if (propertries != null) {
1713             String sceneSave;
1714             for (String key : propertries.keySet()) {
1715                 if (key.startsWith(DigitalSTROMBindingConstants.DEVICE_SCENE)) {
1716                     try {
1717                         short sceneID = Short.parseShort((String) key
1718                                 .subSequence(DigitalSTROMBindingConstants.DEVICE_SCENE.length(), key.length()));
1719                         sceneSave = propertries.get(key);
1720                         if (sceneSave != null && !sceneSave.isBlank()) {
1721                             logger.debug("Find saved scene configuration for device with dSID {} and sceneID {}", dsid,
1722                                     key);
1723                             String[] sceneParm = sceneSave.replace(" ", "").split(",");
1724                             JSONDeviceSceneSpecImpl sceneSpecNew = null;
1725                             int sceneValue = -1;
1726                             int sceneAngle = -1;
1727                             for (int j = 0; j < sceneParm.length; j++) {
1728                                 String[] sceneParmSplit = sceneParm[j].split(":");
1729                                 switch (sceneParmSplit[0]) {
1730                                     case "Scene":
1731                                         sceneSpecNew = new JSONDeviceSceneSpecImpl(sceneParmSplit[1]);
1732                                         break;
1733                                     case "dontcare":
1734                                         sceneSpecNew.setDontcare(Boolean.parseBoolean(sceneParmSplit[1]));
1735                                         break;
1736                                     case "localPrio":
1737                                         sceneSpecNew.setLocalPrio(Boolean.parseBoolean(sceneParmSplit[1]));
1738                                         break;
1739                                     case "specialMode":
1740                                         sceneSpecNew.setSpecialMode(Boolean.parseBoolean(sceneParmSplit[1]));
1741                                         break;
1742                                     case "sceneValue":
1743                                         sceneValue = Integer.parseInt(sceneParmSplit[1]);
1744                                         break;
1745                                     case "sceneAngle":
1746                                         sceneAngle = Integer.parseInt(sceneParmSplit[1]);
1747                                         break;
1748                                 }
1749                             }
1750                             if (sceneValue > -1) {
1751                                 logger.debug(
1752                                         "Saved sceneValue {}, sceneAngle {} for scene id {} into device with dsid {}",
1753                                         sceneValue, sceneAngle, sceneID, getDSID().getValue());
1754                                 internalSetSceneOutputValue(sceneID, sceneValue, sceneAngle);
1755                                 deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_OUTPUT,
1756                                         new Short[] { sceneID, (short) -1 }));
1757                             }
1758                             if (sceneSpecNew != null) {
1759                                 logger.debug("Saved sceneConfig: [{}] for scene id {} into device with dsid {}",
1760                                         sceneSpecNew.toString(), sceneID, getDSID().getValue());
1761                                 synchronized (sceneConfigMap) {
1762                                     sceneConfigMap.put(sceneID, sceneSpecNew);
1763                                 }
1764                                 deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_CONFIG,
1765                                         new Short[] { sceneID, (short) -1 }));
1766                             }
1767                         }
1768                     } catch (NumberFormatException e) {
1769                         // ignore
1770                     }
1771                 }
1772             }
1773         }
1774     }
1775
1776     @SuppressWarnings("null")
1777     @Override
1778     public void saveConfigSceneSpecificationIntoDevice(String propertries) {
1779         String[] scenes = propertries.split("\n");
1780         for (int i = 0; i < scenes.length; i++) {
1781             logger.debug("Find saved scene configuration for device with dSID {} and sceneID {}", dsid, i);
1782             String[] sceneIdToConfig = scenes[i].replaceAll(" ", "").split("=");
1783             String[] sceneParm = sceneIdToConfig[1].split(",");
1784             JSONDeviceSceneSpecImpl sceneSpecNew = null;
1785             int sceneValue = -1;
1786             int sceneAngle = -1;
1787             for (int j = 0; j < sceneParm.length; j++) {
1788                 String[] sceneParmSplit = sceneParm[j].split(":");
1789                 switch (sceneParmSplit[0]) {
1790                     case "Scene":
1791                         sceneSpecNew = new JSONDeviceSceneSpecImpl(sceneParmSplit[1]);
1792                         break;
1793                     case "dontcare":
1794                         sceneSpecNew.setDontcare(Boolean.parseBoolean(sceneParmSplit[1]));
1795                         break;
1796                     case "localPrio":
1797                         sceneSpecNew.setLocalPrio(Boolean.parseBoolean(sceneParmSplit[1]));
1798                         break;
1799                     case "specialMode":
1800                         sceneSpecNew.setSpecialMode(Boolean.parseBoolean(sceneParmSplit[1]));
1801                         break;
1802                     case "sceneValue":
1803                         sceneValue = Integer.parseInt(sceneParmSplit[1]);
1804                         break;
1805                     case "sceneAngle":
1806                         sceneAngle = Integer.parseInt(sceneParmSplit[1]);
1807                         break;
1808                 }
1809             }
1810             if (sceneValue > -1) {
1811                 logger.debug("Saved sceneValue {}, sceneAngle {} for scene id {} into device with dsid {}", sceneValue,
1812                         sceneAngle, i, getDSID().getValue());
1813                 synchronized (sceneOutputMap) {
1814                     sceneOutputMap.put(sceneSpecNew.getScene().getSceneNumber(),
1815                             new Integer[] { sceneValue, sceneAngle });
1816                 }
1817             }
1818             if (sceneSpecNew != null) {
1819                 logger.debug("Saved sceneConfig: [{}] for scene id {} into device with dsid {}",
1820                         sceneSpecNew.toString(), i, getDSID().getValue());
1821                 synchronized (sceneConfigMap) {
1822                     sceneConfigMap.put(sceneSpecNew.getScene().getSceneNumber(), sceneSpecNew);
1823                 }
1824             }
1825         }
1826     }
1827
1828     @Override
1829     public void setConfig(Config config) {
1830         this.config = config;
1831     }
1832
1833     private String powerSensorRefreshToString() {
1834         String powSenRef = "";
1835         for (int i = 0; i < powerSensorRefresh.length; i++) {
1836             powSenRef = powSenRef + " [" + i + "]=Prio: "
1837                     + ((String[]) powerSensorRefresh[i])[REFRESH_PRIORITY_ARRAY_FIELD] + ", Initialized: "
1838                     + ((String[]) powerSensorRefresh[i])[READING_INITIALIZED_ARRAY_FIELD] + " ";
1839         }
1840         return powSenRef;
1841     }
1842
1843     @Override
1844     public boolean isBinaryInputDevice() {
1845         return !deviceBinaryInputs.isEmpty();
1846     }
1847
1848     @Override
1849     public List<DeviceBinaryInput> getBinaryInputs() {
1850         return deviceBinaryInputs;
1851     }
1852
1853     @Override
1854     public DeviceBinaryInput getBinaryInput(DeviceBinarayInputEnum binaryInputType) {
1855         if (binaryInputType != null) {
1856             for (DeviceBinaryInput binInput : deviceBinaryInputs) {
1857                 if (binaryInputType.getBinaryInputType().equals(binInput.getInputType())) {
1858                     return binInput;
1859                 }
1860             }
1861         }
1862         return null;
1863     }
1864
1865     @Override
1866     public Short getBinaryInputState(DeviceBinarayInputEnum binaryInputType) {
1867         DeviceBinaryInput devBinInput = getBinaryInput(binaryInputType);
1868         if (devBinInput != null) {
1869             return devBinInput.getState();
1870         }
1871         return null;
1872     }
1873
1874     @Override
1875     public boolean setBinaryInputState(DeviceBinarayInputEnum binaryInputType, Short newState) {
1876         DeviceBinaryInput devBinInput = getBinaryInput(binaryInputType);
1877         if (devBinInput != null) {
1878             devBinInput.setState(newState);
1879             informListenerAboutStateUpdate(new DeviceStateUpdateImpl(binaryInputType, newState));
1880             return true;
1881         }
1882         return false;
1883     }
1884
1885     @Override
1886     public void setBinaryInputs(List<DeviceBinaryInput> newBinaryInputs) {
1887         this.deviceBinaryInputs.clear();
1888         this.deviceBinaryInputs.addAll(newBinaryInputs);
1889         informListenerAboutConfigChange(ChangeableDeviceConfigEnum.BINARY_INPUTS);
1890     }
1891
1892     @Override
1893     public String toString() {
1894         return "DeviceImpl [meterDSID=" + meterDSID + ", zoneId=" + zoneId + ", groupList=" + groupList + ", hwInfo="
1895                 + hwInfo + ", getName()=" + getName() + ", getDSID()=" + getDSID() + ", getDSUID()=" + getDSUID()
1896                 + ", isPresent()=" + isPresent() + ", isValide()=" + isValid() + ", getDisplayID()=" + getDisplayID()
1897                 + ", outputMode=" + outputMode + ", getSensorTypes()=" + getSensorTypes() + ", getDeviceSensorValues()="
1898                 + getDeviceSensorValues() + ", powerSensorRefresh=" + powerSensorRefreshToString() + "]";
1899     }
1900 }