added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.irtrans</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

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,100 @@
# IRtrans Binding
This binding integrates infrared receivers and blasters manufactured by IRtrans (www.irtrans.de)
## Supported Things
The *ethernet* Bridge supports the Ethernet (PoE) IRtrans transceiver equipped with an on-board IRDB database. Blasters and receivers are defined as Channels on the Bridge, but one can also define blasters as a *blaster* child Thing on the Bridge.
## Discovery
There is no Discovery feature available.
## Binding Configuration
There is no specific binding configuration required.
## Thing Configuration
The *ethernet* Bridge requires an *ipAddress* IP address and *portNumber* TCP port number in order to configure it. Optionally, one can add the following parameters to the configuration:
*bufferSize* : Buffer size used by the TCP socket when sending and receiving commands to the transceiver (default: 1024)
*responseTimeOut* : Specifies the time milliseconds to wait for a response from the transceiver when sending a command (default: 100)
*pingTimeOut* : Specifies the time milliseconds to wait for a response from the transceiver when pinging the device (default: 1000)
*reconnectInterval* : Specifies the time seconds to wait before reconnecting to a transceiver after a communication failure (default: 10)
The *blaster* Thing requires a *led* parameter to specify on which infrared commands will be emitted, *remote* the remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' remote), and *command* the name of the command will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' command).
## Channels
The *ethernet* Thing supports the following Channel Types:
| Channel Type ID | Item Type | Description |
|-----------------|-----------|-------------------------------------------------------------------------------------|
| blaster | String | Send (filtered) infrared commands over the specified blaster LED of the transceiver |
| receiver | String | Receive (filtered) infrared commands on the receiver LED of the transceiver |
The *blaster* Channel Type requires a *led* configuration parameter to specify on which infrared commands will be emitted, *remote* the remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' remote), and *command* the name of the command will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' command).
The *receiver* Channel Type requires *remote* the remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' remote), and *command* the name of the command will be allowed, as defined in the IRtrans server database that is flashed into the transceiver (can be '*' for 'any' command).
The *blaster* Thing supports a *io* Channel (of Item Type String) that allows to read infrared commands received by the blaster, as well as to write infrared commands to be sent by the blaster.
The IRtrans transceivers store infrared commands in a "remote,command" table, e.g. "telenet,power". Sending the literal text string "telenet,power" to the transceiver will make the transceiver "translate" that into the actual infrared command that will be emitted by the transceiver. A "remote,command" string sent to a Channel that does not match the defined filter will be ignored.
## Full Example
demo.things:
```
Bridge irtrans:ethernet:kitchen [ ipAddress="192.168.0.56", portNumber=21000, bufferSize=1024, responseTimeOut=100, pingTimeOut=2000, reconnectInterval=10 ]
{
Channels:
Type receiver : any [remote="*", command="*"]
Type receiver : telenet_power [remote="telenet", command="power"]
Type blaster : samsung [led="E", remote="samsung", command="*"]
}
```
In the above example, the first channel will be updated when any IR command from any type of device is received. The second channel will only be updated if a "power" infrared command from the remote/device type "telenet" is received. The third channel can be used to feed any type of infrared command to a Samsung television by means of the "E" emitter of the IRtrans device.
The led can be "E"-External, "I"-Internal, "B"-Both, and a numeric for a selected led.
Depending on the number of remotes, the bufferSize must be adjusted. E.g. for 7 remotes and 47 commands a bufferSize of 2048 is needed.
```
Bridge irtrans:ethernet:technicalfacilities [ ipAddress="192.168.0.58", portNumber=21000, bufferSize=1024, responseTimeOut=100, pingTimeOut=2000, reconnectInterval=10 ]
{
Channels:
Type receiver : any [remote="*", command="*"]
Type blaster : telenet1 [led="2", remote="telenet", command="*"]
Type blaster : telenet2 [led="1", remote="telenet", command="*"]
Type blaster : appletv [led="3", remote="appletv", command="*"]
}
```
In the above channel a single IRtrans transceiver has 3 output LEDs in use, 2 to drive 2 DTV SetTopBoxes, and a third one to drive an Apple TV device.
demo.items:
```
String KitchenIRReceiverAny {channel="irtrans:ethernet:kitchen:any"}
String KitchenIRReceiverTelenetPower {channel="irtrans:ethernet:kitchen:telenet_power"}
String KitchenIRBlasterSamsung {channel="irtrans:ethernet:kitchen:samsung"}
String TechnicalFacilitiesIRReceiverAny {channel="irtrans:ethernet:technicalfacilities:any"}
String TechnicalFacilitiesIRBlasterTelenet2 {channel="irtrans:ethernet:technicalfacilities:telenet2"}
String TechnicalFacilitiesIRBlasterTelenet1 {channel="irtrans:ethernet:technicalfacilities:telenet1"}
String TechnicalFacilitiesIRBlasterAppleTV {channel="irtrans:ethernet:technicalfacilities:appletv"}
```
demo.rules:
```
rule "Kitchen switch IR rule"
when
Item KitchenIRReceiverTelenetPower received update
then
createTimer(now.plusSeconds(5)) [|
KitchenIRBlasterSamsung.sendCommand("samsung,power")
]
end
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.irtrans</artifactId>
<name>openHAB Add-ons :: Bundles :: IRtrans Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.irtrans-${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-irtrans" description="IRTrans Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.irtrans/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,75 @@
/**
* 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.irtrans.internal;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link IRtransBindingConstants} contains constants used by the IRtrans
* handler classes
*
* @author Karel Goderis - Initial contribution
*
**/
public class IRtransBindingConstants {
public static final String BINDING_ID = "irtrans";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_ETHERNET_BRIDGE = new ThingTypeUID(BINDING_ID, "ethernet");
public static final ThingTypeUID THING_TYPE_BLASTER = new ThingTypeUID(BINDING_ID, "blaster");
// List of all Channel ids
public static final String CHANNEL_IO = "io";
// List of all Channel types
public static final String BLASTER_CHANNEL_TYPE = "blaster";
public static final String RECEIVER_CHANNEL_TYPE = "receiver";
// List of possible leds on a IRtrans transceiver
public enum Led {
DEFAULT("D"),
INTERNAL("I"),
EXTERNAL("E"),
ALL("B"),
ONE("1"),
TWO("2"),
THREE("3"),
FOUR("4"),
FIVE("5"),
SIX("6"),
SEVEN("7"),
EIGHT("8");
private final String text;
private Led(final String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
public static Led get(String valueSelectorText) throws IllegalArgumentException {
for (Led c : Led.values()) {
if (c.text.equals(valueSelectorText)) {
return c;
}
}
throw new IllegalArgumentException("Not valid value selector");
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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.irtrans.internal;
import static org.openhab.binding.irtrans.internal.IRtransBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.irtrans.internal.handler.BlasterHandler;
import org.openhab.binding.irtrans.internal.handler.EthernetBridgeHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link IRtransHandlerFactory} is responsible for creating things and
* thing handlers.
*
* @author Karel Goderis - Initial contribution
*
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.irtrans")
public class IRtransHandlerFactory extends BaseThingHandlerFactory {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_BLASTER, THING_TYPE_ETHERNET_BRIDGE).collect(Collectors.toSet()));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
ThingUID bridgeUID) {
if (IRtransBindingConstants.THING_TYPE_ETHERNET_BRIDGE.equals(thingTypeUID)) {
ThingUID ethernetBridgeUID = getEthernetBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, ethernetBridgeUID, null);
}
if (IRtransBindingConstants.THING_TYPE_BLASTER.equals(thingTypeUID)) {
ThingUID blasterUID = getBlasterUID(thingTypeUID, thingUID, configuration, bridgeUID);
return super.createThing(thingTypeUID, configuration, blasterUID, bridgeUID);
}
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the IRtrans binding.");
}
@Override
protected ThingHandler createHandler(Thing thing) {
if (thing.getThingTypeUID().equals(IRtransBindingConstants.THING_TYPE_ETHERNET_BRIDGE)) {
return new EthernetBridgeHandler((Bridge) thing);
} else if (thing.getThingTypeUID().equals(IRtransBindingConstants.THING_TYPE_BLASTER)) {
return new BlasterHandler(thing);
} else {
return null;
}
}
private ThingUID getEthernetBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID,
Configuration configuration) {
if (thingUID == null) {
String ipAddress = (String) configuration.get(EthernetBridgeHandler.IP_ADDRESS);
return new ThingUID(thingTypeUID, ipAddress);
}
return thingUID;
}
private ThingUID getBlasterUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration,
ThingUID bridgeUID) {
String ledId = (String) configuration.get(BlasterHandler.LED);
if (thingUID == null) {
return new ThingUID(thingTypeUID, "Led" + ledId, bridgeUID.getId());
}
return thingUID;
}
}

View File

@@ -0,0 +1,367 @@
/**
* 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.irtrans.internal;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IrCommand} is a structure to store and manipulate infrared command
* in various formats
*
* @author Karel Goderis - Initial contribution
*
*/
public class IrCommand {
private Logger logger = LoggerFactory.getLogger(IrCommand.class);
/**
*
* Each infrared command is in essence a sequence of characters/pointers
* that refer to pulse/pause timing pairs. So, in order to build an infrared
* command one has to collate the pulse/pause timings as defined by the
* sequence
*
* PulsePair is a small datastructure to capture each pulse/pair timing pair
*
*/
private class PulsePair {
public int Pulse;
public int Pause;
}
private String remote;
private String command;
private String sequence;
private List<PulsePair> pulsePairs;
private int numberOfRepeats;
private int frequency;
private int frameLength;
private int pause;
private boolean startBit;
private boolean repeatStartBit;
private boolean noTog;
private boolean rc5;
private boolean rc6;
public String getRemote() {
return remote;
}
public void setRemote(String remote) {
this.remote = remote;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public String getSequence() {
return sequence;
}
public void setSequence(String sequence) {
this.sequence = sequence;
}
public List<PulsePair> getPulsePairs() {
return pulsePairs;
}
public void setPulsePairs(ArrayList<PulsePair> pulsePairs) {
this.pulsePairs = pulsePairs;
}
public int getNumberOfRepeats() {
return numberOfRepeats;
}
public void setNumberOfRepeats(int numberOfRepeats) {
this.numberOfRepeats = numberOfRepeats;
}
public int getFrequency() {
return frequency;
}
public void setFrequency(int frequency) {
this.frequency = frequency;
}
public int getFrameLength() {
return frameLength;
}
public void setFrameLength(int frameLength) {
this.frameLength = frameLength;
}
public int getPause() {
return pause;
}
public void setPause(int pause) {
this.pause = pause;
}
public boolean isStartBit() {
return startBit;
}
public void setStartBit(boolean startBit) {
this.startBit = startBit;
}
public boolean isRepeatStartBit() {
return repeatStartBit;
}
public void setRepeatStartBit(boolean repeatStartBit) {
this.repeatStartBit = repeatStartBit;
}
public boolean isNoTog() {
return noTog;
}
public void setNoTog(boolean noTog) {
this.noTog = noTog;
}
public boolean isRc5() {
return rc5;
}
public void setRc5(boolean rc5) {
this.rc5 = rc5;
}
public boolean isRc6() {
return rc6;
}
public void setRc6(boolean rc6) {
this.rc6 = rc6;
}
/**
* Matches two IrCommands Commands match if they have the same remote and
* the same command
*
* @param anotherCommand the another command
* @return true, if successful
*/
public boolean matches(IrCommand anotherCommand) {
return (matchRemote(anotherCommand) && matchCommand(anotherCommand));
}
/**
* Match remote fields of two IrCommands In everything we do in the IRtrans
* binding, the "*" stands for a wilcard character and will match anything
*
* @param S the infrared command
* @return true, if successful
*/
private boolean matchRemote(IrCommand S) {
if ("*".equals(getRemote()) || "*".equals(S.getRemote())) {
return true;
} else {
return S.getRemote().equals(getRemote());
}
}
/**
* Match command fields of two IrCommands
*
* @param S the infrared command
* @return true, if successful
*/
private boolean matchCommand(IrCommand S) {
if ("*".equals(command) || "*".equals(S.command)) {
return true;
} else {
return S.command.equals(command);
}
}
/**
* Convert/Parse the IRCommand into a ByteBuffer that is compatible with the
* IRTrans devices
*
* @return the byte buffer
*/
public ByteBuffer toByteBuffer() {
ByteBuffer byteBuffer = ByteBuffer.allocate(44 + 210 + 1);
// skip first byte for length - we will fill it in at the end
byteBuffer.position(1);
// Checksum - 1 byte - not used in the ethernet version of the device
byteBuffer.put((byte) 0);
// Command - 1 byte - not used
byteBuffer.put((byte) 0);
// Address - 1 byte - not used
byteBuffer.put((byte) 0);
// Mask - 2 bytes - not used
byteBuffer.putShort((short) 0);
// Number of pulse pairs - 1 byte
byte[] byteSequence = sequence.getBytes(StandardCharsets.US_ASCII);
byteBuffer.put((byte) (byteSequence.length));
// Frequency - 1 byte
byteBuffer.put((byte) frequency);
// Mode / Flags - 1 byte
byte modeFlags = 0;
if (startBit) {
modeFlags = (byte) (modeFlags | 1);
}
if (repeatStartBit) {
modeFlags = (byte) (modeFlags | 2);
}
if (rc5) {
modeFlags = (byte) (modeFlags | 4);
}
if (rc6) {
modeFlags = (byte) (modeFlags | 8);
}
byteBuffer.put(modeFlags);
// Pause timings - 8 Shorts = 16 bytes
for (int i = 0; i < pulsePairs.size(); i++) {
byteBuffer.putShort((short) Math.round(pulsePairs.get(i).Pause / 8));
}
for (int i = pulsePairs.size(); i <= 7; i++) {
byteBuffer.putShort((short) 0);
}
// Pulse timings - 8 Shorts = 16 bytes
for (int i = 0; i < pulsePairs.size(); i++) {
byteBuffer.putShort((short) Math.round(pulsePairs.get(i).Pulse / 8));
}
for (int i = pulsePairs.size(); i <= 7; i++) {
byteBuffer.putShort((short) 0);
}
// Time Counts - 1 Byte
byteBuffer.put((byte) pulsePairs.size());
// Repeats - 1 Byte
byte repeat = (byte) 0;
repeat = (byte) numberOfRepeats;
if (frameLength > 0) {
repeat = (byte) (repeat | 128);
}
byteBuffer.put(repeat);
// Repeat Pause or Frame Length - 1 byte
if ((repeat & 128) == 128) {
byteBuffer.put((byte) frameLength);
} else {
byteBuffer.put((byte) pause);
}
// IR pulse sequence
try {
byteBuffer.put(sequence.getBytes("ASCII"));
} catch (UnsupportedEncodingException e) {
logger.warn("An exception occurred while encoding the sequence : '{}'", e.getMessage(), e);
}
// Add <CR> (ASCII 13) at the end of the sequence
byteBuffer.put((byte) ((char) 13));
// set the length of the byte sequence
byteBuffer.flip();
byteBuffer.position(0);
byteBuffer.put((byte) (byteBuffer.limit() - 1));
byteBuffer.position(0);
return byteBuffer;
}
/**
* Convert the the infrared command to a Hexadecimal notation/string that
* can be interpreted by the IRTrans device
*
* Convert the first 44 bytes to hex notation, then copy the remainder (= IR
* command piece) as ASCII string
*
* @return the byte buffer in Hex format
*/
public ByteBuffer toHEXByteBuffer() {
byte hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
ByteBuffer byteBuffer = toByteBuffer();
byte[] toConvert = new byte[byteBuffer.limit()];
byteBuffer.get(toConvert, 0, byteBuffer.limit());
byte[] converted = new byte[toConvert.length * 2];
for (int k = 0; k < toConvert.length - 1; k++) {
converted[2 * k] = hexDigit[(toConvert[k] >> 4) & 0x0f];
converted[2 * k + 1] = hexDigit[toConvert[k] & 0x0f];
}
ByteBuffer convertedBuffer = ByteBuffer.allocate(converted.length);
convertedBuffer.put(converted);
convertedBuffer.flip();
return convertedBuffer;
}
/**
* Convert 'sequence' bit of the IRTrans compatible byte buffer to a
* Hexidecimal string
*
* @return the string
*/
public String sequenceToHEXString() {
byte[] byteArray = toHEXByteArray();
return new String(byteArray, 88, byteArray.length - 88 - 2);
}
/**
* Convert the IRTrans compatible byte buffer to a string
*
* @return the string
*/
public String toHEXString() {
return new String(toHEXByteArray());
}
/**
* Convert the IRTrans compatible byte buffer to a byte array.
*
* @return the byte[]
*/
public byte[] toHEXByteArray() {
return toHEXByteBuffer().array();
}
}

View File

@@ -0,0 +1,109 @@
/**
* 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.irtrans.internal.handler;
import static org.openhab.binding.irtrans.internal.IRtransBindingConstants.CHANNEL_IO;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led;
import org.openhab.binding.irtrans.internal.IrCommand;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BlasterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Karel Goderis - Initial contribution
*
*/
public class BlasterHandler extends BaseThingHandler implements TransceiverStatusListener {
// List of Configuration constants
public static final String COMMAND = "command";
public static final String LED = "led";
public static final String REMOTE = "remote";
private Logger logger = LoggerFactory.getLogger(BlasterHandler.class);
public BlasterHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
((EthernetBridgeHandler) getBridge().getHandler()).registerTransceiverStatusListener(this);
}
@Override
public void handleRemoval() {
((EthernetBridgeHandler) getBridge().getHandler()).unregisterTransceiverStatusListener(this);
updateStatus(ThingStatus.REMOVED);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
EthernetBridgeHandler ethernetBridge = (EthernetBridgeHandler) getBridge().getHandler();
if (ethernetBridge == null) {
logger.warn("IRtrans Ethernet bridge handler not found. Cannot handle command without bridge.");
return;
}
if (!(command instanceof RefreshType)) {
if (channelUID.getId().equals(CHANNEL_IO)) {
if (command instanceof StringType) {
String remoteName = StringUtils.substringBefore(command.toString(), ",");
String irCommandName = StringUtils.substringAfter(command.toString(), ",");
IrCommand ircommand = new IrCommand();
ircommand.setRemote(remoteName);
ircommand.setCommand(irCommandName);
IrCommand thingCompatibleCommand = new IrCommand();
thingCompatibleCommand.setRemote((String) getConfig().get(REMOTE));
thingCompatibleCommand.setCommand((String) getConfig().get(COMMAND));
if (ircommand.matches(thingCompatibleCommand)) {
if (!ethernetBridge.sendIRcommand(ircommand, Led.get((String) getConfig().get(LED)))) {
logger.warn("An error occured whilst sending the infrared command '{}' for Channel '{}'",
ircommand, channelUID);
}
}
}
}
}
}
@Override
public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command) {
logger.debug("Received command {},{} for thing {}", command.getRemote(), command.getCommand(),
this.getThing().getUID());
IrCommand thingCompatibleCommand = new IrCommand();
thingCompatibleCommand.setRemote((String) getConfig().get(REMOTE));
thingCompatibleCommand.setCommand((String) getConfig().get(COMMAND));
if (command.matches(thingCompatibleCommand)) {
StringType stringType = new StringType(command.getRemote() + "," + command.getCommand());
updateState(CHANNEL_IO, stringType);
}
}
}

View File

@@ -0,0 +1,948 @@
/**
* 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.irtrans.internal.handler;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NoConnectionPendingException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.irtrans.internal.IRtransBindingConstants;
import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led;
import org.openhab.binding.irtrans.internal.IrCommand;
import org.openhab.core.config.core.Configuration;
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.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.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EthernetBridgeHandler} is responsible for handling commands, which
* are sent to one of the channels.
*
* @author Karel Goderis - Initial contribution
*
*/
public class EthernetBridgeHandler extends BaseBridgeHandler implements TransceiverStatusListener {
// List of Configuration constants
public static final String BUFFER_SIZE = "bufferSize";
public static final String IP_ADDRESS = "ipAddress";
public static final String IS_LISTENER = "isListener";
public static final String FIRMWARE_VERSION = "firmwareVersion";
public static final String LISTENER_PORT = "listenerPort";
public static final String MODE = "mode";
public static final String PING_TIME_OUT = "pingTimeOut";
public static final String PORT_NUMBER = "portNumber";
public static final String RECONNECT_INTERVAL = "reconnectInterval";
public static final String REFRESH_INTERVAL = "refreshInterval";
public static final String RESPONSE_TIME_OUT = "responseTimeOut";
public static final String COMMAND = "command";
public static final String LED = "led";
public static final String REMOTE = "remote";
public static final int LISTENING_INTERVAL = 100;
private static final Pattern RESPONSE_PATTERN = Pattern.compile("..(\\d{5}) (.*)", Pattern.DOTALL);
private static final Pattern HEX_PATTERN = Pattern.compile("RCV_HEX (.*)");
private static final Pattern IRDB_PATTERN = Pattern.compile("RCV_COM (.*),(.*),(.*),(.*)");
private Logger logger = LoggerFactory.getLogger(EthernetBridgeHandler.class);
private Selector selector;
private Thread pollingThread;
private SocketChannel socketChannel;
protected SelectionKey socketChannelKey;
protected ServerSocketChannel listenerChannel;
protected SelectionKey listenerKey;
protected boolean previousConnectionState;
private final Lock lock = new ReentrantLock();
private List<TransceiverStatusListener> transceiverStatusListeners = new CopyOnWriteArrayList<>();
/**
* Data structure to store the infrared commands that are 'loaded' from the
* configuration files. Command loading from pre-defined configuration files is not supported
* (anymore), but the code is maintained in case this functionality is re-added in the future
**/
protected final Collection<IrCommand> irCommands = new HashSet<>();
public EthernetBridgeHandler(Bridge bridge) {
super(bridge);
// Nothing to do here
}
@Override
public void initialize() {
// register ourselves as a Transceiver Status Listener
registerTransceiverStatusListener(this);
try {
selector = Selector.open();
} catch (IOException e) {
logger.debug("An exception occurred while registering the selector: '{}'", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
}
if (selector != null) {
if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
if (pollingThread == null) {
pollingThread = new Thread(pollingRunnable, "ESH-IRtrans-Polling " + getThing().getUID());
pollingThread.start();
}
} else {
logger.debug("Cannot connect to IRtrans Ethernet device. IP address or port number not set.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"IP address or port number not set.");
}
if (getConfig().get(IS_LISTENER) != null) {
configureListener((String) getConfig().get(LISTENER_PORT));
}
}
}
@Override
public void dispose() {
unregisterTransceiverStatusListener(this);
try {
if (socketChannel != null) {
socketChannel.close();
}
} catch (IOException e) {
logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
}
try {
if (listenerChannel != null) {
listenerChannel.close();
}
} catch (IOException e) {
logger.warn("An exception occurred while closing the channel '{}': {}", listenerChannel, e.getMessage());
}
try {
if (selector != null) {
selector.close();
}
} catch (IOException e) {
logger.debug("An exception occurred while closing the selector: '{}'", e.getMessage());
}
logger.debug("Stopping the IRtrans polling Thread for {}", getThing().getUID());
if (pollingThread != null) {
pollingThread.interrupt();
try {
pollingThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
pollingThread = null;
}
}
public boolean registerTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) {
return transceiverStatusListeners.add(transceiverStatusListener);
}
public boolean unregisterTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) {
return transceiverStatusListeners.remove(transceiverStatusListener);
}
public void onConnectionLost() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
try {
if (socketChannel != null) {
socketChannel.close();
}
} catch (IOException e) {
logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
}
establishConnection();
}
public void onConnectionResumed() {
configureTransceiver();
updateStatus(ThingStatus.ONLINE);
}
private void establishConnection() {
lock.lock();
try {
if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
try {
socketChannel = SocketChannel.open();
socketChannel.socket().setKeepAlive(true);
socketChannel.configureBlocking(false);
synchronized (selector) {
selector.wakeup();
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT;
socketChannelKey = socketChannel.register(selector, interestSet);
}
InetSocketAddress remoteAddress = new InetSocketAddress((String) getConfig().get(IP_ADDRESS),
((BigDecimal) getConfig().get(PORT_NUMBER)).intValue());
socketChannel.connect(remoteAddress);
} catch (IOException e) {
logger.debug("An exception occurred while connecting to '{}:{}' : {}", getConfig().get(IP_ADDRESS),
((BigDecimal) getConfig().get(PORT_NUMBER)).intValue(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
try {
Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
} catch (NumberFormatException | InterruptedException e) {
Thread.currentThread().interrupt();
logger.debug("An exception occurred while putting a thread to sleep: '{}'", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
onConnectable();
}
} finally {
lock.unlock();
}
}
@Override
public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command) {
logger.debug("Received infrared command '{},{}' for thing '{}'", command.getRemote(), command.getCommand(),
this.getThing().getUID());
for (Channel channel : getThing().getChannels()) {
Configuration channelConfiguration = channel.getConfiguration();
if (channel.getChannelTypeUID() != null
&& channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
IrCommand thingCompatibleCommand = new IrCommand();
thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
if (command.matches(thingCompatibleCommand)) {
StringType stringType = new StringType(command.getRemote() + "," + command.getCommand());
logger.debug("Received a matching infrared command '{}' for channel '{}'", stringType,
channel.getUID());
updateState(channel.getUID(), stringType);
}
}
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (!(command instanceof RefreshType)) {
Channel channel = this.getThing().getChannel(channelUID.getId());
if (channel != null) {
Configuration channelConfiguration = channel.getConfiguration();
if (channel.getChannelTypeUID() != null
&& channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.BLASTER_CHANNEL_TYPE)) {
if (command instanceof StringType) {
String remoteName = StringUtils.substringBefore(command.toString(), ",");
String irCommandName = StringUtils.substringAfter(command.toString(), ",");
IrCommand ircommand = new IrCommand();
ircommand.setRemote(remoteName);
ircommand.setCommand(irCommandName);
IrCommand thingCompatibleCommand = new IrCommand();
thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
if (ircommand.matches(thingCompatibleCommand)) {
if (sendIRcommand(ircommand, Led.get((String) channelConfiguration.get(LED)))) {
logger.debug("Sent a matching infrared command '{}' for channel '{}'", command,
channelUID);
} else {
logger.warn(
"An error occured whilst sending the infrared command '{}' for Channel '{}'",
command, channelUID);
}
}
}
}
if (channel.getAcceptedItemType() != null
&& channel.getAcceptedItemType().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
logger.warn("Receivers can only receive infrared commands, not send them");
}
}
}
}
private void configureListener(String listenerPort) {
try {
listenerChannel = ServerSocketChannel.open();
listenerChannel.socket().bind(new InetSocketAddress(Integer.parseInt(listenerPort)));
listenerChannel.configureBlocking(false);
logger.info("Listening for incoming connections on {}", listenerChannel.getLocalAddress());
synchronized (selector) {
selector.wakeup();
try {
listenerKey = listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (ClosedChannelException e1) {
logger.debug("An exception occurred while registering a selector: '{}'", e1.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
}
}
} catch (IOException e3) {
logger.error(
"An exception occurred while creating configuring the listener channel on port number {}: '{}'",
Integer.parseInt(listenerPort), e3.getMessage());
}
}
protected void configureTransceiver() {
lock.lock();
try {
String putInASCIImode = "ASCI";
ByteBuffer response = sendCommand(putInASCIImode);
String getFirmwareVersion = "Aver" + (char) 13;
response = sendCommand(getFirmwareVersion);
if (response != null) {
String message = stripByteCount(response).split("\0")[0];
if (message != null) {
if (message.contains("VERSION")) {
logger.info("'{}' matches an IRtrans device with firmware {}", getThing().getUID(), message);
getConfig().put(FIRMWARE_VERSION, message);
} else {
logger.debug("Received some non-compliant garbage ({})", message);
onConnectionLost();
}
}
} else {
try {
logger.debug("Did not receive an answer from the IRtrans transceiver '{}' - Parsing is skipped",
socketChannel.getRemoteAddress());
onConnectionLost();
} catch (IOException e1) {
logger.debug("An exception occurred while getting a remote address: '{}'", e1.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
}
}
int numberOfRemotes = 0;
int numberOfRemotesProcessed = 0;
int numberOfRemotesInBatch = 0;
String[] remoteList = getRemoteList(0);
if (remoteList.length > 0) {
logger.debug("The IRtrans device for '{}' supports '{}' remotes", getThing().getUID(), remoteList[1]);
numberOfRemotes = Integer.valueOf(remoteList[1]);
numberOfRemotesInBatch = Integer.valueOf(remoteList[2]);
}
while (numberOfRemotesProcessed < numberOfRemotes) {
for (int i = 1; i <= numberOfRemotesInBatch; i++) {
String remote = remoteList[2 + i];
// get remote commands
String[] commands = getRemoteCommands(remote, 0);
StringBuilder result = new StringBuilder();
int numberOfCommands = 0;
int numberOfCommandsInBatch = 0;
int numberOfCommandsProcessed = 0;
if (commands.length > 0) {
numberOfCommands = Integer.valueOf(commands[1]);
numberOfCommandsInBatch = Integer.valueOf(commands[2]);
numberOfCommandsProcessed = 0;
}
while (numberOfCommandsProcessed < numberOfCommands) {
for (int j = 1; j <= numberOfCommandsInBatch; j++) {
String command = commands[2 + j];
result.append(command);
numberOfCommandsProcessed++;
if (numberOfCommandsProcessed < numberOfCommands) {
result.append(", ");
}
}
if (numberOfCommandsProcessed < numberOfCommands) {
commands = getRemoteCommands(remote, numberOfCommandsProcessed);
if (commands.length == 0) {
break;
}
numberOfCommandsInBatch = Integer.valueOf(commands[2]);
} else {
numberOfCommandsInBatch = 0;
}
}
logger.debug("The remote '{}' on '{}' supports '{}' commands: {}", remote, getThing().getUID(),
numberOfCommands, result.toString());
numberOfRemotesProcessed++;
}
if (numberOfRemotesProcessed < numberOfRemotes) {
remoteList = getRemoteList(numberOfRemotesProcessed);
if (remoteList.length == 0) {
break;
}
numberOfRemotesInBatch = Integer.valueOf(remoteList[2]);
} else {
numberOfRemotesInBatch = 0;
}
}
} finally {
lock.unlock();
}
}
private String[] getRemoteCommands(String remote, int index) {
String getCommands = "Agetcommands " + remote + "," + index + (char) 13;
ByteBuffer response = sendCommand(getCommands);
String[] commandList = new String[0];
if (response != null) {
String message = stripByteCount(response).split("\0")[0];
logger.trace("commands returned {}", message);
if (message != null) {
if (message.contains("COMMANDLIST")) {
commandList = message.split(",");
} else {
logger.debug("Received some non-compliant command ({})", message);
onConnectionLost();
}
}
} else {
logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
getThing().getUID());
onConnectionLost();
}
return commandList;
}
private String[] getRemoteList(int index) {
String getRemotes = "Agetremotes " + index + (char) 13;
ByteBuffer response = sendCommand(getRemotes);
String[] remoteList = new String[0];
if (response != null) {
String message = stripByteCount(response).split("\0")[0];
logger.trace("remotes returned {}", message);
if (message != null) {
if (message.contains("REMOTELIST")) {
remoteList = message.split(",");
} else {
logger.debug("Received some non-compliant command ({})", message);
onConnectionLost();
}
}
} else {
logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
getThing().getUID());
onConnectionLost();
}
return remoteList;
}
public boolean sendIRcommand(IrCommand command, Led led) {
// construct the string we need to send to the IRtrans device
String output = packIRDBCommand(led, command);
lock.lock();
try {
ByteBuffer response = sendCommand(output);
if (response != null) {
String message = stripByteCount(response).split("\0")[0];
if (message != null && message.contains("RESULT OK")) {
return true;
} else {
logger.debug("Received an unexpected response from the IRtrans transceiver: '{}'", message);
return false;
}
}
} finally {
lock.unlock();
}
return false;
}
private ByteBuffer sendCommand(String command) {
if (command != null) {
ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length);
try {
byteBuffer.put(command.getBytes("ASCII"));
onWritable(byteBuffer);
Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
return onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), true);
} catch (UnsupportedEncodingException | NumberFormatException | InterruptedException e) {
Thread.currentThread().interrupt();
logger.debug("An exception occurred while sending a command to the IRtrans transceiver for '{}': {}",
getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
return null;
}
private Runnable pollingRunnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
if (socketChannel == null) {
previousConnectionState = false;
onConnectionLost();
} else {
if (!previousConnectionState && socketChannel.isConnected()) {
previousConnectionState = true;
onConnectionResumed();
}
if (previousConnectionState && !socketChannel.isConnected()
&& !socketChannel.isConnectionPending()) {
previousConnectionState = false;
onConnectionLost();
}
if (!socketChannel.isConnectionPending() && !socketChannel.isConnected()) {
previousConnectionState = false;
logger.debug("Disconnecting '{}' because of a network error", getThing().getUID());
socketChannel.close();
Thread.sleep(1000 * ((BigDecimal) getConfig().get(RECONNECT_INTERVAL)).intValue());
establishConnection();
}
long stamp = System.currentTimeMillis();
if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS)))
.isReachable(((BigDecimal) getConfig().get(PING_TIME_OUT)).intValue())) {
logger.debug(
"Ping timed out after '{}' milliseconds. Disconnecting '{}' because of a ping timeout",
System.currentTimeMillis() - stamp, getThing().getUID());
socketChannel.close();
}
onConnectable();
ByteBuffer buffer = onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), false);
if (buffer != null && buffer.remaining() > 0) {
onRead(buffer);
}
}
onAcceptable();
if (!Thread.currentThread().isInterrupted()) {
Thread.sleep(LISTENING_INTERVAL);
} else {
return;
}
} catch (IOException e) {
logger.trace("An exception occurred while polling the transceiver : '{}'", e.getMessage(), e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
};
protected void onAcceptable() {
lock.lock();
try {
try {
selector.selectNow();
} catch (IOException e) {
logger.debug("An exception occurred while selecting: {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey selKey = it.next();
it.remove();
if (selKey.isValid()) {
if (selKey.isAcceptable() && selKey == listenerKey) {
try {
SocketChannel newChannel = listenerChannel.accept();
newChannel.configureBlocking(false);
logger.trace("Received a connection request from '{}'", newChannel.getRemoteAddress());
synchronized (selector) {
selector.wakeup();
newChannel.register(selector, newChannel.validOps());
}
} catch (IOException e) {
logger.debug("An exception occurred while accepting a connection on channel '{}': {}",
listenerChannel, e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
}
} finally {
lock.unlock();
}
}
protected void onConnectable() {
lock.lock();
SocketChannel aSocketChannel = null;
try {
synchronized (selector) {
selector.selectNow();
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey selKey = it.next();
it.remove();
if (selKey.isValid() && selKey.isConnectable()) {
aSocketChannel = (SocketChannel) selKey.channel();
aSocketChannel.finishConnect();
logger.trace("The channel for '{}' is connected", aSocketChannel.getRemoteAddress());
}
}
} catch (IOException | NoConnectionPendingException e) {
if (aSocketChannel != null) {
logger.debug("Disconnecting '{}' because of a socket error : '{}'", getThing().getUID(), e.getMessage(),
e);
try {
aSocketChannel.close();
} catch (IOException e1) {
logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
e1.getMessage());
}
}
} finally {
lock.unlock();
}
}
protected ByteBuffer onReadable(int bufferSize, boolean isSelective) {
lock.lock();
try {
synchronized (selector) {
try {
selector.selectNow();
} catch (IOException e) {
logger.error("An exception occurred while selecting: {}", e.getMessage());
}
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey selKey = it.next();
it.remove();
if (selKey.isValid() && selKey.isReadable()) {
SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
if ((aSocketChannel.equals(socketChannel) && isSelective) || !isSelective) {
ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize);
int numberBytesRead = 0;
boolean error = false;
try {
numberBytesRead = aSocketChannel.read(readBuffer);
} catch (NotYetConnectedException e) {
logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
if (!aSocketChannel.isConnectionPending()) {
error = true;
}
} catch (IOException e) {
// If some other I/O error occurs
logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
error = true;
}
if (numberBytesRead == -1) {
error = true;
}
if (error) {
logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID());
try {
aSocketChannel.close();
} catch (IOException e1) {
logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
e1.getMessage());
}
} else {
readBuffer.flip();
return readBuffer;
}
}
}
}
return null;
} finally {
lock.unlock();
}
}
protected void onWritable(ByteBuffer buffer) {
lock.lock();
try {
synchronized (selector) {
try {
selector.selectNow();
} catch (IOException e) {
logger.error("An exception occurred while selecting: {}", e.getMessage());
}
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey selKey = it.next();
it.remove();
if (selKey.isValid() && selKey.isWritable()) {
SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
if (aSocketChannel.equals(socketChannel)) {
boolean error = false;
buffer.rewind();
try {
logger.trace("Sending '{}' on the channel '{}'->'{}'", new String(buffer.array()),
aSocketChannel.getLocalAddress(), aSocketChannel.getRemoteAddress());
aSocketChannel.write(buffer);
} catch (NotYetConnectedException e) {
logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
if (!aSocketChannel.isConnectionPending()) {
error = true;
}
} catch (ClosedChannelException e) {
// If some other I/O error occurs
logger.warn("The channel for '{}' is closed: {}", aSocketChannel, e.getMessage());
error = true;
} catch (IOException e) {
// If some other I/O error occurs
logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
error = true;
}
if (error) {
try {
aSocketChannel.close();
} catch (IOException e) {
logger.warn("An exception occurred while closing the channel '{}': {}", aSocketChannel,
e.getMessage());
}
}
}
}
}
} finally {
lock.unlock();
}
}
protected void onRead(ByteBuffer byteBuffer) {
try {
if (logger.isTraceEnabled()) {
logger.trace("Received bytebuffer : '{}'", HexUtils.bytesToHex(byteBuffer.array()));
}
int byteCount = getByteCount(byteBuffer);
while (byteCount > 0) {
byte[] message = new byte[byteCount];
byteBuffer.get(message, 0, byteCount);
if (logger.isTraceEnabled()) {
logger.trace("Received message : '{}'", HexUtils.bytesToHex(message));
}
String strippedBuffer = stripByteCount(ByteBuffer.wrap(message));
if (strippedBuffer != null) {
String strippedMessage = strippedBuffer.split("\0")[0];
// IRTrans devices return "RESULT OK" when it succeeds to emit an
// infrared sequence
if (strippedMessage.contains("RESULT OK")) {
parseOKMessage(strippedMessage);
}
// IRTrans devices return a string starting with RCV_HEX each time
// it captures an infrared sequence from a remote control
if (strippedMessage.contains("RCV_HEX")) {
parseHexMessage(strippedMessage);
}
// IRTrans devices return a string starting with RCV_COM each time
// it captures an infrared sequence from a remote control that is stored in the device's internal dB
if (strippedMessage.contains("RCV_COM")) {
parseIRDBMessage(strippedMessage);
}
byteCount = getByteCount(byteBuffer);
} else {
logger.warn("Received some non-compliant garbage '{}' - Parsing is skipped",
new String(byteBuffer.array()));
}
}
} catch (Exception e) {
logger.error("An exception occurred while reading bytebuffer '{}' : {}",
HexUtils.bytesToHex(byteBuffer.array()), e.getMessage(), e);
}
}
protected int getByteCount(ByteBuffer byteBuffer) {
String response = new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
response = StringUtils.chomp(response);
Matcher matcher = RESPONSE_PATTERN.matcher(response);
if (matcher.matches()) {
return Integer.parseInt(matcher.group(1));
}
return 0;
}
protected String stripByteCount(ByteBuffer byteBuffer) {
String message = null;
String response = new String(byteBuffer.array(), 0, byteBuffer.limit());
response = StringUtils.chomp(response);
Matcher matcher = RESPONSE_PATTERN.matcher(response);
if (matcher.matches()) {
String byteCountAsString = matcher.group(1);
message = matcher.group(2);
}
return message;
}
protected void parseOKMessage(String message) {
// Nothing to do here
}
protected void parseHexMessage(String message) {
Matcher matcher = HEX_PATTERN.matcher(message);
if (matcher.matches()) {
String command = matcher.group(1);
IrCommand theCommand = null;
for (IrCommand aCommand : irCommands) {
if (aCommand.sequenceToHEXString().equals(command)) {
theCommand = aCommand;
break;
}
}
if (theCommand != null) {
for (TransceiverStatusListener listener : transceiverStatusListeners) {
listener.onCommandReceived(this, theCommand);
}
} else {
logger.error("{} does not match any know infrared command", command);
}
} else {
logger.error("{} does not match the infrared message format '{}'", message, matcher.pattern());
}
}
protected void parseIRDBMessage(String message) {
Matcher matcher = IRDB_PATTERN.matcher(message);
if (matcher.matches()) {
IrCommand command = new IrCommand();
command.setRemote(matcher.group(1));
command.setCommand(matcher.group(2));
for (TransceiverStatusListener listener : transceiverStatusListeners) {
listener.onCommandReceived(this, command);
}
} else {
logger.error("{} does not match the IRDB infrared message format '{}'", message, matcher.pattern());
}
}
/**
* "Pack" the infrared command so that it can be sent to the IRTrans device
*
* @param led the led
* @param command the the command
* @return a string which is the full command to be sent to the device
*/
protected String packIRDBCommand(Led led, IrCommand command) {
String output = new String();
output = "Asnd ";
output += command.getRemote();
output += ",";
output += command.getCommand();
output += ",l";
output += led.toString();
output += "\r\n";
return output;
}
/**
* "Pack" the infrared command so that it can be sent to the IRTrans device
*
* @param led the led
* @param command the the command
* @return a string which is the full command to be sent to the device
*/
protected String packHexCommand(Led led, IrCommand command) {
String output = new String();
output = "Asndhex ";
output += "L";
output += led.toString();
output += ",";
output += "H" + command.toHEXString();
output += (char) 13;
return output;
}
}

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.irtrans.internal.handler;
import org.openhab.binding.irtrans.internal.IrCommand;
/**
* The {@link TransceiverStatusListener} is interface that is to be implemented
* by all classes that wish to be informed of events happening to a infrared
* transceiver
*
* @author Karel Goderis - Initial contribution
*
*/
public interface TransceiverStatusListener {
/**
*
* Called when the ethernet transceiver/bridge receives an infrared command
*
* @param bridge
* @param command the infrared command
*/
public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command);
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="irtrans" 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>IRtrans Binding</name>
<description>This is the binding for IRtrans (www.irtrans.de) Transceivers</description>
<author>Karel Goderis</author>
</binding:binding>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="irtrans"
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">
<!-- Blaster/Receiver diode connected to an IRtrans transceiver -->
<thing-type id="blaster">
<supported-bridge-type-refs>
<bridge-type-ref id="ethernet"/>
</supported-bridge-type-refs>
<label>Blaster</label>
<description>This is an infrared transmitter that can send infrared commands</description>
<channels>
<channel id="io" typeId="io"/>
</channels>
<config-description>
<parameter name="led" type="text" required="true">
<label>Led</label>
<description>The Led on which infrared commands will be emitted</description>
</parameter>
<parameter name="remote" type="text" required="true">
<label>Remote</label>
<description>The remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server
database and flashed into the transceiver. Can be '*' for any remote</description>
</parameter>
<parameter name="command" type="text" required="true">
<label>Command</label>
<description>The name of the command will be allowed, as defined in the IRtrans server database and flashed into the
transceiver. Can be '*' for any command</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="io">
<item-type>String</item-type>
<label>Input/Output</label>
<description>Read commands received by the blaster, or write commands to be sent by the blaster</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="irtrans"
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">
<!-- Bridge Thing Type -->
<bridge-type id="ethernet">
<label>IRtrans Ethernet Bridge</label>
<description>This is an Ethernet (PoE) IRtrans transceiver equipped with an on-board IRDB database</description>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>Network address of the ethernet transceiver</description>
<context>network-address</context>
</parameter>
<parameter name="portNumber" type="integer" required="true">
<label>Port Number</label>
<description>TCP port number of the transceiver service</description>
</parameter>
<parameter name="bufferSize" type="integer" required="false">
<label>Buffer Size</label>
<description>Buffer size used by the TCP socket when sending and receiving commands to the transceiver</description>
<default>1024</default>
</parameter>
<parameter name="responseTimeOut" type="integer" required="false">
<label>Response Time Out</label>
<description>Specifies the time milliseconds to wait for a response from the transceiver when sending a command.</description>
<default>100</default>
</parameter>
<parameter name="pingTimeOut" type="integer" required="false">
<label>Ping Time Out</label>
<description>Specifies the time milliseconds to wait for a response from the transceiver when pinging the device</description>
<default>1000</default>
</parameter>
<parameter name="reconnectInterval" type="integer" required="false">
<label>Reconnect Interval</label>
<description>Specifies the time seconds to wait before reconnecting to a transceiver after a communication failure</description>
<default>10</default>
</parameter>
</config-description>
</bridge-type>
<channel-type id="blaster">
<item-type>String</item-type>
<label>Blaster Channel</label>
<description>The Blaster Channel allows to send (filtered) infrared commands over the specified blaster led of the
transceiver</description>
<config-description>
<parameter name="led" type="text" required="true">
<label>Led</label>
<description>The Led on which infrared commands will be emitted</description>
</parameter>
<parameter name="remote" type="text" required="true">
<label>Remote</label>
<description>The remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server
database and flashed into the transceiver. Can be '*' for any remote</description>
</parameter>
<parameter name="command" type="text" required="true">
<label>Command</label>
<description>The name of the command will be allowed, as defined in the IRtrans server database and flashed into the
transceiver. Can be '*' for any command</description>
</parameter>
</config-description>
</channel-type>
<channel-type id="receiver">
<item-type>String</item-type>
<label>Receiver Channel</label>
<description>The Receiver Channel allows to receive (filtered) infrared commands on the receiver led of the
transceiver</description>
<config-description>
<parameter name="remote" type="text" required="true">
<label>Remote</label>
<description>The remote or manufacturer name which's commands will be allowed, as defined in the IRtrans server
database and flashed into the transceiver. Can be '*' for any remote</description>
</parameter>
<parameter name="command" type="text" required="true">
<label>Command</label>
<description>The name of the command will be allowed, as defined in the IRtrans server database and flashed into the
transceiver. Can be '*' for any command</description>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>