[serial] Initial contribution (#8851)

Signed-off-by: Mike Major <mike_j_major@hotmail.com>
This commit is contained in:
Mike Major 2020-11-19 18:51:54 +00:00 committed by GitHub
parent fa21dff364
commit 55bf82c477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2055 additions and 0 deletions

View File

@ -215,6 +215,7 @@
/bundles/org.openhab.binding.seneye/ @nikotanghe /bundles/org.openhab.binding.seneye/ @nikotanghe
/bundles/org.openhab.binding.sensebox/ @hakan42 /bundles/org.openhab.binding.sensebox/ @hakan42
/bundles/org.openhab.binding.sensibo/ @seime /bundles/org.openhab.binding.sensibo/ @seime
/bundles/org.openhab.binding.serial/ @MikeJMajor
/bundles/org.openhab.binding.serialbutton/ @kaikreuzer /bundles/org.openhab.binding.serialbutton/ @kaikreuzer
/bundles/org.openhab.binding.shelly/ @markus7017 /bundles/org.openhab.binding.shelly/ @markus7017
/bundles/org.openhab.binding.siemensrds/ @andrewfg /bundles/org.openhab.binding.siemensrds/ @andrewfg

View File

@ -1066,6 +1066,11 @@
<artifactId>org.openhab.binding.sensibo</artifactId> <artifactId>org.openhab.binding.sensibo</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.serial</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.addons.bundles</groupId> <groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.serialbutton</artifactId> <artifactId>org.openhab.binding.serialbutton</artifactId>

View File

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

View File

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

View File

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

View File

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

View File

@ -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";
}

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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 + "]";
}
}

View File

@ -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());
}
}
}

View File

@ -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 + "]";
}
}

View File

@ -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)));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 + "'}";
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -247,6 +247,7 @@
<module>org.openhab.binding.seneye</module> <module>org.openhab.binding.seneye</module>
<module>org.openhab.binding.sensebox</module> <module>org.openhab.binding.sensebox</module>
<module>org.openhab.binding.sensibo</module> <module>org.openhab.binding.sensibo</module>
<module>org.openhab.binding.serial</module>
<module>org.openhab.binding.serialbutton</module> <module>org.openhab.binding.serialbutton</module>
<module>org.openhab.binding.shelly</module> <module>org.openhab.binding.shelly</module>
<module>org.openhab.binding.silvercrestwifisocket</module> <module>org.openhab.binding.silvercrestwifisocket</module>