diff --git a/bundles/binding/org.openhab.binding.upb/.classpath b/bundles/binding/org.openhab.binding.upb/.classpath
new file mode 100644
index 00000000000..7bc456b9c25
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.upb/.project b/bundles/binding/org.openhab.binding.upb/.project
new file mode 100644
index 00000000000..9fcb319f30f
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/.project
@@ -0,0 +1,39 @@
+
+
+ org.openhab.binding.upb
+ This is the UPB binding of the open Home Automation Bus (openHAB)
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.ds.core.builder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.pde.PluginNature
+
+
diff --git a/bundles/binding/org.openhab.binding.upb/META-INF/MANIFEST.MF b/bundles/binding/org.openhab.binding.upb/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..f4c6e4ce467
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/META-INF/MANIFEST.MF
@@ -0,0 +1,31 @@
+Manifest-Version: 1.0
+Private-Package: org.openhab.binding.upb.internal
+Ignore-Package: org.openhab.binding.upb.internal
+Bundle-License: http://www.eclipse.org/legal/epl-v10.html
+Bundle-Name: openHAB UPB Binding
+Bundle-SymbolicName: org.openhab.binding.upb
+Bundle-Vendor: openHAB.org
+Bundle-Version: 1.9.0.qualifier
+Bundle-ManifestVersion: 2
+Bundle-Description: This is the UPB binding of the open Home Aut
+ omation Bus (openHAB)
+Import-Package:
+ org.apache.commons.lang,
+ org.openhab.core.binding,
+ org.openhab.core.events,
+ org.openhab.core.items,
+ org.openhab.core.library.items,
+ org.openhab.core.library.types,
+ org.openhab.core.types,
+ org.openhab.model.item.binding,
+ org.osgi.framework,
+ org.osgi.service.cm,
+ org.osgi.service.component,
+ org.osgi.service.event,
+ org.slf4j
+Export-Package: org.openhab.binding.upb
+Bundle-DocURL: http://www.openhab.org
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Service-Component: OSGI-INF/binding.xml, OSGI-INF/genericbindingprovider.xml
+Bundle-ClassPath: ., lib/nrjavaserial-3.11.0.jar
+Bundle-ActivationPolicy: lazy
diff --git a/bundles/binding/org.openhab.binding.upb/OSGI-INF/binding.xml b/bundles/binding/org.openhab.binding.upb/OSGI-INF/binding.xml
new file mode 100644
index 00000000000..72975dc5b66
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/OSGI-INF/binding.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.upb/OSGI-INF/genericbindingprovider.xml b/bundles/binding/org.openhab.binding.upb/OSGI-INF/genericbindingprovider.xml
new file mode 100644
index 00000000000..79538baa98e
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/OSGI-INF/genericbindingprovider.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.upb/build.properties b/bundles/binding/org.openhab.binding.upb/build.properties
new file mode 100644
index 00000000000..45e1fea35d6
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/build.properties
@@ -0,0 +1,7 @@
+source.. = src/main/java/,\
+ src/main/resources/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ lib/nrjavaserial-3.11.0.jar
+output.. = target/classes/
diff --git a/bundles/binding/org.openhab.binding.upb/lib/nrjavaserial-3.11.0.jar b/bundles/binding/org.openhab.binding.upb/lib/nrjavaserial-3.11.0.jar
new file mode 100644
index 00000000000..6e7e7c46ba2
Binary files /dev/null and b/bundles/binding/org.openhab.binding.upb/lib/nrjavaserial-3.11.0.jar differ
diff --git a/bundles/binding/org.openhab.binding.upb/pom.xml b/bundles/binding/org.openhab.binding.upb/pom.xml
new file mode 100644
index 00000000000..b64a6530ba3
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ org.openhab.bundles
+ binding
+ 1.9.0-SNAPSHOT
+
+
+
+ org.openhab.binding.upb
+ org.openhab.binding.upb
+ openhab-addon-binding-upb
+ ${project.name}
+
+
+ 4.0.0
+ org.openhab.binding
+ org.openhab.binding.upb
+
+ openHAB UPB Binding
+
+ eclipse-plugin
+
+
+
+
+ org.vafer
+ jdeb
+
+
+
+
+
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/UPBBindingProvider.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/UPBBindingProvider.java
new file mode 100644
index 00000000000..6f5c72dc501
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/UPBBindingProvider.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb;
+
+import org.openhab.binding.upb.internal.UPBBindingConfig;
+import org.openhab.core.binding.BindingProvider;
+
+/**
+ * Interface for the {@link BindingProvider} for the UPB binding.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public interface UPBBindingProvider extends BindingProvider {
+
+ /**
+ * Gets the configuration of an item.
+ *
+ * @param itemName
+ * the name of the item.
+ * @return the {@link UPBBindingConfig} for the given item or null if one
+ * does not exist.
+ */
+ UPBBindingConfig getConfig(String itemName);
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/ControlWord.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/ControlWord.java
new file mode 100644
index 00000000000..44f9cb2fc74
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/ControlWord.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+/**
+ * Model for the first two bytes of UPB messages.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class ControlWord {
+
+ private static final int TRANSMIT_COUNT_SHIFT = 2;
+ private static final int TRANSMIT_COUNT_MASK = 0b00001100;
+ private static final int TRANSMIT_SEQUENCE_MASK = 0b00000011;
+ private static final int ACK_PULSE_MASK = 0b00010000;
+ private static final int ID_PULSE_MASK = 0b00100000;
+ private static final int ACK_MESSAGE_MASK = 0b01000000;
+ private static final int REPEATER_COUNT_SHIFT = 5;
+ private static final int REPEATER_COUNT_MASK = 0b01100000;
+ private static final int PACKET_LENGTH_MASK = 0b00011111;
+ private static final int LINK_MASK = 0b10000000;
+
+ private byte one = 0;
+ private byte two = 0;
+
+ /**
+ * Sets the two bytes of the control word.
+ *
+ * @param one
+ * the first byte.
+ * @param two
+ * the second byte.
+ */
+ public void setBytes(byte one, byte two) {
+ this.one = one;
+ this.two = two;
+ }
+
+ /**
+ * @return the two bytes of the control word.
+ */
+ public byte[] getBytes() {
+ return new byte[] { two, one };
+ }
+
+ /**
+ * @return the link
+ */
+ public boolean isLink() {
+ return (two & LINK_MASK) > 0;
+ }
+
+ /**
+ * @param link
+ * the link to set
+ */
+ public void setLink(boolean link) {
+ two = (byte) (link ? two | LINK_MASK : two & ~LINK_MASK);
+ }
+
+ /**
+ * @return the repeaterCount
+ */
+ public int getRepeaterCount() {
+ return (two & REPEATER_COUNT_MASK) >> REPEATER_COUNT_SHIFT;
+ }
+
+ /**
+ * @param repeaterCount
+ * the repeaterCount to set
+ */
+ public void setRepeaterCount(int repeaterCount) {
+ two = (byte) (two | (repeaterCount << REPEATER_COUNT_SHIFT));
+ }
+
+ /**
+ * @return the packetLength
+ */
+ public int getPacketLength() {
+ return two & PACKET_LENGTH_MASK;
+ }
+
+ /**
+ * @param packetLength
+ * the packetLength to set
+ */
+ public void setPacketLength(int packetLength) {
+ two = (byte) (two | packetLength);
+ }
+
+ /**
+ * @return the transmitCount
+ */
+ public int getTransmitCount() {
+ return (one & TRANSMIT_COUNT_MASK) >> TRANSMIT_COUNT_SHIFT;
+ }
+
+ /**
+ * @param transmitCount
+ * the transmitCount to set
+ */
+ public void setTransmitCount(int transmitCount) {
+ one = (byte) (one | (transmitCount << TRANSMIT_COUNT_SHIFT));
+ }
+
+ /**
+ * @return the transmitSequence
+ */
+ public int getTransmitSequence() {
+ return one & TRANSMIT_SEQUENCE_MASK;
+ }
+
+ /**
+ * @param transmitSequence
+ * the transmitSequence to set
+ */
+ public void setTransmitSequence(int transmitSequence) {
+ one = (byte) (one | transmitSequence);
+ }
+
+ /**
+ * @return the ackPulse
+ */
+ public boolean isAckPulse() {
+ return (one & ACK_PULSE_MASK) > 0;
+ }
+
+ /**
+ * @param ackPulse
+ * the ackPulse to set
+ */
+ public void setAckPulse(boolean ackPulse) {
+ one = (byte) (ackPulse ? one | ACK_PULSE_MASK : one & ~ACK_PULSE_MASK);
+ }
+
+ /**
+ * @return the idPulse
+ */
+ public boolean isIdPulse() {
+ return (one & ID_PULSE_MASK) > 0;
+ }
+
+ /**
+ * @param idPulse
+ * the idPulse to set
+ */
+ public void setIdPulse(boolean idPulse) {
+ one = (byte) (idPulse ? one | ID_PULSE_MASK : one & ~ID_PULSE_MASK);
+ }
+
+ /**
+ * @return the ackMessage
+ */
+ public boolean isAckMessage() {
+ return (one & ACK_MESSAGE_MASK) > 0;
+ }
+
+ /**
+ * @param ackMessage
+ * the ackMessage to set
+ */
+ public void setAckMessage(boolean ackMessage) {
+ one = (byte) (ackMessage ? one | ACK_MESSAGE_MASK : one & ~ACK_MESSAGE_MASK);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/MessageBuilder.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/MessageBuilder.java
new file mode 100644
index 00000000000..0997e31a4bc
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/MessageBuilder.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * Builder class for building UPB messages.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public final class MessageBuilder {
+
+ private byte network;
+ private byte source = -1;
+ private byte destination;
+ private byte[] commands;
+ private boolean link = false;
+
+ /**
+ * Gets a new {@link MessageBuilder} instance.
+ *
+ * @return a new MessageBuilder.
+ */
+ public static MessageBuilder create() {
+ return new MessageBuilder();
+ }
+
+ private MessageBuilder() {
+
+ }
+
+ /**
+ * Sets where this message is for a device or a link.
+ *
+ * @param link
+ * set to true if this message is for a link.
+ * @return the same MessageBuilder instance.
+ */
+ public MessageBuilder link(boolean link) {
+ this.link = link;
+ return this;
+ }
+
+ /**
+ * Sets the UPB network of the message.
+ *
+ * @param network
+ * the network of the message.
+ * @return the same MessageBuilder instance.
+ */
+ public MessageBuilder network(byte network) {
+ this.network = network;
+ return this;
+ }
+
+ /**
+ * Sets the source id of the message (defaults to 0xFF).
+ *
+ * @param source
+ * the source if of the message.
+ * @return the same MessageBuilder instance.
+ */
+ public MessageBuilder source(byte source) {
+ this.source = source;
+ return this;
+ }
+
+ /**
+ * Sets the destination id of the message.
+ *
+ * @param destination
+ * the destination id.
+ * @return the same MessageBuilder instance.
+ */
+ public MessageBuilder destination(byte destination) {
+ this.destination = destination;
+ return this;
+ }
+
+ /**
+ * Sets the command and any arguments of the message.
+ *
+ * @param commands
+ * the command followed by any arguments.
+ * @return the same MessageBuilder instance.
+ */
+ public MessageBuilder command(byte... commands) {
+ this.commands = commands;
+ return this;
+ }
+
+ /**
+ * Builds the message as a HEX string.
+ *
+ * @return a HEX string of the message.
+ */
+ public String build() {
+ ControlWord controlWord = new ControlWord();
+
+ int packetLength = 6 + commands.length;
+
+ controlWord.setPacketLength(packetLength);
+ controlWord.setAckPulse(true);
+ controlWord.setLink(link);
+
+ int index = 2;
+ byte[] bytes = new byte[packetLength];
+ bytes[index++] = network;
+ bytes[index++] = destination;
+ bytes[index++] = source;
+
+ // Copy in the header
+ System.arraycopy(controlWord.getBytes(), 0, bytes, 0, 2);
+
+ // Copy in the actual command and arguments being sent.
+ System.arraycopy(commands, 0, bytes, index, commands.length);
+
+ // Calculate the checksum
+ // The checksum is the 2's complement of the sum.
+ int sum = 0;
+ for (byte b : bytes) {
+ sum += b;
+ }
+
+ bytes[bytes.length - 1] = new Integer(-sum >>> 0).byteValue();
+
+ return DatatypeConverter.printHexBinary(bytes);
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBinding.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBinding.java
new file mode 100644
index 00000000000..3faf40ff770
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBinding.java
@@ -0,0 +1,320 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import java.util.Map;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.openhab.binding.upb.UPBBindingProvider;
+import org.openhab.binding.upb.internal.UPBMessage.Type;
+import org.openhab.core.binding.AbstractActiveBinding;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.NoSuchPortException;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+/**
+ * Binding for Universal Powerline Bus (UPB) that reads and writes messages to
+ * and from the UPB modem.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBBinding extends AbstractActiveBinding implements UPBReader.Listener {
+
+ private static final Logger logger = LoggerFactory.getLogger(UPBBinding.class);
+
+ private String port;
+ private byte network = 0;
+ private SerialPort serialPort;
+ private UPBReader upbReader;
+ private UPBWriter upbWriter;
+
+ /**
+ * the refresh interval which is used to poll values from the UPB server
+ * (optional, defaults to 3600000ms)
+ */
+ private long refreshInterval = 3600000;
+
+ /**
+ * Called by the SCR to activate the component with its configuration read
+ * from CAS
+ *
+ * @param bundleContext
+ * BundleContext of the Bundle that defines this component
+ * @param configuration
+ * Configuration properties for this component obtained from the
+ * ConfigAdmin service
+ */
+ public void activate(final BundleContext bundleContext, final Map configuration) {
+
+ String refreshIntervalString = (String) configuration.get("refresh");
+ if (StringUtils.isNotBlank(refreshIntervalString)) {
+ refreshInterval = Long.parseLong(refreshIntervalString);
+ }
+
+ parseConfiguration(configuration);
+
+ logger.info("UPB binding starting up...");
+
+ try {
+ serialPort = openSerialPort();
+ upbReader = new UPBReader(serialPort.getInputStream());
+ upbWriter = new UPBWriter(serialPort.getOutputStream(), upbReader);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to open serial port.", e);
+ }
+
+ upbReader.addListener(this);
+
+ setProperlyConfigured(true);
+ }
+
+ /**
+ * Called by the SCR when the configuration of a binding has been changed
+ * through the ConfigAdmin service.
+ *
+ * @param configuration
+ * Updated configuration properties
+ */
+ public void modified(final Map configuration) {
+ parseConfiguration(configuration);
+ }
+
+ private void parseConfiguration(final Map configuration) {
+ port = ObjectUtils.toString(configuration.get("port"), null);
+ network = Integer.valueOf(ObjectUtils.toString(configuration.get("network"), "0")).byteValue();
+
+ logger.debug("Parsed UPB configuration:");
+ logger.debug("Serial port: {}", port);
+ logger.debug("UPB Network: {}", network & 0xff);
+
+ }
+
+ /**
+ * Called by the SCR to deactivate the component when either the
+ * configuration is removed or mandatory references are no longer satisfied
+ * or the component has simply been stopped.
+ *
+ * @param reason
+ * Reason code for the deactivation:
+ *
+ * - 0 – Unspecified
+ *
- 1 – The component was disabled
+ *
- 2 – A reference became unsatisfied
+ *
- 3 – A configuration was changed
+ *
- 4 – A configuration was deleted
+ *
- 5 – The component was disposed
+ *
- 6 – The bundle was stopped
+ *
+ */
+ public void deactivate(final int reason) {
+ logger.info("UPB binding shutting down...");
+
+ if (upbReader != null) {
+ upbReader.shutdown();
+ }
+
+ if (upbWriter != null) {
+ upbWriter.shutdown();
+ }
+
+ if (serialPort != null) {
+ logger.debug("Closing serial port");
+ serialPort.close();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getRefreshInterval() {
+ return refreshInterval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String getName() {
+ return "UPB Service";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void execute() {
+ // the frequently executed code (polling) goes here ...
+ for (UPBBindingProvider p : providers) {
+ for (String s : p.getItemNames()) {
+ UPBBindingConfig config = p.getConfig(s);
+ if (!config.isLink()) {
+ MessageBuilder message = MessageBuilder.create().network(network).destination(config.getId())
+ .command(UPBMessage.Command.REPORT_STATE.toByte());
+ // Here we write the command to the PIM.
+ upbWriter.queueMessage(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void internalReceiveCommand(String itemName, Command command) {
+ UPBBindingConfig config = getConfig(itemName);
+
+ if (config != null) {
+ byte[] commandByte = { UPBMessage.Command.DEACTIVATE.toByte() };
+ if (command == OnOffType.ON) {
+ commandByte = new byte[] { UPBMessage.Command.ACTIVATE.toByte() };
+ } else if (command instanceof PercentType) {
+ commandByte = new byte[] { UPBMessage.Command.GOTO.toByte(), ((PercentType) command).byteValue() };
+ }
+
+ MessageBuilder message = MessageBuilder.create().network(network).destination(config.getId())
+ .link(config.isLink()).command(commandByte);
+
+ // Here we write the command to the PIM.
+ upbWriter.queueMessage(message);
+ }
+ }
+
+ private String getItemName(byte id, boolean link) {
+ for (UPBBindingProvider p : providers) {
+ for (String itemName : p.getItemNames()) {
+ UPBBindingConfig config = p.getConfig(itemName);
+
+ if (config != null && config.getId() == id && config.isLink() == link) {
+ return itemName;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private UPBBindingConfig getConfig(String itemName) {
+ for (UPBBindingProvider p : providers) {
+ UPBBindingConfig config = p.getConfig(itemName);
+
+ if (config != null) {
+ return config;
+ }
+ }
+
+ return null;
+ }
+
+ private SerialPort openSerialPort() {
+ SerialPort serialPort = null;
+ CommPortIdentifier portId;
+ try {
+ portId = CommPortIdentifier.getPortIdentifier(port);
+ } catch (NoSuchPortException e1) {
+ throw new RuntimeException("Port does not exist", e1);
+ }
+
+ if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
+ if (portId.getName().equals(port)) {
+ try {
+ serialPort = portId.open("UPB", 1000);
+ } catch (PortInUseException e) {
+ throw new RuntimeException("Port is in use", e);
+ }
+ try {
+ serialPort.setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
+ SerialPort.PARITY_NONE);
+ serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
+ serialPort.enableReceiveTimeout(100);
+ } catch (UnsupportedCommOperationException e) {
+ throw new RuntimeException("Failed to configure serial port");
+ }
+ }
+ }
+
+ return serialPort;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void messageReceived(UPBMessage message) {
+ if (message.getType() != Type.MESSAGE_REPORT) {
+ return;
+ }
+
+ String sourceName = getItemName(message.getSource(), false);
+ String destinationName = getItemName(message.getDestination(), message.getControlWord().isLink());
+ UPBBindingConfig sourceConfig = getConfig(sourceName);
+ UPBBindingConfig destinationConfig = getConfig(destinationName);
+
+ String itemName = isValidId(message.getDestination()) ? destinationName : sourceName;
+ UPBBindingConfig config = isValidId(message.getDestination()) ? destinationConfig : sourceConfig;
+
+ if (itemName == null || config == null) {
+ logger.debug("Received message for unknown {} with id {}.",
+ message.getControlWord().isLink() ? "Link" : "Device", message.getDestination() & 0xff);
+ return;
+ }
+
+ State newState = null;
+ byte level = 100;
+
+ switch (message.getCommand()) {
+ case GOTO:
+ case DEVICE_STATE:
+ case ACTIVATE:
+
+ if (message.getArguments() != null && message.getArguments().length > 0) {
+ level = message.getArguments()[0];
+ } else {
+ level = (byte) (message.getCommand() == UPBMessage.Command.ACTIVATE ? 100 : 0);
+ }
+
+ // Links will send FF (-1) for their level.
+ if (level == -1 || level >= 100 || (level > 0 && !config.isDimmable())) {
+ newState = OnOffType.ON;
+ } else if (level == 0) {
+ newState = OnOffType.OFF;
+ } else {
+ newState = new PercentType(level);
+ }
+ break;
+ case DEACTIVATE:
+ newState = OnOffType.OFF;
+ break;
+ default:
+ break;
+ }
+
+ if (newState != null) {
+ logger.debug("Posting update: {},{}", itemName, newState);
+ eventPublisher.postUpdate(itemName, newState);
+ }
+ }
+
+ private boolean isValidId(byte id) {
+ return id != 0 && id != -1;
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBindingConfig.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBindingConfig.java
new file mode 100644
index 00000000000..f023b3602fe
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBBindingConfig.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openhab.core.binding.BindingConfig;
+
+/**
+ * This is a helper class holding binding specific configuration details.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBBindingConfig implements BindingConfig {
+ private Byte id;
+ private boolean dimmable;
+ private boolean link;
+ private Map properties = new HashMap<>();
+
+ /**
+ * Instantiate a new UPBBindingConfig
+ *
+ * @param properties
+ * @param dimmable
+ */
+ public UPBBindingConfig(String[] properties, boolean dimmable) {
+ this.dimmable = dimmable;
+ for (String s : properties) {
+ String[] entry = s.split("=");
+
+ if (entry.length == 2) {
+ setProperty(entry[0], entry[1]);
+ }
+ }
+ }
+
+ /**
+ * @return the id
+ */
+ public Byte getId() {
+ return id;
+ }
+
+ /**
+ * @return the requested property or null if it is not specified.
+ */
+ public String getProperty(String property) {
+ return properties.get(property);
+ }
+
+ /**
+ * @return the link
+ */
+ public boolean isLink() {
+ return link;
+ }
+
+ /**
+ * @return the dimmable
+ */
+ public boolean isDimmable() {
+ return dimmable;
+ }
+
+ private void setProperty(String prop, String value) {
+
+ if ("id".equals(prop)) {
+ this.id = Integer.valueOf(value).byteValue();
+ } else if ("link".equals(prop)) {
+ this.link = Boolean.valueOf(value);
+ } else {
+ properties.put(prop, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBGenericBindingProvider.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBGenericBindingProvider.java
new file mode 100644
index 00000000000..9bf171c5403
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBGenericBindingProvider.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import org.openhab.binding.upb.UPBBindingProvider;
+import org.openhab.core.items.Item;
+import org.openhab.core.library.items.DimmerItem;
+import org.openhab.core.library.items.SwitchItem;
+import org.openhab.model.item.binding.AbstractGenericBindingProvider;
+import org.openhab.model.item.binding.BindingConfigParseException;
+
+/**
+ * This class is responsible for parsing the binding configuration.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBGenericBindingProvider extends AbstractGenericBindingProvider implements UPBBindingProvider {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBindingType() {
+ return "UPB";
+ }
+
+ /**
+ * @{inheritDoc
+ */
+ @Override
+ public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException {
+ if (!(item instanceof SwitchItem || item instanceof DimmerItem)) {
+ throw new BindingConfigParseException(
+ "item '" + item.getName() + "' is of type '" + item.getClass().getSimpleName()
+ + "', only Switch- and DimmerItems are allowed - please check your *.items configuration");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void processBindingConfiguration(String context, Item item, String bindingConfig)
+ throws BindingConfigParseException {
+ super.processBindingConfiguration(context, item, bindingConfig);
+
+ // parse bindingconfig here ...
+ String[] properties = bindingConfig.split(" ");
+ UPBBindingConfig config = new UPBBindingConfig(properties, item instanceof DimmerItem);
+
+ if (config.getId() == null) {
+ throw new BindingConfigParseException("item config must have an id value");
+ }
+ addBindingConfig(item, config);
+ }
+
+ @Override
+ public UPBBindingConfig getConfig(String itemName) {
+ if (itemName != null) {
+ return (UPBBindingConfig) bindingConfigs.get(itemName);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBMessage.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBMessage.java
new file mode 100644
index 00000000000..74fdf0c9cb3
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBMessage.java
@@ -0,0 +1,276 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import javax.xml.bind.DatatypeConverter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Model for a message sent or received from a UPB modem.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBMessage {
+
+ /**
+ * An enum of possible commands.
+ *
+ * @author cvanorman
+ *
+ */
+ public enum Command {
+ ACTIVATE,
+ DEACTIVATE,
+ GOTO,
+ START_FADE,
+ STOP_FADE,
+ BLINK,
+ REPORT_STATE,
+ STORE_STATE,
+ DEVICE_STATE,
+ NONE;
+
+ /**
+ * Gets the protocol byte code for this Command.
+ *
+ * @return
+ */
+ public byte toByte() {
+ for (Entry e : commandMap.entrySet()) {
+ if (e.getValue() == this) {
+ return e.getKey().byteValue();
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Converts a byte value into a Command.
+ *
+ * @param value
+ * the byte value.
+ * @return the Command that is represented by the given byte value.
+ */
+ public static Command valueOf(byte value) {
+ return commandMap.get(value);
+ }
+ }
+
+ /**
+ * An enum of possible modem response types.
+ *
+ * @author cvanorman
+ *
+ */
+ public enum Type {
+ ACCEPT,
+ BUSY,
+ ERROR,
+ ACK,
+ NAK,
+ MESSAGE_REPORT,
+ NONE;
+ }
+
+ private static final Logger logger = LoggerFactory.getLogger(UPBMessage.class);
+
+ private static HashMap commandMap = new HashMap<>();
+
+ static {
+ commandMap.put(0x20, Command.ACTIVATE);
+ commandMap.put(0x21, Command.DEACTIVATE);
+ commandMap.put(0x22, Command.GOTO);
+ commandMap.put(0x23, Command.START_FADE);
+ commandMap.put(0x24, Command.STOP_FADE);
+ commandMap.put(0x25, Command.BLINK);
+ commandMap.put(0x30, Command.REPORT_STATE);
+ commandMap.put(0x31, Command.STORE_STATE);
+ commandMap.put(0x86, Command.DEVICE_STATE);
+ }
+
+ private static HashMap typeMap = new HashMap<>();
+
+ static {
+ typeMap.put("PA", Type.ACCEPT);
+ typeMap.put("PB", Type.BUSY);
+ typeMap.put("PE", Type.ERROR);
+ typeMap.put("PK", Type.ACK);
+ typeMap.put("PN", Type.NAK);
+ typeMap.put("PU", Type.MESSAGE_REPORT);
+ }
+
+ /**
+ * Converts a hex string into a {@link UPBMessage}.
+ *
+ * @param commandString
+ * the string as returned by the modem.
+ * @return a new UPBMessage.
+ */
+ public static UPBMessage fromString(String commandString) {
+ UPBMessage command = new UPBMessage();
+
+ String typeString = commandString.substring(0, 2);
+ Type type = Type.NONE;
+
+ if (typeMap.containsKey(typeString)) {
+ type = typeMap.get(typeString);
+ }
+
+ command.setType(type);
+
+ try {
+ if (commandString.length() > 2) {
+ byte[] data = DatatypeConverter.parseHexBinary(commandString.substring(2));
+ command.getControlWord().setBytes(data[1], data[0]);
+ int index = 2;
+ command.setNetwork(data[index++]);
+ command.setDestination(data[index++]);
+ command.setSource(data[index++]);
+
+ int commandCode = data[index++] & 0xFF;
+
+ if (commandMap.containsKey(commandCode)) {
+ command.setCommand(commandMap.get(commandCode));
+ } else {
+ command.setCommand(Command.NONE);
+ }
+
+ if (index < data.length - 1) {
+ command.setArguments(Arrays.copyOfRange(data, index, data.length - 1));
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Attempted to parse invalid message: {}", commandString, e);
+ }
+
+ return command;
+ }
+
+ private Type type;
+ private ControlWord controlWord = new ControlWord();
+ private byte network;
+ private byte destination;
+ private byte source;
+
+ private Command command = Command.NONE;
+ private byte[] arguments;
+
+ /**
+ * @return the type
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * @param type
+ * the type to set
+ */
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the controlWord
+ */
+ public ControlWord getControlWord() {
+ return controlWord;
+ }
+
+ /**
+ * @param controlWord
+ * the controlWord to set
+ */
+ public void setControlWord(ControlWord controlWord) {
+ this.controlWord = controlWord;
+ }
+
+ /**
+ * @return the network
+ */
+ public byte getNetwork() {
+ return network;
+ }
+
+ /**
+ * @param network
+ * the network to set
+ */
+ public void setNetwork(byte network) {
+ this.network = network;
+ }
+
+ /**
+ * @return the destination
+ */
+ public byte getDestination() {
+ return destination;
+ }
+
+ /**
+ * @param destination
+ * the destination to set
+ */
+ public void setDestination(byte destination) {
+ this.destination = destination;
+ }
+
+ /**
+ * @return the source
+ */
+ public byte getSource() {
+ return source;
+ }
+
+ /**
+ * @param source
+ * the source to set
+ */
+ public void setSource(byte source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the command
+ */
+ public Command getCommand() {
+ return command;
+ }
+
+ /**
+ * @param command
+ * the command to set
+ */
+ public void setCommand(Command command) {
+ this.command = command;
+ }
+
+ /**
+ * @return the arguments
+ */
+ public byte[] getArguments() {
+ return arguments;
+ }
+
+ /**
+ * @param arguments
+ * the arguments to set
+ */
+ public void setArguments(byte[] arguments) {
+ this.arguments = arguments;
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBReader.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBReader.java
new file mode 100644
index 00000000000..e93f25d8fcb
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBReader.java
@@ -0,0 +1,190 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class monitors the input stream of a UPB modem. This is done
+ * asynchronously. When messages are received, they are broadcast to all
+ * subscribed {@link Listener listeners}.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBReader implements Runnable {
+
+ /**
+ * Listener class for handling received messages. A listener can be added by
+ * calling {@link UPBReader#addListener(Listener)}.
+ *
+ * @author cvanorman
+ *
+ */
+ public interface Listener {
+
+ /**
+ * Called whenever a message has been received from the UPB modem.
+ *
+ * @param message
+ * the message that was received.
+ */
+ void messageReceived(UPBMessage message);
+ }
+
+ private static final Logger logger = LoggerFactory.getLogger(UPBReader.class);
+
+ private Collection listeners = new LinkedHashSet<>();
+ private byte[] buffer = new byte[512];
+ private int bufferLength = 0;
+ private InputStream inputStream;
+ private Thread thread;
+
+ /**
+ * Instantiates a new {@link UPBReader}.
+ *
+ * @param inputStream
+ * the inputStream from the UPB modem.
+ */
+ public UPBReader(InputStream inputStream) {
+ this.inputStream = inputStream;
+
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ /**
+ * Subscribes the listener to any future message events.
+ *
+ * @param listener
+ * the listener to add.
+ */
+ public synchronized void addListener(Listener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes the listener from further messages.
+ *
+ * @param listener
+ * the listener to remove.
+ */
+ public synchronized void removeListener(Listener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Adds data to the buffer.
+ *
+ * @param data
+ * the data to add.
+ * @param length
+ * the length of data to add.
+ */
+ private void addData(byte[] data, int length) {
+
+ if (bufferLength + length > buffer.length) {
+ // buffer overflow discard entire buffer
+ bufferLength = 0;
+ }
+
+ System.arraycopy(data, 0, buffer, bufferLength, length);
+
+ bufferLength += length;
+
+ interpretBuffer();
+ }
+
+ /**
+ * Shuts the reader down.
+ */
+ public void shutdown() {
+ if (thread != null) {
+ thread.interrupt();
+ }
+
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ private int findMessageLength(byte[] buffer, int bufferLength) {
+ int messageLength = ArrayUtils.INDEX_NOT_FOUND;
+
+ for (int i = 0; i < bufferLength; i++) {
+ if (buffer[i] == 13) {
+ messageLength = i;
+ break;
+ }
+ }
+
+ return messageLength;
+ }
+
+ /**
+ * Attempts to interpret any messages that may be contained in the buffer.
+ */
+ private void interpretBuffer() {
+ int messageLength = findMessageLength(buffer, bufferLength);
+
+ while (messageLength != ArrayUtils.INDEX_NOT_FOUND) {
+ String message = new String(Arrays.copyOfRange(buffer, 0, messageLength));
+ logger.debug("UPB Message: {}", message);
+
+ int remainingBuffer = bufferLength - messageLength - 1;
+
+ if (remainingBuffer > 0) {
+ System.arraycopy(buffer, messageLength + 1, buffer, 0, remainingBuffer);
+ }
+ bufferLength = remainingBuffer;
+
+ notifyListeners(UPBMessage.fromString(message));
+
+ messageLength = findMessageLength(buffer, bufferLength);
+ }
+ }
+
+ private synchronized void notifyListeners(UPBMessage message) {
+ for (Listener l : new ArrayList<>(listeners)) {
+ l.messageReceived(message);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ byte[] buffer = new byte[256];
+ try {
+ for (int len = -1; (len = inputStream.read(buffer)) >= 0;) {
+ if (len > 0) {
+ logger.debug("Received: {}", ArrayUtils.subarray(buffer, 0, len));
+ }
+ addData(buffer, len);
+ if (Thread.interrupted()) {
+ break;
+ }
+ }
+ } catch (Exception e) {
+ logger.debug("Failed to read input stream.", e);
+ }
+ logger.debug("UPBReader stopped.");
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBWriter.java b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBWriter.java
new file mode 100644
index 00000000000..cd165047bac
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/java/org/openhab/binding/upb/internal/UPBWriter.java
@@ -0,0 +1,176 @@
+/**
+ * Copyright (c) 2010-2016, openHAB.org and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.openhab.binding.upb.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.openhab.binding.upb.internal.UPBReader.Listener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Used to write data to the UPB modem.
+ *
+ * @author cvanorman
+ * @since 1.9.0
+ */
+public class UPBWriter {
+
+ /**
+ * Time in milliseconds to wait for an ACK from the modem after writing a
+ * message.
+ */
+ private static long ACK_TIMEOUT = 500;
+
+ private static final Logger logger = LoggerFactory.getLogger(UPBWriter.class);
+
+ /**
+ * Asynchronous queue for writing data to the UPB modem.
+ */
+ private ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ /**
+ * The UPB modem's OutputStream.
+ */
+ private OutputStream outputStream;
+
+ /**
+ * UPBReader that is monitoring the modem's InputStream.
+ */
+ private UPBReader upbReader;
+
+ /**
+ * Instantiates a new {@link UPBWriter} using the given modem
+ * {@link OutputStream}.
+ *
+ * @param outputStream
+ * the {@link OutputStream} from the UPB modem.
+ * @param upbReader
+ * the {@link UPBReader} that is monitoring the same UPB modem.
+ */
+ public UPBWriter(OutputStream outputStream, UPBReader upbReader) {
+ this.outputStream = outputStream;
+ this.upbReader = upbReader;
+ }
+
+ /**
+ * Queues a message to be written to the modem.
+ *
+ * @param message
+ * the message to be written.
+ */
+ public void queueMessage(MessageBuilder message) {
+ String data = message.build();
+ logger.debug("Queueing message {}.", data);
+ executor.execute(new Message(data.getBytes()));
+ }
+
+ /**
+ * Cancels all queued messages and releases resources. This instance cannot
+ * be used again and a new {@link UPBWriter} must be instantiated after
+ * calling this method.
+ */
+ public void shutdown() {
+ executor.shutdownNow();
+
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ }
+ logger.debug("UPBWriter shutdown");
+ }
+
+ /**
+ * {@link Runnable} implementation used to write data to the UPB modem.
+ *
+ * @author cvanorman
+ *
+ */
+ private class Message implements Runnable, Listener {
+
+ private boolean waitingOnAck = true;
+ private boolean ackReceived = false;
+ private byte[] data;
+
+ private Message(byte[] data) {
+ this.data = data;
+ }
+
+ private synchronized void ackReceived(boolean ack) {
+ waitingOnAck = false;
+ ackReceived = ack;
+ notify();
+ }
+
+ private synchronized boolean waitForAck(int retryCount) {
+ long start = System.currentTimeMillis();
+ while (waitingOnAck && (System.currentTimeMillis() - start) < ACK_TIMEOUT) {
+ try {
+ wait(ACK_TIMEOUT);
+ } catch (InterruptedException e) {
+
+ }
+
+ if (!waitingOnAck) {
+ if (ackReceived) {
+ logger.debug("Message {} ack received.", new String(data));
+ } else {
+ logger.debug("Message {} not ack'd.", new String(data));
+ }
+ } else {
+ logger.debug("Message {} ack timed out.", new String(data));
+ }
+ }
+
+ return ackReceived || retryCount == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void messageReceived(UPBMessage message) {
+ switch (message.getType()) {
+ case BUSY:
+ case NAK:
+ ackReceived(false);
+ break;
+ case ACK:
+ ackReceived(true);
+ break;
+ default:
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ try {
+ upbReader.addListener(this);
+ int retryCount = 3;
+ do {
+ ackReceived = false;
+ waitingOnAck = true;
+ logger.debug("Writing bytes: {}", new String(data));
+ outputStream.write(0x14);
+ outputStream.write(data);
+ outputStream.write(0x0d);
+ } while (!waitForAck(retryCount--));
+ } catch (IOException e) {
+ } finally {
+ upbReader.removeListener(this);
+ }
+ }
+ }
+}
diff --git a/bundles/binding/org.openhab.binding.upb/src/main/resources/readme.txt b/bundles/binding/org.openhab.binding.upb/src/main/resources/readme.txt
new file mode 100644
index 00000000000..98698c670dc
--- /dev/null
+++ b/bundles/binding/org.openhab.binding.upb/src/main/resources/readme.txt
@@ -0,0 +1 @@
+Bundle resources go in here!
\ No newline at end of file
diff --git a/bundles/binding/pom.xml b/bundles/binding/pom.xml
index bbc84ef4e96..1e1dd62dd7f 100644
--- a/bundles/binding/pom.xml
+++ b/bundles/binding/pom.xml
@@ -183,5 +183,6 @@
org.openhab.binding.smarthomatic
org.openhab.binding.gc100ir
org.openhab.binding.powermax
+ org.openhab.binding.upb