]> git.basschouten.com Git - openhab-addons.git/commitdiff
[serial] Initial contribution (#8851)
authorMike Major <mike_j_major@hotmail.com>
Thu, 19 Nov 2020 18:51:54 +0000 (18:51 +0000)
committerGitHub <noreply@github.com>
Thu, 19 Nov 2020 18:51:54 +0000 (19:51 +0100)
Signed-off-by: Mike Major <mike_j_major@hotmail.com>
30 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.serial/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.serial/README.md [new file with mode: 0644]
bundles/org.openhab.binding.serial/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/ChannelConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannelFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DimmerChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/NumberChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/RollershutterChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/StringChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/SwitchChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/CascadedValueTransformationImpl.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/NoOpValueTransformation.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/SingleValueTransformation.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformation.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformationProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/Parity.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/StopBits.java [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.serial/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 171df689062735fe035930963195160566422d48..45dfce187bfa5471b9f3b37c96d54378360f2a16 100644 (file)
 /bundles/org.openhab.binding.seneye/ @nikotanghe
 /bundles/org.openhab.binding.sensebox/ @hakan42
 /bundles/org.openhab.binding.sensibo/ @seime
+/bundles/org.openhab.binding.serial/ @MikeJMajor
 /bundles/org.openhab.binding.serialbutton/ @kaikreuzer
 /bundles/org.openhab.binding.shelly/ @markus7017
 /bundles/org.openhab.binding.siemensrds/ @andrewfg
index 25de4404bc0d0701b49b366b067cff19dd3eee43..a64b92e1ca5c567fae6eba95b028424c983c19c7 100644 (file)
       <artifactId>org.openhab.binding.sensibo</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.serial</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.serialbutton</artifactId>
diff --git a/bundles/org.openhab.binding.serial/NOTICE b/bundles/org.openhab.binding.serial/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.serial/README.md b/bundles/org.openhab.binding.serial/README.md
new file mode 100644 (file)
index 0000000..522cc1f
--- /dev/null
@@ -0,0 +1,140 @@
+# Serial Binding
+
+The Serial binding allows openHAB to communicate over serial ports attached to the openHAB server.
+
+The binding allows data to be sent and received from a serial port. 
+The binding does not support any particular serial protocols and simply reads what is available and sends what is provided.
+
+The binding can be used to communicate with simple serial devices for which a dedicated openHAB binding does not exist.
+
+## Overview
+
+The Serial binding represents a serial port as a bridge thing and data matching defined patterns as things connected to the bridge.
+
+### Serial Bridge
+
+A Serial Bridge thing (`serialBridge`) represents a single serial port.
+
+The bridge supports a String channel which is set to the currently received data from the serial port. 
+Sending a command to this channel sends the command as a string to the serial port.
+
+The bridge also supports a String channel which encodes the received data as the string representation of a RawType to handle data that is 
+not supported by the REST interface. 
+A command sent to this channel will only be sent to the serial port if it is encoded as the string representation of a RawType.
+
+A trigger channel is also provided which triggers when data is received.
+
+### Serial Device
+
+A Serial Device thing (`serialDevice`) can be used to represent data matching a defined pattern as a device. 
+The serial port may be providing data for many different devices/sensors, such as a temperature sensor or a doorbell. 
+Usually such devices can be identified by performing a pattern match on the received data. 
+For example, a Serial Device could be configured to represent a temperature sensor.
+
+The thing will only update its channels if the received data matches the defined pattern. 
+
+The thing supports generic String and Number channels which can apply a transform on the received data to set the channel state. 
+Commands sent to the channels can be formatted and transformed before being sent to the device.
+
+The thing also supports Switch and Rollershutter channels which provide simple mappings for the ON, OFF, UP, DOWN and STOP commands.
+
+When using a Serial Device the expectation is that the received data for each device is terminated by a line break.
+
+## Thing Configuration
+
+The configuration for the `serialBridge` consists of the following parameters:
+
+| Parameter           | Description                                                                                            |
+|---------------------|--------------------------------------------------------------------------------------------------------|
+| serialPort          | The serial port to use (e.g. Linux: /dev/ttyUSB0, Windows: COM1) (mandatory)                           |
+| baudRate            | Set the baud rate. Valid values: 4800, 9600, 19200, 38400, 57600, 115200 (default 9600)                |
+| dataBits            | Set the data bits. Valid values: 5, 6, 7, 8 (default 8)                                                |
+| parity              | Set the parity. Valid values: N(one), O(dd), E(even), M(ark), S(pace) (default N)                      |
+| stopBits            | Set the stop bits. Valid values: 1, 1.5, 2 (default 1)                                                 |
+| charset             | The charset to use for converting between bytes and string (e.g. UTF-8,ISO-8859-1)                     |
+
+The configuration for the `serialDevice` consists of the following parameters:
+
+| Parameter           | Description                                                                                            |
+|---------------------|--------------------------------------------------------------------------------------------------------|
+| patternMatch        | Regular expression used to identify device from received data (must match the whole line) (mandatory)  |
+
+## Channels
+
+The channels supported by the `serialBridge` are:
+
+| Channel  | Type             | Description                                                                                              |
+|----------|------------------|----------------------------------------------------------------------------------------------------------|
+| `string`   | String           | Channel for sending/receiving data as a string to/from the serial port. The channel will update its state to a StringType that is the data received from the serial port. A command sent to this channel will be sent out as data through the serial port. |
+| `binary`   | String           | Channel for sending/receiving data in Base64 format to/from the serial port. The channel will update its state to a StringType which is the string representation of a RawType that contains the data received from the serial port. A command sent to this channel must be encoded as the string representation of a RawType, e.g. `"data:application/octet-stream;base64 MjA7MDU7Q3Jlc3RhO0lEPTI4MDE7VEVNUD0yNTtIVU09NTU7QkFUPU9LOwo="` |
+| `data`     | system.rawbutton | Trigger which emits `PRESSED` events (no `RELEASED` events) whenever data is available on the serial port                                                                                                                                     |
+
+
+The channels supported by the `serialDevice` are:
+
+| Channel Type  | Type             | Description                                                                                              |
+|---------------|------------------|----------------------------------------------------------------------------------------------------------|
+| `string` | String | Channel for receiving string based commands. The channel can be configured to apply a transform on the received data to convert to the channel state. Commands received by the channel can be formatted and transformed before sending to the device. |
+| `number` | Number | Channel for receiving number based commands. The channel can be configured to apply a transform on the received data to convert to the channel state. Commands received by the channel can be formatted and transformed before sending to the device. |
+| `dimmer` | Dimmer | Channel for receiving commands from a Dimmer. The channel can be configured to apply a transform on the received data to convert to the channel state. The channel can be configured to apply a simple mapping for the ON, OFF, INCREASE and DECREASE commands. |
+| `switch` | Switch | Channel for receiving commands from a Switch. The channel can be configured to apply a transform on the received data to convert to the channel state. The channel can be configured to apply a simple mapping for the ON and OFF commands. |
+| `rollershutter` | Rollershutter | Channel for receiving commands from a Rollershutter. The channel can be configured to apply a transform on the received data to convert to the channel state. The channel can be configured to apply a simple mapping for the UP, DOWN and STOP commands. |
+
+The configuration for the `serialBridge` channels consists of the following parameters:
+
+| Parameter        | Description                                                                            | Supported Channels |
+|------------------|----------------------------------------------------------------------------------------|--------------------|
+| `stateTransformation` | One or more transformation (concatenated with `∩`) used to convert device data to channel state, e.g. `REGEX:.*?STATE=(.*?);.*` | string, number, dimmer, switch, rollershutter |
+| `commandTransformation` | One or more transformation (concatenated with `∩`) used to convert command to device data, e.g. `JS:device.js` | string, number, dimmer, switch, rollershutter |
+| `commandFormat` | Format string applied to the command before transform, e.g. `ID=671;COMMAND=%s` | string, number, dimmer, rollershutter |
+| `onValue` | Send this value when receiving an ON command | switch, dimmer |
+| `offValue` | Send this value when receiving an OFF command | switch, dimmer |
+| `increaseValue` | Send this value when receiving an INCREASE command | dimmer |
+| `decreaseValue` | Send this value when receiving a DECREASE command | dimmer |
+| `upValue` | Send this value when receiving an UP command | rollershutter |
+| `downValue` | Send this value when receiving a DOWN command | rollershutter |
+| `stopValue` | Send this value when receiving a STOP command | rollershutter |
+
+
+## Full Example
+
+The following example is for a device connected to a serial port which provides data for many different sensors and we are interested in the temperature from a particular sensor.
+
+The data for the sensor of interest is `20;05;Cresta;ID=2801;TEMP=25;HUM=55;BAT=OK;`
+
+demo.things:
+
+```
+Bridge serial:serialBridge:sensors [serialPort="/dev/ttyUSB01", baudRate=57600] {
+    Thing serialDevice temperatureSensor [patternMatch="20;05;Cresta;ID=2801;.*"] {
+        Channels:
+            Type number : temperature [transform="REGEX:.*?TEMP=(.*?);.*"]
+            Type number : humidity [transform="REGEX:.*?HUM=(.*?);.*"]
+    }
+    Thing serialDevice rollershutter [patternMatch=".*"] {
+        Channels:
+            Type rollershutter : serialRollo [transform="REGEX:Position:([0-9.]*)", up="Rollo_UP\n", down="Rollo_DOWN\n", stop="Rollo_STOP\n"]
+            Type switch : roloAt100 [transform="REGEX:s/Position:100/ON/"]
+    }
+    Thing serialDevice relay [patternMatch=".*"] {
+        Channels:
+            Type switch : serialRelay [on="Q1_ON\n", off="Q1_OFF\n"]
+    }
+    Thing serialDevice myDevice [patternMatch="ID=2341;.*"] {
+        Channels:
+            Type string : control [commandTransform="JS:addCheckSum.js", commandFormat="ID=2341;COMMAND=%s;"]
+    }
+}
+
+```
+
+demo.items:
+
+```
+Number:Temperature myTemp "My Temperature" {channel="serial:serialDevice:sensors:temperatureSensor:temperature"}
+Number myHum "My Humidity" {channel="serial:serialDevice:sensors:temperatureSensor:humidity"}
+Switch serialRelay "Relay Q1" (Entrance) {channel="serial:serialDevice:sensors:relay:serialRelay"}
+Rollershutter serialRollo "Entrance Rollo" (Entrance) {channel="serial:serialDevice:sensors:rollershutter:serialRollo"}
+Rollershutter roloAt100 "Rolo at 100" (Entrance) {channel="serial:serialDevice:sensors:rollershutter:roloAt100"}
+String deviceControl {channel="serial:serialDevice:sensors:myDevice:control"}
+```
diff --git a/bundles/org.openhab.binding.serial/pom.xml b/bundles/org.openhab.binding.serial/pom.xml
new file mode 100644 (file)
index 0000000..977960f
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.serial</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Serial Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.serial/src/main/feature/feature.xml b/bundles/org.openhab.binding.serial/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..65279b0
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+       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
+
+-->
+<features name="org.openhab.binding.serial-${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-serial" description="Serial Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <feature>openhab-transport-serial</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.serial/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialBindingConstants.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialBindingConstants.java
new file mode 100644 (file)
index 0000000..1aa6116
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * 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.serial.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link SerialBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SerialBindingConstants {
+
+    private static final String BINDING_ID = "serial";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "serialBridge");
+    public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "serialDevice");
+
+    // List of all Channel ids
+    public static final String TRIGGER_CHANNEL = "data";
+    public static final String STRING_CHANNEL = "string";
+    public static final String BINARY_CHANNEL = "binary";
+    public static final String DEVICE_STRING_CHANNEL = "string";
+    public static final String DEVICE_NUMBER_CHANNEL = "number";
+    public static final String DEVICE_DIMMER_CHANNEL = "dimmer";
+    public static final String DEVICE_SWITCH_CHANNEL = "switch";
+    public static final String DEVICE_ROLLERSHUTTER_CHANNEL = "rollershutter";
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialHandlerFactory.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/SerialHandlerFactory.java
new file mode 100644 (file)
index 0000000..346e48c
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * 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.serial.internal;
+
+import static org.openhab.binding.serial.internal.SerialBindingConstants.THING_TYPE_BRIDGE;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.THING_TYPE_DEVICE;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.serial.internal.handler.SerialBridgeHandler;
+import org.openhab.binding.serial.internal.handler.SerialDeviceHandler;
+import org.openhab.binding.serial.internal.transform.CascadedValueTransformationImpl;
+import org.openhab.binding.serial.internal.transform.NoOpValueTransformation;
+import org.openhab.binding.serial.internal.transform.ValueTransformation;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.io.transport.serial.SerialPortManager;
+import org.openhab.core.thing.Bridge;
+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.openhab.core.transform.TransformationHelper;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link SerialHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.serial", service = ThingHandlerFactory.class)
+public class SerialHandlerFactory extends BaseThingHandlerFactory implements ValueTransformationProvider {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_DEVICE);
+
+    private final SerialPortManager serialPortManager;
+
+    @Activate
+    public SerialHandlerFactory(@Reference final SerialPortManager serialPortManager) {
+        this.serialPortManager = serialPortManager;
+    }
+
+    @Override
+    public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(final Thing thing) {
+        final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
+            return new SerialBridgeHandler((Bridge) thing, serialPortManager);
+        } else if (THING_TYPE_DEVICE.equals(thingTypeUID)) {
+            return new SerialDeviceHandler(thing, this);
+        }
+
+        return null;
+    }
+
+    @Override
+    public ValueTransformation getValueTransformation(@Nullable final String pattern) {
+        if (pattern == null) {
+            return NoOpValueTransformation.getInstance();
+        }
+        return new CascadedValueTransformationImpl(pattern,
+                name -> TransformationHelper.getTransformationService(bundleContext, name));
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/ChannelConfig.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/ChannelConfig.java
new file mode 100644 (file)
index 0000000..5848d1e
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * 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.serial.internal.channel;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class describing the channel user configuration
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class ChannelConfig {
+    /**
+     * Transform for received data
+     */
+    public @Nullable String stateTransformation;
+
+    /**
+     * Transform for command
+     */
+    public @Nullable String commandTransformation;
+
+    /**
+     * Format string for command
+     */
+    public @Nullable String commandFormat;
+
+    /**
+     * On value
+     */
+    public @Nullable String onValue;
+
+    /**
+     * Off value
+     */
+    public @Nullable String offValue;
+
+    /**
+     * Up value
+     */
+    public @Nullable String upValue;
+
+    /**
+     * Down value
+     */
+    public @Nullable String downValue;
+
+    /**
+     * Stop value
+     */
+    public @Nullable String stopValue;
+
+    /**
+     * Increase value
+     */
+    public @Nullable String increaseValue;
+
+    /**
+     * Decrease value
+     */
+    public @Nullable String decreaseValue;
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannel.java
new file mode 100644 (file)
index 0000000..adc4656
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * 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.serial.internal.channel;
+
+import java.util.IllegalFormatException;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformation;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link DeviceChannel} is the abstract class for handling a channel. Provides
+ * the ability to transform the device data into the channel state.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public abstract class DeviceChannel {
+    protected final Logger logger = LoggerFactory.getLogger(DeviceChannel.class);
+
+    protected final ChannelConfig config;
+
+    private final ValueTransformation stateTransform;
+    private final ValueTransformation commandTransform;
+
+    protected DeviceChannel(final ValueTransformationProvider valueTransformationProvider, final ChannelConfig config) {
+        this.config = config;
+        stateTransform = valueTransformationProvider.getValueTransformation(config.stateTransformation);
+        commandTransform = valueTransformationProvider.getValueTransformation(config.commandTransformation);
+    }
+
+    /**
+     * Map the supplied command into the data to send to the device by
+     * applying a format followed by a transform.
+     * 
+     * @param command the command to map
+     * @return the mapped data if the mapping produced a result.
+     */
+    public Optional<String> mapCommand(final Command command) {
+        final Optional<String> result = transformCommand(formatCommand(command));
+
+        logger.debug("Mapped command is '{}'", result.orElse(null));
+
+        return result;
+    }
+
+    /**
+     * Transform the data using the configured transform
+     * 
+     * @param data the data to transform
+     * @return the transformed data if the transform produced a result.
+     */
+    public Optional<String> transformData(final String data) {
+        return stateTransform.apply(data);
+    }
+
+    /**
+     * Transform the data using the configured command transform
+     * 
+     * @param data the command to transform
+     * @return the transformed data if the transform produced a result.
+     */
+    protected Optional<String> transformCommand(final String data) {
+        return commandTransform.apply(data);
+    }
+
+    /**
+     * Format the commnd using the configured format
+     * 
+     * @param data the command to transform
+     * @return the formatted data. The orginal data is returned if there is no format string
+     *         or if there is an error performing the format.
+     */
+    protected String formatCommand(final Command command) {
+        String data;
+
+        final String commandFormat = config.commandFormat;
+        if (commandFormat != null) {
+            try {
+                data = command.format(commandFormat);
+            } catch (final IllegalFormatException e) {
+                logger.warn("Couldn't format commmand because format string '{}' is invalid", commandFormat);
+                data = command.toFullString();
+            }
+        } else {
+            data = command.toFullString();
+        }
+
+        return data;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannelFactory.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DeviceChannelFactory.java
new file mode 100644 (file)
index 0000000..2c4d3a4
--- /dev/null
@@ -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.serial.internal.channel;
+
+import static org.openhab.binding.serial.internal.SerialBindingConstants.DEVICE_DIMMER_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.DEVICE_NUMBER_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.DEVICE_ROLLERSHUTTER_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.DEVICE_STRING_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.DEVICE_SWITCH_CHANNEL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+
+/**
+ * A factory to create {@link DeviceChannel} objects
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class DeviceChannelFactory {
+
+    /**
+     * Create a {@link DeviceChannel} for the channel type
+     * 
+     * @param bundleContext the bundle context
+     * @param channelConfig the channel configuration
+     * @param channelTypeID the channel type id
+     * @return the DeviceChannel or null if the channel type is not supported.
+     */
+    public static @Nullable DeviceChannel createDeviceChannel(
+            final ValueTransformationProvider valueTransformationProvider, final ChannelConfig channelConfig,
+            final String channelTypeID) {
+        DeviceChannel deviceChannel;
+
+        switch (channelTypeID) {
+            case DEVICE_STRING_CHANNEL:
+                deviceChannel = new StringChannel(valueTransformationProvider, channelConfig);
+                break;
+            case DEVICE_NUMBER_CHANNEL:
+                deviceChannel = new NumberChannel(valueTransformationProvider, channelConfig);
+                break;
+            case DEVICE_DIMMER_CHANNEL:
+                deviceChannel = new DimmerChannel(valueTransformationProvider, channelConfig);
+                break;
+            case DEVICE_SWITCH_CHANNEL:
+                deviceChannel = new SwitchChannel(valueTransformationProvider, channelConfig);
+                break;
+            case DEVICE_ROLLERSHUTTER_CHANNEL:
+                deviceChannel = new RollershutterChannel(valueTransformationProvider, channelConfig);
+                break;
+            default:
+                deviceChannel = null;
+                break;
+        }
+
+        return deviceChannel;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DimmerChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/DimmerChannel.java
new file mode 100644 (file)
index 0000000..f79c9e7
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * 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.serial.internal.channel;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link DimmerChannel} channel applies a format followed by a transform.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class DimmerChannel extends SwitchChannel {
+
+    public DimmerChannel(final ValueTransformationProvider valueTransformationProvider, final ChannelConfig config) {
+        super(valueTransformationProvider, config);
+    }
+
+    @Override
+    public Optional<String> mapCommand(final Command command) {
+        Optional<String> result;
+
+        if (command instanceof OnOffType) {
+            result = super.mapCommand(command);
+        } else {
+            String data;
+
+            final String increaseValue = config.increaseValue;
+            final String decreaseValue = config.decreaseValue;
+
+            if (command instanceof IncreaseDecreaseType) {
+                if (increaseValue != null && IncreaseDecreaseType.INCREASE.equals(command)) {
+                    data = increaseValue;
+                } else if (decreaseValue != null && IncreaseDecreaseType.DECREASE.equals(command)) {
+                    data = decreaseValue;
+                } else {
+                    data = command.toFullString();
+                }
+            } else {
+                data = formatCommand(command);
+            }
+
+            result = transformCommand(data);
+
+            logger.debug("Mapped command is '{}'", result.orElse(null));
+        }
+
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/NumberChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/NumberChannel.java
new file mode 100644 (file)
index 0000000..a530af0
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * 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.serial.internal.channel;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+
+/**
+ * The {@link NumberChannel} channel applies a format followed by a transform.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class NumberChannel extends DeviceChannel {
+
+    public NumberChannel(final ValueTransformationProvider valueTransformationProvider, final ChannelConfig config) {
+        super(valueTransformationProvider, config);
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/RollershutterChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/RollershutterChannel.java
new file mode 100644 (file)
index 0000000..87a78a3
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * 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.serial.internal.channel;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link RollershutterChannel} channel provides mappings for the UP, DOWN and STOP commands
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class RollershutterChannel extends DeviceChannel {
+
+    public RollershutterChannel(final ValueTransformationProvider valueTransformationProvider,
+            final ChannelConfig config) {
+        super(valueTransformationProvider, config);
+    }
+
+    @Override
+    public Optional<String> mapCommand(final Command command) {
+        String data;
+
+        final String upValue = config.upValue;
+        final String downValue = config.downValue;
+        final String stopValue = config.stopValue;
+
+        if (command instanceof UpDownType) {
+            if (upValue != null && UpDownType.UP.equals(command)) {
+                data = upValue;
+            } else if (downValue != null && UpDownType.DOWN.equals(command)) {
+                data = downValue;
+            } else {
+                data = command.toFullString();
+            }
+        } else if (command instanceof StopMoveType) {
+            if (stopValue != null && StopMoveType.STOP.equals(command)) {
+                data = stopValue;
+            } else {
+                data = command.toFullString();
+            }
+        } else {
+            data = formatCommand(command);
+        }
+
+        final Optional<String> result = transformCommand(data);
+
+        logger.debug("Mapped command is '{}'", result.orElse(null));
+
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/StringChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/StringChannel.java
new file mode 100644 (file)
index 0000000..0bd46e1
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * 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.serial.internal.channel;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+
+/**
+ * The {@link StringChannel} channel applies a format followed by a transform.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class StringChannel extends DeviceChannel {
+
+    public StringChannel(final ValueTransformationProvider valueTransformationProvider, final ChannelConfig config) {
+        super(valueTransformationProvider, config);
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/SwitchChannel.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/channel/SwitchChannel.java
new file mode 100644 (file)
index 0000000..c4a4e6b
--- /dev/null
@@ -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.serial.internal.channel;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link SwitchChannel} channel provides mappings for the ON and OFF commands
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SwitchChannel extends DeviceChannel {
+
+    public SwitchChannel(final ValueTransformationProvider valueTransformationProvider, final ChannelConfig config) {
+        super(valueTransformationProvider, config);
+    }
+
+    @Override
+    public Optional<String> mapCommand(final Command command) {
+        String data;
+
+        final String onValue = config.onValue;
+        final String offValue = config.offValue;
+
+        if (onValue != null && OnOffType.ON.equals(command)) {
+            data = onValue;
+        } else if (offValue != null && OnOffType.OFF.equals(command)) {
+            data = offValue;
+        } else {
+            data = command.toFullString();
+        }
+
+        final Optional<String> result = transformCommand(data);
+
+        logger.debug("Mapped command is '{}'", result.orElse(null));
+
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeConfiguration.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeConfiguration.java
new file mode 100644 (file)
index 0000000..ec2c7f1
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * 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.serial.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class describing the serial bridge user configuration
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SerialBridgeConfiguration {
+    /**
+     * Serial port name
+     */
+    public @Nullable String serialPort;
+
+    /**
+     * Serial port baud rate
+     */
+    public int baudRate = 9600;
+
+    /**
+     * Serial port data bits
+     */
+    public int dataBits = 8;
+
+    /**
+     * Serial port parity
+     */
+    public String parity = "N";
+
+    /**
+     * Serial port stop bits
+     */
+    public String stopBits = "1";
+
+    /**
+     * Charset
+     */
+    public @Nullable String charset;
+
+    @Override
+    public String toString() {
+        return "SerialBridgeConfiguration [serialPort=" + serialPort + ", Baudrate=" + baudRate + ", Databits="
+                + dataBits + ", Parity=" + parity + ", Stopbits=" + stopBits + ", charset=" + charset + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeHandler.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialBridgeHandler.java
new file mode 100644 (file)
index 0000000..4830af0
--- /dev/null
@@ -0,0 +1,343 @@
+/**
+ * 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.serial.internal.handler;
+
+import static org.openhab.binding.serial.internal.SerialBindingConstants.BINARY_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.STRING_CHANNEL;
+import static org.openhab.binding.serial.internal.SerialBindingConstants.TRIGGER_CHANNEL;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.TooManyListenersException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.serial.internal.util.Parity;
+import org.openhab.binding.serial.internal.util.StopBits;
+import org.openhab.core.io.transport.serial.PortInUseException;
+import org.openhab.core.io.transport.serial.SerialPort;
+import org.openhab.core.io.transport.serial.SerialPortEvent;
+import org.openhab.core.io.transport.serial.SerialPortEventListener;
+import org.openhab.core.io.transport.serial.SerialPortIdentifier;
+import org.openhab.core.io.transport.serial.SerialPortManager;
+import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
+import org.openhab.core.library.types.RawType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.CommonTriggerEvents;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SerialBridgeHandler} is responsible for handling commands, which
+ * are sent to one of the channels.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SerialBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
+
+    private final Logger logger = LoggerFactory.getLogger(SerialBridgeHandler.class);
+
+    private SerialBridgeConfiguration config = new SerialBridgeConfiguration();
+
+    private final SerialPortManager serialPortManager;
+    private @Nullable SerialPort serialPort;
+
+    private @Nullable InputStream inputStream;
+    private @Nullable OutputStream outputStream;
+
+    private Charset charset = StandardCharsets.UTF_8;
+
+    private @Nullable String lastValue;
+
+    private final AtomicBoolean readerActive = new AtomicBoolean(false);
+    private @Nullable ScheduledFuture<?> reader;
+
+    public SerialBridgeHandler(final Bridge bridge, final SerialPortManager serialPortManager) {
+        super(bridge);
+        this.serialPortManager = serialPortManager;
+    }
+
+    @Override
+    public void handleCommand(final ChannelUID channelUID, final Command command) {
+        if (command instanceof RefreshType) {
+            final String lastValue = this.lastValue;
+
+            if (lastValue != null) {
+                refresh(channelUID.getId(), lastValue);
+            }
+        } else {
+            switch (channelUID.getId()) {
+                case STRING_CHANNEL:
+                    writeString(command.toFullString(), false);
+                    break;
+                case BINARY_CHANNEL:
+                    writeString(command.toFullString(), true);
+                    break;
+                default:
+                    break;
+            }
+
+        }
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(SerialBridgeConfiguration.class);
+
+        try {
+            if (config.charset != null) {
+                charset = Charset.forName(config.charset);
+            }
+            logger.debug("Serial port '{}' charset '{}' set", config.serialPort, charset);
+        } catch (final IllegalCharsetNameException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Invalid charset");
+            return;
+        }
+
+        final String port = config.serialPort;
+        if (port == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set");
+            return;
+        }
+
+        // parse ports and if the port is found, initialize the reader
+        final SerialPortIdentifier portId = serialPortManager.getIdentifier(port);
+        if (portId == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port is not known");
+            return;
+        }
+
+        // initialize serial port
+        try {
+            final SerialPort serialPort = portId.open(getThing().getUID().toString(), 2000);
+            this.serialPort = serialPort;
+
+            serialPort.setSerialPortParams(config.baudRate, config.dataBits,
+                    StopBits.fromConfig(config.stopBits).getSerialPortValue(),
+                    Parity.fromConfig(config.parity).getSerialPortValue());
+
+            serialPort.addEventListener(this);
+
+            // activate the DATA_AVAILABLE notifier
+            serialPort.notifyOnDataAvailable(true);
+            inputStream = serialPort.getInputStream();
+            outputStream = serialPort.getOutputStream();
+
+            updateStatus(ThingStatus.ONLINE);
+        } catch (final IOException ex) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error");
+        } catch (final PortInUseException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use");
+        } catch (final TooManyListenersException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    "Cannot attach listener to port");
+        } catch (final UnsupportedCommOperationException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    "Unsupported port parameters: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void dispose() {
+        final SerialPort serialPort = this.serialPort;
+        if (serialPort != null) {
+            serialPort.removeEventListener();
+            serialPort.close();
+            this.serialPort = null;
+        }
+
+        final InputStream inputStream = this.inputStream;
+        if (inputStream != null) {
+            try {
+                inputStream.close();
+            } catch (final IOException e) {
+                logger.debug("Error while closing the input stream: {}", e.getMessage());
+            }
+            this.inputStream = null;
+        }
+
+        final OutputStream outputStream = this.outputStream;
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (final IOException e) {
+                logger.debug("Error while closing the output stream: {}", e.getMessage());
+            }
+            this.outputStream = null;
+        }
+
+        readerActive.set(false);
+        final ScheduledFuture<?> reader = this.reader;
+        if (reader != null) {
+            reader.cancel(false);
+            this.reader = null;
+        }
+
+        lastValue = null;
+    }
+
+    @Override
+    public void serialEvent(final SerialPortEvent event) {
+        switch (event.getEventType()) {
+            case SerialPortEvent.DATA_AVAILABLE:
+                if (readerActive.compareAndSet(false, true)) {
+                    reader = scheduler.schedule(() -> receiveAndProcess(new StringBuilder(), true), 0,
+                            TimeUnit.MILLISECONDS);
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Sends a string to the serial port.
+     *
+     * @param string the string to send
+     */
+    public void writeString(final String string) {
+        writeString(string, false);
+    }
+
+    /**
+     * Refreshes the channel with the last received data
+     *
+     * @param channelId the channel to refresh
+     * @param channelId the data to use
+     */
+    private void refresh(final String channelId, final String data) {
+        if (!isLinked(channelId)) {
+            return;
+        }
+
+        switch (channelId) {
+            case STRING_CHANNEL:
+                updateState(channelId, new StringType(data));
+                break;
+            case BINARY_CHANNEL:
+                final StringBuilder sb = new StringBuilder("data:");
+                sb.append(RawType.DEFAULT_MIME_TYPE).append(";base64,")
+                        .append(Base64.getEncoder().encodeToString(data.getBytes(charset)));
+                updateState(channelId, new StringType(sb.toString()));
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Read from the serial port and process the data
+     * 
+     * @param sb the string builder to receive the data
+     * @param firstAttempt indicates if this is the first read attempt without waiting
+     */
+    private void receiveAndProcess(final StringBuilder sb, final boolean firstAttempt) {
+        final InputStream inputStream = this.inputStream;
+
+        if (inputStream == null) {
+            readerActive.set(false);
+            return;
+        }
+
+        try {
+            if (firstAttempt || inputStream.available() > 0) {
+                final byte[] readBuffer = new byte[20];
+
+                // read data from serial device
+                while (inputStream.available() > 0) {
+                    final int bytes = inputStream.read(readBuffer);
+                    sb.append(new String(readBuffer, 0, bytes, charset));
+                }
+
+                // Add wait states around reading the stream, so that interrupted transmissions
+                // are merged
+                if (readerActive.get()) {
+                    reader = scheduler.schedule(() -> receiveAndProcess(sb, false), 100, TimeUnit.MILLISECONDS);
+                }
+
+            } else {
+                final String result = sb.toString();
+
+                triggerChannel(TRIGGER_CHANNEL, CommonTriggerEvents.PRESSED);
+                refresh(STRING_CHANNEL, result);
+                refresh(BINARY_CHANNEL, result);
+
+                result.lines().forEach(l -> getThing().getThings().forEach(t -> {
+                    final SerialDeviceHandler device = (SerialDeviceHandler) t.getHandler();
+                    if (device != null) {
+                        device.handleData(l);
+                    }
+                }));
+
+                lastValue = result;
+
+                if (readerActive.compareAndSet(true, false)) {
+                    // Check we haven't received more data while processing
+                    if (inputStream.available() > 0 && readerActive.compareAndSet(false, true)) {
+                        reader = scheduler.schedule(() -> receiveAndProcess(new StringBuilder(), true), 0,
+                                TimeUnit.MILLISECONDS);
+                    }
+                }
+            }
+        } catch (final IOException e) {
+            logger.debug("Error reading from serial port: {}", e.getMessage(), e);
+            readerActive.set(false);
+        }
+    }
+
+    /**
+     * Sends a string to the serial port.
+     *
+     * @param string the string to send
+     * @param isRawType the string should be handled as a RawType
+     */
+    private void writeString(final String string, final boolean isRawType) {
+        final OutputStream outputStream = this.outputStream;
+
+        if (outputStream == null) {
+            return;
+        }
+
+        logger.debug("Writing '{}' to serial port {}", string, config.serialPort);
+
+        try {
+            // write string to serial port
+            if (isRawType) {
+                final RawType rt = RawType.valueOf(string);
+                outputStream.write(rt.getBytes());
+            } else {
+                outputStream.write(string.getBytes(charset));
+            }
+
+            outputStream.flush();
+        } catch (final IOException | IllegalArgumentException e) {
+            logger.warn("Error writing '{}' to serial port {}: {}", string, config.serialPort, e.getMessage());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceConfiguration.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceConfiguration.java
new file mode 100644 (file)
index 0000000..6186fbd
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.serial.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Class describing the serial device user configuration
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SerialDeviceConfiguration {
+    /**
+     * 
+     * Pattern match
+     */
+    public String patternMatch = "";
+
+    @Override
+    public String toString() {
+        return "SerialDeviceConfiguration [patternMatch=" + patternMatch + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceHandler.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/handler/SerialDeviceHandler.java
new file mode 100644 (file)
index 0000000..e20e5ce
--- /dev/null
@@ -0,0 +1,168 @@
+/**
+ * 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.serial.internal.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.serial.internal.channel.ChannelConfig;
+import org.openhab.binding.serial.internal.channel.DeviceChannel;
+import org.openhab.binding.serial.internal.channel.DeviceChannelFactory;
+import org.openhab.binding.serial.internal.transform.ValueTransformationProvider;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+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.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+
+/**
+ * The {@link SerialDeviceHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public class SerialDeviceHandler extends BaseThingHandler {
+
+    private final ValueTransformationProvider valueTransformationProvider;
+
+    private @Nullable Pattern devicePattern;
+
+    private @Nullable String lastValue;
+
+    private final Map<ChannelUID, DeviceChannel> channels = new HashMap<>();
+
+    public SerialDeviceHandler(final Thing thing, final ValueTransformationProvider valueTransformationProvider) {
+        super(thing);
+        this.valueTransformationProvider = valueTransformationProvider;
+    }
+
+    @Override
+    public void handleCommand(final ChannelUID channelUID, final Command command) {
+        if (command instanceof RefreshType) {
+            final String lastValue = this.lastValue;
+
+            if (lastValue != null) {
+                final DeviceChannel channel = channels.get(channelUID);
+                if (channel != null) {
+                    refresh(channelUID, channel, lastValue);
+                }
+            }
+        } else {
+            final DeviceChannel channel = channels.get(channelUID);
+            if (channel != null) {
+                final Bridge bridge = getBridge();
+                if (bridge != null) {
+                    final SerialBridgeHandler handler = (SerialBridgeHandler) bridge.getHandler();
+                    if (handler != null) {
+                        channel.mapCommand(command).ifPresent(value -> handler.writeString(value));
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void initialize() {
+        final SerialDeviceConfiguration config = getConfigAs(SerialDeviceConfiguration.class);
+
+        try {
+            devicePattern = Pattern.compile(config.patternMatch);
+        } catch (final PatternSyntaxException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "Invalid device pattern: " + e.getMessage());
+            return;
+        }
+
+        for (final Channel c : getThing().getChannels()) {
+            final ChannelTypeUID type = c.getChannelTypeUID();
+            if (type != null) {
+                final ChannelConfig channelConfig = c.getConfiguration().as(ChannelConfig.class);
+                try {
+                    final DeviceChannel deviceChannel = DeviceChannelFactory
+                            .createDeviceChannel(valueTransformationProvider, channelConfig, type.getId());
+                    if (deviceChannel != null) {
+                        channels.put(c.getUID(), deviceChannel);
+                    }
+                } catch (final IllegalArgumentException e) {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                            "Configuration error for channel " + c.getUID().getId() + ": " + e.getMessage());
+                    return;
+                }
+            }
+        }
+
+        if (getBridgeStatus().getStatus() == ThingStatus.ONLINE) {
+            updateStatus(ThingStatus.ONLINE);
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+        }
+    }
+
+    @Override
+    public void dispose() {
+        channels.clear();
+        lastValue = null;
+        super.dispose();
+    }
+
+    /**
+     * Handle a line of data received from the bridge
+     *
+     * @param data the line of data
+     */
+    public void handleData(final String data) {
+        final Pattern devicePattern = this.devicePattern;
+
+        if (devicePattern != null && devicePattern.matcher(data).matches()) {
+            channels.forEach((channelUID, channel) -> refresh(channelUID, channel, data));
+            this.lastValue = data;
+        }
+    }
+
+    /**
+     * Return the bridge status.
+     */
+    private ThingStatusInfo getBridgeStatus() {
+        final Bridge b = getBridge();
+        if (b != null) {
+            return b.getStatusInfo();
+        } else {
+            return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
+        }
+    }
+
+    /**
+     * Refreshes the channel with the last received data
+     *
+     * @param channelId the channel to refresh
+     */
+    private void refresh(final ChannelUID channelUID, final DeviceChannel channel, final String data) {
+        if (!isLinked(channelUID)) {
+            return;
+        }
+
+        channel.transformData(data).ifPresent(value -> updateState(channelUID, new StringType(value)));
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/CascadedValueTransformationImpl.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/CascadedValueTransformationImpl.java
new file mode 100644 (file)
index 0000000..d515f69
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * 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.serial.internal.transform;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.transform.TransformationService;
+
+/**
+ * The {@link CascadedValueTransformationImpl} implements {@link ValueTransformation for a cascaded set of
+ * transformations}
+ *
+ * @author Jan N. Klug - Initial contribution
+ * @author Mike Major - Copied from HTTP binding to provide consistent user experience
+ */
+@NonNullByDefault
+public class CascadedValueTransformationImpl implements ValueTransformation {
+    private final List<ValueTransformation> transformations;
+
+    public CascadedValueTransformationImpl(final String transformationString,
+            final Function<String, @Nullable TransformationService> transformationServiceSupplier) {
+        transformations = Arrays.stream(transformationString.split("∩")).filter(s -> !s.isEmpty())
+                .map(transformation -> new SingleValueTransformation(transformation, transformationServiceSupplier))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Optional<String> apply(final String value) {
+        Optional<String> valueOptional = Optional.of(value);
+
+        // process all transformations
+        for (final ValueTransformation transformation : transformations) {
+            valueOptional = valueOptional.flatMap(transformation::apply);
+        }
+
+        return valueOptional;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/NoOpValueTransformation.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/NoOpValueTransformation.java
new file mode 100644 (file)
index 0000000..dfdf6ee
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * 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.serial.internal.transform;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link NoOpValueTransformation} implements a no-op (identity) transformation
+ *
+ * @author Jan N. Klug - Initial contribution
+ * @author Mike Major - Copied from HTTP binding to provide consistent user experience
+ */
+@NonNullByDefault
+public class NoOpValueTransformation implements ValueTransformation {
+    private static final NoOpValueTransformation NO_OP_VALUE_TRANSFORMATION = new NoOpValueTransformation();
+
+    @Override
+    public Optional<String> apply(final String value) {
+        return Optional.of(value);
+    }
+
+    /**
+     * get the static value transformation for identity
+     *
+     * @return
+     */
+    public static ValueTransformation getInstance() {
+        return NO_OP_VALUE_TRANSFORMATION;
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/SingleValueTransformation.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/SingleValueTransformation.java
new file mode 100644 (file)
index 0000000..f9f5603
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * 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.serial.internal.transform;
+
+import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.transform.TransformationException;
+import org.openhab.core.transform.TransformationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A transformation for a value used in {@DeviceChannel}.
+ *
+ * @author David Graeff - Initial contribution
+ * @author Jan N. Klug - adapted from MQTT binding to HTTP binding
+ * @author Mike Major - Copied from HTTP binding to provide consistent user experience
+ */
+@NonNullByDefault
+public class SingleValueTransformation implements ValueTransformation {
+    private final Logger logger = LoggerFactory.getLogger(SingleValueTransformation.class);
+    private final Function<String, @Nullable TransformationService> transformationServiceSupplier;
+    private WeakReference<@Nullable TransformationService> transformationService = new WeakReference<>(null);
+    private final String pattern;
+    private final String serviceName;
+
+    /**
+     * Creates a new channel state transformer.
+     *
+     * @param pattern A transformation pattern, starting with the transformation service
+     *            name, followed by a colon and the transformation itself.
+     * @param transformationServiceSupplier
+     */
+    public SingleValueTransformation(final String pattern,
+            final Function<String, @Nullable TransformationService> transformationServiceSupplier) {
+        this.transformationServiceSupplier = transformationServiceSupplier;
+        final int index = pattern.indexOf(':');
+        if (index == -1) {
+            throw new IllegalArgumentException(
+                    "The transformation pattern must consist of the type and the pattern separated by a colon");
+        }
+        this.serviceName = pattern.substring(0, index).toUpperCase();
+        this.pattern = pattern.substring(index + 1);
+    }
+
+    @Override
+    public Optional<String> apply(final String value) {
+        TransformationService transformationService = this.transformationService.get();
+        if (transformationService == null) {
+            transformationService = transformationServiceSupplier.apply(serviceName);
+            if (transformationService == null) {
+                logger.warn("Transformation service {} for pattern {} not found!", serviceName, pattern);
+                return Optional.empty();
+            }
+            this.transformationService = new WeakReference<>(transformationService);
+        }
+
+        try {
+            final String result = transformationService.transform(pattern, value);
+            if (result == null) {
+                logger.debug("Transformation {} returned empty result when applied to {}.", this, value);
+                return Optional.empty();
+            }
+            return Optional.of(result);
+        } catch (final TransformationException e) {
+            logger.warn("Executing transformation {} failed: {}", this, e.getMessage());
+        }
+
+        return Optional.empty();
+    }
+
+    @Override
+    public String toString() {
+        return "ChannelStateTransformation{pattern='" + pattern + "', serviceName='" + serviceName + "'}";
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformation.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformation.java
new file mode 100644 (file)
index 0000000..3cb88a5
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * 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.serial.internal.transform;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ValueTransformation} applies a set of transformations to a value
+ *
+ * @author Jan N. Klug - Initial contribution
+ * @author Mike Major - Copied from HTTP binding to provide consistent user experience
+ */
+@NonNullByDefault
+public interface ValueTransformation {
+
+    /**
+     * applies the value transformation to a value
+     *
+     * @param value The value
+     * @return Optional of string representing the transformed value (empty if transformation not present or failed)
+     */
+    Optional<String> apply(String value);
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformationProvider.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/transform/ValueTransformationProvider.java
new file mode 100644 (file)
index 0000000..d3c3ddb
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.serial.internal.transform;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link ValueTransformationProvider} allows to retrieve a transformation service by name
+ *
+ * @author Jan N. Klug - Initial contribution
+ * @author Mike Major - Copied from HTTP binding to provide consistent user experience
+ */
+@NonNullByDefault
+public interface ValueTransformationProvider {
+
+    /**
+     *
+     * @param pattern A transformation pattern, starting with the transformation service
+     *            * name, followed by a colon and the transformation itself.
+     * @return
+     */
+    ValueTransformation getValueTransformation(@Nullable String pattern);
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/Parity.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/Parity.java
new file mode 100644 (file)
index 0000000..9f7b5a3
--- /dev/null
@@ -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.serial.internal.util;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.transport.serial.SerialPort;
+
+/**
+ * Enum to convert config parity value to serial port value
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public enum Parity {
+    NONE("N", SerialPort.PARITY_NONE),
+    ODD("O", SerialPort.PARITY_ODD),
+    EVEN("E", SerialPort.PARITY_EVEN),
+    MARK("M", SerialPort.PARITY_MARK),
+    SPACE("S", SerialPort.PARITY_SPACE);
+
+    final String configValue;
+    final int serialPortValue;
+
+    private Parity(final String configValue, final int serialPortValue) {
+        this.configValue = configValue;
+        this.serialPortValue = serialPortValue;
+    }
+
+    /**
+     * Return the serial port value
+     *
+     * @return the serial port value
+     */
+    public int getSerialPortValue() {
+        return serialPortValue;
+    }
+
+    /**
+     * Return the enum value from the config value
+     *
+     * @param configValue the config value
+     * @return the enum value
+     */
+    public static Parity fromConfig(final String configValue) {
+        return Arrays.asList(values()).stream().filter(p -> p.configValue.equals(configValue)).findFirst().orElse(NONE);
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/StopBits.java b/bundles/org.openhab.binding.serial/src/main/java/org/openhab/binding/serial/internal/util/StopBits.java
new file mode 100644 (file)
index 0000000..9e836dc
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * 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.serial.internal.util;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.transport.serial.SerialPort;
+
+/**
+ * Enum to convert config stopBits value to serial port value
+ *
+ * @author Mike Major - Initial contribution
+ */
+@NonNullByDefault
+public enum StopBits {
+    STOPBITS_1("1", SerialPort.STOPBITS_1),
+    STOPBITS_1_5("1.5", SerialPort.STOPBITS_1_5),
+    STOPBITS_2("2", SerialPort.STOPBITS_2);
+
+    final String configValue;
+    final int serialPortValue;
+
+    private StopBits(final String configValue, final int serialPortValue) {
+        this.configValue = configValue;
+        this.serialPortValue = serialPortValue;
+    }
+
+    /**
+     * Return the serial port value
+     *
+     * @return the serial port value
+     */
+    public int getSerialPortValue() {
+        return serialPortValue;
+    }
+
+    /**
+     * Return the enum value from the config value
+     *
+     * @param configValue the config value
+     * @return the enum value
+     */
+    public static StopBits fromConfig(final String configValue) {
+        return Arrays.asList(values()).stream().filter(p -> p.configValue.equals(configValue)).findFirst()
+                .orElse(STOPBITS_1);
+    }
+}
diff --git a/bundles/org.openhab.binding.serial/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.serial/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..d471083
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="serial" 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>Serial Binding</name>
+       <description>This binding supports sending/receiving data to/from a serial port</description>
+       <author>Mike Major</author>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.serial/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.serial/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..5060e10
--- /dev/null
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="serial"
+       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 Types -->
+       <bridge-type id="serialBridge">
+               <label>Serial Bridge</label>
+               <description>Serial port which can send and receive data</description>
+
+               <channels>
+                       <channel id="string" typeId="stringData"/>
+                       <channel id="binary" typeId="binaryData"/>
+                       <channel id="data" typeId="system.rawbutton"/>
+               </channels>
+
+               <config-description>
+                       <parameter name="serialPort" type="text" required="true">
+                               <context>serial-port</context>
+                               <label>Serial Port</label>
+                               <description>The serial port to use (e.g. Linux: /dev/ttyUSB0, Windows: COM1)</description>
+                       </parameter>
+                       <parameter name="baudRate" type="integer">
+                               <advanced>true</advanced>
+                               <label>Baud Rate</label>
+                               <description>Set the baud rate</description>
+                               <default>9600</default>
+                               <options>
+                                       <option value="4800">4800</option>
+                                       <option value="9600">9600</option>
+                                       <option value="19200">19200</option>
+                                       <option value="38400">38400</option>
+                                       <option value="57600">57600</option>
+                                       <option value="115200">115200</option>
+                               </options>
+                       </parameter>
+                       <parameter name="dataBits" type="integer">
+                               <advanced>true</advanced>
+                               <label>Data Bits</label>
+                               <description>Set the data bits</description>
+                               <default>8</default>
+                               <options>
+                                       <option value="5">5</option>
+                                       <option value="6">6</option>
+                                       <option value="7">7</option>
+                                       <option value="8">8</option>
+                               </options>
+                       </parameter>
+                       <parameter name="parity" type="text">
+                               <advanced>true</advanced>
+                               <label>Parity</label>
+                               <description>Set the parity</description>
+                               <default>N</default>
+                               <options>
+                                       <option value="N">N(one)</option>
+                                       <option value="O">O(dd)</option>
+                                       <option value="E">E(even)</option>
+                                       <option value="M">M(ark)</option>
+                                       <option value="S">S(pace)</option>
+                               </options>
+                       </parameter>
+                       <parameter name="stopBits" type="text">
+                               <advanced>true</advanced>
+                               <label>Stop Bits</label>
+                               <description>Set the stop bits</description>
+                               <default>1</default>
+                               <options>
+                                       <option value="1">1</option>
+                                       <option value="1.5">1.5</option>
+                                       <option value="2">2</option>
+                               </options>
+                       </parameter>
+                       <parameter name="charset" type="text">
+                               <advanced>true</advanced>
+                               <label>Charset</label>
+                               <description>The charset to use for converting between bytes and string (e.g. UTF-8, ISO-8859-1)</description>
+                       </parameter>
+               </config-description>
+       </bridge-type>
+
+       <thing-type id="serialDevice" extensible="string, number, dimmer, switch, rollershutter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="serialBridge"/>
+               </supported-bridge-type-refs>
+
+               <label>Serial Device</label>
+               <description>Represents a device</description>
+
+               <config-description>
+                       <parameter name="patternMatch" type="text">
+                               <label>Patern Match</label>
+                               <context>pattern-match</context>
+                               <required>true</required>
+                               <description>Regular expression used to identify device from received data (must match the whole line)</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <!-- Channel Types -->
+       <channel-type id="stringData">
+               <item-type>String</item-type>
+               <label>String Data</label>
+               <description>Channel for sending/receiving data as a string to/from the serial port</description>
+       </channel-type>
+
+       <channel-type id="binaryData">
+               <item-type>String</item-type>
+               <label>Binary Data</label>
+               <description>Channel for sending/receiving data encoded as Base64 to/from the serial port</description>
+       </channel-type>
+
+       <channel-type id="string">
+               <item-type>String</item-type>
+               <label>String</label>
+               <description>Channel to receive commands as a string</description>
+               <config-description>
+                       <parameter name="stateTransformation" type="text">
+                               <label>State Transformation</label>
+                               <description>Transform used to convert device data to channel state, e.g. REGEX:.*?STATE=(.*?);.*</description>
+                       </parameter>
+                       <parameter name="commandFormat" type="text">
+                               <label>String Format</label>
+                               <description>Format string applied to the command, e.g. ID=671;COMMAND=%s</description>
+                       </parameter>
+                       <parameter name="commandTransformation" type="text">
+                               <label>Command Transformation</label>
+                               <description>Transform used to convert command to device data, e.g. JS:device.js</description>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="number">
+               <item-type>Number</item-type>
+               <label>Number</label>
+               <description>Channel to receive commands as a number</description>
+               <config-description>
+                       <parameter name="stateTransformation" type="text">
+                               <label>State Transformation</label>
+                               <description>Transform used to convert device data to channel state, e.g. REGEX:.*?STATE=(.*?);.*</description>
+                       </parameter>
+                       <parameter name="commandFormat" type="text">
+                               <label>Number Format</label>
+                               <description>Format string applied to the command, e.g. ID=671;VAL=%f</description>
+                       </parameter>
+                       <parameter name="commandTransformation" type="text">
+                               <label>Command Transformation</label>
+                               <description>Transform used to convert command to device data, e.g. JS:device.js</description>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="dimmer">
+               <item-type>Dimmer</item-type>
+               <label>Dimmer</label>
+               <description>Channel to receive commands from a Dimmer</description>
+               <config-description>
+                       <parameter name="stateTransformation" type="text">
+                               <label>State Transformation</label>
+                               <description>Transform used to convert device data to channel state, e.g. REGEX:.*?STATE=(.*?);.*</description>
+                       </parameter>
+                       <parameter name="onValue" type="text">
+                               <label>On Value</label>
+                               <description>Send this value when receiving an ON command</description>
+                       </parameter>
+                       <parameter name="offValue" type="text">
+                               <label>Off Value</label>
+                               <description>Send this value when receiving an OFF command</description>
+                       </parameter>
+                       <parameter name="increaseValue" type="text">
+                               <label>Increase Value</label>
+                               <description>Send this value when receiving an INCREASE command</description>
+                       </parameter>
+                       <parameter name="decreaseValue" type="text">
+                               <label>Decrease Value</label>
+                               <description>Send this value when receiving a DECREASE command</description>
+                       </parameter>
+                       <parameter name="commandFormat" type="text">
+                               <label>Percent Format</label>
+                               <description>Format string applied to the percent command, e.g. ID=671;VAL=%d</description>
+                       </parameter>
+                       <parameter name="commandTransformation" type="text">
+                               <label>Command Transformation</label>
+                               <description>Transform used to convert command to device data, e.g. JS:device.js</description>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="switch">
+               <item-type>Switch</item-type>
+               <label>Switch</label>
+               <description>Channel to receive commands from a Switch</description>
+               <config-description>
+                       <parameter name="stateTransformation" type="text">
+                               <label>State Transformation</label>
+                               <description>Transform used to convert device data to channel state, e.g. REGEX:.*?STATE=(.*?);.*</description>
+                       </parameter>
+                       <parameter name="onValue" type="text">
+                               <label>On Value</label>
+                               <description>Send this value when receiving an ON command</description>
+                       </parameter>
+                       <parameter name="offValue" type="text">
+                               <label>Off Value</label>
+                               <description>Send this value when receiving an OFF command</description>
+                       </parameter>
+                       <parameter name="commandTransformation" type="text">
+                               <label>Command Transformation</label>
+                               <description>Transform used to convert command to device data, e.g. JS:device.js</description>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+       <channel-type id="rollershutter">
+               <item-type>Rollershutter</item-type>
+               <label>Rollershutter</label>
+               <description>Channel to receive commands from a Rollershutter</description>
+               <config-description>
+                       <parameter name="stateTransformation" type="text">
+                               <label>State Transformation</label>
+                               <description>Transform used to convert device data to channel state, e.g. REGEX:.*?STATE=(.*?);.*</description>
+                       </parameter>
+                       <parameter name="upValue" type="text">
+                               <label>Up Value</label>
+                               <description>Send this value when receiving an UP command</description>
+                       </parameter>
+                       <parameter name="downValue" type="text">
+                               <label>Down Value</label>
+                               <description>Send this value when receiving a DOWN command</description>
+                       </parameter>
+                       <parameter name="stopValue" type="text">
+                               <label>Stop Value</label>
+                               <description>Send this value when receiving a STOP command</description>
+                       </parameter>
+                       <parameter name="commandFormat" type="text">
+                               <label>Percent Format</label>
+                               <description>Format string applied to the percent command, e.g. ID=671;VAL=%d</description>
+                       </parameter>
+                       <parameter name="commandTransformation" type="text">
+                               <label>Command Transformation</label>
+                               <description>Transform used to convert command to device data, e.g. JS:device.js</description>
+                       </parameter>
+               </config-description>
+       </channel-type>
+
+</thing:thing-descriptions>
index 44ae4c02d5c7efcfae18ee2b6ff527140d2ab467..8e46d1fc4618c343f498736f6d16301d06118270 100644 (file)
     <module>org.openhab.binding.seneye</module>
     <module>org.openhab.binding.sensebox</module>
     <module>org.openhab.binding.sensibo</module>
+    <module>org.openhab.binding.serial</module>
     <module>org.openhab.binding.serialbutton</module>
     <module>org.openhab.binding.shelly</module>
     <module>org.openhab.binding.silvercrestwifisocket</module>