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
76 changes: 39 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 | DpId004 | "Kesseltemperatur" | |
lsiepel marked this conversation as resolved.
Show resolved Hide resolved

Name:

Expand All @@ -65,11 +59,19 @@ 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 +80,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 temperature-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,13 @@ public class Ism8BindingConstants {
*
*/
public static final String PORT_NUMBER = "portNumber";

// Channel Configuration parameters

/**
* Channel id configuration parameter
*
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Channel id configuration parameter
*
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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,75 +51,51 @@ 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) {
this.logger.debug("Ism8: Handle command = {} {}", channelUID.getId(), command);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8: Handle command = {} {}", channelUID.getId(), command);
logger.debug("Ism8: Handle command = {} {}", channelUID.getId(), command);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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
Expand All @@ -124,7 +104,7 @@ public void dataPointChanged(@Nullable DataPointChangedEvent e) {
IDataPoint dataPoint = e.getDataPoint();
if (dataPoint != null) {
this.logger.debug("Ism8: dataPointChanged {}", dataPoint.toString());
this.updateDataPoint(dataPoint);
this.updateChannel(dataPoint);
}
}
}
Expand All @@ -134,25 +114,91 @@ 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) {
this.logger.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.warn(
logger.warn(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"Ism8: ID couldn't be converted correctly. Check the configuration of channel {}. Cfg={}",
description, config);
}
} else {
this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", description, config);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", description, config);
logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", description, config);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
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) {
this.logger.debug("Writting to Ism8 DataPoint '{}' failed. '{}'", dataPoint.getId(), e.getMessage());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Writting to Ism8 DataPoint '{}' failed. '{}'", dataPoint.getId(), e.getMessage());
logger.debug("Writing to Ism8 DataPoint '{}' failed. '{}'", dataPoint.getId(), e.getMessage());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}
}

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) {
this.logger.debug("Retrieving Ism8 DataPoint '{}' failed. '{}'", channel.getConfiguration(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Retrieving Ism8 DataPoint '{}' failed. '{}'", channel.getConfiguration(),
logger.debug("Retrieving Ism8 DataPoint '{}' failed. '{}'", channel.getConfiguration(),

e.getMessage());
}
} else {
this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(),
logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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) {
this.logger.debug("Ism8: updating channel {} with datapoint: {}", channel.getUID().getAsString(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8: updating channel {} with datapoint: {}", channel.getUID().getAsString(),
logger.debug("Ism8: updating channel {} with datapoint: {}", channel.getUID().getAsString(),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

dataPoint.getId());
updateState(channel.getUID(), Ism8DomainMap.toOpenHABState(dataPoint));
return true;
}
} else {
this.logger.debug("Ism8 channel: {} and DataPoint do not have a matching Id: {} vs {}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8 channel: {} and DataPoint do not have a matching Id: {} vs {}",
logger.debug("Ism8 channel: {} and DataPoint do not have a matching Id: {} vs {}",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

channel.getUID(), id, dataPoint.getId());
}
} catch (NumberFormatException e) {
this.logger.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.warn(
logger.warn(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"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;
}
}
}
this.logger.debug("Ism8: no channel was found for DataPoint id: {}", dataPoint.getId());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.logger.debug("Ism8: no channel was found for DataPoint id: {}", dataPoint.getId());
logger.debug("Ism8: no channel was found for DataPoint id: {}", dataPoint.getId());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}
Loading