added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.ism8-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-ism8" description="ism8 Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ism8/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ism8BindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ism8BindingConstants {
|
||||
// Binding ID
|
||||
private static final String BINDING_ID = "ism8";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
|
||||
/**
|
||||
* Defines the thing type UID
|
||||
*
|
||||
*/
|
||||
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
|
||||
|
||||
// Thing Configuration parameters
|
||||
|
||||
/**
|
||||
* The port number configuration parameter
|
||||
*
|
||||
*/
|
||||
public static final String PORT_NUMBER = "portNumber";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ism8Configuration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ism8Configuration {
|
||||
private int portNumber;
|
||||
|
||||
/**
|
||||
* Gets the port number for the ISM8.
|
||||
*
|
||||
*/
|
||||
public int getPortNumber() {
|
||||
return portNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Ism8Handler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ism8Handler extends BaseThingHandler implements IDataPointChangeListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(Ism8Handler.class);
|
||||
|
||||
private @Nullable Ism8Configuration config;
|
||||
private @Nullable Server server;
|
||||
|
||||
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);
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
this.logger.debug("Ism8: ID or type missing - Channel={} Cfg={}", channel.getLabel(),
|
||||
channel.getConfiguration());
|
||||
}
|
||||
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 dataPointChanged(@Nullable DataPointChangedEvent e) {
|
||||
if (e != null) {
|
||||
IDataPoint dataPoint = e.getDataPoint();
|
||||
if (dataPoint != null) {
|
||||
this.logger.debug("Ism8: dataPointChanged {}", dataPoint.toString());
|
||||
this.updateDataPoint(dataPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionStatusChanged(ThingStatus status) {
|
||||
this.updateStatus(status);
|
||||
}
|
||||
|
||||
private void updateDataPoint(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.openhab.binding.ism8.internal.Ism8BindingConstants.THING_TYPE_DEVICE;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link ism8HandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.ism8")
|
||||
public class Ism8HandlerFactory extends BaseThingHandlerFactory {
|
||||
public static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Arrays.asList(THING_TYPE_DEVICE);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (THING_TYPE_DEVICE.equals(thingTypeUID)) {
|
||||
return new Ism8Handler(thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointBase} is the base class for all data points
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class DataPointBase<@Nullable T> implements IDataPoint {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointBase.class);
|
||||
|
||||
private final int id;
|
||||
private final String knxDataType;
|
||||
private final String description;
|
||||
private T value;
|
||||
private String unit = "";
|
||||
|
||||
protected DataPointBase(int id, String knxDataType, String description) {
|
||||
this.id = id;
|
||||
this.knxDataType = knxDataType;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKnxDataType() {
|
||||
return this.knxDataType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the data-point
|
||||
*
|
||||
*/
|
||||
public T getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the data-point
|
||||
*
|
||||
*/
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getValueObject() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String getValueText();
|
||||
|
||||
@Override
|
||||
public String getUnit() {
|
||||
return this.unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unit of the data-point.
|
||||
*
|
||||
*/
|
||||
public void setUnit(String value) {
|
||||
this.unit = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void processData(byte[] data);
|
||||
|
||||
@Override
|
||||
public byte[] createWriteData(Object value) {
|
||||
logger.debug("Convert into byte array '{}'", value);
|
||||
byte[] val = this.convertWriteValue(value);
|
||||
byte length = (byte) (val.length + 20);
|
||||
ByteBuffer list = ByteBuffer.allocate(length);
|
||||
list.put(KnxNetFrame.KNX_HEADER);
|
||||
list.put(KnxNetFrame.CONNECTION_HEADER);
|
||||
list.put((byte) 0xF0); // Main Service
|
||||
list.put(SubServiceType.DATAPOINT_VALUE_WRITE); // Sub Service
|
||||
byte low = (byte) (this.getId() & 0xFF);
|
||||
byte high = (byte) ((this.getId() & 0xFF) / 256);
|
||||
list.put(high);
|
||||
list.put(low); // Start DataPoint
|
||||
list.put((byte) 0x00); // Amount DataPoints (high-byte)
|
||||
list.put((byte) 0x01); // Amount DataPoints (low-byte)
|
||||
list.put(high);
|
||||
list.put(low); // Write: ID of DataPoint
|
||||
list.put((byte) 0x00); // State
|
||||
list.put((byte) val.length); // Length of Data
|
||||
list.put(val); // Data Value
|
||||
list.put(5, length);
|
||||
return list.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DataPoint %d=%s", this.getId(), this.getValueText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the value to be written into a data array of bytes.
|
||||
*
|
||||
*/
|
||||
protected abstract byte[] convertWriteValue(Object value);
|
||||
|
||||
/**
|
||||
* Checks the data to be processed.
|
||||
*
|
||||
*/
|
||||
protected boolean checkProcessData(byte[] data) {
|
||||
if (data.length < 4) {
|
||||
logger.debug("DataPoint-ProcessData: Data size too small ({}).", data.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
int dataPointId = Byte.toUnsignedInt(data[0]) * 256 + Byte.toUnsignedInt(data[1]);
|
||||
if (dataPointId != this.getId()) {
|
||||
logger.debug("DataPoint-ProcessData: Data contains the wrong ID ({}/{}).", dataPointId, this.getId());
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = data[3];
|
||||
int expectedLength = length + 4;
|
||||
if (length <= 0 && expectedLength != data.length) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong ({}/{}).", data.length, expectedLength);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointBool} is the data points for boolean values
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointBool extends DataPointBase<@Nullable Boolean> {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointBool.class);
|
||||
|
||||
public DataPointBool(int id, String knxDataType, String description) {
|
||||
super(id, knxDataType, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText() {
|
||||
return this.getValue() ? "True" : "False";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getValueObject() {
|
||||
return this.getValue() ? "1" : "0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processData(byte[] data) {
|
||||
if (this.checkProcessData(data)) {
|
||||
if (data[3] != 1 && data.length <= 4) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong for this type({}/1).", data[3]);
|
||||
return;
|
||||
}
|
||||
this.setValue((data[4] & 0x1) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertWriteValue(Object value) {
|
||||
String valueText = value.toString().toLowerCase();
|
||||
if (valueText.equalsIgnoreCase("true") || valueText.equalsIgnoreCase("1")) {
|
||||
this.setValue(true);
|
||||
return new byte[] { 0x01 };
|
||||
}
|
||||
this.setValue(false);
|
||||
return new byte[] { 0x00 };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointByteValue} is the data points for byte values
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointByteValue extends DataPointBase<@Nullable Byte> {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointByteValue.class);
|
||||
|
||||
public DataPointByteValue(int id, String knxDataType, String description) {
|
||||
super(id, knxDataType, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText() {
|
||||
Object val = this.getValue();
|
||||
return val != null ? val.toString() : "0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processData(byte[] data) {
|
||||
if (this.checkProcessData(data)) {
|
||||
if (data[3] != 1 && data.length <= 4) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong for this type({}/1).", data[3]);
|
||||
return;
|
||||
}
|
||||
this.setValue(data[4]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertWriteValue(Object value) {
|
||||
this.setValue(Byte.parseByte(value.toString()));
|
||||
return new byte[] { this.getValue() };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DataPointChangedEvent} is an event container for data point changes
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointChangedEvent {
|
||||
protected IDataPoint dataPoint;
|
||||
|
||||
public DataPointChangedEvent(Object source, IDataPoint dataPoint) {
|
||||
this.dataPoint = dataPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data-point of the event.
|
||||
*
|
||||
*/
|
||||
@Nullable
|
||||
public IDataPoint getDataPoint() {
|
||||
return this.dataPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DataPointFactory} creates the data points depending on the types
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointFactory {
|
||||
|
||||
/**
|
||||
* Creates the concrete data-point based on the type.
|
||||
*
|
||||
*/
|
||||
@Nullable
|
||||
public static IDataPoint createDataPoint(int id, String knxType, String description) {
|
||||
IDataPoint dataPoint = null;
|
||||
switch (knxType) {
|
||||
case "1.001":
|
||||
case "1.002":
|
||||
case "1.003":
|
||||
case "1.009":
|
||||
dataPoint = new DataPointBool(id, knxType, description);
|
||||
break;
|
||||
case "5.001":
|
||||
dataPoint = new DataPointScaling(id, knxType, description);
|
||||
break;
|
||||
case "9.001":
|
||||
case "9.002":
|
||||
case "9.006":
|
||||
dataPoint = new DataPointValue(id, knxType, description);
|
||||
break;
|
||||
case "13.002":
|
||||
dataPoint = new DataPointLongValue(id, knxType, description);
|
||||
break;
|
||||
case "20.102":
|
||||
case "20.103":
|
||||
case "20.105":
|
||||
dataPoint = new DataPointByteValue(id, knxType, description);
|
||||
break;
|
||||
}
|
||||
return dataPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointLongValue} is the data points for long values
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointLongValue extends DataPointBase<@Nullable Double> {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointLongValue.class);
|
||||
private final float factor;
|
||||
private final String outputFormat;
|
||||
|
||||
public DataPointLongValue(int id, String knxDataType, String description) {
|
||||
super(id, knxDataType, description);
|
||||
|
||||
if (knxDataType.equals("13.002")) {
|
||||
this.setUnit("m³/h");
|
||||
this.factor = 0.0001f;
|
||||
this.outputFormat = "%.1f";
|
||||
} else {
|
||||
this.setUnit("");
|
||||
this.factor = 1.0f;
|
||||
this.outputFormat = "%.1f";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText() {
|
||||
return String.format(this.outputFormat, this.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processData(byte[] data) {
|
||||
if (this.checkProcessData(data)) {
|
||||
if (data[3] != 4 && data.length <= 7) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong for this type({}/4).", data[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
int rawValue = Byte.toUnsignedInt(data[4]) * 0x1000000 + Byte.toUnsignedInt(data[5]) * 0x10000
|
||||
+ Byte.toUnsignedInt(data[6]) * 0x100 + Byte.toUnsignedInt(data[7]);
|
||||
this.setValue((double) rawValue * this.factor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertWriteValue(Object value) {
|
||||
ByteBuffer data = ByteBuffer.allocate(4);
|
||||
double dblVal;
|
||||
try {
|
||||
dblVal = Double.parseDouble(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
dblVal = 0.0;
|
||||
}
|
||||
|
||||
int val = (int) (dblVal / this.factor);
|
||||
data.put((byte) (val & 0xFF));
|
||||
val = (val & 0xFF) / 256;
|
||||
data.put((byte) (val & 0xFF));
|
||||
val = (val & 0xFF) / 256;
|
||||
data.put((byte) (val & 0xFF));
|
||||
val = (val & 0xFF) / 256;
|
||||
data.put((byte) (val & 0xFF));
|
||||
return data.array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointScaling} is the data points for scaling values
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointScaling extends DataPointBase<@Nullable Double> {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointScaling.class);
|
||||
private String outputFormat = "";
|
||||
|
||||
public DataPointScaling(int id, String knxDataType, String description) {
|
||||
super(id, knxDataType, description);
|
||||
this.setUnit("%");
|
||||
this.outputFormat = "%.1f";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText() {
|
||||
return String.format(this.outputFormat, this.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processData(byte[] data) {
|
||||
if (this.checkProcessData(data)) {
|
||||
if (data[3] != 1 && data.length <= 4) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong for this type({}/1).", data[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setValue((Byte.toUnsignedInt(data[4]) * 100.0) / 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertWriteValue(Object value) {
|
||||
this.setValue(Math.max(Math.min(Double.parseDouble(value.toString()), 100), 0));
|
||||
Object rawVal = this.getValue();
|
||||
double rawValResult = rawVal != null ? (Double) rawVal : 0.0;
|
||||
byte val = (byte) (rawValResult / 100.0 * 255.0);
|
||||
return new byte[] { val };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DataPointValue} is the data points for double values
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataPointValue extends DataPointBase<@Nullable Double> {
|
||||
private final Logger logger = LoggerFactory.getLogger(DataPointValue.class);
|
||||
private float factor;
|
||||
private String outputFormat = "";
|
||||
|
||||
public DataPointValue(int id, String knxDataType, String description) {
|
||||
super(id, knxDataType, description);
|
||||
this.factor = 0.0f;
|
||||
if (knxDataType.equals("9.001")) {
|
||||
this.setUnit("°C");
|
||||
this.factor = 0.01f;
|
||||
this.outputFormat = "%.1f";
|
||||
} else if (knxDataType.equals("9.002")) {
|
||||
this.setUnit("°K");
|
||||
this.factor = 0.01f;
|
||||
this.outputFormat = "%.1f";
|
||||
} else if (knxDataType.equals("9.006")) {
|
||||
this.setUnit("Bar");
|
||||
this.factor = 0.0000001f;
|
||||
this.outputFormat = "%.2f";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText() {
|
||||
return String.format(this.outputFormat, this.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processData(byte[] data) {
|
||||
if (this.checkProcessData(data)) {
|
||||
if (data[3] != 2 && data.length <= 5) {
|
||||
logger.debug("DataPoint-ProcessData: Data size wrong for this type({}/2).", data[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
int rawValue = Byte.toUnsignedInt(data[4]) * 256 + Byte.toUnsignedInt(data[5]);
|
||||
boolean inverted = (rawValue & 0x8000) > 0;
|
||||
double exp = (rawValue & 0x7800) / 2048;
|
||||
rawValue = rawValue & 0x07FF;
|
||||
exp = Math.pow(2, exp);
|
||||
if (inverted) {
|
||||
rawValue = rawValue - 1;
|
||||
rawValue = rawValue ^ 0x7FF;
|
||||
this.setValue(rawValue * exp * this.factor * (-1.0));
|
||||
} else {
|
||||
this.setValue(rawValue * exp * this.factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] convertWriteValue(Object value) {
|
||||
ByteBuffer data = ByteBuffer.allocate(2);
|
||||
this.setValue(Double.parseDouble(value.toString()));
|
||||
Object rawVal = this.getValue();
|
||||
double rawValResult = rawVal != null ? (Double) rawVal : 0.0;
|
||||
double dblValue = rawValResult / this.factor;
|
||||
boolean inverted = dblValue < 0.0;
|
||||
int exp = 0;
|
||||
dblValue = Math.abs(dblValue);
|
||||
while (dblValue > 2047.0) {
|
||||
dblValue = dblValue / 2.0;
|
||||
exp++;
|
||||
}
|
||||
int val = (int) dblValue;
|
||||
if (inverted) {
|
||||
val = val ^ 0x7FF;
|
||||
val = val + 1;
|
||||
val |= 0x8000;
|
||||
}
|
||||
|
||||
val |= exp * 2048;
|
||||
byte low = (byte) (val & 0xFF);
|
||||
val = (val & 0xFF00) / 256;
|
||||
byte high = (byte) (val & 0xFF);
|
||||
data.put(high);
|
||||
data.put(low);
|
||||
return data.array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IDataPoint} is the interface for all data points
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDataPoint {
|
||||
int getId();
|
||||
|
||||
/**
|
||||
* Gets the unit of the data-point.
|
||||
*
|
||||
*/
|
||||
String getUnit();
|
||||
|
||||
/**
|
||||
* Gets the type of the data-point.
|
||||
*
|
||||
*/
|
||||
String getKnxDataType();
|
||||
|
||||
/**
|
||||
* Gets the description of the data-point.
|
||||
*
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Gets the value as formated text.
|
||||
*
|
||||
*/
|
||||
String getValueText();
|
||||
|
||||
/**
|
||||
* Gets the value object.
|
||||
*
|
||||
*/
|
||||
@Nullable
|
||||
Object getValueObject();
|
||||
|
||||
/**
|
||||
* Processes the data received
|
||||
*
|
||||
*/
|
||||
void processData(byte[] data);
|
||||
|
||||
/**
|
||||
* Creates the data to be written
|
||||
*
|
||||
*/
|
||||
byte[] createWriteData(Object value);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
|
||||
/**
|
||||
* The {@link IDataPointChangeListener} is in interface for a data point changed consumer
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDataPointChangeListener {
|
||||
/**
|
||||
* This method will be called in case a data-point has changed.
|
||||
*
|
||||
*/
|
||||
void dataPointChanged(DataPointChangedEvent e);
|
||||
|
||||
/**
|
||||
* This method will be called in case the connection status has changed.
|
||||
*
|
||||
*/
|
||||
void connectionStatusChanged(ThingStatus status);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KnxNetFrame} is used for handling the received KNX.Net frames
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KnxNetFrame {
|
||||
public static byte[] KNX_HEADER = new byte[6];
|
||||
public static byte[] CONNECTION_HEADER = new byte[4];
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(KnxNetFrame.class);
|
||||
private ArrayList<SetDatapointValueMessage> valueMessages = new ArrayList<SetDatapointValueMessage>();
|
||||
private byte mainService;
|
||||
private byte subService = SubServiceType.SET_DATAPOINT_VALUE_REQUEST;
|
||||
private int startDataPoint;
|
||||
|
||||
static {
|
||||
KNX_HEADER[0] = (byte) 0x06; // Header size
|
||||
KNX_HEADER[1] = (byte) 0x20; // Version (2.0)
|
||||
KNX_HEADER[2] = (byte) 0xF0; // Object server request
|
||||
KNX_HEADER[3] = (byte) 0x80; // Object server request
|
||||
KNX_HEADER[4] = (byte) 0x00; // Frame size
|
||||
KNX_HEADER[5] = (byte) 0x00; // Frame size
|
||||
|
||||
CONNECTION_HEADER[0] = (byte) 0x04; // Structure length
|
||||
CONNECTION_HEADER[1] = (byte) 0x00; // Reserved
|
||||
CONNECTION_HEADER[2] = (byte) 0x00; // Reserved
|
||||
CONNECTION_HEADER[3] = (byte) 0x00; // Reserved
|
||||
}
|
||||
|
||||
public KnxNetFrame() {
|
||||
this.valueMessages = new ArrayList<SetDatapointValueMessage>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main service of the KNX frame
|
||||
*
|
||||
*/
|
||||
public byte getMainService() {
|
||||
return this.mainService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main service of the KNX frame
|
||||
*
|
||||
*/
|
||||
public void setMainService(byte value) {
|
||||
this.mainService = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sub service of the KNX frame
|
||||
*
|
||||
*/
|
||||
public byte getSubService() {
|
||||
return this.subService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sub service of the KNX frame
|
||||
*
|
||||
*/
|
||||
public void setSubService(byte value) {
|
||||
this.subService = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start data-point of the KNX frame
|
||||
*
|
||||
*/
|
||||
public int getStartDataPoint() {
|
||||
return this.startDataPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start data-point of the KNX frame
|
||||
*
|
||||
*/
|
||||
public void setStartDataPoint(int value) {
|
||||
this.startDataPoint = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value messages of the KNX frame
|
||||
*
|
||||
*/
|
||||
public SetDatapointValueMessage[] getValueMessages() {
|
||||
SetDatapointValueMessage[] result = new SetDatapointValueMessage[this.valueMessages.size()];
|
||||
this.valueMessages.toArray(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a KNX frame based on the data-array
|
||||
*
|
||||
*/
|
||||
@Nullable
|
||||
public static KnxNetFrame createKnxNetPackage(byte[] data, int amount) {
|
||||
if (data.length < 16 || amount < 16 || data.length < amount) {
|
||||
LOGGER.debug("Length of the data too short for a KNXnet/IP package ({}).", data.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data[0] != KNX_HEADER[0] || data[1] != KNX_HEADER[1] || data[2] != KNX_HEADER[2]
|
||||
|| data[3] != KNX_HEADER[3]) {
|
||||
LOGGER.debug("Incorrect KNXnet/IP header.");
|
||||
return null;
|
||||
}
|
||||
|
||||
int frameSize = Byte.toUnsignedInt(data[4]) * 256 + Byte.toUnsignedInt(data[5]);
|
||||
if (frameSize != amount) {
|
||||
LOGGER.debug("CreateKnxNetPackage: Error TelegrammLength/FrameSize missmatch. ({}/{})", data.length,
|
||||
frameSize);
|
||||
return null;
|
||||
}
|
||||
|
||||
KnxNetFrame frame = new KnxNetFrame();
|
||||
frame.setMainService(data[10]);
|
||||
if (frame.getMainService() != (byte) 0xF0) {
|
||||
LOGGER.debug("CreateKnxNetPackage: Main-Service not supported. ({})", frame.getMainService());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data[11] == (byte) 0x06) {
|
||||
frame.setSubService(SubServiceType.SET_DATAPOINT_VALUE_REQUEST);
|
||||
} else if (data[11] == (byte) 0x86) {
|
||||
frame.setSubService(SubServiceType.SET_DATAPOINT_VALUE_RESULT);
|
||||
} else if (data[11] == (byte) 0xC1) {
|
||||
frame.setSubService(SubServiceType.DATAPOINT_VALUE_WRITE);
|
||||
} else if (data[11] == (byte) 0xD0) {
|
||||
frame.setSubService(SubServiceType.REQUEST_ALL_DATAPOINTS);
|
||||
} else {
|
||||
LOGGER.debug("CreateKnxNetPackage: Sub-Service not supported. ({})", frame.getSubService());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (frame.getSubService() == SubServiceType.SET_DATAPOINT_VALUE_REQUEST) {
|
||||
frame.setStartDataPoint(Byte.toUnsignedInt(data[12]) * 256 + Byte.toUnsignedInt(data[13]));
|
||||
int numberOfDatapoints = Byte.toUnsignedInt(data[14]) * 256 + Byte.toUnsignedInt(data[15]);
|
||||
int offset = 16;
|
||||
|
||||
ByteBuffer list = ByteBuffer.allocate(data.length);
|
||||
list.put(data);
|
||||
|
||||
try {
|
||||
for (int i = 0; i < numberOfDatapoints; i++) {
|
||||
byte[] msgData = new byte[amount - offset];
|
||||
list.position(offset);
|
||||
list.get(msgData);
|
||||
SetDatapointValueMessage msg = new SetDatapointValueMessage(msgData);
|
||||
offset = offset + msg.getLength() + 4;
|
||||
frame.valueMessages.add(msg);
|
||||
}
|
||||
return frame;
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.debug("Error creating KnxNetPackage. {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the answer of the KNX frame
|
||||
*
|
||||
*/
|
||||
public byte[] createFrameAnswer() {
|
||||
ByteBuffer answer = ByteBuffer.allocate(17);
|
||||
if (this.getSubService() == SubServiceType.SET_DATAPOINT_VALUE_REQUEST) {
|
||||
answer.put(KNX_HEADER);
|
||||
answer.put(5, (byte) 0x11); // static size (17 bytes)
|
||||
answer.put(CONNECTION_HEADER);
|
||||
|
||||
answer.put(this.getMainService());
|
||||
answer.put(SubServiceType.SET_DATAPOINT_VALUE_RESULT);
|
||||
byte low = (byte) (this.getStartDataPoint() & (byte) 0xFF);
|
||||
byte high = (byte) ((this.getStartDataPoint() & (byte) 0xFF) / 256);
|
||||
answer.put(high);
|
||||
answer.put(low);
|
||||
answer.put((byte) 0);
|
||||
answer.put((byte) 0);
|
||||
answer.put((byte) 0);
|
||||
}
|
||||
return answer.array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ism8Server} is responsible for listening to the Ism8 information
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Server extends Thread {
|
||||
private final Logger logger = LoggerFactory.getLogger(Server.class);
|
||||
|
||||
private int port;
|
||||
private int startRetries;
|
||||
private boolean connected;
|
||||
private HashMap<Integer, IDataPoint> dataPoints = new HashMap<>();
|
||||
private @Nullable ServerSocket serverSocket = null;
|
||||
private @Nullable Socket client;
|
||||
private @Nullable IDataPointChangeListener changeListener;
|
||||
|
||||
public Server(int port, String uid) {
|
||||
super("OH-binding-" + uid);
|
||||
setDaemon(true);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port of the server
|
||||
*
|
||||
*/
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection state of the server
|
||||
*
|
||||
*/
|
||||
public boolean getConnected() {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data points of the server
|
||||
*
|
||||
*/
|
||||
public @Nullable IDataPoint getDataPoint(int id) {
|
||||
IDataPoint dataPoint = null;
|
||||
if (this.dataPoints.containsKey(id)) {
|
||||
dataPoint = this.dataPoints.get(id);
|
||||
}
|
||||
|
||||
return dataPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server
|
||||
*
|
||||
*/
|
||||
public void run() {
|
||||
this.startRetries = 0;
|
||||
while (!this.isInterrupted()) {
|
||||
try {
|
||||
this.handleCommunication();
|
||||
if (this.startRetries > 10) {
|
||||
Thread.sleep(6000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Thread interrupted");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
this.startRetries++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the server
|
||||
*
|
||||
*/
|
||||
public void stopServerThread() {
|
||||
this.interrupt();
|
||||
this.stopServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data-point change listener to the server
|
||||
*
|
||||
*/
|
||||
public void addDataPointChangeListener(IDataPointChangeListener listener) {
|
||||
if (this.changeListener == null) {
|
||||
this.changeListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data-point to the server
|
||||
*
|
||||
*/
|
||||
public void addDataPoint(int id, String knxType, String description) {
|
||||
if (this.dataPoints.containsKey(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDataPoint dp = DataPointFactory.createDataPoint(id, knxType, description);
|
||||
if (dp != null) {
|
||||
this.dataPoints.put(new Integer(id), dp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the ISM8 partner
|
||||
*
|
||||
*/
|
||||
public void sendData(byte[] data) throws IOException {
|
||||
Socket clientSocket = this.client;
|
||||
if (clientSocket != null && clientSocket.isConnected() && data.length > 0) {
|
||||
OutputStream stream = clientSocket.getOutputStream();
|
||||
stream.write(data);
|
||||
stream.flush();
|
||||
logger.debug("Data sent: {}", this.printBytes(data));
|
||||
}
|
||||
}
|
||||
|
||||
private void stopServer() {
|
||||
logger.debug("Stop Ism8 server.");
|
||||
try {
|
||||
ServerSocket serverSock = this.serverSocket;
|
||||
if (serverSock != null) {
|
||||
serverSock.close();
|
||||
}
|
||||
|
||||
Socket clientSocket = this.client;
|
||||
if (clientSocket != null) {
|
||||
clientSocket.close();
|
||||
this.client = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error stopping Communication. {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCommunication() {
|
||||
try {
|
||||
logger.debug("Waiting for connection in port {}.", this.getPort());
|
||||
IDataPointChangeListener listener = this.changeListener;
|
||||
if (listener != null) {
|
||||
listener.connectionStatusChanged(ThingStatus.OFFLINE);
|
||||
}
|
||||
ServerSocket serverSock = new ServerSocket(this.getPort());
|
||||
this.serverSocket = serverSock;
|
||||
Socket clientSocket = serverSock.accept();
|
||||
this.client = clientSocket;
|
||||
logger.debug("Connection from Partner established {}", clientSocket.getRemoteSocketAddress());
|
||||
if (listener != null) {
|
||||
listener.connectionStatusChanged(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
this.startRetries = 0;
|
||||
this.sendUpdateCommand();
|
||||
while (!this.isInterrupted()) {
|
||||
byte[] bytes = getBytesFromInputStream(clientSocket.getInputStream());
|
||||
ArrayList<byte[]> packages = this.getPackages(bytes);
|
||||
|
||||
for (byte[] pack : packages) {
|
||||
logger.debug("Data received: {}", this.printBytes(pack));
|
||||
KnxNetFrame frame = KnxNetFrame.createKnxNetPackage(pack, pack.length);
|
||||
if (frame != null) {
|
||||
byte[] answer = frame.createFrameAnswer();
|
||||
if (answer.length > 0) {
|
||||
this.sendData(answer);
|
||||
}
|
||||
|
||||
for (SetDatapointValueMessage message : frame.getValueMessages()) {
|
||||
logger.debug("Message received: {} {}", message.getId(),
|
||||
this.printBytes(message.getData()));
|
||||
|
||||
IDataPoint dataPoint = this.getDataPoint(message.getId());
|
||||
if (dataPoint != null) {
|
||||
dataPoint.processData(message.getData());
|
||||
logger.debug("{} {}", dataPoint.getDescription(), dataPoint.getValueText());
|
||||
if (listener != null) {
|
||||
listener.dataPointChanged(new DataPointChangedEvent(this, dataPoint));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error handle client data stream. {}", e.getMessage());
|
||||
this.stopServer();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdateCommand() throws IOException {
|
||||
byte[] data = new byte[] { (byte) 0x06, (byte) 0x20, (byte) 0xF0, (byte) 0x80, (byte) 0x00, (byte) 0x16,
|
||||
(byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xF0, (byte) 0xD0 };
|
||||
this.sendData(data);
|
||||
}
|
||||
|
||||
private byte[] getBytesFromInputStream(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[0xFF];
|
||||
int len = is.read(buffer);
|
||||
os.write(buffer, 0, len);
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> getPackages(byte[] data) {
|
||||
ArrayList<byte[]> result = new ArrayList<byte[]>();
|
||||
if (data.length >= 0) {
|
||||
ByteBuffer list = ByteBuffer.allocate(data.length);
|
||||
list.put(data);
|
||||
int start = -1;
|
||||
for (int i = 0; i < data.length - 4; i++) {
|
||||
if (list.get(i + 0) == (byte) 0x06 && list.get(i + 1) == (byte) 0x20 && list.get(i + 2) == (byte) 0xF0
|
||||
&& list.get(i + 3) == (byte) 0x80) {
|
||||
if (start >= 0) {
|
||||
byte[] pkgData = new byte[i - start];
|
||||
list.position(start);
|
||||
list.get(pkgData);
|
||||
result.add(pkgData);
|
||||
}
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
if (start >= 0) {
|
||||
byte[] pkgData = new byte[data.length - start];
|
||||
list.position(start);
|
||||
list.get(pkgData);
|
||||
result.add(pkgData);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String printBytes(byte[] bytes) {
|
||||
return HexUtils.bytesToHex(bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SetDatapointValueMessage} is a message within the KNX frame containing
|
||||
* the information of one data point
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetDatapointValueMessage {
|
||||
private int id;
|
||||
private byte command;
|
||||
private byte[] data = new byte[0];
|
||||
private byte length;
|
||||
|
||||
public SetDatapointValueMessage() {
|
||||
}
|
||||
|
||||
public SetDatapointValueMessage(byte[] data) throws IllegalArgumentException {
|
||||
if (data.length < 5) {
|
||||
throw new IllegalArgumentException("Data size too small for a SetDatapointValueMessage.");
|
||||
}
|
||||
|
||||
this.setId(Byte.toUnsignedInt(data[0]) * 256 + Byte.toUnsignedInt(data[1]));
|
||||
this.setCommand(data[2]);
|
||||
this.setLength(data[3]);
|
||||
if (data.length < (this.getLength() + 4)) {
|
||||
throw new IllegalArgumentException("Data size incorrect (" + data.length + "/" + this.getLength() + ").");
|
||||
}
|
||||
|
||||
ByteBuffer list = ByteBuffer.allocate(this.getLength() + 4);
|
||||
list.put(data, 0, this.getLength() + 4);
|
||||
this.setData(list.array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the data-point message
|
||||
*
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the data-point message
|
||||
*
|
||||
*/
|
||||
public void setId(int value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command of the data-point message
|
||||
*
|
||||
*/
|
||||
public byte getCommand() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the command of the data-point message
|
||||
*
|
||||
*/
|
||||
public void setCommand(byte value) {
|
||||
this.command = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the data-point message
|
||||
*
|
||||
*/
|
||||
public byte getLength() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the length of the data-point message
|
||||
*
|
||||
*/
|
||||
public void setLength(byte value) {
|
||||
this.length = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data array of the data-point message
|
||||
*
|
||||
*/
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data array of the data-point message
|
||||
*
|
||||
*/
|
||||
public void setData(byte[] value) {
|
||||
this.data = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.server;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SubServiceType} contains all supported sub-service types
|
||||
*
|
||||
* @author Hans-Reiner Hoffmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SubServiceType {
|
||||
/**
|
||||
* Sub-Service: Set data-point value request.
|
||||
*
|
||||
*/
|
||||
public static final byte SET_DATAPOINT_VALUE_REQUEST = (byte) 0x06;
|
||||
|
||||
/**
|
||||
* Sub-Service: Set data-point value result.
|
||||
*
|
||||
*/
|
||||
public static final byte SET_DATAPOINT_VALUE_RESULT = (byte) 0x86;
|
||||
|
||||
/**
|
||||
* Sub-Service: Write data-point value.
|
||||
*
|
||||
*/
|
||||
public static final byte DATAPOINT_VALUE_WRITE = (byte) 0xC1;
|
||||
|
||||
/**
|
||||
* Sub-Service: Request all data-points.
|
||||
*
|
||||
*/
|
||||
public static final byte REQUEST_ALL_DATAPOINTS = (byte) 0xD0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="ism8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>ISM8 Binding</name>
|
||||
<description>This is the binding for the ISM8 card used for Wolf heating systems or other Wolf eBus devices.</description>
|
||||
<author>Hans-Reiner Hoffmann</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="ism8"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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">
|
||||
<label>ISM8 Device</label>
|
||||
<description>ISM8 Interface</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="portNumber" type="integer" required="true" min="1" max="65535">
|
||||
<description>Port number of the object server</description>
|
||||
<label>Port</label>
|
||||
<default>12004</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Digital 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_Switch / 1.001)</description>
|
||||
<options>
|
||||
<option value="1.001">DPT_Switch</option>
|
||||
<option value="1.002">DPT_Bool</option>
|
||||
<option value="1.003">DPT_Enable</option>
|
||||
<option value="1.009">DPT_OpenClose</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="switch-readonly">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Digital 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_Switch / 1.001)</description>
|
||||
<options>
|
||||
<option value="1.001">DPT_Switch</option>
|
||||
<option value="1.002">DPT_Bool</option>
|
||||
<option value="1.003">DPT_Enable</option>
|
||||
<option value="1.009">DPT_OpenClose</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="number-readonly">
|
||||
<item-type>number</item-type>
|
||||
<label>Value 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="5.001">DPT_Scaling</option>
|
||||
<option value="9.001">DPT_Value_Temp</option>
|
||||
<option value="9.002">DPT_Value_Tempd</option>
|
||||
<option value="9.006">DPT_Value_Pres</option>
|
||||
<option value="13.002">DPT_FlowRate</option>
|
||||
<option value="20.102">DPT_HVACMode</option>
|
||||
<option value="20.103">DPT_DHWMode</option>
|
||||
<option value="20.105">DPT_HVACContrMode</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="number">
|
||||
<item-type>number</item-type>
|
||||
<label>Value 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="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>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user