added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.nikobus/.classpath
Normal file
32
bundles/org.openhab.binding.nikobus/.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.nikobus/.project
Normal file
23
bundles/org.openhab.binding.nikobus/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.nikobus</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.nikobus/NOTICE
Normal file
13
bundles/org.openhab.binding.nikobus/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
|
||||
229
bundles/org.openhab.binding.nikobus/README.md
Normal file
229
bundles/org.openhab.binding.nikobus/README.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Nikobus Binding
|
||||
|
||||
This binding allows openHAB to interact with the Nikobus home automation system.
|
||||
|
||||
[](https://www.youtube.com/watch?v=QiNb-8QxXpo)
|
||||
|
||||
More specifically, it allows openHAB to:
|
||||
|
||||
* send (simulated) button presses to the Nikobus,
|
||||
* react to button presses which occur on the Nikobus,
|
||||
* change the status of switch channels on a Nikobus switch module,
|
||||
* request the status of switch channels on a Nikobus switch module,
|
||||
* change the status of dimmer channels on a Nikobus dimmer module,
|
||||
* request the status of dimmer channels on a Nikobus dimmer module,
|
||||
* send commands to the Nikobus roller shutter module.
|
||||
|
||||
This binding works with at least the following hardware:
|
||||
|
||||
* PC-link module (05-200),
|
||||
* Push buttons (05-060-01, 05-064-01), RF Transmitter (05-314), PIR Sensor (430-00500),
|
||||
* 4 channel switch module (05-002-02),
|
||||
* 12 channel switch module (05-000-02),
|
||||
* 12 channel dimmer module.
|
||||
|
||||
## Supported Things
|
||||
|
||||
The binding supports a serial connection via `nikobus:pc-link` bridge to the Nikobus installation (PC-Link module):
|
||||
|
||||
The bridge enables communication with other Nikobus components:
|
||||
|
||||
* `switch-module` - Nikobus switch module, i.e. `05-000-02`,
|
||||
* `dimmer-module` - Nikobus dim-controller module, i.e. `05-007-02`,
|
||||
* `rollershutter-module` - Nikobus roller shutter module,
|
||||
* `push-button` - Nikobus physical push button.
|
||||
|
||||
## Discovery
|
||||
|
||||
The binding does not support any automatic discovery of Things.
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
The binding can connect to the PC-Link via serial interface.
|
||||
|
||||
```
|
||||
Bridge nikobus:pc-link:mypclink [ port="<serial port>", refreshInterval=<interval> ] {
|
||||
}
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
* `port` is the name of the serial port used to connect to the Nikobus installation
|
||||
* `refreshInterval` defines how often the binding reads Nikobus module's status, so having i.e. 30 as above, the binding will read one module’s status each 30s, iterating through all modules, one by one. If one does not specify `refreshInterval`, a default value of 60s is used.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Once connected to the Nikobus installation using a bridge, one can communicate with:
|
||||
|
||||
* `switch-module`,
|
||||
* `dimmer-module`,
|
||||
* `rollershutter-module`,
|
||||
* `push-button`.
|
||||
|
||||
### Modules
|
||||
|
||||
Each module is defined by its address and contains 12 outputs (channels), where `output-1` corresponds to module's first output, `output-2` to module's second output and so on.
|
||||
If physical module has less outputs, only those channels can be used (i.e. `05-002-02` has only 4 outputs, so only channels 1-4 can be used).
|
||||
|
||||
Large module contains 2 channel groups, where the first group controls channels 1-6 and the second one controls channels 7-12.
|
||||
The small module contains only a single channel group controlling all 4 channels.
|
||||
|
||||
All commands sent to/received from the Nikobus switch module are for a single channel group.
|
||||
|
||||
In order to be able to read the status of a Nikobus module channel or to switch a channel directly on the module without mimicking a button press, items for each channel of a module needs to be configured.
|
||||
|
||||
#### switch-module
|
||||
|
||||
```
|
||||
Thing switch-module s1 [ address = "BC00" ]
|
||||
```
|
||||
|
||||
Defines a `switch-module` with address `BC00`.
|
||||
|
||||
| channel | type | description |
|
||||
|-----------|--------|--------------|
|
||||
| output-1 | Switch | Output 1 |
|
||||
| output-2 | Switch | Output 2 |
|
||||
| output-3 | Switch | Output 3 |
|
||||
| output-4 | Switch | Output 4 |
|
||||
| output-5 | Switch | Output 5 |
|
||||
| output-6 | Switch | Output 6 |
|
||||
| output-7 | Switch | Output 7 |
|
||||
| output-8 | Switch | Output 8 |
|
||||
| output-9 | Switch | Output 9 |
|
||||
| output-10 | Switch | Output 10 |
|
||||
| output-11 | Switch | Output 11 |
|
||||
| output-12 | Switch | Output 12 |
|
||||
|
||||
#### dimmer-module
|
||||
|
||||
```
|
||||
Thing dimmer-module d1 [ address = "D969" ]
|
||||
```
|
||||
|
||||
Defines a `dimmer-module` with address `D969`.
|
||||
|
||||
| channel | type | description |
|
||||
|-----------|--------|--------------|
|
||||
| output-1 | Dimmer | Output 1 |
|
||||
| output-2 | Dimmer | Output 2 |
|
||||
| output-3 | Dimmer | Output 3 |
|
||||
| output-4 | Dimmer | Output 4 |
|
||||
| output-5 | Dimmer | Output 5 |
|
||||
| output-6 | Dimmer | Output 6 |
|
||||
| output-7 | Dimmer | Output 7 |
|
||||
| output-8 | Dimmer | Output 8 |
|
||||
| output-9 | Dimmer | Output 9 |
|
||||
| output-10 | Dimmer | Output 10 |
|
||||
| output-11 | Dimmer | Output 11 |
|
||||
| output-12 | Dimmer | Output 12 |
|
||||
|
||||
#### rollershutter-module
|
||||
|
||||
```
|
||||
Thing rollershutter-module r1 [ address = "4C6C" ]
|
||||
```
|
||||
|
||||
Defines a `rollershutter-module` with address `4C6C`.
|
||||
|
||||
| channel | type | description |
|
||||
|-----------|---------------|--------------|
|
||||
| output-1 | Rollershutter | Output 1 |
|
||||
| output-2 | Rollershutter | Output 2 |
|
||||
| output-3 | Rollershutter | Output 3 |
|
||||
| output-4 | Rollershutter | Output 4 |
|
||||
| output-5 | Rollershutter | Output 5 |
|
||||
| output-6 | Rollershutter | Output 6 |
|
||||
|
||||
### Buttons
|
||||
|
||||
Once an openHAB item has been configured as a Nikobus button, it will receive a status update to ON when the physical button is pressed.
|
||||
When an item receives the ON command from openHAB, it will send a simulated button press to the Nikobus.
|
||||
This means one could also define virtual buttons in openHAB with non-existing addresses (e.g., `000001`) and use those in the programming of Nikobus installation.
|
||||
|
||||
To configure an item for a button in openHAB with address `28092A`, use the following format:
|
||||
|
||||
```
|
||||
Thing push-button pb1 [ address = "28092A" ]
|
||||
```
|
||||
|
||||
Since all the channels in the entire channel group are switched to their new state, it is important that openHAB knows the current state of all the channels in that group.
|
||||
Otherwise a channel which was switched on by a button, may be switched off again by the command.
|
||||
|
||||
In order to keep an up to date state of the channels in openHAB, button configurations can be extended to include detail on which channel groups the button press affects.
|
||||
|
||||
When configured, the status of the channel groups to which the button is linked, will be queried every time the button is pressed.
|
||||
Every status query takes between ~300 ms, so to get the best performance, only add the affected channel groups in the configuration, which has the following format:
|
||||
|
||||
```
|
||||
Thing push-button <id> [ address = "<address>", impactedModules = "<moduleType>:<moduleId>:<channelGroup>, <moduleType>:<moduleId>:<channelGroup>, ..." ]
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
* `moduleType` represents module's type,
|
||||
* `moduleId` represents module's id,
|
||||
* `channelGroup` represents the first (1) or second (2) channel group in the module.
|
||||
|
||||
Example configurations may look like:
|
||||
|
||||
```
|
||||
Thing switch-module s1 [ address = "FF2A" ]
|
||||
Thing push-button pb1 [ address = "28092A", impactedModules = "switch-module:s1:1" ]
|
||||
```
|
||||
|
||||
In addition to the status requests triggered by button presses, there is also a scheduled status update interval defined by the `refreshInterval` parameter and explained above.
|
||||
|
||||
## Full Example
|
||||
|
||||
### nikobus.things
|
||||
|
||||
```
|
||||
Bridge nikobus:pc-link:mypclink [ port = "/dev/ttyUSB0", refreshInterval = 10 ] {
|
||||
Thing dimmer-module d1 [ address = "0700" ]
|
||||
Thing dimmer-module d2 [ address = "6B00" ]
|
||||
|
||||
Thing switch-module s1 [ address = "FF2A" ]
|
||||
Thing switch-module s2 [ address = "4C6C" ]
|
||||
Thing switch-module s3 [ address = "A063" ]
|
||||
|
||||
Thing rollershutter-module r1 [ address = "D769" ]
|
||||
|
||||
Thing push-button 92092A "S_2_1_2A" [ address = "92092A", impactedModules = "switch-module:s1:1" ]
|
||||
Thing push-button D2092A "S_2_1_2B" [ address = "D2092A", impactedModules = "switch-module:s1:1" ]
|
||||
Thing push-button 12092A "S_2_1_2C" [ address = "12092A", impactedModules = "dimmer-module:d1:1" ]
|
||||
Thing push-button 52092A "S_2_1_2D" [ address = "52092A", impactedModules = "dimmer-module:d1:1" ]
|
||||
|
||||
Thing push-button 1EE5F2 "S_2_3_A" [ address = "1EE5F2", impactedModules = "dimmer-module:d1:2" ]
|
||||
Thing push-button 5EE5F2 "S_2_3_B" [ address = "5EE5F2", impactedModules = "dimmer-module:d1:2" ]
|
||||
|
||||
Thing push-button 0C274A "S_2_4_A" [ address = "0C274A", impactedModules = "dimmer-module:d1:2" ]
|
||||
Thing push-button 4C274A "S_2_4_B" [ address = "4C274A", impactedModules = "dimmer-module:d1:2" ]
|
||||
|
||||
Thing push-button 1D1FF2 "S_2_5_A" [ address = "1D1FF2", impactedModules = "switch-module:s1:1" ]
|
||||
Thing push-button 5D1FF2 "S_2_5_B" [ address = "5D1FF2", impactedModules = "switch-module:s1:1" ]
|
||||
}
|
||||
```
|
||||
|
||||
### nikobus.items
|
||||
|
||||
```
|
||||
Dimmer Light_FF_Gallery_Ceiling "Ceiling" (FF_Gallery, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-1" }
|
||||
Dimmer Light_FF_Bed_Ceiling "Ceiling" (FF_Bed, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-7" }
|
||||
Dimmer Light_FF_Child_Ceiling "Ceiling" (FF_Child, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d2:output-10" }
|
||||
Dimmer Light_FF_Child_Wall_Left "Wall Left" (FF_Child, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-11" }
|
||||
Dimmer Light_FF_Child_Wall_Right "Wall Right" (FF_Child, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-12" }
|
||||
Dimmer Light_FF_PlayRoom_Ceiling "Ceiling" (FF_PlayRoom, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-6" }
|
||||
Dimmer Light_FF_PlayRoom_Wall "Wall" (FF_PlayRoom, Lights) [ "Lighting" ] { channel="nikobus:dimmer-module:mypclink:d1:output-4" }
|
||||
|
||||
Switch Light_FF_Gallery_Wall "Wall" (FF_Gallery, Lights) [ "Lighting" ] { channel="nikobus:switch-module:mypclink:s1:output-4" }
|
||||
Switch Light_FF_Bath_Ceiling "Ceiling" (FF_Bath, Lights) [ "Lighting" ] { channel="nikobus:switch-module:mypclink:s3:output-2" }
|
||||
Switch Light_FF_Wardrobe_Ceiling "Ceiling" (FF_Wardrobe, Lights) [ "Lighting" ] { channel="nikobus:switch-module:mypclink:s1:output-1" }
|
||||
Switch Light_FF_Corridor_Ceiling "Ceiling" (FF_Corridor, Lights) [ "Lighting" ] { channel="nikobus:switch-module:mypclink:s2:output-3" }
|
||||
|
||||
Rollershutter Shutter_GF_Corridor "Corridor" (GF_Corridor, gShuttersGF) { channel="nikobus:rollershutter-module:mypclink:r1:output-1" }
|
||||
Rollershutter Shutter_GF_Bed "Bedroom" (GF_Bed, gShuttersGF) { channel="nikobus:rollershutter-module:mypclink:r1:output-3" }
|
||||
Rollershutter Shutter_GF_Bath "Bathroom" (GF_Bath, gShuttersGF) { channel="nikobus:rollershutter-module:mypclink:r1:output-2" }
|
||||
Rollershutter Shutter_FF_Child "Child's room" (FF_Child, gShuttersFF) { channel="nikobus:rollershutter-module:mypclink:r1:output-4" }
|
||||
Rollershutter Shutter_FF_Gallery "Gallery" (FF_Gallery, gShuttersFF) { channel="nikobus:rollershutter-module:mypclink:r1:output-5" }
|
||||
```
|
||||
17
bundles/org.openhab.binding.nikobus/pom.xml
Normal file
17
bundles/org.openhab.binding.nikobus/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.nikobus</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Nikobus Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.nikobus-${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-nikobus" description="Nikobus 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.nikobus/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.nikobus.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NikobusBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "nikobus";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_TYPE_PCLINK = new ThingTypeUID(BINDING_ID, "pc-link");
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_PUSH_BUTTON = new ThingTypeUID(BINDING_ID, "push-button");
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH_MODULE = new ThingTypeUID(BINDING_ID, "switch-module");
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER_MODULE = new ThingTypeUID(BINDING_ID, "dimmer-module");
|
||||
public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER_MODULE = new ThingTypeUID(BINDING_ID,
|
||||
"rollershutter-module");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_BUTTON = "button";
|
||||
public static final String CHANNEL_OUTPUT_PREFIX = "output-";
|
||||
|
||||
// Configuration parameters
|
||||
public static final String CONFIG_REFRESH_INTERVAL = "refreshInterval";
|
||||
public static final String CONFIG_IMPACTED_MODULES = "impactedModules";
|
||||
public static final String CONFIG_ADDRESS = "address";
|
||||
public static final String CONFIG_PORT_NAME = "port";
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.nikobus.internal;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
|
||||
|
||||
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.nikobus.internal.handler.NikobusDimmerModuleHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusPcLinkHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusPushButtonHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusRollershutterModuleHandler;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusSwitchModuleHandler;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link NikobusHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.nikobus", service = ThingHandlerFactory.class)
|
||||
public class NikobusHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(BRIDGE_TYPE_PCLINK, THING_TYPE_PUSH_BUTTON, THING_TYPE_SWITCH_MODULE,
|
||||
THING_TYPE_DIMMER_MODULE, THING_TYPE_ROLLERSHUTTER_MODULE).collect(Collectors.toSet()));
|
||||
|
||||
private @NonNullByDefault({}) SerialPortManager serialPortManager;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (BRIDGE_TYPE_PCLINK.equals(thingTypeUID)) {
|
||||
return new NikobusPcLinkHandler((Bridge) thing, serialPortManager);
|
||||
}
|
||||
|
||||
if (THING_TYPE_PUSH_BUTTON.equals(thingTypeUID)) {
|
||||
return new NikobusPushButtonHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_SWITCH_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusSwitchModuleHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_DIMMER_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusDimmerModuleHandler(thing);
|
||||
}
|
||||
|
||||
if (THING_TYPE_ROLLERSHUTTER_MODULE.equals(thingTypeUID)) {
|
||||
return new NikobusRollershutterModuleHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.NikobusBindingConstants;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link NikobusBaseThingHandler} class defines utility logic to be consumed by Nikobus thing(s).
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class NikobusBaseThingHandler extends BaseThingHandler {
|
||||
private @Nullable String address;
|
||||
|
||||
protected NikobusBaseThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
address = (String) getConfig().get(NikobusBindingConstants.CONFIG_ADDRESS);
|
||||
if (address == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Address must be set!");
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
protected @Nullable NikobusPcLinkHandler getPcLink() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
return (NikobusPcLinkHandler) bridge.getHandler();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getAddress() {
|
||||
String address = this.address;
|
||||
if (address == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.handler;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusDimmerModuleHandler} is responsible for communication between Nikobus dim-controller and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusDimmerModuleHandler extends NikobusSwitchModuleHandler {
|
||||
private @Nullable Future<?> requestUpdateFuture;
|
||||
|
||||
public NikobusDimmerModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestStatus(SwitchModuleGroup group) {
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
super.requestStatus(group);
|
||||
requestUpdateFuture = scheduler.schedule(() -> super.requestStatus(group), 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
return Math.round(((PercentType) command).floatValue() / 100f * 255f);
|
||||
}
|
||||
|
||||
return super.valueFromCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
int result = Math.round(value * 100f / 255f);
|
||||
return new PercentType(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CHANNEL_OUTPUT_PREFIX;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleCommandFactory;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusSwitchModuleHandler} is responsible for communication between Nikobus modules and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class NikobusModuleHandler extends NikobusBaseThingHandler {
|
||||
private final EnumSet<SwitchModuleGroup> pendingRefresh = EnumSet.noneOf(SwitchModuleGroup.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusModuleHandler.class);
|
||||
private final Map<String, Integer> cachedStates = new HashMap<>();
|
||||
private final List<ChannelUID> linkedChannels = new ArrayList<>();
|
||||
|
||||
protected NikobusModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
synchronized (cachedStates) {
|
||||
cachedStates.clear();
|
||||
}
|
||||
|
||||
synchronized (pendingRefresh) {
|
||||
pendingRefresh.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
refreshChannel(channelUID);
|
||||
} else {
|
||||
processWrite(channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshChannel(ChannelUID channelUID) {
|
||||
logger.debug("Refreshing channel '{}'", channelUID.getId());
|
||||
|
||||
if (!isLinked(channelUID)) {
|
||||
logger.debug("Refreshing channel '{}' skipped since it is not linked", channelUID.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
updateGroup(SwitchModuleGroup.mapFromChannel(channelUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
synchronized (linkedChannels) {
|
||||
linkedChannels.add(channelUID);
|
||||
}
|
||||
super.channelLinked(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnlinked(ChannelUID channelUID) {
|
||||
synchronized (linkedChannels) {
|
||||
linkedChannels.remove(channelUID);
|
||||
}
|
||||
super.channelUnlinked(channelUID);
|
||||
}
|
||||
|
||||
public void refreshModule() {
|
||||
Set<SwitchModuleGroup> groups = new HashSet<>();
|
||||
synchronized (linkedChannels) {
|
||||
for (ChannelUID channelUID : linkedChannels) {
|
||||
groups.add(SwitchModuleGroup.mapFromChannel(channelUID));
|
||||
}
|
||||
}
|
||||
|
||||
if (groups.isEmpty()) {
|
||||
logger.debug("Nothing to refresh for '{}'", thing.getUID());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Refreshing {} - {}", thing.getUID(), groups);
|
||||
|
||||
for (SwitchModuleGroup group : groups) {
|
||||
updateGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestStatus(SwitchModuleGroup group) {
|
||||
updateGroup(group);
|
||||
}
|
||||
|
||||
private void updateGroup(SwitchModuleGroup group) {
|
||||
synchronized (pendingRefresh) {
|
||||
if (pendingRefresh.contains(group)) {
|
||||
logger.debug("Refresh already scheduled for group {} of module '{}'", group, getAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
pendingRefresh.add(group);
|
||||
}
|
||||
|
||||
logger.debug("Refreshing group {} of switch module '{}'", group, getAddress());
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
NikobusCommand command = SwitchModuleCommandFactory.createReadCommand(getAddress(), group,
|
||||
result -> processStatusUpdate(result, group));
|
||||
pcLink.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
private void processStatusUpdate(NikobusCommand.Result result, SwitchModuleGroup group) {
|
||||
try {
|
||||
String responsePayload = result.get();
|
||||
|
||||
logger.debug("processStatusUpdate '{}' for group {} in module '{}'", responsePayload, group, getAddress());
|
||||
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
// Update channel's statuses based on response.
|
||||
for (int i = 0; i < group.getCount(); i++) {
|
||||
String channelId = CHANNEL_OUTPUT_PREFIX + (i + group.getOffset());
|
||||
String responseDigits = responsePayload.substring(9 + (i * 2), 11 + (i * 2));
|
||||
|
||||
int value = Integer.parseInt(responseDigits, 16);
|
||||
|
||||
updateStateAndCacheValue(channelId, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Processing response for '{}'-{} failed with {}", getAddress(), group, e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
synchronized (pendingRefresh) {
|
||||
pendingRefresh.remove(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStateAndCacheValue(String channelId, int value) {
|
||||
if (value < 0x00 || value > 0xff) {
|
||||
throw new IllegalArgumentException("Invalid range. 0x00 - 0xff expected but got value " + value);
|
||||
}
|
||||
|
||||
logger.debug("setting channel '{}' to {}", channelId, value);
|
||||
|
||||
synchronized (cachedStates) {
|
||||
cachedStates.put(channelId, value);
|
||||
}
|
||||
|
||||
updateState(channelId, stateFromValue(value));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processWrite(ChannelUID channelUID, Command command) {
|
||||
StringBuilder commandPayload = new StringBuilder();
|
||||
SwitchModuleGroup group = SwitchModuleGroup.mapFromChannel(channelUID);
|
||||
|
||||
for (int i = group.getOffset(); i < group.getOffset() + group.getCount(); i++) {
|
||||
String channelId = CHANNEL_OUTPUT_PREFIX + i;
|
||||
Integer digits;
|
||||
|
||||
if (channelId.equals(channelUID.getId())) {
|
||||
digits = valueFromCommand(command);
|
||||
updateStateAndCacheValue(channelId, digits.intValue());
|
||||
} else {
|
||||
synchronized (cachedStates) {
|
||||
digits = cachedStates.get(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
if (digits == null) {
|
||||
commandPayload.append("00");
|
||||
logger.warn("no cached value found for '{}' in module '{}'", channelId, getAddress());
|
||||
} else {
|
||||
commandPayload.append(String.format("%02X", digits.intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.sendCommand(SwitchModuleCommandFactory.createWriteCommand(getAddress(), group,
|
||||
commandPayload.toString(), this::processWriteCommandResponse));
|
||||
}
|
||||
}
|
||||
|
||||
private void processWriteCommandResponse(NikobusCommand.Result result) {
|
||||
try {
|
||||
String responsePayload = result.get();
|
||||
|
||||
logger.debug("processWriteCommandResponse '{}'", responsePayload);
|
||||
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Processing write confirmation failed with {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int valueFromCommand(Command command);
|
||||
|
||||
protected abstract State stateFromValue(int value);
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CONFIG_REFRESH_INTERVAL;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
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.nikobus.internal.NikobusBindingConstants;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusConnection;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
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.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusPcLinkHandler} is responsible for handling commands, which are
|
||||
* sent or received from the PC-Link Nikobus component.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusPcLinkHandler.class);
|
||||
private final Map<String, Runnable> commandListeners = Collections.synchronizedMap(new HashMap<>());
|
||||
private final LinkedList<NikobusCommand> pendingCommands = new LinkedList<>();
|
||||
private final StringBuilder stringBuilder = new StringBuilder();
|
||||
private final SerialPortManager serialPortManager;
|
||||
private @Nullable NikobusConnection connection;
|
||||
private @Nullable NikobusCommand currentCommand;
|
||||
private @Nullable ScheduledFuture<?> scheduledRefreshFuture;
|
||||
private @Nullable ScheduledFuture<?> scheduledSendCommandWatchdogFuture;
|
||||
private @Nullable String ack;
|
||||
private int refreshThingIndex = 0;
|
||||
|
||||
public NikobusPcLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
ack = null;
|
||||
stringBuilder.setLength(0);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
String portName = (String) getConfig().get(NikobusBindingConstants.CONFIG_PORT_NAME);
|
||||
if (portName == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
|
||||
return;
|
||||
}
|
||||
|
||||
connection = new NikobusConnection(serialPortManager, portName, this::processReceivedValue);
|
||||
|
||||
int refreshInterval = ((Number) getConfig().get(CONFIG_REFRESH_INTERVAL)).intValue();
|
||||
scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::refresh, refreshInterval, refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
Utils.cancel(scheduledSendCommandWatchdogFuture);
|
||||
scheduledSendCommandWatchdogFuture = null;
|
||||
|
||||
Utils.cancel(scheduledRefreshFuture);
|
||||
scheduledRefreshFuture = null;
|
||||
|
||||
NikobusConnection connection = this.connection;
|
||||
this.connection = null;
|
||||
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Noop.
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private void processReceivedValue(byte value) {
|
||||
logger.trace("Received {}", value);
|
||||
|
||||
if (value == 13) {
|
||||
String command = stringBuilder.toString();
|
||||
stringBuilder.setLength(0);
|
||||
|
||||
logger.debug("Received command '{}', ack = '{}'", command, ack);
|
||||
|
||||
try {
|
||||
if (command.startsWith("$")) {
|
||||
String ack = this.ack;
|
||||
this.ack = null;
|
||||
|
||||
processResponse(command, ack);
|
||||
} else {
|
||||
Runnable listener = commandListeners.get(command);
|
||||
if (listener != null) {
|
||||
listener.run();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.debug("Processing command '{}' failed due {}", command, e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append((char) value);
|
||||
|
||||
// Take ACK part, i.e. "$0512"
|
||||
if (stringBuilder.length() == 5) {
|
||||
String payload = stringBuilder.toString();
|
||||
if (payload.startsWith("$05")) {
|
||||
ack = payload;
|
||||
logger.debug("Received ack '{}'", ack);
|
||||
stringBuilder.setLength(0);
|
||||
}
|
||||
} else if (stringBuilder.length() > 128) {
|
||||
// Fuse, if for some reason we don't receive \r don't fill buffer.
|
||||
stringBuilder.setLength(0);
|
||||
logger.warn("Resetting read buffer, should not happen, am I connected to Nikobus?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void addListener(String command, Runnable listener) {
|
||||
if (commandListeners.put(command, listener) != null) {
|
||||
logger.warn("Multiple registrations for '{}'", command);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(String command) {
|
||||
commandListeners.remove(command);
|
||||
}
|
||||
|
||||
private void processResponse(String commandPayload, @Nullable String ack) {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
command = currentCommand;
|
||||
}
|
||||
|
||||
if (command == null) {
|
||||
logger.debug("Processing response but no command pending");
|
||||
return;
|
||||
}
|
||||
|
||||
NikobusCommand.ResponseHandler responseHandler = command.getResponseHandler();
|
||||
if (responseHandler == null) {
|
||||
logger.debug("No response expected for current command");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ack == null) {
|
||||
logger.debug("No ack received");
|
||||
return;
|
||||
}
|
||||
|
||||
String requestCommandId = command.getPayload().substring(3, 5);
|
||||
String ackCommandId = ack.substring(3, 5);
|
||||
if (!ackCommandId.equals(requestCommandId)) {
|
||||
logger.debug("Unexpected command's ack '{}' != '{}'", requestCommandId, ackCommandId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if response has expected length.
|
||||
if (commandPayload.length() != responseHandler.getResponseLength()) {
|
||||
logger.debug("Unexpected response length");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commandPayload.startsWith(responseHandler.getResponseCode())) {
|
||||
logger.debug("Unexpected response command code");
|
||||
return;
|
||||
}
|
||||
|
||||
String requestCommandAddress = command.getPayload().substring(5, 9);
|
||||
String ackCommandAddress = commandPayload.substring(responseHandler.getAddressStart(),
|
||||
responseHandler.getAddressStart() + 4);
|
||||
if (!requestCommandAddress.equals(ackCommandAddress)) {
|
||||
logger.debug("Unexpected response address");
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseHandler.complete(commandPayload)) {
|
||||
resetProcessingAndProcessNext();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(NikobusCommand command) {
|
||||
synchronized (pendingCommands) {
|
||||
pendingCommands.addLast(command);
|
||||
}
|
||||
|
||||
scheduler.submit(this::processCommand);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processCommand() {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
if (currentCommand != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
command = pendingCommands.pollFirst();
|
||||
if (command == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentCommand = command;
|
||||
}
|
||||
sendCommand(command, 3);
|
||||
}
|
||||
|
||||
private void sendCommand(NikobusCommand command, int retry) {
|
||||
logger.debug("Sending retry = {}, command '{}'", retry, command.getPayload());
|
||||
|
||||
NikobusConnection connection = this.connection;
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
connectIfNeeded(connection);
|
||||
|
||||
OutputStream outputStream = connection.getOutputStream();
|
||||
if (outputStream == null) {
|
||||
return;
|
||||
}
|
||||
outputStream.write(command.getPayload().getBytes());
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Sending command failed due {}", e.getMessage(), e);
|
||||
connection.close();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
NikobusCommand.ResponseHandler responseHandler = command.getResponseHandler();
|
||||
if (responseHandler == null) {
|
||||
resetProcessingAndProcessNext();
|
||||
} else if (retry > 0) {
|
||||
scheduleSendCommandTimeout(() -> {
|
||||
if (!responseHandler.isCompleted()) {
|
||||
sendCommand(command, retry - 1);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scheduleSendCommandTimeout(() -> processTimeout(responseHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleSendCommandTimeout(Runnable command) {
|
||||
scheduledSendCommandWatchdogFuture = scheduler.schedule(command, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void processTimeout(NikobusCommand.ResponseHandler responseHandler) {
|
||||
if (responseHandler.completeExceptionally(new TimeoutException("Waiting for response timed-out."))) {
|
||||
resetProcessingAndProcessNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetProcessingAndProcessNext() {
|
||||
Utils.cancel(scheduledSendCommandWatchdogFuture);
|
||||
synchronized (pendingCommands) {
|
||||
currentCommand = null;
|
||||
}
|
||||
scheduler.submit(this::processCommand);
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
List<Thing> things = getThing().getThings().stream()
|
||||
.filter(thing -> thing.getHandler() instanceof NikobusModuleHandler).collect(Collectors.toList());
|
||||
|
||||
if (things.isEmpty()) {
|
||||
logger.debug("Nothing to refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
refreshThingIndex = (refreshThingIndex + 1) % things.size();
|
||||
|
||||
ThingHandler thingHandler = things.get(refreshThingIndex).getHandler();
|
||||
if (thingHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
NikobusModuleHandler handler = (NikobusModuleHandler) thingHandler;
|
||||
handler.refreshModule();
|
||||
}
|
||||
|
||||
private synchronized void connectIfNeeded(NikobusConnection connection) throws IOException {
|
||||
if (!connection.isConnected()) {
|
||||
connection.connect();
|
||||
|
||||
// Send connection sequence, mimicking the Nikobus software. If this is not send, PC-Link
|
||||
// sometimes does not forward button presses via serial interface.
|
||||
Stream.of(new String[] { "++++", "ATH0", "ATZ", "$10110000B8CF9D", "#L0", "#E0", "#L0", "#E1" })
|
||||
.map(NikobusCommand::new).forEach(this::sendCommand);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
|
||||
import static org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.common.AbstractUID;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusPushButtonHandler} is responsible for handling Nikobus push buttons.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusPushButtonHandler extends NikobusBaseThingHandler {
|
||||
private static class ImpactedModule {
|
||||
private final ThingUID thingUID;
|
||||
private final SwitchModuleGroup group;
|
||||
|
||||
ImpactedModule(ThingUID thingUID, SwitchModuleGroup group) {
|
||||
this.thingUID = thingUID;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public ThingUID getThingUID() {
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
public SwitchModuleGroup getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "'" + thingUID + "'-" + group;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImpactedModuleUID extends AbstractUID {
|
||||
ImpactedModuleUID(String uid) {
|
||||
super(uid);
|
||||
}
|
||||
|
||||
String getThingTypeId() {
|
||||
return getSegment(0);
|
||||
}
|
||||
|
||||
String getThingId() {
|
||||
return getSegment(1);
|
||||
}
|
||||
|
||||
SwitchModuleGroup getGroup() {
|
||||
if (getSegment(2).equals("1")) {
|
||||
return FIRST;
|
||||
}
|
||||
if (getSegment(2).equals("2")) {
|
||||
return SECOND;
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected group found " + getSegment(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMinimalNumberOfSegments() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String END_OF_TRANSMISSION = "\r#E1";
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusPushButtonHandler.class);
|
||||
private final List<ImpactedModule> impactedModules = Collections.synchronizedList(new ArrayList<>());
|
||||
private @Nullable Future<?> requestUpdateFuture;
|
||||
|
||||
public NikobusPushButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
if (thing.getStatus() == ThingStatus.OFFLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
impactedModules.clear();
|
||||
|
||||
try {
|
||||
ThingUID bridgeUID = thing.getBridgeUID();
|
||||
if (bridgeUID == null) {
|
||||
throw new IllegalArgumentException("Bridge does not exist!");
|
||||
}
|
||||
|
||||
String[] impactedModulesString = getConfig().get(CONFIG_IMPACTED_MODULES).toString().split(",");
|
||||
for (String impactedModuleString : impactedModulesString) {
|
||||
ImpactedModuleUID impactedModuleUID = new ImpactedModuleUID(impactedModuleString.trim());
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(bridgeUID.getBindingId(),
|
||||
impactedModuleUID.getThingTypeId());
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, impactedModuleUID.getThingId());
|
||||
impactedModules.add(new ImpactedModule(thingUID, impactedModuleUID.getGroup()));
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Impacted modules for {} = {}", thing.getUID(), impactedModules);
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.addListener(getAddress(), this::commandReceived);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = null;
|
||||
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.removeListener(getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("handleCommand '{}' '{}'", channelUID, command);
|
||||
|
||||
if (!CHANNEL_BUTTON.equals(channelUID.getId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Whenever the button receives an ON command,
|
||||
// we send a simulated button press to the Nikobus.
|
||||
if (command == OnOffType.ON) {
|
||||
NikobusPcLinkHandler pcLink = getPcLink();
|
||||
if (pcLink != null) {
|
||||
pcLink.sendCommand(new NikobusCommand(getAddress() + END_OF_TRANSMISSION));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commandReceived() {
|
||||
if (thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
updateState(CHANNEL_BUTTON, OnOffType.ON);
|
||||
|
||||
Utils.cancel(requestUpdateFuture);
|
||||
requestUpdateFuture = scheduler.schedule(this::update, 400, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
for (ImpactedModule module : impactedModules) {
|
||||
NikobusModuleHandler switchModule = getModuleWithId(module.getThingUID());
|
||||
if (switchModule != null) {
|
||||
switchModule.requestStatus(module.getGroup());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable NikobusModuleHandler getModuleWithId(ThingUID thingUID) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Thing thing = bridge.getThing(thingUID);
|
||||
if (thing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingHandler thingHandler = thing.getHandler();
|
||||
if (thingHandler instanceof NikobusModuleHandler) {
|
||||
return (NikobusModuleHandler) thingHandler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAddress() {
|
||||
return "#N" + super.getAddress();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
|
||||
* rollershutter-controller and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
|
||||
public NikobusRollershutterModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
|
||||
return 0x02;
|
||||
}
|
||||
if (command == UpDownType.UP) {
|
||||
return 0x01;
|
||||
}
|
||||
if (command == StopMoveType.STOP) {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command '" + command + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
if (value == 0x00) {
|
||||
return OnOffType.OFF;
|
||||
}
|
||||
if (value == 0x01) {
|
||||
return UpDownType.UP;
|
||||
}
|
||||
if (value == 0x02) {
|
||||
return UpDownType.DOWN;
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected value " + value + " received");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.nikobus.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link NikobusSwitchModuleHandler} is responsible for communication between Nikobus switch module and binding.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusSwitchModuleHandler extends NikobusModuleHandler {
|
||||
public NikobusSwitchModuleHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int valueFromCommand(Command command) {
|
||||
if (command == OnOffType.ON) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if (command == OnOffType.OFF) {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command '" + command + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State stateFromValue(int value) {
|
||||
return value != 0 ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.nikobus.internal.protocol;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusCommand} class holds a command that can be send to Nikobus installation.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusCommand {
|
||||
public static class Result {
|
||||
private final Callable<String> callable;
|
||||
|
||||
private Result(String result) {
|
||||
callable = () -> result;
|
||||
}
|
||||
|
||||
private Result(Exception exception) {
|
||||
callable = () -> {
|
||||
throw exception;
|
||||
};
|
||||
}
|
||||
|
||||
public String get() throws Exception {
|
||||
return callable.call();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResponseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ResponseHandler.class);
|
||||
private final Consumer<Result> resultConsumer;
|
||||
private final int responseLength;
|
||||
private final int addressStart;
|
||||
private final String responseCode;
|
||||
private final AtomicBoolean isCompleted = new AtomicBoolean();
|
||||
|
||||
private ResponseHandler(int responseLength, int addressStart, String responseCode,
|
||||
Consumer<Result> resultConsumer) {
|
||||
this.responseLength = responseLength;
|
||||
this.addressStart = addressStart;
|
||||
this.responseCode = responseCode;
|
||||
this.resultConsumer = resultConsumer;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return isCompleted.get();
|
||||
}
|
||||
|
||||
public boolean complete(String result) {
|
||||
return complete(new Result(result));
|
||||
}
|
||||
|
||||
public boolean completeExceptionally(Exception exception) {
|
||||
return complete(new Result(exception));
|
||||
}
|
||||
|
||||
private boolean complete(Result result) {
|
||||
if (isCompleted.getAndSet(true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
resultConsumer.accept(result);
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Processing result {} failed with {}", result, e.getMessage(), e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getResponseLength() {
|
||||
return responseLength;
|
||||
}
|
||||
|
||||
public int getAddressStart() {
|
||||
return addressStart;
|
||||
}
|
||||
|
||||
public String getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
private final String payload;
|
||||
private final @Nullable ResponseHandler responseHandler;
|
||||
|
||||
public NikobusCommand(String payload) {
|
||||
this.payload = payload + '\r';
|
||||
this.responseHandler = null;
|
||||
}
|
||||
|
||||
public NikobusCommand(String payload, int responseLength, int addressStart, String responseCode,
|
||||
Consumer<Result> resultConsumer) {
|
||||
this.payload = payload + '\r';
|
||||
this.responseHandler = new ResponseHandler(responseLength, addressStart, responseCode, resultConsumer);
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public @Nullable ResponseHandler getResponseHandler() {
|
||||
return responseHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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.nikobus.internal.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusConnection } is responsible for creating connections to clients.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusConnection implements SerialPortEventListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusConnection.class);
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final String portName;
|
||||
private final Consumer<Byte> processData;
|
||||
private @Nullable SerialPort serialPort;
|
||||
|
||||
public NikobusConnection(SerialPortManager serialPortManager, String portName, Consumer<Byte> processData) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.portName = portName;
|
||||
this.processData = processData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this manager is connected.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return serialPort != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the receiver.
|
||||
*
|
||||
**/
|
||||
public void connect() throws IOException {
|
||||
if (isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerialPortIdentifier portId = serialPortManager.getIdentifier(portName);
|
||||
if (portId == null) {
|
||||
throw new IOException(String.format("Port '%s' is not known!", portName));
|
||||
}
|
||||
|
||||
logger.info("Connecting to {}", portName);
|
||||
|
||||
try {
|
||||
SerialPort serialPort = portId.open("org.openhab.binding.nikobus.pc-link", 2000);
|
||||
serialPort.addEventListener(this);
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
this.serialPort = serialPort;
|
||||
logger.info("Connected to {}", portName);
|
||||
} catch (PortInUseException e) {
|
||||
throw new IOException(String.format("Port '%s' is in use!", portName), e);
|
||||
} catch (TooManyListenersException e) {
|
||||
throw new IOException(String.format("Cannot attach listener to port '%s'!", portName), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
**/
|
||||
public void close() {
|
||||
SerialPort serialPort = this.serialPort;
|
||||
this.serialPort = null;
|
||||
|
||||
if (serialPort != null) {
|
||||
try {
|
||||
serialPort.removeEventListener();
|
||||
OutputStream outputStream = serialPort.getOutputStream();
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing serial port.", e);
|
||||
} finally {
|
||||
serialPort.close();
|
||||
logger.debug("Closed serial port.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an output stream for this connection.
|
||||
*/
|
||||
public @Nullable OutputStream getOutputStream() throws IOException {
|
||||
SerialPort serialPort = this.serialPort;
|
||||
if (serialPort == null) {
|
||||
return null;
|
||||
}
|
||||
return serialPort.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent event) {
|
||||
if (event.getEventType() != SerialPortEvent.DATA_AVAILABLE) {
|
||||
return;
|
||||
}
|
||||
SerialPort serialPort = this.serialPort;
|
||||
if (serialPort == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
InputStream inputStream = serialPort.getInputStream();
|
||||
if (inputStream == null) {
|
||||
return;
|
||||
}
|
||||
byte[] readBuffer = new byte[64];
|
||||
while (inputStream.available() > 0) {
|
||||
int length = inputStream.read(readBuffer);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
processData.accept(readBuffer[i]);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error reading from serial port: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.nikobus.internal.protocol;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand.Result;
|
||||
import org.openhab.binding.nikobus.internal.utils.CRCUtil;
|
||||
|
||||
/**
|
||||
* The {@link NikobusCommand} class defines factory functions to create commands that can be send to Nikobus
|
||||
* installation.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchModuleCommandFactory {
|
||||
public static NikobusCommand createReadCommand(String address, SwitchModuleGroup group,
|
||||
Consumer<Result> resultConsumer) {
|
||||
checkAddress(address);
|
||||
|
||||
String commandPayload = CRCUtil.appendCRC2("$10" + CRCUtil.appendCRC(group.getStatusRequest() + address));
|
||||
return new NikobusCommand(commandPayload, 27, 3, "$1C", resultConsumer);
|
||||
}
|
||||
|
||||
public static NikobusCommand createWriteCommand(String address, SwitchModuleGroup group, String value,
|
||||
Consumer<Result> resultConsumer) {
|
||||
checkAddress(address);
|
||||
if (value.length() != 12) {
|
||||
throw new IllegalArgumentException(String.format("Value must have 12 chars but got '%s'", value));
|
||||
}
|
||||
|
||||
String payload = group.getStatusUpdate() + address + value + "FF";
|
||||
return new NikobusCommand(CRCUtil.appendCRC2("$1E" + CRCUtil.appendCRC(payload)), 13, 5, "$0E", resultConsumer);
|
||||
}
|
||||
|
||||
private static void checkAddress(String address) {
|
||||
if (address.length() != 4) {
|
||||
throw new IllegalArgumentException(String.format("Address must have 4 chars but got '%s'", address));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.protocol;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CHANNEL_OUTPUT_PREFIX;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
|
||||
/**
|
||||
* The {@link SwitchModuleGroup} class defines Nikobus module group used for reading status or set its new value.
|
||||
* Nikobus module can always operate only in groups and not per-channel.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum SwitchModuleGroup {
|
||||
|
||||
FIRST("12", "15", 1),
|
||||
SECOND("17", "16", 7);
|
||||
|
||||
private final String statusRequest;
|
||||
private final String statusUpdate;
|
||||
private final int offset;
|
||||
|
||||
private SwitchModuleGroup(String statusRequest, String statusUpdate, int offset) {
|
||||
this.statusRequest = statusRequest;
|
||||
this.statusUpdate = statusUpdate;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public String getStatusRequest() {
|
||||
return statusRequest;
|
||||
}
|
||||
|
||||
public String getStatusUpdate() {
|
||||
return statusUpdate;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
public static SwitchModuleGroup mapFromChannel(ChannelUID channelUID) {
|
||||
if (!channelUID.getIdWithoutGroup().startsWith(CHANNEL_OUTPUT_PREFIX)) {
|
||||
throw new IllegalArgumentException("Unexpected channel " + channelUID.getId());
|
||||
}
|
||||
|
||||
String channelNumber = channelUID.getIdWithoutGroup().substring(CHANNEL_OUTPUT_PREFIX.length());
|
||||
return mapFromChannel(Integer.parseInt(channelNumber));
|
||||
}
|
||||
|
||||
public static SwitchModuleGroup mapFromChannel(int channelNumber) {
|
||||
int max = SECOND.getOffset() + SECOND.getCount();
|
||||
if (channelNumber < FIRST.getOffset() || channelNumber > max) {
|
||||
throw new IllegalArgumentException(String.format("Channel number should be between [%d, %d], but got %d",
|
||||
FIRST.getOffset(), max, channelNumber));
|
||||
}
|
||||
return channelNumber >= SECOND.getOffset() ? SECOND : FIRST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nikobus.internal.utils;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
|
||||
/**
|
||||
* The {@link CRCUtil} class defines utility functions to calculate CRC used by the Nikobus communication protocol.
|
||||
*
|
||||
* @author Davy Vanherbergen - Initial contribution
|
||||
* @author Boris Krivonog - Removed dependency to javax.xml.bind.DatatypeConverter
|
||||
*/
|
||||
public class CRCUtil {
|
||||
|
||||
private static final int CRC_INIT = 0xFFFF;
|
||||
|
||||
private static final int POLYNOMIAL = 0x1021;
|
||||
|
||||
/**
|
||||
* Calculate the CRC16-CCITT checksum on the input string and return the
|
||||
* input string with the checksum appended.
|
||||
*
|
||||
* @param input
|
||||
* String representing hex numbers.
|
||||
* @return input string + CRC.
|
||||
*/
|
||||
public static String appendCRC(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int check = CRC_INIT;
|
||||
|
||||
for (byte b : HexUtils.hexToBytes(input)) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (((b >> (7 - i) & 1) == 1) ^ ((check >> 15 & 1) == 1)) {
|
||||
check = check << 1;
|
||||
check = check ^ POLYNOMIAL;
|
||||
} else {
|
||||
check = check << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check = check & CRC_INIT;
|
||||
String checksum = StringUtils.leftPad(Integer.toHexString(check), 4, "0");
|
||||
return (input + checksum).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the second checksum on the input string and return the
|
||||
* input string with the checksum appended.
|
||||
*
|
||||
* @param input
|
||||
* String representing a nikobus command.
|
||||
* @return input string + CRC.
|
||||
*/
|
||||
public static String appendCRC2(String input) {
|
||||
int check = 0;
|
||||
|
||||
for (byte b : input.getBytes()) {
|
||||
|
||||
check = check ^ b;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
||||
if (((check & 0xff) >> 7) != 0) {
|
||||
check = check << 1;
|
||||
check = check ^ 0x99;
|
||||
} else {
|
||||
check = check << 1;
|
||||
}
|
||||
check = check & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return input + StringUtils.leftPad(Integer.toHexString(check), 2, "0").toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.nikobus.internal.utils;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* The {@link Utils} class defines commonly used utility functions.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
public class Utils {
|
||||
public static void cancel(Future<?> future) {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="nikobus" 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>Nikobus Binding</name>
|
||||
<description>This is the binding for Nikobus.</description>
|
||||
<author>Boris Krivonog</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,228 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="nikobus"
|
||||
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-type id="pc-link">
|
||||
<label>PC-Link</label>
|
||||
<description>PC-Link via serial connection</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="port" type="text" required="true">
|
||||
<label>Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>The serial port used to connect to the Nikobus PC Link.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" max="65535" min="10" required="false">
|
||||
<default>60</default>
|
||||
<label>Refresh Interval</label>
|
||||
<description>Refresh interval in seconds.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="push-button">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Push Button</label>
|
||||
<description>A single push button</description>
|
||||
|
||||
<channels>
|
||||
<channel id="button" typeId="button"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
<parameter name="impactedModules" type="text">
|
||||
<label>Impacted Modules</label>
|
||||
<description>Comma separated list of impacted modules, i.e. 4C6C-1,4C6C-2</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Button Event</label>
|
||||
<description>Fires when the button is pressed</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<thing-type id="switch-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Switch Module</label>
|
||||
<description>Nikobus Switch module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="switch-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="switch-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="switch-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="switch-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="switch-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="switch-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
<channel id="output-7" typeId="switch-output">
|
||||
<label>Output 7</label>
|
||||
</channel>
|
||||
<channel id="output-8" typeId="switch-output">
|
||||
<label>Output 8</label>
|
||||
</channel>
|
||||
<channel id="output-9" typeId="switch-output">
|
||||
<label>Output 9</label>
|
||||
</channel>
|
||||
<channel id="output-10" typeId="switch-output">
|
||||
<label>Output 10</label>
|
||||
</channel>
|
||||
<channel id="output-11" typeId="switch-output">
|
||||
<label>Output 11</label>
|
||||
</channel>
|
||||
<channel id="output-12" typeId="switch-output">
|
||||
<label>Output 12</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="dimmer-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Dimmer Module</label>
|
||||
<description>Nikobus Dimmer module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="dimmer-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="dimmer-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="dimmer-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="dimmer-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="dimmer-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="dimmer-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
<channel id="output-7" typeId="dimmer-output">
|
||||
<label>Output 7</label>
|
||||
</channel>
|
||||
<channel id="output-8" typeId="dimmer-output">
|
||||
<label>Output 8</label>
|
||||
</channel>
|
||||
<channel id="output-9" typeId="dimmer-output">
|
||||
<label>Output 9</label>
|
||||
</channel>
|
||||
<channel id="output-10" typeId="dimmer-output">
|
||||
<label>Output 10</label>
|
||||
</channel>
|
||||
<channel id="output-11" typeId="dimmer-output">
|
||||
<label>Output 11</label>
|
||||
</channel>
|
||||
<channel id="output-12" typeId="dimmer-output">
|
||||
<label>Output 12</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="rollershutter-module">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="pc-link"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Rollershutter Module</label>
|
||||
<description>Nikobus Rollershutter module</description>
|
||||
|
||||
<channels>
|
||||
<channel id="output-1" typeId="rollershutter-output">
|
||||
<label>Output 1</label>
|
||||
</channel>
|
||||
<channel id="output-2" typeId="rollershutter-output">
|
||||
<label>Output 2</label>
|
||||
</channel>
|
||||
<channel id="output-3" typeId="rollershutter-output">
|
||||
<label>Output 3</label>
|
||||
</channel>
|
||||
<channel id="output-4" typeId="rollershutter-output">
|
||||
<label>Output 4</label>
|
||||
</channel>
|
||||
<channel id="output-5" typeId="rollershutter-output">
|
||||
<label>Output 5</label>
|
||||
</channel>
|
||||
<channel id="output-6" typeId="rollershutter-output">
|
||||
<label>Output 6</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text">
|
||||
<label>Address</label>
|
||||
<description>The Nikobus address of the module</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="switch-output">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Output</label>
|
||||
<description>Switch Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dimmer-output">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Output</label>
|
||||
<description>Dimmer Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rollershutter-output">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Output</label>
|
||||
<description>Rollershutter Module's Output</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user