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:
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
```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"]
}
```
@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
*
*/
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";
}
*/
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;
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
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);
}
}
}
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());
}
}
--- /dev/null
+/**
+ * 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];
+ }
+}
import java.nio.ByteBuffer;
+import javax.measure.Unit;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
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;
public abstract String getValueText();
@Override
- public String getUnit() {
+ public @Nullable Unit<?> getUnit() {
return this.unit;
}
* Sets the unit of the data-point.
*
*/
- public void setUnit(String value) {
+ public void setUnit(@Nullable Unit<?> value) {
this.unit = value;
}
@Override
@Nullable
public Object getValueObject() {
- return this.getValue() ? "1" : "0";
+ return this.getValue();
}
@Override
@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 };
}
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;
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";
}
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;
public DataPointScaling(int id, String knxDataType, String description) {
super(id, knxDataType, description);
- this.setUnit("%");
+ this.setUnit(Units.PERCENT);
this.outputFormat = "%.1f";
}
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;
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";
}
*/
package org.openhab.binding.ism8.server;
+import javax.measure.Unit;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
* Gets the unit of the data-point.
*
*/
- String getUnit();
+ @Nullable
+ Unit<?> getUnit();
/**
* Gets the type of the data-point.
ServerSocket serverSock = this.serverSocket;
if (serverSock != null) {
serverSock.close();
+ this.serverSocket = null;
}
Socket clientSocket = this.client;
}
} catch (IOException e) {
logger.debug("Error stopping Communication. {}", e.getMessage());
+ this.serverSocket = null;
+ this.client = null;
}
}
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>
</config-description>
</thing-type>
- <channel-type id="switch">
+ <channel-type id="switch-rw">
<item-type>Switch</item-type>
<label>Digital DataPoint</label>
<config-description>
</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"/>
</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>
--- /dev/null
+/**
+ * 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)));
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}