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