diff --git a/bundles/org.openhab.binding.lcn/README.md b/bundles/org.openhab.binding.lcn/README.md index f59f4d5e734cd..26e6beb629c31 100644 --- a/bundles/org.openhab.binding.lcn/README.md +++ b/bundles/org.openhab.binding.lcn/README.md @@ -280,6 +280,8 @@ S0 counter Channels need to be the pulses per kWh configured. If the value is le The Rollershutter Channels provide the boolean parameter `invertUpDown`, which can be set to 'true' if the Up/Down wires are interchanged. +The Binarysensor Channels provide the boolean parameter `invertState`, which can be set to 'true' if the binary sensor connected uses reverse logic for signaling open/closed. + ### Transponder LCN transponder readers can be integrated in openHAB e.g. for access control. diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java index be31b581abc37..d4e84078d501e 100644 --- a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java @@ -52,6 +52,7 @@ import org.openhab.binding.lcn.internal.connection.ModInfo; import org.openhab.binding.lcn.internal.converter.Converter; import org.openhab.binding.lcn.internal.converter.Converters; +import org.openhab.binding.lcn.internal.converter.InversionConverter; import org.openhab.binding.lcn.internal.converter.S0Converter; import org.openhab.binding.lcn.internal.subhandler.AbstractLcnModuleSubHandler; import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaAckSubHandler; @@ -68,7 +69,8 @@ @NonNullByDefault public class LcnModuleHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(LcnModuleHandler.class); - private static final Map CONVERTERS = new HashMap<>(); + private static final Map VALUE_CONVERTERS = new HashMap<>(); + private static final InversionConverter INVERSION_CONVERTER = new InversionConverter(); private static final String SERIAL_NUMBER = "serialNumber"; private @Nullable LcnAddrMod moduleAddress; private final Map subHandlers = new HashMap<>(); @@ -76,13 +78,13 @@ public class LcnModuleHandler extends BaseThingHandler { private final Map converters = new HashMap<>(); static { - CONVERTERS.put("temperature", Converters.TEMPERATURE); - CONVERTERS.put("light", Converters.LIGHT); - CONVERTERS.put("co2", Converters.CO2); - CONVERTERS.put("current", Converters.CURRENT); - CONVERTERS.put("voltage", Converters.VOLTAGE); - CONVERTERS.put("angle", Converters.ANGLE); - CONVERTERS.put("windspeed", Converters.WINDSPEED); + VALUE_CONVERTERS.put("temperature", Converters.TEMPERATURE); + VALUE_CONVERTERS.put("light", Converters.LIGHT); + VALUE_CONVERTERS.put("co2", Converters.CO2); + VALUE_CONVERTERS.put("current", Converters.CURRENT); + VALUE_CONVERTERS.put("voltage", Converters.VOLTAGE); + VALUE_CONVERTERS.put("angle", Converters.ANGLE); + VALUE_CONVERTERS.put("windspeed", Converters.WINDSPEED); } public LcnModuleHandler(Thing thing) { @@ -129,11 +131,13 @@ && getThing().getProperties().get(SERIAL_NUMBER).isEmpty()) { metadataSubHandlers.add(new LcnModuleMetaAckSubHandler(this, info)); metadataSubHandlers.add(new LcnModuleMetaFirmwareSubHandler(this, info)); - // initialize variable value converters + // initialize converters for (Channel channel : thing.getChannels()) { Object unitObject = channel.getConfiguration().get("unit"); Object parameterObject = channel.getConfiguration().get("parameter"); + Object invertConfig = channel.getConfiguration().get("invertState"); + // Initialize value converters if (unitObject instanceof String) { switch ((String) unitObject) { case "power": @@ -141,12 +145,18 @@ && getThing().getProperties().get(SERIAL_NUMBER).isEmpty()) { converters.put(channel.getUID(), new S0Converter(parameterObject)); break; default: - if (CONVERTERS.containsKey(unitObject)) { - converters.put(channel.getUID(), CONVERTERS.get(unitObject)); + if (VALUE_CONVERTERS.containsKey(unitObject)) { + converters.put(channel.getUID(), VALUE_CONVERTERS.get(unitObject)); } break; } } + + // Initialize inversion converter + if (invertConfig instanceof Boolean && invertConfig.equals(true)) { + converters.put(channel.getUID(), INVERSION_CONVERTER); + } + } // module is assumed as online, when the corresponding Bridge (PckGatewayHandler) is online. @@ -316,6 +326,7 @@ public void updateChannel(LcnChannelGroup channelGroup, String channelId, State if (converter != null) { convertedState = converter.onStateUpdateFromHandler(state); } + updateState(channelUid, convertedState); } diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java index 2054a4677c765..0a30fc72bcdc1 100644 --- a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java @@ -820,5 +820,4 @@ private static int convertMsecToLCNTimer(double ms) { } return lcntimer; } - } diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java index 456bbe847da48..a8e9a6ef903bf 100644 --- a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java @@ -12,55 +12,27 @@ */ package org.openhab.binding.lcn.internal.converter; -import java.util.function.Function; - -import javax.measure.Unit; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.QuantityType; import org.eclipse.smarthome.core.types.State; import org.openhab.binding.lcn.internal.common.LcnException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Base class for all LCN variable value converters. + * Base class for all converters. * * @author Fabian Wolter - Initial Contribution */ @NonNullByDefault public class Converter { - private final Logger logger = LoggerFactory.getLogger(Converter.class); - private @Nullable final Unit unit; - private final Function toHuman; - private final Function toNative; - - public Converter(@Nullable Unit unit, Function toHuman, Function toNative) { - this.unit = unit; - this.toHuman = toHuman; - this.toNative = toNative; - } - - /** - * Converts the given human readable value into the native LCN value. - * - * @param humanReadableValue the value to convert - * @return the native value - */ - protected long toNative(double humanReadableValue) { - return toNative.apply(humanReadableValue); - } - /** - * Converts the given native LCN value into a human readable value. + * Converts a state update from the Thing into a human readable representational State. * - * @param nativeValue the value to convert - * @return the human readable value + * @param state from the Thing + * @return human readable representational State */ - protected double toHumanReadable(long nativeValue) { - return toHuman.apply(nativeValue); + public State onStateUpdateFromHandler(State state) { + return state; } /** @@ -70,7 +42,7 @@ protected double toHumanReadable(long nativeValue) { * @return the native LCN value */ public DecimalType onCommandFromItem(double humanReadable) { - return new DecimalType(toNative(humanReadable)); + return new DecimalType(humanReadable); } /** @@ -81,38 +53,6 @@ public DecimalType onCommandFromItem(double humanReadable) { * @throws LcnException when the value could not be converted to the base unit */ public DecimalType onCommandFromItem(QuantityType quantityType) throws LcnException { - Unit localUnit = unit; - if (localUnit == null) { - return onCommandFromItem(quantityType.doubleValue()); - } - - QuantityType quantityInBaseUnit = quantityType.toUnit(localUnit); - - if (quantityInBaseUnit != null) { - return onCommandFromItem(quantityInBaseUnit.doubleValue()); - } else { - throw new LcnException(quantityType + ": Incompatible Channel unit configured: " + localUnit); - } - } - - /** - * Converts a state update from the Thing into a human readable unit. - * - * @param state from the Thing - * @return human readable State - */ - public State onStateUpdateFromHandler(State state) { - State result = state; - - if (state instanceof DecimalType) { - Unit localUnit = unit; - if (localUnit != null) { - result = QuantityType.valueOf(toHumanReadable(((DecimalType) state).longValue()), localUnit); - } - } else { - logger.warn("Unexpected state type: {}", state.getClass().getSimpleName()); - } - - return result; + return onCommandFromItem(quantityType.doubleValue()); } } diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java index 3a8c5fff7aa04..20cc41acf78ae 100644 --- a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java @@ -33,14 +33,14 @@ public class Converters { public static final Converter IDENTITY; static { - TEMPERATURE = new Converter(SIUnits.CELSIUS, n -> (n - 1000) / 10d, h -> Math.round(h * 10) + 1000); - LIGHT = new Converter(SmartHomeUnits.LUX, Converters::lightToHumanReadable, Converters::lightToNative); - CO2 = new Converter(SmartHomeUnits.PARTS_PER_MILLION, n -> (double) n, Math::round); - CURRENT = new Converter(SmartHomeUnits.AMPERE, n -> n / 100d, h -> Math.round(h * 100)); - VOLTAGE = new Converter(SmartHomeUnits.VOLT, n -> n / 400d, h -> Math.round(h * 400)); - ANGLE = new Converter(SmartHomeUnits.DEGREE_ANGLE, n -> (n - 1000) / 10d, Converters::angleToNative); - WINDSPEED = new Converter(SmartHomeUnits.METRE_PER_SECOND, n -> n / 10d, h -> Math.round(h * 10)); - IDENTITY = new Converter(null, n -> (double) n, Math::round); + TEMPERATURE = new ValueConverter(SIUnits.CELSIUS, n -> (n - 1000) / 10d, h -> Math.round(h * 10) + 1000); + LIGHT = new ValueConverter(SmartHomeUnits.LUX, Converters::lightToHumanReadable, Converters::lightToNative); + CO2 = new ValueConverter(SmartHomeUnits.PARTS_PER_MILLION, n -> (double) n, Math::round); + CURRENT = new ValueConverter(SmartHomeUnits.AMPERE, n -> n / 100d, h -> Math.round(h * 100)); + VOLTAGE = new ValueConverter(SmartHomeUnits.VOLT, n -> n / 400d, h -> Math.round(h * 400)); + ANGLE = new ValueConverter(SmartHomeUnits.DEGREE_ANGLE, n -> (n - 1000) / 10d, Converters::angleToNative); + WINDSPEED = new ValueConverter(SmartHomeUnits.METRE_PER_SECOND, n -> n / 10d, h -> Math.round(h * 10)); + IDENTITY = new ValueConverter(null, n -> (double) n, Math::round); } private static long lightToNative(double value) { diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/InversionConverter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/InversionConverter.java new file mode 100644 index 0000000000000..a2280b484e2d3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/InversionConverter.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.converter; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.eclipse.smarthome.core.types.State; + +/** + * Converter for all states representing antonyms. + * + * @author Thomas Weiler - Initial Contribution + */ +@NonNullByDefault +public class InversionConverter extends Converter { + /** + * Converts a state into its antonym where applicable. + * + * @param state to be inverted + * @return inverted state + */ + @Override + public State onStateUpdateFromHandler(State state) { + State convertedState = state; + + if (state instanceof OpenClosedType) { + convertedState = state.equals(OpenClosedType.OPEN) ? OpenClosedType.CLOSED : OpenClosedType.OPEN; + } else if (state instanceof OnOffType) { + convertedState = state.equals(OnOffType.ON) ? OnOffType.OFF : OnOffType.ON; + } else if (state instanceof UpDownType) { + convertedState = state.equals(UpDownType.UP) ? UpDownType.DOWN : UpDownType.UP; + } + + return convertedState; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java index b2b4989a668c8..bbb355472dba1 100644 --- a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java @@ -26,7 +26,7 @@ * @author Fabian Wolter - Initial Contribution */ @NonNullByDefault -public class S0Converter extends Converter { +public class S0Converter extends ValueConverter { private final Logger logger = LoggerFactory.getLogger(S0Converter.class); protected double pulsesPerKwh; diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/ValueConverter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/ValueConverter.java new file mode 100644 index 0000000000000..724cb50eb63e9 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/ValueConverter.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.converter; + +import java.util.function.Function; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for all LCN variable value converters. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ValueConverter extends Converter { + private final Logger logger = LoggerFactory.getLogger(ValueConverter.class); + private @Nullable final Unit unit; + private final Function toHuman; + private final Function toNative; + + public ValueConverter(@Nullable Unit unit, Function toHuman, Function toNative) { + this.unit = unit; + this.toHuman = toHuman; + this.toNative = toNative; + } + + /** + * Converts the given human readable value into the native LCN value. + * + * @param humanReadableValue the value to convert + * @return the native value + */ + protected long toNative(double humanReadableValue) { + return toNative.apply(humanReadableValue); + } + + /** + * Converts the given native LCN value into a human readable value. + * + * @param nativeValue the value to convert + * @return the human readable value + */ + protected double toHumanReadable(long nativeValue) { + return toHuman.apply(nativeValue); + } + + /** + * Converts a human readable value into LCN native value. + * + * @param humanReadable value to convert + * @return the native LCN value + */ + @Override + public DecimalType onCommandFromItem(double humanReadable) { + return new DecimalType(toNative(humanReadable)); + } + + /** + * Converts a human readable value into LCN native value. + * + * @param humanReadable value to convert + * @return the native LCN value + * @throws LcnException when the value could not be converted to the base unit + */ + @Override + public DecimalType onCommandFromItem(QuantityType quantityType) throws LcnException { + Unit localUnit = unit; + if (localUnit == null) { + return onCommandFromItem(quantityType.doubleValue()); + } + + QuantityType quantityInBaseUnit = quantityType.toUnit(localUnit); + + if (quantityInBaseUnit != null) { + return onCommandFromItem(quantityInBaseUnit.doubleValue()); + } else { + throw new LcnException(quantityType + ": Incompatible Channel unit configured: " + localUnit); + } + } + + /** + * Converts a state update from the Thing into a human readable unit. + * + * @param state from the Thing + * @return human readable State + */ + @Override + public State onStateUpdateFromHandler(State state) { + State result = state; + + if (state instanceof DecimalType) { + Unit localUnit = unit; + if (localUnit != null) { + result = QuantityType.valueOf(toHumanReadable(((DecimalType) state).longValue()), localUnit); + } + } else { + logger.warn("Unexpected state type: {}", state.getClass().getSimpleName()); + } + + return result; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml index 23bc8941491fc..cff2bff8712e0 100644 --- a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml @@ -281,6 +281,14 @@ Contact veto + + + + Per default a binary sensor state 0 corresponds to "closed" while 1 corresponds to "open". Use this + parameter to invert that logic. + false + +