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="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="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="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.phc</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,166 @@
# PHC Binding
This binding allows you to integrate modules(at the Moment AM, EM, JRM and DIM) of PHC, without the PHC control (STM), in openHAB.
The serial protocol is mainly extracted, with thanks to the developers from the projects [PHCtoUDP](https://sourceforge.net/projects/phctoudp/) and [OpenHC](https://sourceforge.net/projects/openhc/?source=directory).
The basics of the module bus protocol can also be found in the [Wiki of the PHC-Forum (german)](https://wiki.phc-forum.de/index.php/PHC-Protokoll_des_internen_Bus).
While the Wiki is offline you can find a PDF version [here](https://phc-forum.de/index.php/forum/phc-programmierung/129-phc-protokoll?start=15#1329).
## Serial Communication
The binding was tested with QinHeng Electronics HL-340 USB-Serial adapter (RS485) and the Digitus DA-70157 (FTDI/FT323RL) on Raspbian Ubilinux (Up Board) and Windows 10:
| Device/OS | adaptor | result |
|--------------------------|---------------|--------------|
| Windows 10 | HL-340 | ok |
| | FTDI | good |
| Raspberry Pi 3B/Jessie | HL-340 | not reliable |
| | FTDI | doesn´t work |
| | on board | bad |
| Up Board/ubilinux(Jessie)| HL-340 | not reliable |
| | FTDI | good |
If there are many modules on one bridge, the initialization can take a few minutes. If it does not work you can plug in the modules one after the other.
Sometimes after initialization, you might have to switch two times or the reaction could be a bit slow, but after you used a channel it should all work fine.
For all devices running with Linux that use the ch341 driver (HL-340), the new version (ch34x) is needed.
A guide how to install this can be found here: [CH340/341 UART Driver for Raspberry Pi](https://github.com/aperepel/raspberrypi-ch340-driver).
If you don´t have the same kernel as used in the guide you have to compile the module yourself. In the guide is described a specific way for the Raspberry Pi. With another Linux version you can go the normal way with linux-headers.
According to the [Wiki of the PHC-Forum](https://wiki.phc-forum.de/index.php/PHC-Protokoll_des_internen_Bus#USB_RS-485_Adapter) the newer version of the FTDI adapter doesn't really work anymore either.
In Linux amongst others the user 'openhab' must be added to the group 'dialout': ```sudo usermod -a -G dialout openhab``` For more information read the [installation guide](https://www.openhab.org/docs/installation/linux.html#recommended-additional-setup-steps).
### Connection
There are two alternatives, the first of which is much simpler.
#### Connection via power supply (simpler, preferred)
The simplest way would be to connect the RS485 adaptor to the PHC power supply like in the table below and Out at the power supply to the first module like the STM before.
| adaptor | PHC power supply |
|----------|------------------|
| 485+ | +A |
| 485- | -B |
#### Make a direct RJ12 connection
Connect a RJ12 plug with the RS485 adaptor and the power supply as follows.
| RJ12 like in picture below | The cores on the other side |
|----------------------------|-----------------------------|
| 0V | 0V on power supply |
| B- | 485- on adaptor |
| A+ | 485+ on adaptor |
| 24+ | +24V on power supply |
![RJ12 Connector](doc/RJ12-Connector.png)
## Bridge
The Bridge manages the communication between the things and the modules via a serial port (RS485).
It represents the STM.
At the Moment you can only use one Bridge (like one STM).
#### Configurations
**Serial Port:** Type the serial port of the RS485 adaptor, e.g. COM3 (Windows) or /dev/ttyUSB0 (Linux).
## Supported Things
- **AM module:** This represents the AM module with 8 outgoing channels (relays).
- **EM module:** This represents the EM module with 16 incoming (switches) and 8 outgoing (for a LED in the switch) channels.
- **JRM module:** This represents the JRM module with 4 channels for Shutters.
- **DIM:** This represents the DM module with 2 dimmer channels.
## Discovery
Not implemented yet.
## Thing Configuration
A thing accords with a module in the PHC software and the channels (with linked items) accord with the inputs and outputs.
Please note, if you define the things manually (not in the UI) that the ThingID always have to be the address (like the PID switches on the module).
#### Parameters
- **address:** Type the address of the module like the DIP switches (you can also find in the PHC software) of the module, e.g. 10110. (mandatory)
- **upDownTime[1-4] (only JRM):** (advanced) The time in seconds that the shutter needs to move up or down, with a resolution of 1/10 seconds. The default, if no value is specified, is 30 seconds.
- **dimTime[1-2] (only DIM):** (advanced) The time in seconds in that the dimmer should move 100%. The default is 2 seconds, then for example dimming from 0 to 100% takes 2 second.
## Channels
| Thing Type | Channel-Group Id | Channels | Item Type |
|------------------------|------------------|----------|------------------|
| AM | am | 00-07 | Switch |
| EM | em | 00-15 | Switch(read only)|
| EM | emLed | 00-07 | Switch |
| JRM | jrm | 00-03 | Rollershutter |
| JRM | jrmT | 00-03 | Number |
| DIM | dim | 00-01 | Dimmer |
| DIM | dimT | 00-01 | Number |
**Channel UID:** ```phc:<Thing Type>:<ThingID>:<Channel Group>#<Channel>``` e.g. ```phc:AM:01101:am#03```
- **am:** Outgoing switch channels (relay).
- **em:** Incoming channels.
- **emLed:** Outgoing switch channels e.g. for LEDs in light shutters.
- **jrm:** Outgoing shutter channels.
- **jrmT:** Time for shutter channels in seconds with an accuracy of 1/10 seconds.
These channels are used instead of the configuration parameters.
If you send the time via this channel, the Binding uses this time till you send another.
After reboot the config parameter is used by default.
- **dim:** Outgoing dimmer channels.
## Full Example
.things
```
Bridge phc:bridge:demo [port="/dev/ttyUSB0"]{
// The ThingID have to be the address.
Thing AM 01101 [address="01101"]
Thing EM 00110 [address="00110"]
Thing JRM 10111 [address="10111", upDownTime3=60, upDownTime4=20]
Thing DIM 00000 [address="00000"]
```
.items
```
//AM Module
Switch Switch_1 {channel="phc:AM:01101:am#00"}
Switch Switch_2 {channel="phc:AM:01101:am#01"}
Switch Switch_3 {channel="phc:AM:01101:am#02"}
...
Switch Switch_8 {channel="phc:AM:01101:am#07"}
//JRM Module
Rollershutter Shutter_1 {channel="phc:JRM:10111:jrm#00"}
Rollershutter Shutter_2 {channel="phc:JRM:10111:jrm#01"}
Rollershutter Shutter_3 {channel="phc:JRM:10111:jrm#02"}
Rollershutter Shutter_4 {channel="phc:JRM:10111:jrm#03"}
Number ShutterTime_1 {channel="phc:JRM:10111:jrmT#00"}
//DIM Module
Dimmer Dimmer_1 {channel="phc:DIM:00000:dim#00}
Dimmer Dimmer_2 {channel="phc:DIM:00000:dim#01}
// EM Module
Switch InputLed_3 {channel="phc:EM:00110:emLed#03"}
Switch Input_1 {channel="phc:EM:00110:em#00"}
Switch Input_2 {channel="phc:EM:00110:em#01"}
Switch Input_3 {channel="phc:EM:00110:em#02"}
...
Switch Input_16 {channel="phc:EM:00110:em#15"}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

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.phc</artifactId>
<name>openHAB Add-ons :: Bundles :: PHC Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.phc-${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-phc" description="PHC 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.phc/${project.version}</bundle>
</feature>
</features>

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.phc.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PHCBinding} class defines common constants, which are used across
* the whole binding.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCBindingConstants {
public static final String BINDING_ID = "phc";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_AM = new ThingTypeUID(BINDING_ID, "AM");
public static final ThingTypeUID THING_TYPE_EM = new ThingTypeUID(BINDING_ID, "EM");
public static final ThingTypeUID THING_TYPE_JRM = new ThingTypeUID(BINDING_ID, "JRM");
public static final ThingTypeUID THING_TYPE_DIM = new ThingTypeUID(BINDING_ID, "DIM");
// List of all Channel Group IDs
public static final String CHANNELS_AM = "am";
public static final String CHANNELS_EM = "em";
public static final String CHANNELS_EM_LED = "emLed";
public static final String CHANNELS_JRM = "jrm";
public static final String CHANNELS_JRM_TIME = "jrmT";
public static final String CHANNELS_DIM = "dim";
// List of all configuration parameters
public static final String PORT = "port";
public static final String ADDRESS = "address";
public static final String UP_DOWN_TIME_1 = "upDownTime1";
public static final String UP_DOWN_TIME_2 = "upDownTime2";
public static final String UP_DOWN_TIME_3 = "upDownTime3";
public static final String UP_DOWN_TIME_4 = "upDownTime4";
public static final String DIM_TIME_1 = "dimTime1";
public static final String DIM_TIME_2 = "dimTime2";
}

View File

@@ -0,0 +1,102 @@
/**
* 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.phc.internal;
import static org.openhab.binding.phc.internal.PHCBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.phc.internal.handler.PHCBridgeHandler;
import org.openhab.binding.phc.internal.handler.PHCHandler;
import org.openhab.core.config.core.Configuration;
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.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;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PHCHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.phc")
public class PHCHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_BRIDGE, THING_TYPE_AM, THING_TYPE_EM, THING_TYPE_JRM, THING_TYPE_DIM)
.collect(Collectors.toSet()));
private @NonNullByDefault({}) SerialPortManager serialPortManager;
@Reference
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
this.serialPortManager = null;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
Thing thing;
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
thing = super.createThing(thingTypeUID, configuration, thingUID, null);
} else {
ThingUID phcThingUID = new ThingUID(thingTypeUID, configuration.get(ADDRESS).toString());
thing = super.createThing(thingTypeUID, configuration, phcThingUID, bridgeUID);
}
} else {
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the phc binding.");
}
return thing;
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
ThingHandler handler = null;
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
handler = new PHCBridgeHandler((Bridge) thing, serialPortManager);
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
handler = new PHCHandler(thing);
}
return handler;
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.phc.internal;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
/**
* The {@link PHCHelper} is responsible for finding the appropriate Thing(UID)
* to the Channel of the PHC module.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCHelper {
/**
* Get the ThingUID by the given parameters.
*
* @param thingTypeUID
* @param moduleAddr reverse (to the reverse address - DIP switches)
* @return
*/
public static ThingUID getThingUIDreverse(ThingTypeUID thingTypeUID, byte moduleAddr) {
// convert to 5-bit binary string and reverse in second step
String thingID = StringUtils.leftPad(StringUtils.trim(Integer.toBinaryString(moduleAddr & 0xFF)), 5, '0');
thingID = new StringBuilder(thingID).reverse().toString();
ThingUID thingUID = new ThingUID(thingTypeUID, thingID);
return thingUID;
}
/**
* Convert the byte b into an binary String
*
* @param b
* @return
*/
public static Object bytesToBinaryString(byte[] bytes) {
StringBuilder bin = new StringBuilder();
for (byte b : bytes) {
bin.append(StringUtils.leftPad(StringUtils.trim(Integer.toBinaryString(b & 0xFF)), 8, '0'));
bin.append(' ');
}
return bin.toString();
}
}

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.phc.internal.handler;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Buffer for received messages
*
* @author Jonas Hohaus - Initial contribution
*/
class InternalBuffer {
private static final int MAX_SIZE = 512;
private final BlockingQueue<byte[]> byteQueue = new LinkedBlockingQueue<>();
private byte[] buffer;
private int bufferIndex = 0;
private int size;
public void offer(byte[] buffer) {
// If the buffer becomes too large, already processed commands accumulate and
// the reaction becomes slow.
if (size < MAX_SIZE) {
byte[] localBuffer = Arrays.copyOf(buffer, Math.min(MAX_SIZE - size, buffer.length));
byteQueue.offer(localBuffer);
synchronized (this) {
size += localBuffer.length;
}
}
}
public boolean hasNext() {
return (size > 0);
}
public byte get() throws InterruptedException {
byte[] buf = getBuffer();
if (buf != null) {
byte result = buf[bufferIndex++];
synchronized (this) {
size--;
}
return result;
}
throw new IllegalStateException("get without hasNext");
}
public int size() {
return size;
}
private byte[] getBuffer() throws InterruptedException {
if (buffer == null || bufferIndex == buffer.length) {
buffer = byteQueue.take();
bufferIndex = 0;
}
return buffer;
}
}

View File

@@ -0,0 +1,744 @@
/**
* 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.phc.internal.handler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.phc.internal.PHCBindingConstants;
import org.openhab.binding.phc.internal.PHCHelper;
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.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.thing.Bridge;
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.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PHCBridgeHandler} is responsible for handling the serial Communication to and from the PHC Modules.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
private final Logger logger = LoggerFactory.getLogger(PHCBridgeHandler.class);
private static final int BAUD = 19200;
private static final int SEND_RETRY_COUNT = 20; // max count to send the same message
private static final int SEND_RETRY_TIME_MILLIS = 60; // time to wait for an acknowledge before send the message
// again in milliseconds
private @Nullable InputStream serialIn;
private @Nullable OutputStream serialOut;
private @Nullable SerialPort commPort;
private final SerialPortManager serialPortManager;
private final Map<Byte, Boolean> toggleMap = new HashMap<>();
private final InternalBuffer buffer = new InternalBuffer();
private final BlockingQueue<QueueObject> receiveQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<QueueObject> sendQueue = new LinkedBlockingQueue<>();
private final ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(3);
private final byte emLedOutputState[] = new byte[32];
private final byte amOutputState[] = new byte[32];
private final byte dmOutputState[] = new byte[32];
private final List<Byte> modules = new ArrayList<>();
public PHCBridgeHandler(Bridge phcBridge, SerialPortManager serialPortManager) {
super(phcBridge);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
String port = ((String) getConfig().get(PHCBindingConstants.PORT));
// find the given port
SerialPortIdentifier portId = serialPortManager.getIdentifier(port);
if (portId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Serial port '" + port + "' could not be found.");
return;
}
try {
// initialize serial port
SerialPort serialPort = portId.open(this.getClass().getName(), 2000); // owner, timeout
serialIn = serialPort.getInputStream();
// set port parameters
serialPort.setSerialPortParams(BAUD, SerialPort.DATABITS_8, SerialPort.STOPBITS_2, SerialPort.PARITY_NONE);
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
serialPort.addEventListener(this);
// activate the DATA_AVAILABLE notifier
serialPort.notifyOnDataAvailable(true);
// get the output stream
serialOut = serialPort.getOutputStream();
commPort = serialPort;
sendPorBroadcast();
byte[] b = { 0x01 };
for (int j = 0; j <= 0x1F; j++) {
serialWrite(buildMessage((byte) j, 0, b, false));
}
updateStatus(ThingStatus.ONLINE);
// receive messages
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processReceivedBytes();
}
});
// process received messages
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processReceiveQueue();
}
});
// sendig commands to the modules
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processSendQueue();
}
});
} catch (PortInUseException | TooManyListenersException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not open serial port " + port + ": " + e.getMessage());
} catch (UnsupportedCommOperationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not configure serial port " + port + ": " + e.getMessage());
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Failed to get input or output stream for serialPort: " + e.getMessage());
logger.debug("Failed to get inputstream for serialPort", e);
}
}
/**
* Reads the data on serial port and puts it into the internal buffer.
*/
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE && serialIn != null) {
try {
byte[] bytes = new byte[serialIn.available()];
serialIn.read(bytes);
buffer.offer(bytes);
if (logger.isTraceEnabled()) {
logger.trace("buffer offered {}", HexUtils.bytesToHex(bytes, " "));
}
} catch (IOException e) {
logger.warn("Error on reading input stream to internal buffer", e);
}
}
}
/**
* process internal incoming buffer (recognize on read messages)
*/
private void processReceivedBytes() {
int faultCounter = 0;
try {
byte module = buffer.get();
while (true) {
// Recognition of messages from byte buffer.
// not a known module address
if (!modules.contains(module)) {
module = buffer.get();
continue;
}
if (logger.isDebugEnabled()) {
logger.debug("get module: {}", new String(HexUtils.byteToHex(module)));
}
byte sizeToggle = buffer.get();
// read length of command and check if makes sense
if ((sizeToggle < 1 || sizeToggle > 3) && ((sizeToggle & 0xFF) < 0x81 || (sizeToggle & 0xFF) > 0x83)) {
if (logger.isDebugEnabled()) {
logger.debug("get invalid sizeToggle: {}", new String(HexUtils.byteToHex(sizeToggle)));
}
module = sizeToggle;
continue;
}
// read toggle, size and command
int size = (sizeToggle & 0x7F);
boolean toggle = (sizeToggle & 0x80) == 0x80;
logger.debug("get toggle: {}", toggle);
byte[] command = new byte[size];
for (int i = 0; i < size; i++) {
command[i] = buffer.get();
}
// log command
if (logger.isTraceEnabled()) {
logger.trace("command read: {}", PHCHelper.bytesToBinaryString(command));
}
// read crc
byte crcByte1 = buffer.get();
byte crcByte2 = buffer.get();
short crc = (short) (crcByte1 & 0xFF);
crc |= (crcByte2 << 8);
// calculate checkCrc
short checkCrc = calcCrc(module, sizeToggle, command);
// check crc
if (crc != checkCrc) {
logger.debug("CRC not correct (crc from message, calculated crc): {}, {}", crc, checkCrc);
faultCounter = handleCrcFault(faultCounter);
module = buffer.get();
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("get crc: {}", HexUtils.bytesToHex(new byte[] { crcByte1, crcByte2 }, " "));
}
faultCounter = 0;
processReceivedMsg(module, toggle, command);
module = buffer.get();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private int handleCrcFault(int faultCounter) throws InterruptedException {
if (faultCounter > 0) {
// Normally in this case we read the message repeatedly offset to the real -> skip one to 6 bytes
for (int i = 0; i < faultCounter; i++) {
if (buffer.hasNext()) {
buffer.get();
}
}
}
int resCounter = faultCounter + 1;
if (resCounter > 6) {
resCounter = 0;
}
return resCounter;
}
private void processReceivedMsg(byte module, boolean toggle, byte[] command) {
// Acknowledgement received (command first byte 0)
if (command[0] == 0) {
String moduleType;
byte channel = 0; // only needed for dim
if ((module & 0xE0) == 0x40) {
moduleType = PHCBindingConstants.CHANNELS_AM;
} else if ((module & 0xE0) == 0xA0) {
moduleType = PHCBindingConstants.CHANNELS_DIM;
channel = (byte) ((command[0] >>> 5) & 0x0F);
} else {
moduleType = PHCBindingConstants.CHANNELS_EM_LED;
}
setModuleOutputState(moduleType, (byte) (module & 0x1F), command[1], channel);
toggleMap.put(module, !toggle);
// initialization (first byte FF)
} else if (command[0] == (byte) 0xFF) {
if ((module & 0xE0) == 0x00) { // EM
sendEmConfig(module);
} else if ((module & 0xE0) == 0x40 || (module & 0xE0) == 0xA0) { // AM, JRM and DIM
sendAmConfig(module);
}
logger.debug("initialization: {}", module);
// ignored - ping (first byte 01)
} else if (command[0] == 0x01) {
logger.debug("first byte 0x01 -> ignored");
// EM command / update
} else {
if (((module & 0xE0) == 0x00)) {
sendEmAcknowledge(module, toggle);
logger.debug("send acknowledge (modul, toggle) {} {}", module, toggle);
byte channel = (byte) ((command[0] >>> 4) & 0x0F);
OnOffType onOff = OnOffType.OFF;
if ((command[0] & 0x0F) == 2) {
onOff = OnOffType.ON;
}
QueueObject qo = new QueueObject(PHCBindingConstants.CHANNELS_EM, module, channel, onOff);
// put recognized message into queue
if (!receiveQueue.contains(qo)) {
receiveQueue.offer(qo);
}
// ignore if message not from EM module
} else if (logger.isDebugEnabled()) {
logger.debug("Incoming message (module, toggle, command) not from EM module: {} {} {}",
new String(HexUtils.byteToHex(module)), toggle, PHCHelper.bytesToBinaryString(command));
}
}
}
/**
* process receive queue
*/
private void processReceiveQueue() {
while (true) {
try {
QueueObject qo = receiveQueue.take();
logger.debug("Consume Receive QueueObject: {}", qo);
handleIncomingCommand(qo.getModuleAddress(), qo.getChannel(), (OnOffType) qo.getCommand());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* process send queue
*/
private void processSendQueue() {
while (true) {
try {
QueueObject qo = sendQueue.take();
sendQueueObject(qo);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
}
}
private void sendQueueObject(QueueObject qo) {
int sendCount = 0;
// Send the command to the module until a response is received. Max. SEND_RETRY_COUNT repeats.
do {
switch (qo.getModuleType()) {
case PHCBindingConstants.CHANNELS_AM:
sendAm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
break;
case PHCBindingConstants.CHANNELS_EM_LED:
sendEm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
break;
case PHCBindingConstants.CHANNELS_JRM:
sendJrm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
break;
case PHCBindingConstants.CHANNELS_DIM:
sendDim(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
break;
}
sendCount++;
try {
Thread.sleep(SEND_RETRY_TIME_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} while (!isChannelOutputState(qo.getModuleType(), qo.getModuleAddress(), qo.getChannel(), qo.getCommand())
&& sendCount < SEND_RETRY_COUNT);
if (PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
// there aren't state per channel for JRM modules
amOutputState[qo.getModuleAddress() & 0x1F] = -1;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
// state ist the same for every dim level except zero/off -> inizialize state
// with 0x0F after sending an command.
dmOutputState[qo.getModuleAddress() & 0x1F] |= (0x0F << (qo.getChannel() * 4));
}
if (sendCount >= SEND_RETRY_COUNT) {
// change the toggle: if no acknowledge received it may be wrong.
byte module = qo.getModuleAddress();
if (PHCBindingConstants.CHANNELS_AM.equals(qo.getModuleType())
|| PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
module |= 0x40;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
module |= 0xA0;
}
toggleMap.put(module, !getToggle(module));
if (logger.isDebugEnabled()) {
logger.debug("No acknowledge from the module {} received.", qo.getModuleAddress());
}
}
}
private void setModuleOutputState(String moduleType, byte moduleAddress, byte state, byte channel) {
if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
emLedOutputState[moduleAddress] = state;
} else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
amOutputState[moduleAddress & 0x1F] = state;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
dmOutputState[moduleAddress & 0x1F] = (byte) (state << channel * 4);
}
}
private boolean isChannelOutputState(String moduleType, byte moduleAddress, byte channel, Command cmd) {
int state = OnOffType.OFF.equals(cmd) ? 0 : 1;
if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
return ((emLedOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
} else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
return ((amOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
} else if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)) {
return (amOutputState[moduleAddress & 0x1F] != -1);
} else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
return ((dmOutputState[moduleAddress & 0x1F] >>> channel * 4) & 0x0F) != 0x0F;
} else {
return false;
}
}
private boolean getToggle(byte moduleAddress) {
if (!toggleMap.containsKey(moduleAddress)) {
toggleMap.put(moduleAddress, false);
}
return toggleMap.get(moduleAddress);
}
/**
* Put the given command into the queue to send.
*
* @param moduleType
* @param moduleAddress
* @param channel
* @param command
* @param upDownTime
*/
public void send(@Nullable String moduleType, int moduleAddress, String channel, Command command,
short upDownTime) {
if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)
|| PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command, upDownTime));
} else {
sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command));
}
}
private void sendAm(byte moduleAddress, byte channel, Command command) {
byte module = (byte) (moduleAddress | 0x40);
byte[] cmd = { (byte) (channel << 5) };
if (OnOffType.ON.equals(command)) {
cmd[0] |= 2;
} else {
cmd[0] |= 3;
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendEm(byte moduleAddress, byte channel, Command command) {
byte[] cmd = { (byte) (channel << 4) };
if (OnOffType.ON.equals(command)) {
cmd[0] |= 2;
} else {
cmd[0] |= 3;
}
serialWrite(buildMessage(moduleAddress, channel, cmd, getToggle(moduleAddress)));
}
private void sendJrm(byte moduleAddress, byte channel, Command command, short upDownTime) {
// The up and the down message needs two additional bytes for the time.
int size = (command == StopMoveType.STOP) ? 2 : 4;
byte[] cmd = new byte[size];
if (channel == 0) {
channel = 4;
}
byte module = (byte) (moduleAddress | 0x40);
cmd[0] = (byte) (channel << 5);
cmd[1] = 0x3F;
switch (command.toString()) {
case "UP":
cmd[0] |= 5;
cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
break;
case "DOWN":
cmd[0] |= 6;
cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
break;
case "STOP":
cmd[0] |= 2;
break;
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendDim(byte moduleAddress, byte channel, Command command, short dimTime) {
byte module = (byte) (moduleAddress | 0xA0);
byte[] cmd = new byte[(command instanceof PercentType && !(((PercentType) command).byteValue() == 0)) ? 3 : 1];
cmd[0] = (byte) (channel << 5);
if (command instanceof OnOffType) {
if (OnOffType.ON.equals(command)) {
cmd[0] |= 3;
} else if (OnOffType.OFF.equals(command)) {
cmd[0] |= 4;
}
} else {
if (((PercentType) command).byteValue() == 0) {
cmd[0] |= 4;
} else {
cmd[0] |= 22;
cmd[1] = (byte) (((PercentType) command).byteValue() * 2.55);
cmd[2] = (byte) dimTime;
}
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendPorBroadcast() {
byte[] msg = buildMessage((byte) 0xFF, 0, new byte[] { 0 }, false);
for (int i = 0; i < 20; i++) {
serialWrite(msg);
}
}
private void sendAmConfig(byte moduleAddress) {
byte[] cmd = new byte[3];
cmd[0] = (byte) 0xFE;
cmd[1] = 0;
cmd[2] = (byte) 0xFF;
serialWrite(buildMessage(moduleAddress, 0, cmd, false));
}
private void sendEmConfig(byte moduleAddress) {
byte[] cmd = new byte[52];
int pos = 0;
cmd[pos++] = (byte) 0xFE;
cmd[pos++] = (byte) 0x00; // POR
cmd[pos++] = 0x00;
cmd[pos++] = 0x00;
for (int i = 0; i < 16; i++) { // 16 inputs
cmd[pos++] = (byte) ((i << 4) | 0x02);
cmd[pos++] = (byte) ((i << 4) | 0x03);
cmd[pos++] = (byte) ((i << 4) | 0x05);
}
serialWrite(buildMessage(moduleAddress, 0, cmd, false));
}
private void sendEmAcknowledge(byte module, boolean toggle) {
byte[] msg = buildMessage(module, 0, new byte[] { 0 }, toggle);
for (int i = 0; i < 3; i++) { // send three times stops the module faster from sending messages if the first
// response is not recognized.
serialWrite(msg);
}
}
/**
* Build a serial message from the given parameters.
*
* @param modulAddr
* @param channel
* @param cmd
* @param toggle
* @return
*/
private byte[] buildMessage(byte modulAddr, int channel, byte[] cmd, boolean toggle) {
int len = cmd.length;
byte[] buffer = new byte[len + 4];
buffer[0] = modulAddr;
buffer[1] = (byte) (toggle ? (len | 0x80) : len); // 0x80: 1000 0000
System.arraycopy(cmd, 0, buffer, 2, len);
short crc = calcCrc(modulAddr, buffer[1], cmd);
buffer[2 + len] = (byte) (crc & 0xFF);
buffer[3 + len] = (byte) ((crc >> 8) & 0xFF);
return buffer;
}
/**
* Calculate the 16 bit crc of the message.
*
* @param module
* @param sizeToggle
* @param cmd
* @return
*/
private short calcCrc(byte module, byte sizeToggle, byte[] cmd) {
short crc = (short) 0xFFFF;
crc = crc16Update(crc, module);
crc = crc16Update(crc, sizeToggle);
for (byte b : cmd) {
crc = crc16Update(crc, b);
}
crc ^= 0xFFFF;
return crc;
}
/**
* Update the 16 bit crc of the message.
*
* @param crc
* @param data
* @return
*/
private short crc16Update(short crc, byte messagePart) {
byte data = (byte) (messagePart ^ (crc & 0xFF));
data ^= data << 4;
short data16 = data;
return (short) (((data16 << 8) | (((crc >> 8) & 0xFF) & 0xFF)) ^ ((data >> 4) & 0xF)
^ ((data16 << 3) & 0b11111111111));
}
/**
* Send the incoming command to the appropriate handler and channel.
*
* @param moduleAddress
* @param channel
* @param cmd
* @param rcvCrc
*/
private void handleIncomingCommand(byte moduleAddress, int channel, OnOffType onOff) {
ThingUID uid = PHCHelper.getThingUIDreverse(PHCBindingConstants.THING_TYPE_EM, moduleAddress);
Thing thing = getThing().getThing(uid);
String channelId = "em#" + StringUtils.leftPad(Integer.toString(channel), 2, '0');
if (thing != null && thing.getHandler() != null) {
logger.debug("Input: {}, {}, {}", thing.getUID(), channelId, onOff);
PHCHandler handler = (PHCHandler) thing.getHandler();
if (handler != null) {
handler.handleIncoming(channelId, onOff);
} else {
logger.debug("No Handler for Thing {} available.", thing.getUID());
}
} else {
logger.debug("No Thing with UID {} available.", uid.getAsString());
}
}
private void serialWrite(byte[] msg) {
if (serialOut != null) {
try {
// write to serial port
serialOut.write(msg);
serialOut.flush();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error writing '" + msg + "' to serial port : " + e.getMessage());
}
if (logger.isTraceEnabled()) {
logger.trace("send: {}", PHCHelper.bytesToBinaryString(msg));
}
}
}
/**
* Adds the given address to the module list.
*
* @param module
*/
public void addModule(byte module) {
modules.add(module);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// unnecessary
}
@Override
public void dispose() {
threadPoolExecutor.shutdownNow();
if (commPort != null) {
commPort.close();
commPort = null;
}
}
}

View File

@@ -0,0 +1,183 @@
/**
* 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.phc.internal.handler;
import static org.openhab.binding.phc.internal.PHCBindingConstants.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.Bridge;
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.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PHCHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Jonas Hohaus - Initial contribution
*
*/
public class PHCHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PHCHandler.class);
private String moduleAddress; // like DIP switches
private byte module;
private final short[] times = new short[4];
private final Map<String, State> channelState = new HashMap<>();
private PHCBridgeHandler bridgeHandler;
public PHCHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
moduleAddress = (String) getConfig().get(ADDRESS);
if (getPHCBridgeHandler() == null) {
return;
}
module = Byte.parseByte(new StringBuilder(moduleAddress).reverse().toString(), 2);
if (getThing().getThingTypeUID().equals(THING_TYPE_AM) || getThing().getThingTypeUID().equals(THING_TYPE_JRM)) {
module |= 0x40;
} else if (getThing().getThingTypeUID().equals(THING_TYPE_DIM)) {
module |= 0xA0;
}
getPHCBridgeHandler().addModule(module);
if (getThing().getThingTypeUID().equals(THING_TYPE_JRM)) {
times[0] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_1)).shortValue() * 10);
times[1] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_2)).shortValue() * 10);
times[2] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_3)).shortValue() * 10);
times[3] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_4)).shortValue() * 10);
} else if (getThing().getThingTypeUID().equals(THING_TYPE_DIM)) {
times[0] = (((BigDecimal) getConfig().get(DIM_TIME_1)).shortValue());
times[1] = (((BigDecimal) getConfig().get(DIM_TIME_2)).shortValue());
}
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
public void handleIncoming(String channelId, OnOffType state) {
if (logger.isDebugEnabled()) {
logger.debug("EM command: {}, last: {}, in: {}", channelId, channelState.get(channelId), state);
}
if (!channelState.containsKey(channelId) || !channelState.get(channelId).equals(state)) {
postCommand(channelId, state);
channelState.put(channelId, state);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
final String groupId = channelUID.getGroupId();
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
if ((CHANNELS_JRM.equals(groupId) && (command instanceof UpDownType || command instanceof StopMoveType))
|| (CHANNELS_DIM.equals(groupId)
&& (command instanceof OnOffType || command instanceof PercentType))) {
getPHCBridgeHandler().send(groupId, module & 0x1F, channelUID.getIdWithoutGroup(), command,
times[Integer.parseInt(channelUID.getIdWithoutGroup())]);
} else if ((CHANNELS_AM.equals(groupId) || CHANNELS_EM_LED.equals(groupId))
&& command instanceof OnOffType) {
getPHCBridgeHandler().send(groupId, module & 0x1F, channelUID.getIdWithoutGroup(), command, (short) 0);
}
logger.debug("send command: {}, {}", channelUID, command);
} else {
logger.info("The Thing {} is offline.", getThing().getUID());
}
}
@Override
public void handleUpdate(ChannelUID channelUID, State newState) {
if (CHANNELS_JRM_TIME.equals(channelUID.getGroupId())) {
times[Integer
.parseInt(channelUID.getIdWithoutGroup())] = (short) (((DecimalType) newState).floatValue() * 10);
}
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
if (isInitialized()) { // prevents change of address
validateConfigurationParameters(configurationParameters);
Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
if (!configurationParmeter.getKey().equals(ADDRESS)) {
configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue());
} else {
configuration.put(configurationParmeter.getKey(), moduleAddress);
}
}
// persist new configuration and reinitialize handler
dispose();
updateConfiguration(configuration);
initialize();
} else {
super.handleConfigurationUpdate(configurationParameters);
}
}
private PHCBridgeHandler getPHCBridgeHandler() {
if (bridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"The Thing requires to select a Bridge");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof PHCBridgeHandler) {
bridgeHandler = (PHCBridgeHandler) handler;
} else {
logger.debug("No available bridge handler for {}.", bridge.getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR,
"No available bridge handler.");
return null;
}
}
return bridgeHandler;
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.phc.internal.handler;
import org.openhab.core.types.Command;
/**
* Object to save a whole message.
*
* @author Jonas Hohaus - Initial contribution
*/
class QueueObject {
private final String moduleType;
private final byte moduleAddress;
private final byte channel;
private final Command command;
private short time;
public QueueObject(String moduleType, byte moduleAddress, byte channel, Command command) {
this.moduleType = moduleType;
this.moduleAddress = moduleAddress;
this.channel = channel;
this.command = command;
}
public QueueObject(String moduleType, int moduleAddress, String channel, Command command) {
this.moduleType = moduleType;
this.moduleAddress = (byte) moduleAddress;
this.channel = Byte.parseByte(channel);
this.command = command;
}
public QueueObject(String moduleType, int moduleAddress, String channel, Command command, short time) {
this(moduleType, moduleAddress, channel, command);
this.time = time;
}
public String getModuleType() {
return moduleType;
}
public byte getModuleAddress() {
return moduleAddress;
}
public byte getChannel() {
return channel;
}
public Command getCommand() {
return command;
}
public short getTime() {
return time;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("moduleType: ");
sb.append(moduleType);
sb.append(", moduleAddress: ");
sb.append(moduleAddress);
sb.append(", channel: ");
sb.append(channel);
return sb.toString();
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<binding:binding id="phc" 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>PHC Binding</name>
<description>This is a binding for PHC modules (EM, AM, JRM and DIM). It communicates with the PHC Modulbus (RS485).</description>
<author>Jonas Hohaus</author>
</binding:binding>

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="phc" 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">
<!-- Channel Group Types -->
<channel-group-type id="amChannels">
<label>AM Channels</label>
<description>Outgoing switch channels (relay).</description>
<channels>
<channel id="00" typeId="am-channel"/>
<channel id="01" typeId="am-channel"/>
<channel id="02" typeId="am-channel"/>
<channel id="03" typeId="am-channel"/>
<channel id="04" typeId="am-channel"/>
<channel id="05" typeId="am-channel"/>
<channel id="06" typeId="am-channel"/>
<channel id="07" typeId="am-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="emChannels">
<label>EM Channels</label>
<description>Incoming channels.</description>
<channels>
<channel id="00" typeId="em-channel"/>
<channel id="01" typeId="em-channel"/>
<channel id="02" typeId="em-channel"/>
<channel id="03" typeId="em-channel"/>
<channel id="04" typeId="em-channel"/>
<channel id="05" typeId="em-channel"/>
<channel id="06" typeId="em-channel"/>
<channel id="07" typeId="em-channel"/>
<channel id="08" typeId="em-channel"/>
<channel id="09" typeId="em-channel"/>
<channel id="10" typeId="em-channel"/>
<channel id="11" typeId="em-channel"/>
<channel id="12" typeId="em-channel"/>
<channel id="13" typeId="em-channel"/>
<channel id="14" typeId="em-channel"/>
<channel id="15" typeId="em-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="jrmChannels">
<label>JRM Channels</label>
<description>Outgoing shutter channels (relay).</description>
<channels>
<channel id="00" typeId="jrm-channel"/>
<channel id="01" typeId="jrm-channel"/>
<channel id="02" typeId="jrm-channel"/>
<channel id="03" typeId="jrm-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="jrmTimeChannels" advanced="true">
<label>JRM time Channels</label>
<description>Time for shutter channels in seconds with an accuracy of 1/10 seconds.</description>
<channels>
<channel id="00" typeId="jrmTime-channel"/>
<channel id="01" typeId="jrmTime-channel"/>
<channel id="02" typeId="jrmTime-channel"/>
<channel id="03" typeId="jrmTime-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="dimChannels">
<label>DIM Channels</label>
<description>Outgoing dimmer channels.</description>
<channels>
<channel id="00" typeId="dim-channel"/>
<channel id="01" typeId="dim-channel"/>
</channels>
</channel-group-type>
<!-- Channel Types -->
<channel-type id="am-channel">
<item-type>Switch</item-type>
<label>PHC AM Channel</label>
<description>Channel to an AM or EM(LED) module.</description>
</channel-type>
<channel-type id="em-channel">
<item-type>Switch</item-type>
<label>PHC EM Channel</label>
<description>Channel from an EM module.</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="jrm-channel">
<item-type>Rollershutter</item-type>
<label>PHC JRM Channel</label>
<description>Channel to an JRM module.</description>
</channel-type>
<channel-type id="jrmTime-channel" advanced="true">
<item-type>Number</item-type>
<label>JRM-time Channel</label>
<description>The Time in seconds for an JRM channel.</description>
<state min="1" max="65535"/>
</channel-type>
<channel-type id="dim-channel">
<item-type>Dimmer</item-type>
<label>DIM Channel</label>
<description>Channel for a DIM module.</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="phc" 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 -->
<bridge-type id="bridge">
<label>PHC Bridge</label>
<description>The serial bridge to the PHC modules. Max 32 modules per model group(thing type) per Bridge, equates one
STM.</description>
<config-description>
<parameter name="port" type="text">
<label>Serial Port</label>
<description>Serial Port the PHC modules are connected to</description>
<required>true</required>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
</parameter>
</config-description>
</bridge-type>
<!-- Thing Types -->
<thing-type id="AM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC AM</label>
<description>Thing for an output/relay module (AM).</description>
<channel-groups>
<channel-group id="am" typeId="amChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="EM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC EM</label>
<description>Thing for an input/switch module (EM).</description>
<channel-groups>
<channel-group id="em" typeId="emChannels"/>
<channel-group id="emLed" typeId="amChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="JRM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC JRM</label>
<description>Thing for an shutter module (JRM).</description>
<channel-groups>
<channel-group id="jrm" typeId="jrmChannels"/>
<channel-group id="jrmT" typeId="jrmTimeChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
<parameter name="upDownTime1" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 1</label>
<description>The time (in seconds) which the first shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime2" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 2</label>
<description>The time (in seconds) which the second shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime3" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 3</label>
<description>The time (in seconds) which the third shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime4" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 4</label>
<description>The time (in seconds) which the fourth shutter needs to move up/down.</description>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="DIM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC DIM</label>
<description>Thing for a dimmer module (DM).</description>
<channel-groups>
<channel-group id="dim" typeId="dimChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
<parameter name="dimTime1" type="integer" min="1" max="255">
<advanced>true</advanced>
<label>Time Dimmer 1</label>
<description>The time (in seconds) in which the first dimmer should dim 100%.</description>
<default>2</default>
</parameter>
<parameter name="dimTime2" type="integer" min="1" max="255">
<advanced>true</advanced>
<label>Time Dimmer 2</label>
<description>The time (in seconds) in which the second dimmer should dim 100%.</description>
<default>2</default>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>