Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ism8] Add UoM support #14206

Merged
merged 16 commits into from
Feb 25, 2024
75 changes: 38 additions & 37 deletions bundles/org.openhab.binding.ism8/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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

Expand All @@ -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"]
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
@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

Expand All @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -47,84 +51,60 @@ public Ism8Handler(Thing thing) {
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
public void dataPointChanged(@Nullable DataPointChangedEvent e) {
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);
}
}
}
Expand All @@ -134,25 +114,89 @@ public void connectionStatusChanged(ThingStatus status) {
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());
}
}