]> git.basschouten.com Git - openhab-addons.git/blob
745a6915f24d440b0c8473be14b2100e24784072
[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.handler;
14
15 import static org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.math.RoundingMode;
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants;
28 import org.openhab.binding.digitalstrom.internal.lib.GeneralLibConstance;
29 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
30 import org.openhab.binding.digitalstrom.internal.lib.listener.DeviceStatusListener;
31 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Device;
32 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.GeneralDeviceInformation;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceSceneSpec;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceStateUpdate;
35 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ApplicationGroup;
36 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ChangeableDeviceConfigEnum;
37 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
38 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
39 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
40 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceBinaryInput;
41 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceStateUpdateImpl;
42 import org.openhab.binding.digitalstrom.internal.providers.DsChannelTypeProvider;
43 import org.openhab.core.config.core.Configuration;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.IncreaseDecreaseType;
46 import org.openhab.core.library.types.OnOffType;
47 import org.openhab.core.library.types.PercentType;
48 import org.openhab.core.library.types.StopMoveType;
49 import org.openhab.core.library.types.StringType;
50 import org.openhab.core.library.types.UpDownType;
51 import org.openhab.core.thing.Bridge;
52 import org.openhab.core.thing.Channel;
53 import org.openhab.core.thing.ChannelUID;
54 import org.openhab.core.thing.Thing;
55 import org.openhab.core.thing.ThingStatus;
56 import org.openhab.core.thing.ThingStatusDetail;
57 import org.openhab.core.thing.ThingStatusInfo;
58 import org.openhab.core.thing.ThingTypeUID;
59 import org.openhab.core.thing.binding.BaseThingHandler;
60 import org.openhab.core.thing.binding.ThingHandler;
61 import org.openhab.core.thing.binding.builder.ChannelBuilder;
62 import org.openhab.core.thing.binding.builder.ThingBuilder;
63 import org.openhab.core.thing.type.ChannelTypeUID;
64 import org.openhab.core.types.Command;
65 import org.openhab.core.types.RefreshType;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 /**
70  * The {@link DeviceHandler} is responsible for handling the configuration, load supported channels of a
71  * digitalSTROM device and handling commands, which are sent to one of the channels. <br>
72  * <br>
73  * For that it uses the {@link BridgeHandler} and the {@link DeviceStateUpdate} mechanism of the {@link Device} to
74  * execute the actual command and implements the {@link DeviceStatusListener} to get informed about changes from the
75  * accompanying {@link Device}.
76  *
77  * @author Michael Ochel - Initial contribution
78  * @author Matthias Siegele - Initial contribution
79  */
80 public class DeviceHandler extends BaseThingHandler implements DeviceStatusListener {
81
82     private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class);
83
84     /**
85      * Contains all supported thing types of this handler, will be filled by DsDeviceThingTypeProvider.
86      */
87     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>();
88
89     public static final String TWO_STAGE_SWITCH_IDENTICATOR = "2";
90     public static final String THREE_STAGE_SWITCH_IDENTICATOR = "3";
91
92     private String dSID;
93     private Device device;
94     private BridgeHandler dssBridgeHandler;
95
96     private Command lastComand;
97     private String currentChannel;
98     private List<String> loadedSensorChannels;
99
100     /**
101      * Creates a new {@link DeviceHandler}.
102      *
103      * @param thing must not be null
104      */
105     public DeviceHandler(Thing thing) {
106         super(thing);
107     }
108
109     @Override
110     public void initialize() {
111         logger.debug("Initializing DeviceHandler.");
112         dSID = (String) getConfig().get(DigitalSTROMBindingConstants.DEVICE_DSID);
113         if (dSID != null && !dSID.isBlank()) {
114             final Bridge bridge = getBridge();
115             if (bridge != null) {
116                 bridgeStatusChanged(bridge.getStatusInfo());
117             } else {
118                 // Set status to OFFLINE if no bridge is available e.g. because the bridge has been removed and the
119                 // Thing was reinitialized.
120                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Bridge is missing!");
121             }
122         } else {
123             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "dSID is missing");
124         }
125     }
126
127     @Override
128     public void dispose() {
129         logger.debug("Handler disposed... unregister DeviceStatusListener");
130         if (dSID != null) {
131             if (dssBridgeHandler != null) {
132                 dssBridgeHandler.unregisterDeviceStatusListener(this);
133             }
134         }
135         if (device != null) {
136             device.setSensorDataRefreshPriority(Config.REFRESH_PRIORITY_NEVER, Config.REFRESH_PRIORITY_NEVER,
137                     Config.REFRESH_PRIORITY_NEVER);
138         }
139         device = null;
140     }
141
142     @Override
143     public void handleRemoval() {
144         if (getDssBridgeHandler() != null) {
145             this.dssBridgeHandler.childThingRemoved(dSID);
146         }
147         updateStatus(ThingStatus.REMOVED);
148     }
149
150     @Override
151     public void thingUpdated(Thing thing) {
152         this.thing = thing;
153         if (device == null) {
154             initialize();
155         }
156     }
157
158     @Override
159     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
160         if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
161             if (dSID != null) {
162                 if (getDssBridgeHandler() != null) {
163                     if (device == null) {
164                         updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
165                                 "waiting for listener registration");
166                         dssBridgeHandler.registerDeviceStatusListener(this);
167                     } else {
168                         updateStatus(ThingStatus.ONLINE);
169                     }
170                 } else {
171                     updateStatus(ThingStatus.OFFLINE);
172                 }
173             } else {
174                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No dSID is set!");
175             }
176         }
177         if (bridgeStatusInfo.getStatus().equals(ThingStatus.OFFLINE)) {
178             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
179         }
180         logger.debug("Set status to {}", getThing().getStatusInfo());
181     }
182
183     @Override
184     public void handleCommand(ChannelUID channelUID, Command command) {
185         BridgeHandler dssBridgeHandler = getDssBridgeHandler();
186         if (dssBridgeHandler == null) {
187             logger.debug("BridgeHandler not found. Cannot handle command without bridge.");
188             return;
189         }
190
191         if (device == null) {
192             logger.debug(
193                     "Device not known on StructureManager or DeviceStatusListener is not registerd. Cannot handle command.");
194             return;
195         }
196
197         if (command instanceof RefreshType) {
198             try {
199                 SensorEnum sensorType = SensorEnum.valueOf(channelUID.getId());
200                 dssBridgeHandler.sendComandsToDSS(device, new DeviceStateUpdateImpl(sensorType, 1));
201             } catch (IllegalArgumentException e) {
202                 dssBridgeHandler.sendComandsToDSS(device,
203                         new DeviceStateUpdateImpl(DeviceStateUpdate.REFRESH_OUTPUT, 0));
204             }
205         } else if (!device.isShade()) {
206             if (DsChannelTypeProvider.isOutputChannel(channelUID.getId())) {
207                 if (command instanceof PercentType percentCommand) {
208                     device.setOutputValue(
209                             (short) fromPercentToValue(percentCommand.intValue(), device.getMaxOutputValue()));
210                 } else if (command instanceof OnOffType) {
211                     if (OnOffType.ON.equals(command)) {
212                         device.setIsOn(true);
213                     } else {
214                         device.setIsOn(false);
215                     }
216                 } else if (command instanceof IncreaseDecreaseType) {
217                     if (IncreaseDecreaseType.INCREASE.equals(command)) {
218                         device.increase();
219                     } else {
220                         device.decrease();
221                     }
222                 } else if (command instanceof StringType stringCommand) {
223                     device.setOutputValue(Short.parseShort(stringCommand.toString()));
224                 }
225             } else {
226                 logger.debug("Command sent to an unknown channel id: {}", channelUID);
227             }
228         } else {
229             if (channelUID.getId().contains(DsChannelTypeProvider.ANGLE)) {
230                 if (command instanceof PercentType percentCommand) {
231                     device.setAnglePosition(
232                             (short) fromPercentToValue(percentCommand.intValue(), device.getMaxSlatAngle()));
233                 } else if (command instanceof OnOffType) {
234                     if (OnOffType.ON.equals(command)) {
235                         device.setAnglePosition(device.getMaxSlatAngle());
236                     } else {
237                         device.setAnglePosition(device.getMinSlatAngle());
238                     }
239                 } else if (command instanceof IncreaseDecreaseType) {
240                     if (IncreaseDecreaseType.INCREASE.equals(command)) {
241                         device.increaseSlatAngle();
242                     } else {
243                         device.decreaseSlatAngle();
244                     }
245                 }
246             } else if (channelUID.getId().contains(DsChannelTypeProvider.SHADE)) {
247                 if (command instanceof PercentType percentCommand) {
248                     int percent = percentCommand.intValue();
249                     if (!"GR-KL200".equals(device.getHWinfo())) {
250                         percent = 100 - percent;
251                     }
252                     device.setSlatPosition(fromPercentToValue(percent, device.getMaxSlatPosition()));
253                     this.lastComand = command;
254                 } else if (command instanceof StopMoveType) {
255                     if (StopMoveType.MOVE.equals(command)) {
256                         handleCommand(channelUID, this.lastComand);
257                     } else {
258                         dssBridgeHandler.stopOutputValue(device);
259                     }
260                 } else if (command instanceof UpDownType) {
261                     if (UpDownType.UP.equals(command)) {
262                         device.setIsOpen(true);
263                         this.lastComand = command;
264                     } else {
265                         device.setIsOpen(false);
266                         this.lastComand = command;
267                     }
268                 }
269             } else {
270                 logger.debug("Command sent to an unknown channel id: {}", channelUID);
271             }
272         }
273     }
274
275     private int fromPercentToValue(int percent, int max) {
276         if (percent < 0 || percent == 0) {
277             return 0;
278         }
279         if (max < 0 || max == 0) {
280             return 0;
281         }
282         return (int) (max * ((float) percent / 100));
283     }
284
285     private synchronized BridgeHandler getDssBridgeHandler() {
286         if (this.dssBridgeHandler == null) {
287             Bridge bridge = getBridge();
288             if (bridge == null) {
289                 logger.debug("Bride cannot be found");
290                 return null;
291             }
292             ThingHandler handler = bridge.getHandler();
293
294             if (handler instanceof BridgeHandler bridgeHandler) {
295                 dssBridgeHandler = bridgeHandler;
296             } else {
297                 return null;
298             }
299         }
300         return dssBridgeHandler;
301     }
302
303     private boolean sensorChannelsLoaded() {
304         return loadedSensorChannels != null && !loadedSensorChannels.isEmpty();
305     }
306
307     @Override
308     public synchronized void onDeviceStateChanged(DeviceStateUpdate deviceStateUpdate) {
309         if (device != null) {
310             if (deviceStateUpdate != null) {
311                 if (sensorChannelsLoaded()) {
312                     if (deviceStateUpdate.isSensorUpdateType()) {
313                         updateState(getSensorChannelID(deviceStateUpdate.getTypeAsSensorEnum()),
314                                 new DecimalType(deviceStateUpdate.getValueAsFloat()));
315                         logger.debug("Update state");
316                         return;
317                     }
318                     if (deviceStateUpdate.isBinarayInputType()) {
319                         if (deviceStateUpdate.getValueAsShort() == 1) {
320                             updateState(getBinaryInputChannelID(deviceStateUpdate.getTypeAsDeviceBinarayInputEnum()),
321                                     OnOffType.ON);
322                         } else {
323                             updateState(getBinaryInputChannelID(deviceStateUpdate.getTypeAsDeviceBinarayInputEnum()),
324                                     OnOffType.OFF);
325                         }
326                     }
327                 }
328                 if (!device.isShade()) {
329                     if (currentChannel != null) {
330                         switch (deviceStateUpdate.getType()) {
331                             case DeviceStateUpdate.OUTPUT_DECREASE:
332                             case DeviceStateUpdate.OUTPUT_INCREASE:
333                             case DeviceStateUpdate.OUTPUT:
334                                 if (currentChannel.contains(DsChannelTypeProvider.DIMMER)) {
335                                     if (deviceStateUpdate.getValueAsInteger() > 0) {
336                                         updateState(currentChannel, new PercentType(fromValueToPercent(
337                                                 deviceStateUpdate.getValueAsInteger(), device.getMaxOutputValue())));
338                                     } else {
339                                         updateState(currentChannel, OnOffType.OFF);
340                                     }
341                                 } else if (currentChannel.contains(DsChannelTypeProvider.STAGE)) {
342                                     if (currentChannel.contains(TWO_STAGE_SWITCH_IDENTICATOR)) {
343                                         updateState(currentChannel,
344                                                 new StringType(convertStageValue((short) 2, device.getOutputValue())));
345                                     } else {
346                                         updateState(currentChannel,
347                                                 new StringType(convertStageValue((short) 3, device.getOutputValue())));
348                                     }
349                                 }
350                                 break;
351                             case DeviceStateUpdate.ON_OFF:
352                                 if (currentChannel.contains(DsChannelTypeProvider.STAGE)) {
353                                     onDeviceStateChanged(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT,
354                                             device.getOutputValue()));
355                                 }
356                                 if (deviceStateUpdate.getValueAsInteger() > 0) {
357                                     updateState(currentChannel, OnOffType.ON);
358                                 } else {
359                                     updateState(currentChannel, OnOffType.OFF);
360                                 }
361                                 break;
362                             default:
363                                 return;
364                         }
365                     }
366                 } else {
367                     int percent = 0;
368                     switch (deviceStateUpdate.getType()) {
369                         case DeviceStateUpdate.SLAT_DECREASE:
370                         case DeviceStateUpdate.SLAT_INCREASE:
371                         case DeviceStateUpdate.SLATPOSITION:
372                             percent = fromValueToPercent(deviceStateUpdate.getValueAsInteger(),
373                                     device.getMaxSlatPosition());
374                             break;
375                         case DeviceStateUpdate.OPEN_CLOSE:
376                             if (deviceStateUpdate.getValueAsInteger() > 0) {
377                                 percent = 100;
378                             }
379                             break;
380                         case DeviceStateUpdate.OPEN_CLOSE_ANGLE:
381                             if (device.isBlind() && currentChannel != null) {
382                                 if (deviceStateUpdate.getValueAsInteger() > 0) {
383                                     updateState(currentChannel, PercentType.HUNDRED);
384                                 } else {
385                                     updateState(currentChannel, PercentType.ZERO);
386                                 }
387                             }
388                             return;
389                         case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
390                         case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
391                         case DeviceStateUpdate.SLAT_ANGLE:
392                             if (device.isBlind() && currentChannel != null) {
393                                 updateState(currentChannel,
394                                         new PercentType(fromValueToPercent(deviceStateUpdate.getValueAsInteger(),
395                                                 device.getMaxSlatAngle())));
396                             }
397                             return;
398                         default:
399                             return;
400                     }
401                     if (!"GR-KL210".equals(device.getHWinfo())) {
402                         percent = 100 - percent;
403                     }
404                     updateState(DsChannelTypeProvider.SHADE, new PercentType(percent));
405                 }
406                 logger.debug("Update state");
407             }
408         }
409     }
410
411     private int fromValueToPercent(int value, int max) {
412         if (value <= 0 || max <= 0) {
413             return 0;
414         }
415         int percentValue = new BigDecimal(value * ((float) 100 / max)).setScale(0, RoundingMode.HALF_UP).intValue();
416         return percentValue < 0 ? 0 : percentValue > 100 ? 100 : percentValue;
417     }
418
419     @Override
420     public synchronized void onDeviceRemoved(GeneralDeviceInformation device) {
421         if (device instanceof Device dev) {
422             this.device = dev;
423             if (this.getThing().getStatus().equals(ThingStatus.ONLINE)) {
424                 if (!dev.isPresent()) {
425                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
426                             "Device is not present in the digitalSTROM-System.");
427                 } else {
428                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
429                             "Device is not avaible in the digitalSTROM-System.");
430                 }
431
432             }
433             logger.debug("Set status to {}", getThing().getStatus());
434         }
435     }
436
437     @Override
438     public synchronized void onDeviceAdded(GeneralDeviceInformation device) {
439         if (device instanceof Device dev) {
440             this.device = dev;
441             if (this.device.isPresent()) {
442                 ThingStatusInfo statusInfo = this.dssBridgeHandler.getThing().getStatusInfo();
443                 updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
444                 logger.debug("Set status to {}", getThing().getStatus());
445
446                 // load scene configurations persistently into the thing
447                 for (Short i : this.device.getSavedScenes()) {
448                     onSceneConfigAdded(i);
449                 }
450                 logger.debug("Load saved scene specification into device");
451                 this.device.saveConfigSceneSpecificationIntoDevice(getThing().getProperties());
452
453                 checkDeviceInfoProperties(this.device);
454                 // load sensor priorities into the device and load sensor channels of the thing
455                 if (!this.device.isShade()) {
456                     loadSensorChannels();
457                     // check and load output channel of the thing
458                     checkOutputChannel();
459                 } else if (this.device.isBlind()) {
460                     // load channel for set the angle of jalousie devices
461                     ApplicationGroup.Color color = dev.getFunctionalColorGroup() != null
462                             ? dev.getFunctionalColorGroup().getColor()
463                             : null;
464                     String channelTypeID = DsChannelTypeProvider.getOutputChannelTypeID(color, dev.getOutputMode(),
465                             dev.getOutputChannels());
466                     loadOutputChannel(new ChannelTypeUID(BINDING_ID, channelTypeID),
467                             DsChannelTypeProvider.getItemType(channelTypeID));
468                 }
469
470                 // load first channel values
471                 onDeviceStateInitial(this.device);
472                 return;
473             }
474         }
475         onDeviceRemoved(device);
476     }
477
478     /**
479      * Updates device info properties.
480      *
481      * @param device (must not be null)
482      */
483     private void checkDeviceInfoProperties(Device device) {
484         boolean propertiesChanged = false;
485         Map<String, String> properties = editProperties();
486         // check device info
487         if (device.getName() != null) {
488             properties.put(DigitalSTROMBindingConstants.DEVICE_NAME, device.getName());
489             propertiesChanged = true;
490         }
491         if (device.getDSUID() != null) {
492             properties.put(DigitalSTROMBindingConstants.DEVICE_UID, device.getDSUID());
493             propertiesChanged = true;
494         }
495         if (device.getHWinfo() != null) {
496             properties.put(DigitalSTROMBindingConstants.DEVICE_HW_INFO, device.getHWinfo());
497             propertiesChanged = true;
498         }
499         if (device.getZoneId() != -1) {
500             properties.put(DigitalSTROMBindingConstants.DEVICE_ZONE_ID, device.getZoneId() + "");
501             propertiesChanged = true;
502         }
503         if (device.getGroups() != null) {
504             properties.put(DigitalSTROMBindingConstants.DEVICE_GROUPS, device.getGroups().toString());
505             propertiesChanged = true;
506         }
507         if (device.getOutputMode() != null) {
508             properties.put(DigitalSTROMBindingConstants.DEVICE_OUTPUT_MODE, device.getOutputMode().toString());
509             propertiesChanged = true;
510         }
511         if (device.getFunctionalColorGroup() != null) {
512             properties.put(DigitalSTROMBindingConstants.DEVICE_FUNCTIONAL_COLOR_GROUP,
513                     device.getFunctionalColorGroup().toString());
514             propertiesChanged = true;
515         }
516         if (device.getMeterDSID() != null) {
517             properties.put(DigitalSTROMBindingConstants.DEVICE_METER_ID, device.getMeterDSID().toString());
518             propertiesChanged = true;
519         }
520         if (!device.getBinaryInputs().isEmpty()) {
521             properties.put(DigitalSTROMBindingConstants.DEVICE_BINARAY_INPUTS, getBinarayInputList());
522             propertiesChanged = true;
523         }
524         if (propertiesChanged) {
525             super.updateProperties(properties);
526             propertiesChanged = false;
527         }
528     }
529
530     private String getBinarayInputList() {
531         List<String> binarayInputs = new ArrayList<>(device.getBinaryInputs().size());
532         for (DeviceBinaryInput binInput : device.getBinaryInputs()) {
533             DeviceBinarayInputEnum devBinInp = DeviceBinarayInputEnum.getdeviceBinarayInput(binInput.getInputType());
534             if (devBinInp != null) {
535                 binarayInputs.add(devBinInp.toString().toLowerCase());
536             }
537         }
538         return binarayInputs.toString();
539     }
540
541     private void loadSensorChannels() {
542         if (device != null && device.isPresent()) {
543             // load sensor priorities into the device
544             boolean configChanged = false;
545             Configuration config = getThing().getConfiguration();
546             logger.debug("Add sensor priorities to the device");
547
548             String activePowerPrio = Config.REFRESH_PRIORITY_NEVER;
549             if (config.get(DigitalSTROMBindingConstants.ACTIVE_POWER_REFRESH_PRIORITY) != null) {
550                 activePowerPrio = config.get(DigitalSTROMBindingConstants.ACTIVE_POWER_REFRESH_PRIORITY).toString();
551             } else {
552                 config.put(DigitalSTROMBindingConstants.ACTIVE_POWER_REFRESH_PRIORITY, Config.REFRESH_PRIORITY_NEVER);
553                 configChanged = true;
554             }
555             // By devices with output mode WIPE the active power always will be read out to check, if the device is not
556             // in standby any more.
557             if (OutputModeEnum.WIPE.equals(device.getOutputMode())
558                     && activePowerPrio.equals(Config.REFRESH_PRIORITY_NEVER)) {
559                 config.put(DigitalSTROMBindingConstants.ACTIVE_POWER_REFRESH_PRIORITY, Config.REFRESH_PRIORITY_LOW);
560                 configChanged = true;
561             }
562
563             String outputCurrentPrio = Config.REFRESH_PRIORITY_NEVER;
564             if (config.get(DigitalSTROMBindingConstants.OUTPUT_CURRENT_REFRESH_PRIORITY) != null) {
565                 outputCurrentPrio = config.get(DigitalSTROMBindingConstants.OUTPUT_CURRENT_REFRESH_PRIORITY).toString();
566             } else {
567                 config.put(DigitalSTROMBindingConstants.OUTPUT_CURRENT_REFRESH_PRIORITY, Config.REFRESH_PRIORITY_NEVER);
568                 configChanged = true;
569             }
570
571             String electricMeterPrio = Config.REFRESH_PRIORITY_NEVER;
572             if (config.get(DigitalSTROMBindingConstants.ELECTRIC_METER_REFRESH_PRIORITY) != null) {
573                 electricMeterPrio = config.get(DigitalSTROMBindingConstants.ELECTRIC_METER_REFRESH_PRIORITY).toString();
574             } else {
575                 config.put(DigitalSTROMBindingConstants.ELECTRIC_METER_REFRESH_PRIORITY, Config.REFRESH_PRIORITY_NEVER);
576                 configChanged = true;
577             }
578
579             if (configChanged) {
580                 super.updateConfiguration(config);
581                 configChanged = false;
582             }
583
584             device.setSensorDataRefreshPriority(activePowerPrio, electricMeterPrio, outputCurrentPrio);
585             logger.debug(
586                     "add sensor prioritys: active power = {}, output current = {}, electric meter = {} to device with id {}",
587                     activePowerPrio, outputCurrentPrio, electricMeterPrio, device.getDSID());
588
589             // check and load sensor channels of the thing
590             checkSensorChannel();
591         }
592     }
593
594     private boolean addLoadedSensorChannel(String sensorChannelType) {
595         if (loadedSensorChannels == null) {
596             loadedSensorChannels = new LinkedList<>();
597         }
598         if (!loadedSensorChannels.contains(sensorChannelType)) {
599             return loadedSensorChannels.add(sensorChannelType);
600         }
601         return false;
602     }
603
604     private boolean removeLoadedSensorChannel(String sensorChannelType) {
605         if (loadedSensorChannels == null) {
606             return false;
607         }
608         return loadedSensorChannels.remove(sensorChannelType);
609     }
610
611     private boolean isSensorChannelLoaded(String sensorChannelType) {
612         if (loadedSensorChannels == null) {
613             return false;
614         }
615         return loadedSensorChannels.contains(sensorChannelType);
616     }
617
618     private void checkSensorChannel() {
619         List<Channel> channelList = new LinkedList<>(this.getThing().getChannels());
620
621         boolean channelListChanged = false;
622
623         // if sensor channels with priority never are loaded delete these channels
624         if (!channelList.isEmpty()) {
625             Iterator<Channel> channelInter = channelList.iterator();
626             while (channelInter.hasNext()) {
627                 Channel channel = channelInter.next();
628                 String channelID = channel.getUID().getId();
629                 if (channelID.startsWith(DsChannelTypeProvider.BINARY_INPUT_PRE)) {
630                     DeviceBinarayInputEnum devBinInput = getBinaryInput(channelID);
631                     if (device.getBinaryInput(devBinInput) != null) {
632                         addLoadedSensorChannel(channelID);
633                     } else {
634                         logger.debug("remove {} binary input channel", channelID);
635                         channelInter.remove();
636                         channelListChanged = removeLoadedSensorChannel(channelID);
637                     }
638                 } else {
639                     SensorEnum sensorType = getSensorEnum(channelID);
640                     if (sensorType != null) {
641                         if (SensorEnum.isPowerSensor(sensorType)) {
642                             if (device.checkPowerSensorRefreshPriorityNever(sensorType)) {
643                                 logger.debug("remove {} sensor channel", channelID);
644                                 channelInter.remove();
645                                 channelListChanged = removeLoadedSensorChannel(channelID);
646                             } else {
647                                 addLoadedSensorChannel(channelID);
648                             }
649                         } else {
650                             if (device.supportsSensorType(sensorType)) {
651                                 addLoadedSensorChannel(channelID);
652                             } else {
653                                 logger.debug("remove {} sensor channel", channelID);
654                                 channelInter.remove();
655                                 removeLoadedSensorChannel(channelID);
656                                 channelListChanged = true;
657                             }
658                         }
659                     }
660                 }
661             }
662         }
663         for (SensorEnum sensorType : device.getPowerSensorTypes()) {
664             if (!device.checkPowerSensorRefreshPriorityNever(sensorType)
665                     && !isSensorChannelLoaded(getSensorChannelID(sensorType))) {
666                 logger.debug("create {} sensor channel", sensorType.toString());
667                 channelList.add(getSensorChannel(sensorType));
668                 channelListChanged = addLoadedSensorChannel(getSensorChannelID(sensorType));
669             }
670         }
671         if (device.hasClimateSensors()) {
672             for (SensorEnum sensorType : device.getClimateSensorTypes()) {
673                 if (!isSensorChannelLoaded(getSensorChannelID(sensorType))) {
674                     logger.debug("create {} sensor channel", sensorType.toString());
675                     channelList.add(getSensorChannel(sensorType));
676                     channelListChanged = addLoadedSensorChannel(getSensorChannelID(sensorType));
677                 }
678             }
679         }
680         if (device.isBinaryInputDevice()) {
681             for (DeviceBinaryInput binInput : device.getBinaryInputs()) {
682                 DeviceBinarayInputEnum binInputType = DeviceBinarayInputEnum
683                         .getdeviceBinarayInput(binInput.getInputType());
684                 if (binInputType != null && !isSensorChannelLoaded(getBinaryInputChannelID(binInputType))) {
685                     logger.debug("create {} sensor channel", binInputType.toString());
686                     channelList.add(getBinaryChannel(binInputType));
687                     channelListChanged = addLoadedSensorChannel(getBinaryInputChannelID(binInputType));
688                 }
689             }
690         }
691
692         if (channelListChanged) {
693             logger.debug("load new channel list");
694             ThingBuilder thingBuilder = editThing();
695             thingBuilder.withChannels(channelList);
696             updateThing(thingBuilder.build());
697         }
698     }
699
700     private Channel getSensorChannel(SensorEnum sensorType) {
701         return ChannelBuilder.create(getSensorChannelUID(sensorType), "Number")
702                 .withType(DsChannelTypeProvider.getSensorChannelUID(sensorType)).build();
703     }
704
705     private Channel getBinaryChannel(DeviceBinarayInputEnum binaryInputType) {
706         return ChannelBuilder.create(getBinaryInputChannelUID(binaryInputType), "Switch")
707                 .withType(DsChannelTypeProvider.getBinaryInputChannelUID(binaryInputType)).build();
708     }
709
710     private void checkOutputChannel() {
711         if (device == null) {
712             logger.debug("Can not load a channel without a device!");
713             return;
714         }
715         // if the device have no output channel or it is disabled all output channels will be deleted
716         if (!device.isDeviceWithOutput()) {
717             loadOutputChannel(null, null);
718         }
719         ApplicationGroup.Color color = device.getFunctionalColorGroup() != null
720                 ? device.getFunctionalColorGroup().getColor()
721                 : null;
722         String channelTypeID = DsChannelTypeProvider.getOutputChannelTypeID(color, device.getOutputMode(),
723                 device.getOutputChannels());
724         logger.debug("load channel: typeID={}, itemType={}",
725                 DsChannelTypeProvider.getOutputChannelTypeID(device.getFunctionalColorGroup().getColor(),
726                         device.getOutputMode(), device.getOutputChannels()),
727                 DsChannelTypeProvider.getItemType(channelTypeID));
728         if (channelTypeID != null && (currentChannel == null || !currentChannel.equals(channelTypeID))) {
729             loadOutputChannel(new ChannelTypeUID(BINDING_ID, channelTypeID),
730                     DsChannelTypeProvider.getItemType(channelTypeID));
731         }
732     }
733
734     private void loadOutputChannel(ChannelTypeUID channelTypeUID, String acceptedItemType) {
735         if (channelTypeUID == null || acceptedItemType == null) {
736             return;
737         }
738         currentChannel = channelTypeUID.getId();
739
740         List<Channel> channelList = new LinkedList<>(this.getThing().getChannels());
741         boolean channelIsAlreadyLoaded = false;
742         boolean channelListChanged = false;
743
744         if (!channelList.isEmpty()) {
745             Iterator<Channel> channelInter = channelList.iterator();
746             while (channelInter.hasNext()) {
747                 Channel channel = channelInter.next();
748                 if (DsChannelTypeProvider.isOutputChannel(channel.getUID().getId())) {
749                     if (!channel.getUID().getId().equals(currentChannel)
750                             && !(device.isShade() && channel.getUID().getId().equals(DsChannelTypeProvider.SHADE))) {
751                         channelInter.remove();
752                         channelListChanged = true;
753                     } else {
754                         if (!channel.getUID().getId().equals(DsChannelTypeProvider.SHADE)) {
755                             channelIsAlreadyLoaded = true;
756                         }
757                     }
758                 }
759             }
760         }
761
762         if (!channelIsAlreadyLoaded && currentChannel != null) {
763             Channel channel = ChannelBuilder
764                     .create(new ChannelUID(this.getThing().getUID(), channelTypeUID.getId()), acceptedItemType)
765                     .withType(channelTypeUID).build();
766             channelList.add(channel);
767             channelListChanged = true;
768         }
769
770         if (channelListChanged) {
771             ThingBuilder thingBuilder = editThing();
772             thingBuilder.withChannels(channelList);
773             updateThing(thingBuilder.build());
774             logger.debug("load channel: {} with item: {}", channelTypeUID.getAsString(), acceptedItemType);
775         }
776     }
777
778     private ChannelUID getSensorChannelUID(SensorEnum sensorType) {
779         return new ChannelUID(getThing().getUID(), getSensorChannelID(sensorType));
780     }
781
782     private String getSensorChannelID(SensorEnum sensorType) {
783         return sensorType.toString().toLowerCase();
784     }
785
786     private ChannelUID getBinaryInputChannelUID(DeviceBinarayInputEnum binaryInputType) {
787         return new ChannelUID(getThing().getUID(), getBinaryInputChannelID(binaryInputType));
788     }
789
790     private String getBinaryInputChannelID(DeviceBinarayInputEnum binaryInputType) {
791         return DsChannelTypeProvider.BINARY_INPUT_PRE + binaryInputType.toString().toLowerCase();
792     }
793
794     private DeviceBinarayInputEnum getBinaryInput(String channelID) {
795         try {
796             return DeviceBinarayInputEnum
797                     .valueOf(channelID.replace(DsChannelTypeProvider.BINARY_INPUT_PRE, "").toUpperCase());
798         } catch (IllegalArgumentException e) {
799             return null;
800         }
801     }
802
803     private SensorEnum getSensorEnum(String channelID) {
804         try {
805             return SensorEnum.valueOf(channelID.toUpperCase());
806         } catch (IllegalArgumentException e) {
807             return null;
808         }
809     }
810
811     @Override
812     public void channelLinked(ChannelUID channelUID) {
813         if (device != null) {
814             SensorEnum sensorType = getSensorEnum(channelUID.getId());
815             if (sensorType != null) {
816                 Float val = device.getFloatSensorValue(sensorType);
817                 if (val != null) {
818                     updateState(channelUID, new DecimalType(val));
819                 }
820             }
821             Short val = device.getBinaryInputState(getBinaryInput(channelUID.getId()));
822             if (val != null) {
823                 if (val == 1) {
824                     updateState(channelUID, OnOffType.ON);
825                 } else {
826                     updateState(channelUID, OnOffType.OFF);
827                 }
828             }
829
830             if (channelUID.getId().contains(DsChannelTypeProvider.DIMMER)) {
831                 if (device.isOn()) {
832                     updateState(channelUID,
833                             new PercentType(fromValueToPercent(device.getOutputValue(), device.getMaxOutputValue())));
834                 } else {
835                     updateState(channelUID, new PercentType(0));
836                 }
837                 return;
838             }
839             if (channelUID.getId().contains(DsChannelTypeProvider.SWITCH)) {
840                 if (device.isOn()) {
841                     updateState(channelUID, OnOffType.ON);
842                 } else {
843                     updateState(channelUID, OnOffType.OFF);
844                 }
845                 return;
846             }
847             if (channelUID.getId().contains(DsChannelTypeProvider.SHADE)) {
848                 updateState(channelUID,
849                         new PercentType(fromValueToPercent(device.getSlatPosition(), device.getMaxSlatPosition())));
850                 return;
851             }
852             if (channelUID.getId().contains(DsChannelTypeProvider.ANGLE)) {
853                 updateState(channelUID,
854                         new PercentType(fromValueToPercent(device.getAnglePosition(), device.getMaxSlatAngle())));
855                 return;
856             }
857             if (channelUID.getId().contains(DsChannelTypeProvider.STAGE)) {
858                 if (channelUID.getId().contains(TWO_STAGE_SWITCH_IDENTICATOR)) {
859                     updateState(channelUID, new StringType(convertStageValue((short) 2, device.getOutputValue())));
860                     return;
861                 }
862                 if (channelUID.getId().contains(THREE_STAGE_SWITCH_IDENTICATOR)) {
863                     updateState(channelUID, new StringType(convertStageValue((short) 3, device.getOutputValue())));
864                     return;
865                 }
866             }
867         }
868     }
869
870     private String convertStageValue(short stage, short value) {
871         switch (stage) {
872             case 2:
873                 if (value < 85) {
874                     return OPTION_COMBINED_BOTH_OFF;
875                 } else if (value >= 85 && value < 170) {
876                     return OPTION_COMBINED_FIRST_ON;
877                 } else if (value >= 170 && value <= 255) {
878                     return OPTION_COMBINED_BOTH_ON;
879                 }
880             case 3:
881                 if (value < 64) {
882                     return OPTION_COMBINED_BOTH_OFF;
883                 } else if (value >= 64 && value < 128) {
884                     return OPTION_COMBINED_FIRST_ON;
885                 } else if (value >= 128 && value < 192) {
886                     return OPTION_COMBINED_SECOND_ON;
887                 } else if (value >= 192 && value <= 255) {
888                     return OPTION_COMBINED_BOTH_ON;
889                 }
890         }
891         return null;
892     }
893
894     private void onDeviceStateInitial(Device device) {
895         if (device != null) {
896             if (currentChannel != null) {
897                 if (isLinked(currentChannel)) {
898                     channelLinked(new ChannelUID(getThing().getUID(), currentChannel));
899                 }
900             }
901             if (!device.isShade()) {
902                 if (loadedSensorChannels != null) {
903                     for (String sensor : loadedSensorChannels) {
904                         Channel channel = getThing().getChannel(sensor);
905                         if (channel != null && isLinked(sensor)) {
906                             channelLinked(channel.getUID());
907                         }
908                     }
909                 }
910             } else {
911                 if (isLinked(DsChannelTypeProvider.SHADE)) {
912                     channelLinked(new ChannelUID(getThing().getUID(), DsChannelTypeProvider.SHADE));
913                 }
914             }
915         }
916     }
917
918     @Override
919     public synchronized void onSceneConfigAdded(short sceneId) {
920         if (device != null) {
921             String saveScene = "";
922             DeviceSceneSpec sceneSpec = device.getSceneConfig(sceneId);
923             if (sceneSpec != null) {
924                 saveScene = sceneSpec.toString();
925             }
926
927             Integer[] sceneValue = device.getSceneOutputValue(sceneId);
928             if (sceneValue[GeneralLibConstance.SCENE_ARRAY_INDEX_VALUE] != -1) {
929                 saveScene = saveScene + ", sceneValue: " + sceneValue[0];
930             }
931             if (sceneValue[GeneralLibConstance.SCENE_ARRAY_INDEX_ANGLE] != -1) {
932                 saveScene = saveScene + ", sceneAngle: " + sceneValue[1];
933             }
934             String key = DigitalSTROMBindingConstants.DEVICE_SCENE + sceneId;
935             if (!saveScene.isEmpty()) {
936                 logger.debug("Save scene configuration: [{}] to thing with UID {}", saveScene, getThing().getUID());
937                 super.updateProperty(key, saveScene);
938                 // persist the new property
939                 // super.updateThing(editThing().build());
940             }
941         }
942     }
943
944     @Override
945     public void onDeviceConfigChanged(ChangeableDeviceConfigEnum whichConfig) {
946         if (whichConfig != null) {
947             switch (whichConfig) {
948                 case DEVICE_NAME:
949                     super.updateProperty(DEVICE_NAME, device.getName());
950                     break;
951                 case METER_DSID:
952                     super.updateProperty(DEVICE_METER_ID, device.getMeterDSID().getValue());
953                     break;
954                 case ZONE_ID:
955                     super.updateProperty(DEVICE_ZONE_ID, device.getZoneId() + "");
956                     break;
957                 case GROUPS:
958                     super.updateProperty(DEVICE_GROUPS, device.getGroups().toString());
959                     break;
960                 case FUNCTIONAL_GROUP:
961                     super.updateProperty(DEVICE_FUNCTIONAL_COLOR_GROUP, device.getFunctionalColorGroup().toString());
962                     checkOutputChannel();
963                     break;
964                 case OUTPUT_MODE:
965                     super.updateProperty(DEVICE_OUTPUT_MODE, device.getOutputMode().toString());
966                     checkOutputChannel();
967                     break;
968                 case BINARY_INPUTS:
969                     super.updateProperty(DEVICE_BINARAY_INPUTS, getBinarayInputList());
970                     checkSensorChannel();
971                     break;
972                 default:
973                     break;
974             }
975         }
976     }
977
978     @Override
979     public String getDeviceStatusListenerID() {
980         return this.dSID;
981     }
982 }