]> git.basschouten.com Git - openhab-addons.git/blob
e3fc306b09957d7e596dcb078148c9ab8ae50d5b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.innogysmarthome.internal.handler;
14
15 import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*;
16
17 import java.time.format.DateTimeFormatter;
18 import java.time.format.FormatStyle;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Set;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.innogysmarthome.internal.client.entity.action.ShutterAction;
26 import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability;
27 import org.openhab.binding.innogysmarthome.internal.client.entity.capability.CapabilityState;
28 import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device;
29 import org.openhab.binding.innogysmarthome.internal.client.entity.event.Event;
30 import org.openhab.binding.innogysmarthome.internal.listener.DeviceStatusListener;
31 import org.openhab.core.library.types.*;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.Channel;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.CommonTriggerEvents;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link InnogyDeviceHandler} is responsible for handling the {@link Device}s and their commands, which are
50  * sent to one of the channels.
51  *
52  * @author Oliver Kuhl - Initial contribution
53  */
54 @NonNullByDefault
55 public class InnogyDeviceHandler extends BaseThingHandler implements DeviceStatusListener {
56
57     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = SUPPORTED_DEVICE_THING_TYPES;
58
59     private static final String DEBUG = "DEBUG";
60     private static final String LONG_PRESS = "LongPress";
61     private static final String SHORT_PRESS = "ShortPress";
62
63     private final Logger logger = LoggerFactory.getLogger(InnogyDeviceHandler.class);
64     private final Object lock = new Object();
65
66     private String deviceId = "";
67     private @Nullable InnogyBridgeHandler bridgeHandler;
68
69     /**
70      * Constructs a new {@link InnogyDeviceHandler} for the given {@link Thing}.
71      *
72      * @param thing
73      */
74     public InnogyDeviceHandler(final Thing thing) {
75         super(thing);
76     }
77
78     @Override
79     public void handleCommand(final ChannelUID channelUID, final Command command) {
80         logger.debug("handleCommand called for channel '{}' of type '{}' with command '{}'", channelUID,
81                 getThing().getThingTypeUID().getId(), command);
82         @Nullable
83         final InnogyBridgeHandler innogyBridgeHandler = getInnogyBridgeHandler();
84         if (innogyBridgeHandler == null) {
85             logger.warn("BridgeHandler not found. Cannot handle command without bridge.");
86             return;
87         }
88         if (!ThingStatus.ONLINE.equals(innogyBridgeHandler.getThing().getStatus())) {
89             logger.debug("Cannot handle command - bridge is not online. Command ignored.");
90             return;
91         }
92
93         if (command instanceof RefreshType) {
94             @Nullable
95             final Device device = innogyBridgeHandler.getDeviceById(deviceId);
96             if (device != null) {
97                 onDeviceStateChanged(device);
98             }
99             return;
100         }
101
102         // SWITCH
103         if (CHANNEL_SWITCH.equals(channelUID.getId())) {
104             // DEBUGGING HELPER
105             // ----------------
106             @Nullable
107             final Device device = innogyBridgeHandler.getDeviceById(deviceId);
108             if (device != null && DEBUG.equals(device.getConfig().getName())) {
109                 logger.debug("DEBUG SWITCH ACTIVATED!");
110                 if (OnOffType.ON.equals(command)) {
111                     innogyBridgeHandler.onEvent(
112                             "{\"sequenceNumber\": -1,\"type\": \"MessageCreated\",\"desc\": \"/desc/event/MessageCreated\",\"namespace\": \"core.RWE\",\"timestamp\": \"2019-07-07T18:41:47.2970000Z\",\"source\": \"/desc/device/SHC.RWE/1.0\",\"data\": {\"id\": \"6e5ce2290cd247208f95a5b53736958b\",\"type\": \"DeviceLowBattery\",\"read\": false,\"class\": \"Alert\",\"timestamp\": \"2019-07-07T18:41:47.232Z\",\"devices\": [\"/device/fe51785319854f36a621d0b4f8ea0e25\"],\"properties\": {\"deviceName\": \"Heizkörperthermostat\",\"serialNumber\": \"914110165056\",\"locationName\": \"Bad\"},\"namespace\": \"core.RWE\"}}");
113                 } else {
114                     innogyBridgeHandler.onEvent(
115                             "{\"sequenceNumber\": -1,\"type\": \"MessageDeleted\",\"desc\": \"/desc/event/MessageDeleted\",\"namespace\": \"core.RWE\",\"timestamp\": \"2019-07-07T19:15:39.2100000Z\",\"data\": { \"id\": \"6e5ce2290cd247208f95a5b53736958b\" }}");
116                 }
117                 return;
118             }
119             // ----------------
120             if (command instanceof OnOffType) {
121                 innogyBridgeHandler.commandSwitchDevice(deviceId, OnOffType.ON.equals(command));
122             }
123
124             // DIMMER
125         } else if (CHANNEL_DIMMER.equals(channelUID.getId())) {
126             if (command instanceof DecimalType) {
127                 final DecimalType dimLevel = (DecimalType) command;
128                 innogyBridgeHandler.commandSetDimmLevel(deviceId, dimLevel.intValue());
129             } else if (command instanceof OnOffType) {
130                 if (OnOffType.ON.equals(command)) {
131                     innogyBridgeHandler.commandSetDimmLevel(deviceId, 100);
132                 } else {
133                     innogyBridgeHandler.commandSetDimmLevel(deviceId, 0);
134                 }
135             }
136
137             // ROLLERSHUTTER
138         } else if (CHANNEL_ROLLERSHUTTER.equals(channelUID.getId())) {
139             if (command instanceof DecimalType) {
140                 final DecimalType rollerShutterLevel = (DecimalType) command;
141                 innogyBridgeHandler.commandSetRollerShutterLevel(deviceId,
142                         invertValueIfConfigured(CHANNEL_ROLLERSHUTTER, rollerShutterLevel.intValue()));
143             } else if (command instanceof OnOffType) {
144                 if (OnOffType.ON.equals(command)) {
145                     innogyBridgeHandler.commandSetRollerShutterStop(deviceId, ShutterAction.ShutterActions.DOWN);
146                 } else {
147                     innogyBridgeHandler.commandSetRollerShutterStop(deviceId, ShutterAction.ShutterActions.UP);
148                 }
149             } else if (command instanceof UpDownType) {
150                 if (UpDownType.DOWN.equals(command)) {
151                     innogyBridgeHandler.commandSetRollerShutterStop(deviceId, ShutterAction.ShutterActions.DOWN);
152                 } else {
153                     innogyBridgeHandler.commandSetRollerShutterStop(deviceId, ShutterAction.ShutterActions.UP);
154                 }
155             } else if (command instanceof StopMoveType) {
156                 if (StopMoveType.STOP.equals(command)) {
157                     innogyBridgeHandler.commandSetRollerShutterStop(deviceId, ShutterAction.ShutterActions.STOP);
158                 }
159             }
160
161             // SET_TEMPERATURE
162         } else if (CHANNEL_SET_TEMPERATURE.equals(channelUID.getId())) {
163             if (command instanceof DecimalType) {
164                 final DecimalType pointTemperature = (DecimalType) command;
165                 innogyBridgeHandler.commandUpdatePointTemperature(deviceId, pointTemperature.doubleValue());
166             }
167
168             // OPERATION_MODE
169         } else if (CHANNEL_OPERATION_MODE.equals(channelUID.getId())) {
170             if (command instanceof StringType) {
171                 final String autoModeCommand = command.toString();
172
173                 if (CapabilityState.STATE_VALUE_OPERATION_MODE_AUTO.equals(autoModeCommand)) {
174                     innogyBridgeHandler.commandSetOperationMode(deviceId, true);
175                 } else if (CapabilityState.STATE_VALUE_OPERATION_MODE_MANUAL.equals(autoModeCommand)) {
176                     innogyBridgeHandler.commandSetOperationMode(deviceId, false);
177                 } else {
178                     logger.warn("Could not set operationmode. Invalid value '{}'! Only '{}' or '{}' allowed.",
179                             autoModeCommand, CapabilityState.STATE_VALUE_OPERATION_MODE_AUTO,
180                             CapabilityState.STATE_VALUE_OPERATION_MODE_MANUAL);
181                 }
182             }
183
184             // ALARM
185         } else if (CHANNEL_ALARM.equals(channelUID.getId())) {
186             if (command instanceof OnOffType) {
187                 innogyBridgeHandler.commandSwitchAlarm(deviceId, OnOffType.ON.equals(command));
188             }
189         } else {
190             logger.debug("UNSUPPORTED channel {} for device {}.", channelUID.getId(), deviceId);
191         }
192     }
193
194     @Override
195     public void initialize() {
196         logger.debug("Initializing innogy SmartHome device handler.");
197         initializeThing(getBridge() == null ? null : getBridge().getStatus());
198     }
199
200     @Override
201     public void dispose() {
202         if (bridgeHandler != null) {
203             bridgeHandler.unregisterDeviceStatusListener(this);
204         }
205     }
206
207     @Override
208     public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
209         logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
210         initializeThing(bridgeStatusInfo.getStatus());
211     }
212
213     /**
214      * Initializes the {@link Thing} corresponding to the given status of the bridge.
215      *
216      * @param bridgeStatus
217      */
218     private void initializeThing(@Nullable final ThingStatus bridgeStatus) {
219         logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
220         final String configDeviceId = (String) getConfig().get(PROPERTY_ID);
221         if (configDeviceId != null) {
222             deviceId = configDeviceId;
223             // note: this call implicitly registers our handler as a listener on
224             // the bridge
225             if (getInnogyBridgeHandler() != null) {
226                 if (bridgeStatus == ThingStatus.ONLINE) {
227                     if (initializeProperties()) {
228                         updateStatus(ThingStatus.ONLINE);
229                     } else {
230                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
231                                 "Device not found in innogy config. Was it removed?");
232                     }
233                 } else {
234                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
235                 }
236             } else {
237                 updateStatus(ThingStatus.OFFLINE);
238             }
239         } else {
240             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "device id unknown");
241         }
242     }
243
244     /**
245      * Initializes all properties of the {@link Device}, like vendor, serialnumber etc.
246      */
247     private boolean initializeProperties() {
248         synchronized (this.lock) {
249             @Nullable
250             final Device device = getDevice();
251             if (device != null) {
252                 final Map<String, String> properties = editProperties();
253                 properties.put(PROPERTY_ID, device.getId());
254                 properties.put(PROPERTY_PROTOCOL_ID, device.getConfig().getProtocolId());
255                 if (device.hasSerialNumber()) {
256                     properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialnumber());
257                 }
258                 properties.put(Thing.PROPERTY_VENDOR, device.getManufacturer());
259                 properties.put(PROPERTY_VERSION, device.getVersion());
260                 if (device.hasLocation()) {
261                     properties.put(PROPERTY_LOCATION, device.getLocation().getName());
262                 }
263                 if (device.isBatteryPowered()) {
264                     properties.put(PROPERTY_BATTERY_POWERED, "yes");
265                 } else {
266                     properties.put(PROPERTY_BATTERY_POWERED, "no");
267                 }
268                 if (device.isController()) {
269                     properties.put(PROPERTY_DEVICE_TYPE, "Controller");
270                 } else if (device.isVirtualDevice()) {
271                     properties.put(PROPERTY_DEVICE_TYPE, "Virtual");
272                 } else if (device.isRadioDevice()) {
273                     properties.put(PROPERTY_DEVICE_TYPE, "Radio");
274                 }
275
276                 // Thermostat
277                 if (DEVICE_RST.equals(device.getType()) || DEVICE_RST2.equals(device.getType())
278                         || DEVICE_WRT.equals(device.getType())) {
279                     properties.put(PROPERTY_DISPLAY_CURRENT_TEMPERATURE,
280                             device.getConfig().getDisplayCurrentTemperature());
281                 }
282
283                 // Meter
284                 if (DEVICE_ANALOG_METER.equals(device.getType()) || DEVICE_GENERATION_METER.equals(device.getType())
285                         || DEVICE_SMART_METER.equals(device.getType())
286                         || DEVICE_TWO_WAY_METER.equals(device.getType())) {
287                     properties.put(PROPERTY_METER_ID, device.getConfig().getMeterId());
288                     properties.put(PROPERTY_METER_FIRMWARE_VERSION, device.getConfig().getMeterFirmwareVersion());
289                 }
290
291                 if (device.getConfig().getTimeOfAcceptance() != null) {
292                     properties.put(PROPERTY_TIME_OF_ACCEPTANCE, device.getConfig().getTimeOfAcceptance()
293                             .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
294                 }
295                 if (device.getConfig().getTimeOfDiscovery() != null) {
296                     properties.put(PROPERTY_TIME_OF_DISCOVERY, device.getConfig().getTimeOfDiscovery()
297                             .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
298                 }
299
300                 updateProperties(properties);
301
302                 onDeviceStateChanged(device);
303                 return true;
304             } else {
305                 logger.warn("initializeProperties: The device with id {} isn't found", deviceId);
306                 return false;
307             }
308         }
309     }
310
311     /**
312      * Returns the {@link Device} associated with this {@link InnogyDeviceHandler} (referenced by the
313      * {@link InnogyDeviceHandler#deviceId}).
314      *
315      * @return the {@link Device} or null, if not found or no {@link InnogyBridgeHandler} is available
316      */
317     private @Nullable Device getDevice() {
318         if (getInnogyBridgeHandler() != null) {
319             return getInnogyBridgeHandler().getDeviceById(deviceId);
320         }
321         return null;
322     }
323
324     /**
325      * Returns the innogy bridge handler.
326      *
327      * @return the {@link InnogyBridgeHandler} or null
328      */
329     private @Nullable InnogyBridgeHandler getInnogyBridgeHandler() {
330         synchronized (this.lock) {
331             if (this.bridgeHandler == null) {
332                 @Nullable
333                 final Bridge bridge = getBridge();
334                 if (bridge == null) {
335                     return null;
336                 }
337                 @Nullable
338                 final ThingHandler handler = bridge.getHandler();
339                 if (handler instanceof InnogyBridgeHandler) {
340                     this.bridgeHandler = (InnogyBridgeHandler) handler;
341                     this.bridgeHandler.registerDeviceStatusListener(this);
342                 } else {
343                     return null;
344                 }
345             }
346             return this.bridgeHandler;
347         }
348     }
349
350     @Override
351     public void onDeviceStateChanged(final Device device) {
352         synchronized (this.lock) {
353             if (!deviceId.equals(device.getId())) {
354                 logger.trace("DeviceId {} not relevant for this handler (responsible for id {})", device.getId(),
355                         deviceId);
356                 return;
357             }
358
359             logger.debug("onDeviceStateChanged called with device {}/{}", device.getConfig().getName(), device.getId());
360
361             // DEVICE STATES
362             if (device.hasDeviceState()) {
363                 @Nullable
364                 Boolean reachable = null;
365                 if (device.getDeviceState().hasIsReachableState()) {
366                     reachable = device.getDeviceState().isReachable();
367                 }
368
369                 if (reachable != null && !reachable) {
370                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device not reachable.");
371                     return;
372                 } else if ((reachable != null && reachable) || DEVICE_VARIABLE_ACTUATOR.equals(device.getType())) {
373                     if (device.getDeviceState().deviceIsIncluded()) {
374                         updateStatus(ThingStatus.ONLINE);
375                     } else {
376                         updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
377                                 "State is " + device.getDeviceState().getDeviceInclusionState());
378                     }
379                 }
380             }
381
382             if (device.isBatteryPowered()) {
383                 if (device.hasLowBattery()) {
384                     updateState(CHANNEL_BATTERY_LOW, OnOffType.ON);
385                 } else {
386                     updateState(CHANNEL_BATTERY_LOW, OnOffType.OFF);
387                 }
388             }
389
390             // CAPABILITY STATES
391             for (final Entry<String, Capability> entry : device.getCapabilityMap().entrySet()) {
392                 final Capability c = entry.getValue();
393
394                 logger.debug("->capability:{} ({}/{})", c.getId(), c.getType(), c.getName());
395
396                 if (c.getCapabilityState() == null) {
397                     logger.debug("Capability not available for device {} ({})", device.getConfig().getName(),
398                             device.getType());
399                     continue;
400                 }
401                 switch (c.getType()) {
402                     case Capability.TYPE_VARIABLEACTUATOR:
403                         final Boolean variableActuatorState = c.getCapabilityState().getVariableActuatorState();
404                         if (variableActuatorState != null) {
405                             updateState(CHANNEL_SWITCH, variableActuatorState ? OnOffType.ON : OnOffType.OFF);
406                         } else {
407                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
408                                     c.getCapabilityState().getId(), c.getId());
409                         }
410                         break;
411                     case Capability.TYPE_SWITCHACTUATOR:
412                         final Boolean switchActuatorState = c.getCapabilityState().getSwitchActuatorState();
413                         if (switchActuatorState != null) {
414                             updateState(CHANNEL_SWITCH, switchActuatorState ? OnOffType.ON : OnOffType.OFF);
415                         } else {
416                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
417                                     c.getCapabilityState().getId(), c.getId());
418                         }
419                         break;
420                     case Capability.TYPE_DIMMERACTUATOR:
421                         final Integer dimLevel = c.getCapabilityState().getDimmerActuatorState();
422                         if (dimLevel != null) {
423                             logger.debug("Dimlevel state {}", dimLevel);
424                             updateState(CHANNEL_DIMMER, new PercentType(dimLevel));
425                         } else {
426                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
427                                     c.getCapabilityState().getId(), c.getId());
428                         }
429                         break;
430                     case Capability.TYPE_ROLLERSHUTTERACTUATOR:
431                         Integer rollerShutterLevel = c.getCapabilityState().getRollerShutterActuatorState();
432                         if (rollerShutterLevel != null) {
433                             rollerShutterLevel = invertValueIfConfigured(CHANNEL_ROLLERSHUTTER, rollerShutterLevel);
434                             logger.debug("RollerShutterlevel state {}", rollerShutterLevel);
435                             updateState(CHANNEL_ROLLERSHUTTER, new PercentType(rollerShutterLevel));
436                         } else {
437                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
438                                     c.getCapabilityState().getId(), c.getId());
439                         }
440                         break;
441                     case Capability.TYPE_TEMPERATURESENSOR:
442                         // temperature
443                         final Double temperatureSensorState = c.getCapabilityState()
444                                 .getTemperatureSensorTemperatureState();
445                         if (temperatureSensorState != null) {
446                             logger.debug("-> Temperature sensor state: {}", temperatureSensorState);
447                             updateState(CHANNEL_TEMPERATURE, new DecimalType(temperatureSensorState));
448                         } else {
449                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
450                                     c.getCapabilityState().getId(), c.getId());
451                         }
452
453                         // frost warning
454                         final Boolean temperatureSensorFrostWarningState = c.getCapabilityState()
455                                 .getTemperatureSensorFrostWarningState();
456                         if (temperatureSensorFrostWarningState != null) {
457                             updateState(CHANNEL_FROST_WARNING,
458                                     temperatureSensorFrostWarningState ? OnOffType.ON : OnOffType.OFF);
459                         } else {
460                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
461                                     c.getCapabilityState().getId(), c.getId());
462                         }
463
464                         break;
465                     case Capability.TYPE_THERMOSTATACTUATOR:
466                         // point temperature
467                         final Double thermostatActuatorPointTemperatureState = c.getCapabilityState()
468                                 .getThermostatActuatorPointTemperatureState();
469                         if (thermostatActuatorPointTemperatureState != null) {
470                             final DecimalType pointTemp = new DecimalType(thermostatActuatorPointTemperatureState);
471                             logger.debug(
472                                     "Update CHANNEL_SET_TEMPERATURE: state:{}->decType:{} (DeviceName {}, Capab-ID:{})",
473                                     thermostatActuatorPointTemperatureState, pointTemp, device.getConfig().getName(),
474                                     c.getId());
475                             updateState(CHANNEL_SET_TEMPERATURE, pointTemp);
476                         } else {
477                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
478                                     c.getCapabilityState().getId(), c.getId());
479                         }
480
481                         // operation mode
482                         final String thermostatActuatorOperationModeState = c.getCapabilityState()
483                                 .getThermostatActuatorOperationModeState();
484                         if (thermostatActuatorOperationModeState != null) {
485                             final StringType operationMode = new StringType(thermostatActuatorOperationModeState);
486                             updateState(CHANNEL_OPERATION_MODE, operationMode);
487                         } else {
488                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
489                                     c.getCapabilityState().getId(), c.getId());
490                         }
491
492                         // window reduction active
493                         final Boolean thermostatActuatorWindowReductionActiveState = c.getCapabilityState()
494                                 .getThermostatActuatorWindowReductionActiveState();
495                         if (thermostatActuatorWindowReductionActiveState != null) {
496                             updateState(CHANNEL_WINDOW_REDUCTION_ACTIVE,
497                                     thermostatActuatorWindowReductionActiveState ? OnOffType.ON : OnOffType.OFF);
498                         } else {
499                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
500                                     c.getCapabilityState().getId(), c.getId());
501                         }
502                         break;
503                     case Capability.TYPE_HUMIDITYSENSOR:
504                         // humidity
505                         final Double humidityState = c.getCapabilityState().getHumiditySensorHumidityState();
506                         if (humidityState != null) {
507                             final DecimalType humidity = new DecimalType(humidityState);
508                             updateState(CHANNEL_HUMIDITY, humidity);
509                         } else {
510                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
511                                     c.getCapabilityState().getId(), c.getId());
512                         }
513
514                         // mold warning
515                         final Boolean humiditySensorMoldWarningState = c.getCapabilityState()
516                                 .getHumiditySensorMoldWarningState();
517                         if (humiditySensorMoldWarningState != null) {
518                             updateState(CHANNEL_MOLD_WARNING,
519                                     humiditySensorMoldWarningState ? OnOffType.ON : OnOffType.OFF);
520                         } else {
521                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
522                                     c.getCapabilityState().getId(), c.getId());
523                         }
524                         break;
525                     case Capability.TYPE_WINDOWDOORSENSOR:
526                         final Boolean contactState = c.getCapabilityState().getWindowDoorSensorState();
527                         if (contactState != null) {
528                             updateState(CHANNEL_CONTACT, contactState ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
529                         } else {
530                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
531                                     c.getCapabilityState().getId(), c.getId());
532                         }
533                         break;
534                     case Capability.TYPE_SMOKEDETECTORSENSOR:
535                         final Boolean smokeState = c.getCapabilityState().getSmokeDetectorSensorState();
536                         if (smokeState != null) {
537                             updateState(CHANNEL_SMOKE, smokeState ? OnOffType.ON : OnOffType.OFF);
538                         } else {
539                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
540                                     c.getCapabilityState().getId(), c.getId());
541                         }
542                         break;
543                     case Capability.TYPE_ALARMACTUATOR:
544                         final Boolean alarmState = c.getCapabilityState().getAlarmActuatorState();
545                         if (alarmState != null) {
546                             updateState(CHANNEL_ALARM, alarmState ? OnOffType.ON : OnOffType.OFF);
547                         } else {
548                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
549                                     c.getCapabilityState().getId(), c.getId());
550                         }
551                         break;
552                     case Capability.TYPE_MOTIONDETECTIONSENSOR:
553                         final Integer motionState = c.getCapabilityState().getMotionDetectionSensorState();
554                         if (motionState != null) {
555                             final DecimalType motionCount = new DecimalType(motionState);
556                             logger.debug("Motion state {} -> count {}", motionState, motionCount);
557                             updateState(CHANNEL_MOTION_COUNT, motionCount);
558                         } else {
559                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
560                                     c.getCapabilityState().getId(), c.getId());
561                         }
562                         break;
563                     case Capability.TYPE_LUMINANCESENSOR:
564                         final Double luminanceState = c.getCapabilityState().getLuminanceSensorState();
565                         if (luminanceState != null) {
566                             final DecimalType luminance = new DecimalType(luminanceState);
567                             updateState(CHANNEL_LUMINANCE, luminance);
568                         } else {
569                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
570                                     c.getCapabilityState().getId(), c.getId());
571                         }
572                         break;
573                     case Capability.TYPE_PUSHBUTTONSENSOR:
574                         final Integer pushCountState = c.getCapabilityState().getPushButtonSensorCounterState();
575                         final Integer buttonIndexState = c.getCapabilityState().getPushButtonSensorButtonIndexState();
576                         logger.debug("Pushbutton index {} count {}", buttonIndexState, pushCountState);
577                         if (pushCountState != null) {
578                             final DecimalType pushCount = new DecimalType(pushCountState);
579                             // prevent error when buttonIndexState is null
580                             if (buttonIndexState != null) {
581                                 if (buttonIndexState >= 0 && buttonIndexState <= 7) {
582                                     final int channelIndex = buttonIndexState + 1;
583                                     final String type = c.getCapabilityState().getPushButtonSensorButtonIndexType();
584                                     final String triggerEvent = SHORT_PRESS.equals(type)
585                                             ? CommonTriggerEvents.SHORT_PRESSED
586                                             : (LONG_PRESS.equals(type) ? CommonTriggerEvents.LONG_PRESSED
587                                                     : CommonTriggerEvents.PRESSED);
588
589                                     triggerChannel(CHANNEL_BUTTON + channelIndex, triggerEvent);
590                                     updateState(String.format(CHANNEL_BUTTON_COUNT, channelIndex), pushCount);
591                                 } else {
592                                     logger.debug("Button index {} not supported.", buttonIndexState);
593                                 }
594                                 // Button handled so remove state to avoid re-trigger.
595                                 c.getCapabilityState().setPushButtonSensorButtonIndexState(null);
596                                 c.getCapabilityState().setPushButtonSensorButtonIndexType(null);
597                             } else {
598                                 logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
599                                         c.getCapabilityState().getId(), c.getId());
600                             }
601                         } else {
602                             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", c.getType(),
603                                     c.getCapabilityState().getId(), c.getId());
604                         }
605                         break;
606                     case Capability.TYPE_ENERGYCONSUMPTIONSENSOR:
607                         updateStateForEnergyChannel(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
608                                 c.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthKWhState(), c);
609                         updateStateForEnergyChannel(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
610                                 c.getCapabilityState().getEnergyConsumptionSensorAbsoluteEnergyConsumptionState(), c);
611                         updateStateForEnergyChannel(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO,
612                                 c.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthEuroState(), c);
613                         updateStateForEnergyChannel(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO,
614                                 c.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayEuroState(), c);
615                         updateStateForEnergyChannel(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH,
616                                 c.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayKWhState(), c);
617                         break;
618                     case Capability.TYPE_POWERCONSUMPTIONSENSOR:
619                         updateStateForEnergyChannel(CHANNEL_POWER_CONSUMPTION_WATT,
620                                 c.getCapabilityState().getPowerConsumptionSensorPowerConsumptionWattState(), c);
621                         break;
622                     case Capability.TYPE_GENERATIONMETERENERGYSENSOR:
623                         updateStateForEnergyChannel(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
624                                 c.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInKWhState(), c);
625                         updateStateForEnergyChannel(CHANNEL_TOTAL_ENERGY_GENERATION,
626                                 c.getCapabilityState().getGenerationMeterEnergySensorTotalEnergyState(), c);
627                         updateStateForEnergyChannel(CHANNEL_ENERGY_GENERATION_MONTH_EURO,
628                                 c.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInEuroState(), c);
629                         updateStateForEnergyChannel(CHANNEL_ENERGY_GENERATION_DAY_EURO,
630                                 c.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInEuroState(), c);
631                         updateStateForEnergyChannel(CHANNEL_ENERGY_GENERATION_DAY_KWH,
632                                 c.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInKWhState(), c);
633                         break;
634                     case Capability.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR:
635                         updateStateForEnergyChannel(CHANNEL_POWER_GENERATION_WATT,
636                                 c.getCapabilityState().getGenerationMeterPowerConsumptionSensorPowerInWattState(), c);
637                         break;
638                     case Capability.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR:
639                         updateStateForEnergyChannel(CHANNEL_ENERGY_MONTH_KWH,
640                                 c.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(),
641                                 c);
642                         updateStateForEnergyChannel(CHANNEL_TOTAL_ENERGY,
643                                 c.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorTotalEnergyState(), c);
644                         updateStateForEnergyChannel(CHANNEL_ENERGY_MONTH_EURO,
645                                 c.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(),
646                                 c);
647                         updateStateForEnergyChannel(CHANNEL_ENERGY_DAY_EURO,
648                                 c.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(),
649                                 c);
650                         updateStateForEnergyChannel(CHANNEL_ENERGY_DAY_KWH,
651                                 c.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(),
652                                 c);
653                         break;
654                     case Capability.TYPE_TWOWAYMETERENERGYFEEDSENSOR:
655                         updateStateForEnergyChannel(CHANNEL_ENERGY_FEED_MONTH_KWH,
656                                 c.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(), c);
657                         updateStateForEnergyChannel(CHANNEL_TOTAL_ENERGY_FED,
658                                 c.getCapabilityState().getTwoWayMeterEnergyFeedSensorTotalEnergyState(), c);
659                         updateStateForEnergyChannel(CHANNEL_ENERGY_FEED_MONTH_EURO,
660                                 c.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(), c);
661                         updateStateForEnergyChannel(CHANNEL_ENERGY_FEED_DAY_EURO,
662                                 c.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(), c);
663                         updateStateForEnergyChannel(CHANNEL_ENERGY_FEED_DAY_KWH,
664                                 c.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(), c);
665                         break;
666                     case Capability.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR:
667                         updateStateForEnergyChannel(CHANNEL_POWER_WATT,
668                                 c.getCapabilityState().getTwoWayMeterPowerConsumptionSensorPowerInWattState(), c);
669                         break;
670                     default:
671                         logger.debug("Unsupported capability type {}.", c.getType());
672                         break;
673                 }
674             }
675         }
676     }
677
678     /**
679      * Updates the state for the {@link Channel} of an energy {@link Device}.
680      *
681      * @param channelId
682      * @param state
683      * @param capability
684      */
685     private void updateStateForEnergyChannel(final String channelId, @Nullable final Double state,
686             final Capability capability) {
687         if (state != null) {
688             final DecimalType newValue = new DecimalType(state);
689             updateState(channelId, newValue);
690         } else {
691             logger.debug("State for {} is STILL NULL!! cstate-id: {}, c-id: {}", capability.getType(),
692                     capability.getCapabilityState().getId(), capability.getId());
693         }
694     }
695
696     @Override
697     public void onDeviceStateChanged(final Device changedDevice, final Event event) {
698         synchronized (this.lock) {
699             Device device = changedDevice;
700             if (!deviceId.equals(device.getId())) {
701                 return;
702             }
703
704             logger.trace("DeviceId {} relevant for this handler.", device.getId());
705
706             if (event.isLinkedtoCapability()) {
707                 boolean deviceChanged = false;
708                 final String linkedCapabilityId = event.getSourceId();
709
710                 Map<String, Capability> capabilityMap = device.getCapabilityMap();
711                 Capability capability = capabilityMap.get(linkedCapabilityId);
712                 logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}", capability.getType(),
713                         capability.getName(), capability.getId(), capability.getDeviceLink(), device.getId());
714
715                 CapabilityState capabilityState;
716                 if (capability.hasState()) {
717                     capabilityState = capability.getCapabilityState();
718
719                     // VariableActuator
720                     if (capability.isTypeVariableActuator()) {
721                         capabilityState.setVariableActuatorState(event.getProperties().getValue());
722                         deviceChanged = true;
723
724                         // SwitchActuator
725                     } else if (capability.isTypeSwitchActuator()) {
726                         capabilityState.setSwitchActuatorState(event.getProperties().getOnState());
727                         deviceChanged = true;
728
729                         // DimmerActuator
730                     } else if (capability.isTypeDimmerActuator()) {
731                         capabilityState.setDimmerActuatorState(event.getProperties().getDimLevel());
732                         deviceChanged = true;
733
734                         // RollerShutterActuator
735                     } else if (capability.isTypeRollerShutterActuator()) {
736                         capabilityState.setRollerShutterActuatorState(event.getProperties().getShutterLevel());
737                         deviceChanged = true;
738
739                         // TemperatureSensor
740                     } else if (capability.isTypeTemperatureSensor()) {
741                         // when values are changed, they come with separate events
742                         // values should only updated when they are not null
743                         final Double tmpTemperatureState = event.getProperties().getTemperature();
744                         final Boolean tmpFrostWarningState = event.getProperties().getFrostWarning();
745                         if (tmpTemperatureState != null) {
746                             capabilityState.setTemperatureSensorTemperatureState(tmpTemperatureState);
747                         }
748                         if (tmpFrostWarningState != null) {
749                             capabilityState.setTemperatureSensorFrostWarningState(tmpFrostWarningState);
750                         }
751                         deviceChanged = true;
752
753                         // ThermostatActuator
754                     } else if (capability.isTypeThermostatActuator()) {
755                         // when values are changed, they come with separate events
756                         // values should only updated when they are not null
757
758                         final Double tmpPointTemperatureState = event.getProperties().getPointTemperature();
759                         final String tmpOperationModeState = event.getProperties().getOperationMode();
760                         final Boolean tmpWindowReductionActiveState = event.getProperties().getWindowReductionActive();
761
762                         if (tmpPointTemperatureState != null) {
763                             capabilityState.setThermostatActuatorPointTemperatureState(tmpPointTemperatureState);
764                         }
765                         if (tmpOperationModeState != null) {
766                             capabilityState.setThermostatActuatorOperationModeState(tmpOperationModeState);
767                         }
768                         if (tmpWindowReductionActiveState != null) {
769                             capabilityState
770                                     .setThermostatActuatorWindowReductionActiveState(tmpWindowReductionActiveState);
771                         }
772                         deviceChanged = true;
773
774                         // HumiditySensor
775                     } else if (capability.isTypeHumiditySensor()) {
776                         // when values are changed, they come with separate events
777                         // values should only updated when they are not null
778                         final Double tmpHumidityState = event.getProperties().getHumidity();
779                         final Boolean tmpMoldWarningState = event.getProperties().getMoldWarning();
780                         if (tmpHumidityState != null) {
781                             capabilityState.setHumiditySensorHumidityState(tmpHumidityState);
782                         }
783                         if (tmpMoldWarningState != null) {
784                             capabilityState.setHumiditySensorMoldWarningState(tmpMoldWarningState);
785                         }
786                         deviceChanged = true;
787
788                         // WindowDoorSensor
789                     } else if (capability.isTypeWindowDoorSensor()) {
790                         capabilityState.setWindowDoorSensorState(event.getProperties().getIsOpen());
791                         deviceChanged = true;
792
793                         // SmokeDetectorSensor
794                     } else if (capability.isTypeSmokeDetectorSensor()) {
795                         capabilityState.setSmokeDetectorSensorState(event.getProperties().getIsSmokeAlarm());
796                         deviceChanged = true;
797
798                         // AlarmActuator
799                     } else if (capability.isTypeAlarmActuator()) {
800                         capabilityState.setAlarmActuatorState(event.getProperties().getOnState());
801                         deviceChanged = true;
802
803                         // MotionDetectionSensor
804                     } else if (capability.isTypeMotionDetectionSensor()) {
805                         capabilityState.setMotionDetectionSensorState(event.getProperties().getMotionDetectedCount());
806                         deviceChanged = true;
807
808                         // LuminanceSensor
809                     } else if (capability.isTypeLuminanceSensor()) {
810                         capabilityState.setLuminanceSensorState(event.getProperties().getLuminance());
811                         deviceChanged = true;
812
813                         // PushButtonSensor
814                     } else if (capability.isTypePushButtonSensor()) {
815                         // Some devices send both StateChanged and ButtonPressed. But only one should be handled.
816                         // If ButtonPressed is send lastPressedButtonIndex is not set in StateChanged so ignore
817                         // StateChanged.
818                         // type is also not always present if null will be interpreted as a normal key press.
819                         final Integer tmpButtonIndex = event.getProperties().getLastPressedButtonIndex();
820
821                         if (tmpButtonIndex != null) {
822                             capabilityState.setPushButtonSensorButtonIndexState(tmpButtonIndex);
823                             capabilityState
824                                     .setPushButtonSensorButtonIndexType(event.getProperties().getLastKeyPressType());
825
826                             final Integer tmpLastKeyPressCounter = event.getProperties().getLastKeyPressCounter();
827
828                             if (tmpLastKeyPressCounter != null) {
829                                 capabilityState.setPushButtonSensorCounterState(tmpLastKeyPressCounter);
830                             }
831                             deviceChanged = true;
832                         }
833
834                         // EnergyConsumptionSensor
835                     } else if (capability.isTypeEnergyConsumptionSensor()) {
836                         capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(
837                                 event.getProperties().getEnergyConsumptionMonthKWh());
838                         capabilityState.setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(
839                                 event.getProperties().getAbsoluteEnergyConsumption());
840                         capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(
841                                 event.getProperties().getEnergyConsumptionMonthEuro());
842                         capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayEuroState(
843                                 event.getProperties().getEnergyConsumptionDayEuro());
844                         capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayKWhState(
845                                 event.getProperties().getEnergyConsumptionDayKWh());
846                         deviceChanged = true;
847
848                         // PowerConsumptionSensor
849                     } else if (capability.isTypePowerConsumptionSensor()) {
850                         capabilityState.setPowerConsumptionSensorPowerConsumptionWattState(
851                                 event.getProperties().getPowerConsumptionWatt());
852                         deviceChanged = true;
853
854                         // GenerationMeterEnergySensor
855                     } else if (capability.isTypeGenerationMeterEnergySensor()) {
856                         capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInKWhState(
857                                 event.getProperties().getEnergyPerMonthInKWh());
858                         capabilityState
859                                 .setGenerationMeterEnergySensorTotalEnergyState(event.getProperties().getTotalEnergy());
860                         capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInEuroState(
861                                 event.getProperties().getEnergyPerMonthInEuro());
862                         capabilityState.setGenerationMeterEnergySensorEnergyPerDayInEuroState(
863                                 event.getProperties().getEnergyPerDayInEuro());
864                         capabilityState.setGenerationMeterEnergySensorEnergyPerDayInKWhState(
865                                 event.getProperties().getEnergyPerDayInKWh());
866                         deviceChanged = true;
867
868                         // GenerationMeterPowerConsumptionSensor
869                     } else if (capability.isTypeGenerationMeterPowerConsumptionSensor()) {
870                         capabilityState.setGenerationMeterPowerConsumptionSensorPowerInWattState(
871                                 event.getProperties().getPowerInWatt());
872                         deviceChanged = true;
873
874                         // TwoWayMeterEnergyConsumptionSensor
875                     } else if (capability.isTypeTwoWayMeterEnergyConsumptionSensor()) {
876                         capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(
877                                 event.getProperties().getEnergyPerMonthInKWh());
878                         capabilityState.setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(
879                                 event.getProperties().getTotalEnergy());
880                         capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(
881                                 event.getProperties().getEnergyPerMonthInEuro());
882                         capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(
883                                 event.getProperties().getEnergyPerDayInEuro());
884                         capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(
885                                 event.getProperties().getEnergyPerDayInKWh());
886                         deviceChanged = true;
887
888                         // TwoWayMeterEnergyFeedSensor
889                     } else if (capability.isTypeTwoWayMeterEnergyFeedSensor()) {
890                         capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(
891                                 event.getProperties().getEnergyPerMonthInKWh());
892                         capabilityState
893                                 .setTwoWayMeterEnergyFeedSensorTotalEnergyState(event.getProperties().getTotalEnergy());
894                         capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(
895                                 event.getProperties().getEnergyPerMonthInEuro());
896                         capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(
897                                 event.getProperties().getEnergyPerDayInEuro());
898                         capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(
899                                 event.getProperties().getEnergyPerDayInKWh());
900                         deviceChanged = true;
901
902                         // TwoWayMeterPowerConsumptionSensor
903                     } else if (capability.isTypeTwoWayMeterPowerConsumptionSensor()) {
904                         capabilityState.setTwoWayMeterPowerConsumptionSensorPowerInWattState(
905                                 event.getProperties().getPowerInWatt());
906                         deviceChanged = true;
907
908                     } else {
909                         logger.debug("Unsupported capability type {}.", capability.getType());
910                     }
911                 } else {
912                     logger.debug("Capability {} has no state (yet?) - refreshing device.", capability.getName());
913
914                     @Nullable
915                     final InnogyBridgeHandler innogyBridgeHandler = getInnogyBridgeHandler();
916                     if (innogyBridgeHandler != null) {
917                         device = innogyBridgeHandler.refreshDevice(deviceId);
918                     }
919                     if (device != null) {
920                         capabilityMap = device.getCapabilityMap();
921                         capability = capabilityMap.get(linkedCapabilityId);
922                         if (capability.hasState()) {
923                             deviceChanged = true;
924                         }
925                     }
926                 }
927                 if (deviceChanged && device != null) {
928                     onDeviceStateChanged(device);
929                 }
930
931             } else if (event.isLinkedtoDevice()) {
932                 if (device.hasDeviceState()) {
933                     onDeviceStateChanged(device);
934                 } else {
935                     logger.debug("Device {}/{} has no state.", device.getConfig().getName(), device.getId());
936                 }
937             }
938         }
939     }
940
941     /**
942      * Returns the inverted value. Currently only rollershutter channels are supported.
943      *
944      * @param value
945      * @return the value or the inverted value
946      */
947     private int invertValueIfConfigured(final String channelId, final int value) {
948         if (!CHANNEL_ROLLERSHUTTER.equals(channelId)) {
949             logger.debug("Channel {} cannot be inverted.", channelId);
950             return value;
951         }
952
953         @Nullable
954         final Channel channel = getThing().getChannel(channelId);
955         if (channel == null) {
956             logger.debug("Channel {} was null! Value not inverted.", channelId);
957             return value;
958         }
959         final Boolean invert = (Boolean) channel.getConfiguration().get("invert");
960         return invert != null && invert ? value : (100 - value);
961     }
962 }