added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.phc/.classpath
Normal file
32
bundles/org.openhab.binding.phc/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.phc/.project
Normal file
23
bundles/org.openhab.binding.phc/.project
Normal 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>
|
||||
13
bundles/org.openhab.binding.phc/NOTICE
Normal file
13
bundles/org.openhab.binding.phc/NOTICE
Normal 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
|
||||
166
bundles/org.openhab.binding.phc/README.md
Normal file
166
bundles/org.openhab.binding.phc/README.md
Normal 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 |
|
||||
|
||||

|
||||
|
||||
## 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"}
|
||||
```
|
||||
BIN
bundles/org.openhab.binding.phc/doc/RJ12-Connector.png
Normal file
BIN
bundles/org.openhab.binding.phc/doc/RJ12-Connector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
17
bundles/org.openhab.binding.phc/pom.xml
Normal file
17
bundles/org.openhab.binding.phc/pom.xml
Normal 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>
|
||||
10
bundles/org.openhab.binding.phc/src/main/feature/feature.xml
Normal file
10
bundles/org.openhab.binding.phc/src/main/feature/feature.xml
Normal 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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user