]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ism8] Add UoM support (#14206)
authorlsiepel <leosiepel@gmail.com>
Sun, 25 Feb 2024 10:56:34 +0000 (11:56 +0100)
committerGitHub <noreply@github.com>
Sun, 25 Feb 2024 10:56:34 +0000 (11:56 +0100)
* Add UoM support

Signed-off-by: lsiepel <leosiepel@gmail.com>
14 files changed:
bundles/org.openhab.binding.ism8/README.md
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8BindingConstants.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/Ism8Handler.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/internal/util/Ism8DomainMap.java [new file with mode: 0644]
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBase.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointBool.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointLongValue.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointScaling.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/DataPointValue.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/IDataPoint.java
bundles/org.openhab.binding.ism8/src/main/java/org/openhab/binding/ism8/server/Server.java
bundles/org.openhab.binding.ism8/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/Ism8HandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.ism8/src/test/java/org/openhab/binding/ism8/internal/util/Ism8DomainMapTest.java [new file with mode: 0644]

index 72a24a1053293f5d0b3d32349c7e7974dafe7d96..c1fc5666d3ee6ddf16da9b84e7ad7a7e5435ce3f 100644 (file)
@@ -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"]    
     }
 ```
 
index 8514937c79f9d218ef3c298fff7c8d7a98113e99..1d02e43d45263cbcdf5d7cbcdd363746398aeafc 100644 (file)
@@ -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";
 }
index b6cb0a5362959bcbe6bdcd8ce5c5ae26d2652667..de614d6683492b18b1812d0b92fec10e32daf8d3 100644 (file)
  */
 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 (file)
index 0000000..bf5144a
--- /dev/null
@@ -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<Temperature>((Double) value, SIUnits.CELSIUS);
+        } else if (Units.KELVIN.equals(unit)) {
+            return new QuantityType<Temperature>((Double) value, Units.KELVIN);
+        } else if (Units.CUBICMETRE_PER_HOUR.equals(unit)) {
+            return new QuantityType<VolumetricFlowRate>((Double) value, Units.CUBICMETRE_PER_HOUR);
+        } else if (Units.BAR.equals(unit)) {
+            return new QuantityType<Pressure>((Double) value, Units.BAR);
+        } else if (Units.PERCENT.equals(unit)) {
+            return new QuantityType<Dimensionless>((Double) value, Units.PERCENT);
+        } else if (Units.ONE.equals(unit)) {
+            return new QuantityType<Dimensionless>((Double) value, Units.ONE);
+        } else if (value instanceof Boolean) {
+            return OnOffType.from((boolean) value);
+        } else if (value instanceof Byte) {
+            return new QuantityType<Dimensionless>((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];
+    }
+}
index c629909e9b9b6d93b56d6bc005e3fe1dd60d44cd..8fb076f4046dd0b5c2fb50e5feb05c8ea900e53c 100644 (file)
@@ -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;
     }
 
index 6b41f398c047629f67bf46cd7651f89aaca605df..8eba2c97fdb7ae7fee89d4366e0012549b6be398 100644 (file)
@@ -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 };
         }
index 7d29695031b56ae11ecca1691ab6907e50b8ee48..18e9f8dc4ef62196891ace256de770df8987dd4a 100644 (file)
@@ -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";
         }
index a98ec8859f1b293d843385518427d6bb814c2ec1..c714edcf04c51dc11a3db9abc9563c0734b1e00e 100644 (file)
@@ -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";
     }
 
index b5d3be86cb7bf05aa96761cfb37c4e4b8f04bd00..fbb5f5b5d0b75978e62274b6fa9d10c0c1982cc9 100644 (file)
@@ -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";
         }
index c32ce87f434b6fc2bbb0087ee7807a41db288d45..e202a8d9cdcab9f846509696d2bd51d407900bdd 100644 (file)
@@ -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.
index 0ef9c9dc3b8ba847b73f3248cc06ef96decd4fd4..8ae0f850145d085509ddcb6aff3c3d8f03d38bf6 100644 (file)
@@ -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;
         }
     }
 
index 477cf168a041247c5eab7b7742ee027ccdeac13e..e21c8946d5ccbeafc20fae90a343244c2f5e2de3 100644 (file)
@@ -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">
 
-       <thing-type id="device" extensible="switch, switch-readonly, number, number-readonly">
+       <thing-type id="device"
+               extensible="switch-r, switch-rw, percentage-r, percentage-rw, temperature-r, temperature-rw, pressure-r, flowrate-r, mode-rw, mode-r">
                <label>ISM8 Device</label>
                <description>ISM8 Interface</description>
 
@@ -17,7 +18,7 @@
                </config-description>
        </thing-type>
 
-       <channel-type id="switch">
+       <channel-type id="switch-rw">
                <item-type>Switch</item-type>
                <label>Digital DataPoint</label>
                <config-description>
@@ -38,7 +39,7 @@
                </config-description>
        </channel-type>
 
-       <channel-type id="switch-readonly">
+       <channel-type id="switch-r">
                <item-type>Switch</item-type>
                <label>Digital Readonly DataPoint</label>
                <state readOnly="true"/>
@@ -60,9 +61,9 @@
                </config-description>
        </channel-type>
 
-       <channel-type id="number-readonly">
-               <item-type>Number</item-type>
-               <label>Value Readonly DataPoint</label>
+       <channel-type id="percentage-r">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Percentage Readonly DataPoint</label>
                <state readOnly="true"/>
                <config-description>
                        <parameter name="id" type="integer" required="true">
                                <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
                                <options>
                                        <option value="5.001">DPT_Scaling</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="percentage-rw">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Percentage DataPoint</label>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
+                                       <option value="5.001">DPT_Scaling</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="temperature-r">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature Readonly DataPoint</label>
+               <state readOnly="true"/>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
+                                       <option value="9.001">DPT_Value_Temp</option>
+                                       <option value="9.002">DPT_Value_Tempd</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="temperature-rw">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature DataPoint</label>
+               <state readOnly="true"/>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
                                        <option value="9.001">DPT_Value_Temp</option>
                                        <option value="9.002">DPT_Value_Tempd</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="pressure-r">
+               <item-type>Number:Pressure</item-type>
+               <label>Pressure Readonly DataPoint</label>
+               <state readOnly="true"/>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
                                        <option value="9.006">DPT_Value_Pres</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="flowrate-r">
+               <item-type>Number:VolumetricFlowRate</item-type>
+               <label>Flowrate Readonly DataPoint</label>
+               <state readOnly="true"/>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
                                        <option value="13.002">DPT_FlowRate</option>
+                               </options>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="mode-r">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Mode Readonly DataPoint</label>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>DP ID</label>
+                               <description>Put the number of the DataPoint ID to be mapped from the heating sytem.</description>
+                       </parameter>
+                       <parameter name="type" type="text" required="true">
+                               <label>Type</label>
+                               <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
+                               <options>
                                        <option value="20.102">DPT_HVACMode</option>
                                        <option value="20.103">DPT_DHWMode</option>
                                        <option value="20.105">DPT_HVACContrMode</option>
                </config-description>
        </channel-type>
 
-       <channel-type id="number">
-               <item-type>Number</item-type>
-               <label>Value DataPoint</label>
+       <channel-type id="mode-rw">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Mode DataPoint</label>
                <config-description>
                        <parameter name="id" type="integer" required="true">
                                <label>DP ID</label>
                                <label>Type</label>
                                <description>Put the KNX-type of the DataPoint (e.g. DPT_Value_Temp / 9.001)</description>
                                <options>
-                                       <option value="9.001">DPT_Value_Temp</option>
                                        <option value="20.102">DPT_HVACMode</option>
                                        <option value="20.103">DPT_DHWMode</option>
                                        <option value="20.105">DPT_HVACContrMode</option>
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 (file)
index 0000000..cdcb3a1
--- /dev/null
@@ -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<Temperature>(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<Dimensionless>(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 (file)
index 0000000..05ca48c
--- /dev/null
@@ -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<Dimensionless>(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<Temperature>(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<Dimensionless>(1, Units.ONE), result);
+    }
+}