From: lsiepel Date: Sun, 25 Feb 2024 10:56:34 +0000 (+0100) Subject: [ism8] Add UoM support (#14206) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=52f4a648d071d69bc5cc49db1b0b8d025c24eb63;p=openhab-addons.git [ism8] Add UoM support (#14206) * Add UoM support Signed-off-by: lsiepel --- diff --git a/bundles/org.openhab.binding.ism8/README.md b/bundles/org.openhab.binding.ism8/README.md index 72a24a1053..c1fc5666d3 100644 --- a/bundles/org.openhab.binding.ism8/README.md +++ b/bundles/org.openhab.binding.ism8/README.md @@ -34,15 +34,9 @@ The ISM8 does currently support 4 different devices at the same moment of time ( Once you have an overview of your heating system set you can start to create the channels accordingly. Each channel should be created in the following way: -| Type | Name | Description | Configuration | -|--------|---------|----------------------------|-----------------| -| Number | DpId004 | "Kesseltemperatur" | id, type, write | - -Type: - -- Switch use for boolean values -- Number use for any number -- Other types may work as well. +| Type | Name | Description | Configuration | +|--------------------|---------|--------------------|---------------| +| Number:Temperature | DpId004 | "Kesseltemperatur" | | Name: @@ -65,11 +59,18 @@ Note: Not all available types of the ISM8 interface are fully supported, but this can be extended. For the moment the following data types are implemented: -- DPT-Bool: `1.001`, `1.002`, `1.003`, `1.009` -- DPT-Scaling: `5.001` -- DPT-Value: `9.001`, `9.002`, `9.006` -- DPT-FlowRate: `13.002` -- DPT-Mode: `20.102`, `20.103`, `20.105` +| Channel type | Datapoint type | Item type | R/W | KNX-type's | +|----------------|--------------------------------|---------------------------|-----|----------------------------| +| switch-rw | Digital DataPoint | Switch | R/W | 1.001, 1.002, 1.003, 1.009 | +| switch-r | Digital Readonly DataPoint | Switch | R | 1.001, 1.002, 1.003, 1.009 | +| percentage-rw | Percentage DataPoint | Number:Dimensionless | R/W | 5.001 | +| percentage-r | Percentage Readonly DataPoint | Number:Dimensionless | R | 5.001 | +| temperature-rw | Temperature DataPoint | Number:Temperature | R/W | 9.001,9.002 | +| temperature-r | Temperature Readonly DataPoint | Number:Temperature | R | 9.002,9.002 | +| pressure-r | Pressure Readonly DataPoint | Number:Pressure | R | 9.006 | +| flowrate-r | Flowrate Readonly DataPoint | Number:VolumetricFlowRate | R | 13.002 | +| mode-rw | Mode DataPoint | Number:Dimensionless | R/W | 20.102, 20.103, 20.105 | +| mode-r | Mode Readonly DataPoint | Number:Dimensionless | R | 20.102, 20.103, 20.105 | ## Full Example @@ -78,29 +79,29 @@ For the moment the following data types are implemented: ```java Thing ism8:device:heater "Wolf Heizung" [portNumber=12004] { - Type switch-readonly : DpId001 "Störung Heizgerät" [id=1, type="1.001"] - Type number-readonly : DpId002 "Betriebsart" [id=2, type="20.105"] - Type number-readonly : DpId003 "Brennerleistung" [id=3, type="5.001"] - Type number-readonly : DpId004 "Kesseltemperatur" [id=4, type="9.001"] - Type number-readonly : DpId006 "Rücklauftemperatur" [id=6, type="9.001"] - Type number-readonly : DpId007 "Warmwassertemperatur" [id=7, type="9.001"] - Type number-readonly : DpId008 "Außentemperatur" [id=8, type="9.001"] - Type switch-readonly : DpId009 "Status Flamme" [id=9, type="1.001"] - Type number-readonly : DpId013 "Anlagendruck" [id=13, type="9.006"] - Type number-readonly : DpId053 "Störung Systemmodul" [id=53, type="1.001"] - Type number-readonly : DpId054 "Außentemperatur Systemmodul" [id=54, type="9.001"] - Type number : DpId056 "Sollwert Warmwasser" [id=56, type="9.001"] - Type number : DpId057 "Betriebsart Heizkreis" [id=57, type="20.102"] - Type number : DpId058 "Betriebsart Warmwasser" [id=58, type="20.103"] - Type number : DpId065 "Sollwertverschiebung" [id=65, type="9.002"] - Type number-readonly : DpId148 "CML Störung" [id=148, type="1.001"] - Type number : DpId149 "CWL Betriebsart" [id=149, type="20.102"] - Type number-readonly : DpId163 "CWL Lüftungsstufe" [id=163, type="5.001"] - Type number-readonly : DpId164 "CWL Ablufttemperatur" [id=164, type="9.001"] - Type number-readonly : DpId165 "CWL Zulufttemperatur" [id=165, type="9.001"] - Type number-readonly : DpId166 "CWL Luftdurchsatz Zuluft" [id=166, type="13.002"] - Type number-readonly : DpId167 "CWL Luftdurchsatz Abluft" [id=167, type="13.002"] - Type number-readonly : DpId192 "CML Filterwarnung" [id=192, type="1.001"] + Type switch-r : DpId001 "Störung Heizgerät" [id=1, type="1.001"] + Type number-r : DpId002 "Betriebsart" [id=2, type="20.105"] + Type percentage-r : DpId003 "Brennerleistung" [id=3, type="5.001"] + Type temperature-r : DpId004 "Kesseltemperatur" [id=4, type="9.001"] + Type temperature-r : DpId006 "Rücklauftemperatur" [id=6, type="9.001"] + Type temperature-r : DpId007 "Warmwassertemperatur" [id=7, type="9.001"] + Type temperature-r : DpId008 "Außentemperatur" [id=8, type="9.001"] + Type switch-r : DpId009 "Status Flamme" [id=9, type="1.001"] + Type temperature-r : DpId013 "Anlagendruck" [id=13, type="9.006"] + Type switch-r : DpId053 "Störung Systemmodul" [id=53, type="1.001"] + Type temperature-r : DpId054 "Außentemperatur Systemmodul" [id=54, type="9.001"] + Type temperature-rw : DpId056 "Sollwert Warmwasser" [id=56, type="9.001"] + Type mode-rw : DpId057 "Betriebsart Heizkreis" [id=57, type="20.102"] + Type mode-rw : DpId058 "Betriebsart Warmwasser" [id=58, type="20.103"] + Type temperature-rw : DpId065 "Sollwertverschiebung" [id=65, type="9.002"] + Type switch-rw : DpId148 "CML Störung" [id=148, type="1.001"] + Type mode-rw : DpId149 "CWL Betriebsart" [id=149, type="20.102"] + Type percentage-r : DpId163 "CWL Lüftungsstufe" [id=163, type="5.001"] + Type temperature-r : DpId164 "CWL Ablufttemperatur" [id=164, type="9.001"] + Type temperature-r : DpId165 "CWL Zulufttemperatur" [id=165, type="9.001"] + Type flowrate-r : DpId166 "CWL Luftdurchsatz Zuluft" [id=166, type="13.002"] + Type flowrate-r : DpId167 "CWL Luftdurchsatz Abluft" [id=167, type="13.002"] + Type switch-r : DpId192 "CML Filterwarnung" [id=192, type="1.001"] } ``` diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8BindingConstants.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8BindingConstants.java index 8514937c79..1d02e43d45 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8BindingConstants.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8BindingConstants.java @@ -24,7 +24,7 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class Ism8BindingConstants { // Binding ID - private static final String BINDING_ID = "ism8"; + public static final String BINDING_ID = "ism8"; // List of all Thing Type UIDs @@ -41,4 +41,8 @@ public class Ism8BindingConstants { * */ public static final String PORT_NUMBER = "portNumber"; + + // Channel Configuration parameters + public static final String CHANNEL_CONFIG_ID = "id"; + public static final String CHANNEL_CONFIG_TYPE = "type"; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8Handler.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8Handler.java index b6cb0a5362..de614d6683 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8Handler.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8Handler.java @@ -12,21 +12,25 @@ */ package org.openhab.binding.ism8.internal; +import static org.openhab.binding.ism8.internal.Ism8BindingConstants.*; + import java.io.IOException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.ism8.internal.util.Ism8DomainMap; import org.openhab.binding.ism8.server.DataPointChangedEvent; import org.openhab.binding.ism8.server.IDataPoint; import org.openhab.binding.ism8.server.IDataPointChangeListener; import org.openhab.binding.ism8.server.Server; -import org.openhab.core.library.types.QuantityType; +import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,75 +51,51 @@ public class Ism8Handler extends BaseThingHandler implements IDataPointChangeLis super(thing); } - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - this.logger.debug("Ism8: Handle command = {} {}", channelUID.getId(), command); - Channel channel = getThing().getChannel(channelUID); - Server svr = this.server; - if (channel != null && svr != null) { - if (channel.getConfiguration().containsKey("id")) { - IDataPoint dataPoint = null; - try { - int id = Integer.parseInt(channel.getConfiguration().get("id").toString()); - this.logger.debug("Channel '{}' writting into ID '{}'", channel.getUID().getId(), id); - this.updateState(channelUID, new QuantityType<>(command.toString())); - dataPoint = svr.getDataPoint(id); - } catch (NumberFormatException e) { - this.logger.debug("Updating State of ISM DataPoint '{}' failed. '{}'", channel.getConfiguration(), - e.getMessage()); - } - - if (dataPoint != null) { - try { - svr.sendData(dataPoint.createWriteData(command)); - } catch (IOException e) { - this.logger.debug("Writting to ISM DataPoint '{}' failed. '{}'", dataPoint.getId(), - e.getMessage()); - } - } - } - } - } - - @Override - public void dispose() { - Server svr = this.server; - if (svr != null) { - svr.stopServerThread(); - } - } - @Override public void initialize() { this.config = getConfigAs(Ism8Configuration.class); Ism8Configuration cfg = this.config; final String uid = this.getThing().getUID().getAsString(); Server svr = new Server(cfg.getPortNumber(), uid); + this.server = svr; for (Channel channel : getThing().getChannels()) { - if (channel.getConfiguration().containsKey("id") && channel.getConfiguration().containsKey("type")) { - try { - int id = Integer.parseInt(channel.getConfiguration().get("id").toString()); - String type = channel.getConfiguration().get("type").toString(); - String description = channel.getLabel(); - if (type != null && description != null) { - svr.addDataPoint(id, type, description); - } - } catch (NumberFormatException e) { - this.logger.warn( - "Ism8 initialize: ID couldn't be converted correctly. Check the configuration of channel {}. Cfg={}", - channel.getLabel(), channel.getConfiguration()); - } + Configuration channelConfig = channel.getConfiguration(); + if (registerDataPointToServer(channelConfig, channel.getLabel())) { + logger.debug("Ism8: Channel={} registered datapoint", channelConfig.toString()); } else { - this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(), - channel.getConfiguration()); + logger.warn("Ism8: Channel={} failed to register datapoint", channelConfig.toString()); } - this.logger.debug("Ism8: Channel={}", channel.getConfiguration().toString()); } this.updateStatus(ThingStatus.UNKNOWN); svr.addDataPointChangeListener(this); scheduler.execute(svr::start); - this.server = svr; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Ism8: Handle command = {} {}", channelUID.getId(), command); + Channel channel = getThing().getChannel(channelUID); + if (channel == null) { + return; + } + IDataPoint dataPoint = getDataPoint(channel); + if (dataPoint == null) { + return; + } + if (command == RefreshType.REFRESH) { + updateChannel(dataPoint); + } else { + setDataPoint(dataPoint, command); + } + } + + @Override + public void dispose() { + Server svr = this.server; + if (svr != null) { + svr.stopServerThread(); + } } @Override @@ -123,8 +103,8 @@ public class Ism8Handler extends BaseThingHandler implements IDataPointChangeLis if (e != null) { IDataPoint dataPoint = e.getDataPoint(); if (dataPoint != null) { - this.logger.debug("Ism8: dataPointChanged {}", dataPoint.toString()); - this.updateDataPoint(dataPoint); + logger.debug("Ism8: dataPointChanged {}", dataPoint.toString()); + this.updateChannel(dataPoint); } } } @@ -134,25 +114,89 @@ public class Ism8Handler extends BaseThingHandler implements IDataPointChangeLis this.updateStatus(status); } - private void updateDataPoint(IDataPoint dataPoint) { + private boolean registerDataPointToServer(Configuration config, @Nullable String description) { + Server svr = this.server; + if (config.containsKey(CHANNEL_CONFIG_ID) && config.containsKey(CHANNEL_CONFIG_TYPE)) { + try { + int id = Integer.parseInt(config.get(CHANNEL_CONFIG_ID).toString()); + String type = config.get(CHANNEL_CONFIG_TYPE).toString(); + if (svr != null && type != null && description != null) { + svr.addDataPoint(id, type, description); + return true; + } + } catch (NumberFormatException e) { + logger.warn("Ism8: ID couldn't be converted correctly. Check the configuration of channel {}. Cfg={}", + description, config); + } + } else { + logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", description, config); + } + return false; + } + + private void setDataPoint(IDataPoint dataPoint, Command command) { + Server svr = this.server; + if (svr != null) { + try { + svr.sendData(Ism8DomainMap.toISM8WriteData(dataPoint, command)); + } catch (IOException e) { + logger.debug("Writting to Ism8 DataPoint '{}' failed. '{}'", dataPoint.getId(), e.getMessage()); + } + } + } + + private @Nullable IDataPoint getDataPoint(Channel channel) { + IDataPoint dataPoint = null; + Configuration config = channel.getConfiguration(); + Server svr = this.server; + if (svr == null) { + return dataPoint; + } + if (config.containsKey(CHANNEL_CONFIG_ID) && config.containsKey(CHANNEL_CONFIG_TYPE)) { + try { + int id = Integer.parseInt(config.get(CHANNEL_CONFIG_ID).toString()); + dataPoint = svr.getDataPoint(id); + } catch (NumberFormatException e) { + logger.debug("Retrieving Ism8 DataPoint '{}' failed. '{}'", channel.getConfiguration(), e.getMessage()); + } + } else { + logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(), + channel.getConfiguration()); + } + return dataPoint; + } + + private boolean updateChannel(Channel channel, IDataPoint dataPoint) { + try { + int id = Integer.parseInt(channel.getConfiguration().get(CHANNEL_CONFIG_ID).toString()); + if (id == dataPoint.getId()) { + if (dataPoint.getValueObject() != null) { + logger.debug("Ism8: updating channel {} with datapoint: {}", channel.getUID().getAsString(), + dataPoint.getId()); + updateState(channel.getUID(), Ism8DomainMap.toOpenHABState(dataPoint)); + return true; + } + } else { + logger.debug("Ism8 channel: {} and DataPoint do not have a matching Id: {} vs {}", channel.getUID(), id, + dataPoint.getId()); + } + } catch (NumberFormatException e) { + logger.warn( + "Ism8 updateChannel: ID couldn't be converted correctly. Check the configuration of channel {}. {}", + channel.getLabel(), e.getMessage()); + } + return false; + } + + private void updateChannel(IDataPoint dataPoint) { this.updateStatus(ThingStatus.ONLINE); for (Channel channel : getThing().getChannels()) { - if (channel.getConfiguration().containsKey("id")) { - try { - int id = Integer.parseInt(channel.getConfiguration().get("id").toString()); - if (id == dataPoint.getId()) { - this.logger.debug("Ism8 updateDataPoint ID:{} {}", dataPoint.getId(), dataPoint.getValueText()); - Object val = dataPoint.getValueObject(); - if (val != null) { - updateState(channel.getUID(), new QuantityType<>(val.toString())); - } - } - } catch (NumberFormatException e) { - this.logger.warn( - "Ism8 updateDataPoint: ID couldn't be converted correctly. Check the configuration of channel {}. {}", - channel.getLabel(), e.getMessage()); + if (channel.getConfiguration().containsKey(CHANNEL_CONFIG_ID)) { + if (updateChannel(channel, dataPoint)) { + break; } } } + logger.debug("Ism8: no channel was found for DataPoint id: {}", dataPoint.getId()); } } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/util/Ism8DomainMap.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/util/Ism8DomainMap.java new file mode 100644 index 0000000000..bf5144abf0 --- /dev/null +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/util/Ism8DomainMap.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2024 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.ism8.internal.util; + +import java.util.Objects; + +import javax.measure.Unit; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.ism8.server.IDataPoint; +import org.openhab.core.library.dimension.VolumetricFlowRate; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link Ism8DomainMap} class holds static methods for domain mapping + * + * @author Leo Siepel - Initial contribution + */ + +@NonNullByDefault +public final class Ism8DomainMap { + + private static final Logger LOGGER = LoggerFactory.getLogger(Ism8DomainMap.class); + + public static State toOpenHABState(IDataPoint dataPoint) { + Object value = dataPoint.getValueObject(); + if (value == null) { + return UnDefType.NULL; + } + + Unit unit = dataPoint.getUnit(); + if (SIUnits.CELSIUS.equals(unit)) { + return new QuantityType((Double) value, SIUnits.CELSIUS); + } else if (Units.KELVIN.equals(unit)) { + return new QuantityType((Double) value, Units.KELVIN); + } else if (Units.CUBICMETRE_PER_HOUR.equals(unit)) { + return new QuantityType((Double) value, Units.CUBICMETRE_PER_HOUR); + } else if (Units.BAR.equals(unit)) { + return new QuantityType((Double) value, Units.BAR); + } else if (Units.PERCENT.equals(unit)) { + return new QuantityType((Double) value, Units.PERCENT); + } else if (Units.ONE.equals(unit)) { + return new QuantityType((Double) value, Units.ONE); + } else if (value instanceof Boolean) { + return OnOffType.from((boolean) value); + } else if (value instanceof Byte) { + return new QuantityType((byte) value, Units.ONE); + } + + LOGGER.debug("Failed to map DataPoint id: {} val: {}, to UoM state. Performing fallback.", dataPoint.getId(), + dataPoint.getValueText()); + + return new QuantityType<>(value.toString()); + } + + public static byte[] toISM8WriteData(IDataPoint dataPoint, Command command) { + if (command instanceof QuantityType) { + Unit expectedUnit = dataPoint.getUnit(); + if (expectedUnit != null) { + QuantityType state = Objects.requireNonNull(((QuantityType) command).toUnit(expectedUnit)); + return dataPoint.createWriteData(state.doubleValue()); + } + return dataPoint.createWriteData(command); + } else if (command instanceof OnOffType) { + return dataPoint.createWriteData(command); + } + return new byte[0]; + } +} diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBase.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBase.java index c629909e9b..8fb076f404 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBase.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBase.java @@ -14,6 +14,8 @@ package org.openhab.binding.ism8.server; import java.nio.ByteBuffer; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.slf4j.Logger; @@ -32,7 +34,8 @@ public abstract class DataPointBase<@Nullable T> implements IDataPoint { private final String knxDataType; private final String description; private T value; - private String unit = ""; + @Nullable + private Unit unit; protected DataPointBase(int id, String knxDataType, String description) { this.id = id; @@ -81,7 +84,7 @@ public abstract class DataPointBase<@Nullable T> implements IDataPoint { public abstract String getValueText(); @Override - public String getUnit() { + public @Nullable Unit getUnit() { return this.unit; } @@ -89,7 +92,7 @@ public abstract class DataPointBase<@Nullable T> implements IDataPoint { * Sets the unit of the data-point. * */ - public void setUnit(String value) { + public void setUnit(@Nullable Unit value) { this.unit = value; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBool.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBool.java index 6b41f398c0..8eba2c97fd 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBool.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBool.java @@ -38,7 +38,7 @@ public class DataPointBool extends DataPointBase<@Nullable Boolean> { @Override @Nullable public Object getValueObject() { - return this.getValue() ? "1" : "0"; + return this.getValue(); } @Override @@ -55,7 +55,7 @@ public class DataPointBool extends DataPointBase<@Nullable Boolean> { @Override protected byte[] convertWriteValue(Object value) { String valueText = value.toString().toLowerCase(); - if ("true".equalsIgnoreCase(valueText) || "1".equalsIgnoreCase(valueText)) { + if ("true".equalsIgnoreCase(valueText) || "1".equalsIgnoreCase(valueText) || "ON".equalsIgnoreCase(valueText)) { this.setValue(true); return new byte[] { 0x01 }; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointLongValue.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointLongValue.java index 7d29695031..18e9f8dc4e 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointLongValue.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointLongValue.java @@ -16,6 +16,7 @@ import java.nio.ByteBuffer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.unit.Units; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,11 +35,11 @@ public class DataPointLongValue extends DataPointBase<@Nullable Double> { super(id, knxDataType, description); if ("13.002".equals(knxDataType)) { - this.setUnit("m³/h"); + this.setUnit(Units.CUBICMETRE_PER_HOUR); this.factor = 0.0001f; this.outputFormat = "%.1f"; } else { - this.setUnit(""); + this.setUnit(Units.ONE); this.factor = 1.0f; this.outputFormat = "%.1f"; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointScaling.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointScaling.java index a98ec8859f..c714edcf04 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointScaling.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointScaling.java @@ -14,6 +14,7 @@ package org.openhab.binding.ism8.server; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.unit.Units; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +30,7 @@ public class DataPointScaling extends DataPointBase<@Nullable Double> { public DataPointScaling(int id, String knxDataType, String description) { super(id, knxDataType, description); - this.setUnit("%"); + this.setUnit(Units.PERCENT); this.outputFormat = "%.1f"; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointValue.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointValue.java index b5d3be86cb..fbb5f5b5d0 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointValue.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointValue.java @@ -16,6 +16,8 @@ import java.nio.ByteBuffer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,15 +36,15 @@ public class DataPointValue extends DataPointBase<@Nullable Double> { super(id, knxDataType, description); this.factor = 0.0f; if ("9.001".equals(knxDataType)) { - this.setUnit("°C"); + this.setUnit(SIUnits.CELSIUS); this.factor = 0.01f; this.outputFormat = "%.1f"; } else if ("9.002".equals(knxDataType)) { - this.setUnit("°K"); + this.setUnit(Units.KELVIN); this.factor = 0.01f; this.outputFormat = "%.1f"; } else if ("9.006".equals(knxDataType)) { - this.setUnit("Bar"); + this.setUnit(Units.BAR); this.factor = 0.0000001f; this.outputFormat = "%.2f"; } diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/IDataPoint.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/IDataPoint.java index c32ce87f43..e202a8d9cd 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/IDataPoint.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/IDataPoint.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.ism8.server; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -28,7 +30,8 @@ public interface IDataPoint { * Gets the unit of the data-point. * */ - String getUnit(); + @Nullable + Unit getUnit(); /** * Gets the type of the data-point. diff --git a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/Server.java b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/Server.java index 0ef9c9dc3b..8ae0f85014 100644 --- a/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/Server.java +++ b/bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/Server.java @@ -157,6 +157,7 @@ public class Server extends Thread { ServerSocket serverSock = this.serverSocket; if (serverSock != null) { serverSock.close(); + this.serverSocket = null; } Socket clientSocket = this.client; @@ -166,6 +167,8 @@ public class Server extends Thread { } } catch (IOException e) { logger.debug("Error stopping Communication. {}", e.getMessage()); + this.serverSocket = null; + this.client = null; } } diff --git a/bundles/org.openhab.binding.ism8/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ism8/src/main/resources/OH-INF/thing/thing-types.xml index 477cf168a0..e21c8946d5 100644 --- a/bundles/org.openhab.binding.ism8/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.ism8/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,7 +4,8 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + ISM8 Interface @@ -17,7 +18,7 @@ - + Switch @@ -38,7 +39,7 @@ - + Switch @@ -60,9 +61,9 @@ - - Number - + + Number:Dimensionless + @@ -74,10 +75,119 @@ Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + Number:Dimensionless + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + + + Number:Temperature + + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + + + + Number:Temperature + + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + + Number:Pressure + + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + + Number:VolumetricFlowRate + + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + + + + + + + + Number:Dimensionless + + + + + Put the number of the DataPoint ID to be mapped from the heating sytem. + + + + Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) + @@ -86,9 +196,9 @@ - - Number - + + Number:Dimensionless + @@ -98,7 +208,6 @@ Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001) - diff --git a/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/Ism8HandlerTest.java b/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/Ism8HandlerTest.java new file mode 100644 index 0000000000..cdcb3a1e38 --- /dev/null +++ b/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/Ism8HandlerTest.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010-2024 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.ism8.internal; + +import static org.mockito.ArgumentMatchers.eq; +import static org.openhab.binding.ism8.internal.Ism8BindingConstants.*; + +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.ism8.server.DataPointBool; +import org.openhab.binding.ism8.server.DataPointByteValue; +import org.openhab.binding.ism8.server.DataPointChangedEvent; +import org.openhab.binding.ism8.server.DataPointValue; +import org.openhab.binding.ism8.server.IDataPoint; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.util.HexUtils; + +/** + * + * @author Leo Siepel - Initial contribution + * + */ +@ExtendWith(MockitoExtension.class) +@NonNullByDefault +public class Ism8HandlerTest { + + private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback; + private @NonNullByDefault({}) Thing ism8Thing; + private @NonNullByDefault({}) Ism8Handler thingHandler; + private ThingUID thingUID = new ThingUID(BINDING_ID, "ism8server"); + private ChannelUID channel1001 = new ChannelUID(thingUID, "switch1"); + private ChannelUID channel9001 = new ChannelUID(thingUID, "tempC"); + private ChannelUID channel9002 = new ChannelUID(thingUID, "tempD"); + private ChannelUID channel20001 = new ChannelUID(thingUID, "mode1"); + + @BeforeEach + public void initialize() { + Configuration config = new Configuration(); + + ism8Thing = ThingBuilder.create(THING_TYPE_DEVICE, thingUID).withConfiguration(config) + .withChannel(ChannelBuilder.create(channel9002, "Number:Temperature") + .withConfiguration(createChannelConfig("5", "9.002")).build()) + .withChannel(ChannelBuilder.create(channel9001, "Number:Temperature") + .withConfiguration(createChannelConfig("4", "9.001")).build()) + .withChannel(ChannelBuilder.create(channel1001, "Switch") + .withConfiguration(createChannelConfig("9", "1.001")).build()) + .withChannel(ChannelBuilder.create(channel20001, "Switch") + .withConfiguration(createChannelConfig("2", "20.001")).build()) + .build(); + + thingHandler = new Ism8Handler(ism8Thing); + thingHandler.initialize(); + } + + private Configuration createChannelConfig(String id, String type) { + Configuration config = new Configuration(); + config.put(CHANNEL_CONFIG_ID, id); + config.put(CHANNEL_CONFIG_TYPE, type); + return config; + } + + @Test + public void process1001MessageAndUpdateChannel() { + // arrange + IDataPoint dataPoint = new DataPointBool(9, "1.001", "Datapoint_1.001"); + dataPoint.processData(HexUtils.hexToBytes("0009030100")); + DataPointChangedEvent event = new DataPointChangedEvent(new Object(), dataPoint); + thingHandler.setCallback(thingHandlerCallback); + + // act + thingHandler.dataPointChanged(event); + + // assert + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channel1001), eq(OnOffType.from(false))); + } + + @Test + public void process9001MessageAndUpdateChannel() { + // arrange + IDataPoint dataPoint = new DataPointValue(4, "9.001", "Datapoint_9.001"); + dataPoint.processData(HexUtils.hexToBytes("000403020FE9")); + DataPointChangedEvent event = new DataPointChangedEvent(new Object(), dataPoint); + thingHandler.setCallback(thingHandlerCallback); + + // act + thingHandler.dataPointChanged(event); + + // assert + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channel9001), + eq(new QuantityType(40.49999909475446, SIUnits.CELSIUS))); + } + + @Test + public void process20001MessageAndUpdateChannel() { + // arrange + IDataPoint dataPoint = new DataPointByteValue(2, "20.102", "Datapoint_20.102"); + dataPoint.processData(HexUtils.hexToBytes("0002030101")); + DataPointChangedEvent event = new DataPointChangedEvent(new Object(), dataPoint); + thingHandler.setCallback(thingHandlerCallback); + + // act + thingHandler.dataPointChanged(event); + + // assert + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channel20001), + eq(new QuantityType(1, Units.ONE))); + } +} diff --git a/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/util/Ism8DomainMapTest.java b/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/util/Ism8DomainMapTest.java new file mode 100644 index 0000000000..05ca48ca1b --- /dev/null +++ b/bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/util/Ism8DomainMapTest.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2010-2024 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.ism8.internal.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.ism8.server.DataPointBool; +import org.openhab.binding.ism8.server.DataPointByteValue; +import org.openhab.binding.ism8.server.DataPointScaling; +import org.openhab.binding.ism8.server.DataPointValue; +import org.openhab.binding.ism8.server.IDataPoint; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.util.HexUtils; + +/** + * + * @author Leo Siepel - Initial contribution + * + */ +@ExtendWith(MockitoExtension.class) +@NonNullByDefault +public class Ism8DomainMapTest { + + @BeforeEach + public void initialize() { + } + + @Test + public void mapDataPointValueToMessageSameUnit() { + // arrange + Command command = new QuantityType<>(40.5, SIUnits.CELSIUS); + IDataPoint dataPoint = new DataPointValue(4, "9.001", "Datapoint_9.001"); + + // act + byte[] result = Ism8DomainMap.toISM8WriteData(dataPoint, command); + + // assert + assertEquals(String.format("%.1f", 40.5), dataPoint.getValueText()); + assertEquals("0620F080001604000000F0C100040001000400020FE9", HexUtils.bytesToHex(result)); + } + + @Test + public void mapDataPointValueToMessagOtherUnit() { + // arrange + Command command = new QuantityType<>(104.9, ImperialUnits.FAHRENHEIT); + IDataPoint dataPoint = new DataPointValue(4, "9.001", "Datapoint_9.001"); + + // act + byte[] result = Ism8DomainMap.toISM8WriteData(dataPoint, command); + + // assert + assertEquals(String.format("%.1f", 40.5), dataPoint.getValueText()); + assertEquals("0620F080001604000000F0C100040001000400020FE9", HexUtils.bytesToHex(result)); + } + + @Test + public void mapDataPointBoolToMessage() { + // arrange + Command command = OnOffType.from(true); + IDataPoint dataPoint = new DataPointBool(9, "1.001", "Datapoint_1.001"); + + // act + byte[] result = Ism8DomainMap.toISM8WriteData(dataPoint, command); + + // assert + assertEquals("True", dataPoint.getValueText()); + assertEquals("0620F080001504000000F0C1000900010009000101", HexUtils.bytesToHex(result)); + } + + @Test + public void mapDataPointScalingToMessage() { + // arrange + Command command = new QuantityType(13, Units.PERCENT); + IDataPoint dataPoint = new DataPointScaling(3, "5.001", "Datapoint_5.001"); + + // act + byte[] result = Ism8DomainMap.toISM8WriteData(dataPoint, command); + + // assert + assertEquals(String.format("%.1f", 13.0), dataPoint.getValueText()); + assertEquals("0620F080001504000000F0C1000300010003000121", HexUtils.bytesToHex(result)); + } + + @Test + public void mapDataPointValueToOHState() { + // arrange + IDataPoint dataPoint = new DataPointValue(4, "9.001", "Datapoint_9.001"); + dataPoint.processData(HexUtils.hexToBytes("000403020FE9")); + + // act + State result = Ism8DomainMap.toOpenHABState(dataPoint); + + // assert + assertEquals(new QuantityType(40.49999909475446, SIUnits.CELSIUS), result); + } + + @Test + public void mapDataPointLongToOHState() { + // arrange + IDataPoint dataPoint = new DataPointByteValue(2, "20.102", "Datapoint_20.102"); + dataPoint.processData(HexUtils.hexToBytes("0002030101")); + + // act + State result = Ism8DomainMap.toOpenHABState(dataPoint); + + // assert + assertEquals(new QuantityType(1, Units.ONE), result); + } +}