[herzborg] Herzborg binding (#9327)
Supports Herzborg curtain motors over RS-485 network Signed-off-by: Pavel Fedin <pavel_fedin@mail.ru> Co-authored-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
parent
8ebd4e9047
commit
7a407523dc
|
@ -120,6 +120,7 @@
|
||||||
/bundles/org.openhab.binding.helios/ @kgoderis
|
/bundles/org.openhab.binding.helios/ @kgoderis
|
||||||
/bundles/org.openhab.binding.heliosventilation/ @ramack
|
/bundles/org.openhab.binding.heliosventilation/ @ramack
|
||||||
/bundles/org.openhab.binding.heos/ @Wire82
|
/bundles/org.openhab.binding.heos/ @Wire82
|
||||||
|
/bundles/org.openhab.binding.herzborg/ @Sonic-Amiga
|
||||||
/bundles/org.openhab.binding.homeconnect/ @bruestel
|
/bundles/org.openhab.binding.homeconnect/ @bruestel
|
||||||
/bundles/org.openhab.binding.homematic/ @FStolte @gerrieg @mdicke2s
|
/bundles/org.openhab.binding.homematic/ @FStolte @gerrieg @mdicke2s
|
||||||
/bundles/org.openhab.binding.homewizard/ @Daniel-42
|
/bundles/org.openhab.binding.homewizard/ @Daniel-42
|
||||||
|
|
|
@ -591,6 +591,11 @@
|
||||||
<artifactId>org.openhab.binding.heos</artifactId>
|
<artifactId>org.openhab.binding.heos</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.herzborg</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.homeconnect</artifactId>
|
<artifactId>org.openhab.binding.homeconnect</artifactId>
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Herzborg Binding
|
||||||
|
|
||||||
|
This binding supports smart curtain motors by Herzborg (http://www.herzborg.com/pro_list.aspx?TypeID=1)
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
- `herzborg` A bridge thing that connects to a RS485 serial bus.
|
||||||
|
- `curtain` A curtain motor thing that can be controlled via the `herzborg` bridge .
|
||||||
|
|
||||||
|
The binding was developed and tested using DT300TV-1.2/14 type motor; others are expected to be compatible
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Due to nature of serial bus being used, no automatic discovery is possible.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
### Serial Bus Bridge (id "serial_bus")
|
||||||
|
|
||||||
|
| Parameter | Meaning |
|
||||||
|
|-----------|---------------------------------------------------------|
|
||||||
|
| port | Serial port name to use |
|
||||||
|
|
||||||
|
Herzborg devices appear to use fixed 9600 8n1 communication parameters, so no other parameters are needed
|
||||||
|
|
||||||
|
### Curtain Motor Thing (id "curtain")
|
||||||
|
|
||||||
|
| Parameter | Meaning |
|
||||||
|
|---------------|---------------------------------------------------------|
|
||||||
|
| address | Address of the motor on the serial bus. |
|
||||||
|
| poll_interval | Polling interval in seconds |
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
| channel | type | description | Read-only |
|
||||||
|
|------------|---------------|-----------------------------------------------|-----------|
|
||||||
|
| position | RollerShutter | Controls position of the curtain. Position reported back is in percents; 0 - fully closed; 100 - fully open | N |
|
||||||
|
| mode | String | Reports current motor mode: | Y |
|
||||||
|
| | | 0 - Stop | |
|
||||||
|
| | | 1 - Open | |
|
||||||
|
| | | 2 - Close | |
|
||||||
|
| | | 3 - Setting | |
|
||||||
|
| reverse | Switch | Reverses direction when switched on | N |
|
||||||
|
| handStart | Switch | Enable / disable hand start function | N |
|
||||||
|
| extSwitch | String | External (low-voltage) switch mode: | N |
|
||||||
|
| | | 1 - dual channel biased switch | |
|
||||||
|
| | | 2 - dual channel rocker switch | |
|
||||||
|
| | | 3 - DC246 electronic switch | |
|
||||||
|
| | | 4 - single button cyclic switch | |
|
||||||
|
| hvSwitch | String | Main (high-voltage) switch mode: | N |
|
||||||
|
| | | 0 - dual channel biased switch | |
|
||||||
|
| | | 1 - hotel mode(power on while card in) | |
|
||||||
|
| | | 2 - dual channel rocker switch | |
|
||||||
|
|
||||||
|
All the channels are read-write
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
herzborg.things:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bridge herzborg:serial_bus:my_herzborg_bus [ port="/dev/ttyAMA1" ]
|
||||||
|
{
|
||||||
|
Thing herzborg:curtain:livingroom [ address=1234, poll_interval=1 ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
herzborg.items:
|
||||||
|
|
||||||
|
```
|
||||||
|
Rollershutter LivingRoom_Window {channel="herzborg:curtain:livingroom:position"}
|
||||||
|
```
|
||||||
|
|
||||||
|
herzborg.sitemap:
|
||||||
|
|
||||||
|
```
|
||||||
|
Frame label="Living room curtain"
|
||||||
|
{
|
||||||
|
Switch item=LivingRoom_Window label="Control" mappings=["DOWN"="Close", "STOP"="Stop", "UP"="Open"]
|
||||||
|
Slider item=LivingRoom_Window label="Position [%d %%]" minValue=0 maxValue=100
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.herzborg</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: Herzborg Binding</name>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.herzborg-${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-herzborg" description="Herzborg 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.herzborg/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
|
@ -0,0 +1,136 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Function;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Packet;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Bus} is a handy base class, implementing data communication with Herzborg devices.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Bus {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Bus.class);
|
||||||
|
|
||||||
|
protected @Nullable InputStream dataIn;
|
||||||
|
protected @Nullable OutputStream dataOut;
|
||||||
|
|
||||||
|
public static class Result {
|
||||||
|
ThingStatusDetail code;
|
||||||
|
@Nullable
|
||||||
|
String message;
|
||||||
|
|
||||||
|
Result(ThingStatusDetail code, String msg) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result(ThingStatusDetail code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bus() {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeClose(@Nullable Closeable stream) {
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Error closing I/O stream: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
safeClose(dataOut);
|
||||||
|
safeClose(dataIn);
|
||||||
|
|
||||||
|
dataOut = null;
|
||||||
|
dataIn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized @Nullable Packet doPacket(Packet pkt) throws IOException {
|
||||||
|
OutputStream dataOut = this.dataOut;
|
||||||
|
InputStream dataIn = this.dataIn;
|
||||||
|
|
||||||
|
if (dataOut == null || dataIn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int readLength = Packet.MIN_LENGTH;
|
||||||
|
|
||||||
|
switch (pkt.getFunction()) {
|
||||||
|
case Function.READ:
|
||||||
|
// The reply will include data itself
|
||||||
|
readLength += pkt.getDataLength();
|
||||||
|
break;
|
||||||
|
case Function.WRITE:
|
||||||
|
// The reply is number of bytes written
|
||||||
|
readLength += 1;
|
||||||
|
break;
|
||||||
|
case Function.CONTROL:
|
||||||
|
// The whole packet will be echoed back
|
||||||
|
readLength = pkt.getBuffer().length;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// We must not have anything else here
|
||||||
|
throw new IllegalStateException("Unknown function code");
|
||||||
|
}
|
||||||
|
|
||||||
|
dataOut.write(pkt.getBuffer());
|
||||||
|
|
||||||
|
int readOffset = 0;
|
||||||
|
byte[] replyBuffer = new byte[readLength];
|
||||||
|
|
||||||
|
while (readLength > 0) {
|
||||||
|
int n = dataIn.read(replyBuffer, readOffset, readLength);
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
throw new IOException("EOF from serial port");
|
||||||
|
} else if (n == 0) {
|
||||||
|
throw new IOException("Serial read timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
readOffset += n;
|
||||||
|
readLength -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Packet(replyBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
InputStream dataIn = this.dataIn;
|
||||||
|
|
||||||
|
if (dataIn != null) {
|
||||||
|
// Unfortunately Java streams can't be flushed. Just read and drop all the characters
|
||||||
|
while (dataIn.available() > 0) {
|
||||||
|
dataIn.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BusHandler} is a handy base class, implementing data communication with Herzborg devices.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class BusHandler extends BaseBridgeHandler {
|
||||||
|
protected Bus bus;
|
||||||
|
|
||||||
|
public BusHandler(Bridge bridge, Bus bus) {
|
||||||
|
super(bridge);
|
||||||
|
this.bus = bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bus getBus() {
|
||||||
|
return bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
// Nothing to do here, but we have to implement it
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CurtainConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CurtainConfiguration {
|
||||||
|
public int address;
|
||||||
|
public int pollInterval;
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.herzborg.internal.HerzborgBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.xml.bind.DatatypeConverter;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.ControlAddress;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.DataAddress;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Function;
|
||||||
|
import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Packet;
|
||||||
|
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.StringType;
|
||||||
|
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.BridgeHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CurtainHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CurtainHandler extends BaseThingHandler {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(CurtainHandler.class);
|
||||||
|
|
||||||
|
private CurtainConfiguration config = new CurtainConfiguration();
|
||||||
|
private @Nullable ScheduledFuture<?> pollFuture;
|
||||||
|
private @Nullable Bus bus;
|
||||||
|
|
||||||
|
public CurtainHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
String ch = channelUID.getId();
|
||||||
|
Packet pkt = null;
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case CHANNEL_POSITION:
|
||||||
|
if (command instanceof UpDownType) {
|
||||||
|
pkt = buildPacket(Function.CONTROL,
|
||||||
|
(command == UpDownType.UP) ? ControlAddress.OPEN : ControlAddress.CLOSE);
|
||||||
|
} else if (command instanceof StopMoveType) {
|
||||||
|
pkt = buildPacket(Function.CONTROL, ControlAddress.STOP);
|
||||||
|
} else if (command instanceof DecimalType) {
|
||||||
|
pkt = buildPacket(Function.CONTROL, ControlAddress.PERCENT, ((DecimalType) command).byteValue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_REVERSE:
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
pkt = buildPacket(Function.WRITE, DataAddress.DEFAULT_DIR, command.equals(OnOffType.ON) ? 1 : 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_HAND_START:
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
pkt = buildPacket(Function.WRITE, DataAddress.HAND_START, command.equals(OnOffType.ON) ? 0 : 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_EXT_SWITCH:
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
pkt = buildPacket(Function.WRITE, DataAddress.EXT_SWITCH, Byte.valueOf(command.toString()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_HV_SWITCH:
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
pkt = buildPacket(Function.WRITE, DataAddress.EXT_HV_SWITCH, Byte.valueOf(command.toString()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkt != null) {
|
||||||
|
final Packet p = pkt;
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
Packet reply = doPacket(p);
|
||||||
|
|
||||||
|
if (reply != null) {
|
||||||
|
logger.trace("Function {} addr {} reply {}", p.getFunction(), p.getDataAddress(),
|
||||||
|
DatatypeConverter.printHexBinary(reply.getBuffer()));
|
||||||
|
}
|
||||||
|
}, 0, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Packet buildPacket(byte function, byte data_addr) {
|
||||||
|
return new Packet((short) config.address, function, data_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Packet buildPacket(byte function, byte data_addr, byte value) {
|
||||||
|
return new Packet((short) config.address, function, data_addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Packet buildPacket(byte function, byte data_addr, int value) {
|
||||||
|
return buildPacket(function, data_addr, (byte) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
Bridge bridge = getBridge();
|
||||||
|
|
||||||
|
if (bridge == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge not present");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BridgeHandler handler = bridge.getHandler();
|
||||||
|
|
||||||
|
if (handler == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge has no handler");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus = ((BusHandler) handler).getBus();
|
||||||
|
config = getConfigAs(CurtainConfiguration.class);
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
logger.trace("Successfully initialized, starting poll");
|
||||||
|
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, config.pollInterval, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
stopPoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopPoll() {
|
||||||
|
ScheduledFuture<?> poll = pollFuture;
|
||||||
|
pollFuture = null;
|
||||||
|
|
||||||
|
if (poll != null) {
|
||||||
|
poll.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable synchronized Packet doPacket(Packet pkt) {
|
||||||
|
Bus bus = this.bus;
|
||||||
|
|
||||||
|
if (bus == null) {
|
||||||
|
// This is an impossible situation but Eclipse forces us to handle it
|
||||||
|
logger.warn("No Bridge sending commands");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Packet reply = bus.doPacket(pkt);
|
||||||
|
|
||||||
|
if (reply == null) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply.isValid()) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
return reply;
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Invalid response received: " + DatatypeConverter.printHexBinary(reply.getBuffer()));
|
||||||
|
bus.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void poll() {
|
||||||
|
Packet reply = doPacket(buildPacket(Function.READ, DataAddress.POSITION, 4));
|
||||||
|
|
||||||
|
if (reply != null) {
|
||||||
|
byte position = reply.getData(0);
|
||||||
|
byte reverse = reply.getData(1);
|
||||||
|
byte handStart = reply.getData(2);
|
||||||
|
byte mode = reply.getData(3);
|
||||||
|
|
||||||
|
// If calibration has been lost, position is reported as -1.
|
||||||
|
updateState(CHANNEL_POSITION,
|
||||||
|
(position > 100 || position < 0) ? UnDefType.UNDEF : new PercentType(position));
|
||||||
|
updateState(CHANNEL_REVERSE, reverse != 0 ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateState(CHANNEL_HAND_START, handStart == 0 ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
updateState(CHANNEL_MODE, new StringType(String.valueOf(mode)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet extReply = doPacket(buildPacket(Function.READ, DataAddress.EXT_SWITCH, 2));
|
||||||
|
|
||||||
|
if (extReply != null) {
|
||||||
|
byte extSwitch = extReply.getData(0);
|
||||||
|
byte hvSwitch = extReply.getData(1);
|
||||||
|
|
||||||
|
updateState(CHANNEL_EXT_SWITCH, new StringType(String.valueOf(extSwitch)));
|
||||||
|
updateState(CHANNEL_HV_SWITCH, new StringType(String.valueOf(hvSwitch)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HerzborgBindingConstants} class defines common constants, which are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HerzborgBindingConstants {
|
||||||
|
|
||||||
|
private static final String BINDING_ID = "herzborg";
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_SERIAL_BUS = new ThingTypeUID(BINDING_ID, "serialBus");
|
||||||
|
public static final ThingTypeUID THING_TYPE_CURTAIN = new ThingTypeUID(BINDING_ID, "curtain");
|
||||||
|
|
||||||
|
// List of all Channel ids
|
||||||
|
public static final String CHANNEL_POSITION = "position";
|
||||||
|
public static final String CHANNEL_MODE = "mode";
|
||||||
|
public static final String CHANNEL_REVERSE = "reverse";
|
||||||
|
public static final String CHANNEL_HAND_START = "handStart";
|
||||||
|
public static final String CHANNEL_EXT_SWITCH = "extSwitch";
|
||||||
|
public static final String CHANNEL_HV_SWITCH = "hvSwitch";
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.herzborg.internal.HerzborgBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
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.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HerzborgHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.herzborg", service = ThingHandlerFactory.class)
|
||||||
|
public class HerzborgHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SERIAL_BUS,
|
||||||
|
THING_TYPE_CURTAIN);
|
||||||
|
|
||||||
|
private final SerialPortManager serialPortManager;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public HerzborgHandlerFactory(final @Reference SerialPortManager serialPortManager) {
|
||||||
|
this.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 (THING_TYPE_CURTAIN.equals(thingTypeUID)) {
|
||||||
|
return new CurtainHandler(thing);
|
||||||
|
} else if (THING_TYPE_SERIAL_BUS.equals(thingTypeUID)) {
|
||||||
|
return new SerialBusHandler((Bridge) thing, serialPortManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
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.SerialPortIdentifier;
|
||||||
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
|
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SerialBus} implements specific handling for Herzborg serial bus,
|
||||||
|
* connected directly via a serial port.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SerialBus extends Bus {
|
||||||
|
private SerialPortManager serialPortManager;
|
||||||
|
private @Nullable SerialPort serialPort;
|
||||||
|
|
||||||
|
public SerialBus(SerialPortManager manager) {
|
||||||
|
serialPortManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result initialize(@Nullable String port) {
|
||||||
|
if (port == null) {
|
||||||
|
return new Result(ThingStatusDetail.CONFIGURATION_ERROR, "Port is not specified");
|
||||||
|
}
|
||||||
|
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(port);
|
||||||
|
if (portIdentifier == null) {
|
||||||
|
return new Result(ThingStatusDetail.CONFIGURATION_ERROR, "No such port: " + port);
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialPort commPort;
|
||||||
|
try {
|
||||||
|
commPort = portIdentifier.open(this.getClass().getName(), 2000);
|
||||||
|
} catch (PortInUseException e1) {
|
||||||
|
return new Result(ThingStatusDetail.CONFIGURATION_ERROR, "Port " + port + " is in use");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Herzborg serial bus operates with fixed parameters
|
||||||
|
commPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
|
||||||
|
commPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
|
||||||
|
} catch (UnsupportedCommOperationException e) {
|
||||||
|
return new Result(ThingStatusDetail.CONFIGURATION_ERROR, "Invalid port configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
commPort.enableReceiveThreshold(8);
|
||||||
|
commPort.enableReceiveTimeout(1000);
|
||||||
|
} catch (UnsupportedCommOperationException e) {
|
||||||
|
// OpenHAB's serial-over-IP doesn't support these, so let's ignore the exception
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream dataIn = null;
|
||||||
|
OutputStream dataOut = null;
|
||||||
|
String error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dataIn = commPort.getInputStream();
|
||||||
|
dataOut = commPort.getOutputStream();
|
||||||
|
|
||||||
|
if (dataIn == null) {
|
||||||
|
error = "No input stream available on the serial port";
|
||||||
|
} else if (dataOut == null) {
|
||||||
|
error = "No output stream available on the serial port";
|
||||||
|
} else {
|
||||||
|
dataOut.flush();
|
||||||
|
if (dataIn.markSupported()) {
|
||||||
|
dataIn.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
return new Result(ThingStatusDetail.HANDLER_INITIALIZING_ERROR, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serialPort = commPort;
|
||||||
|
this.dataIn = dataIn;
|
||||||
|
this.dataOut = dataOut;
|
||||||
|
|
||||||
|
return new Result(ThingStatusDetail.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
SerialPort port = serialPort;
|
||||||
|
|
||||||
|
if (port == null) {
|
||||||
|
return; // Nothing to do in this case
|
||||||
|
}
|
||||||
|
|
||||||
|
port.removeEventListener();
|
||||||
|
super.dispose();
|
||||||
|
port.close();
|
||||||
|
serialPort = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SerialBusConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SerialBusConfiguration {
|
||||||
|
public @Nullable String port;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SerialBusHandler} implements specific handling for Herzborg serial bus,
|
||||||
|
* connected directly via a serial port.
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SerialBusHandler extends BusHandler {
|
||||||
|
private SerialBusConfiguration config = new SerialBusConfiguration();
|
||||||
|
|
||||||
|
public SerialBusHandler(Bridge bridge, SerialPortManager portManager) {
|
||||||
|
super(bridge, new SerialBus(portManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(SerialBusConfiguration.class);
|
||||||
|
|
||||||
|
Bus.Result result = ((SerialBus) bus).initialize(config.port);
|
||||||
|
|
||||||
|
if (result.code == ThingStatusDetail.NONE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, result.code, result.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
bus.dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.herzborg.internal.dto;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Herzborg binary protocol
|
||||||
|
*
|
||||||
|
* @author Pavel Fedin - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HerzborgProtocol {
|
||||||
|
public static class Function {
|
||||||
|
public static final byte READ = 0x01;
|
||||||
|
public static final byte WRITE = 0x02;
|
||||||
|
public static final byte CONTROL = 0x03;
|
||||||
|
public static final byte REQUEST = 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ControlAddress {
|
||||||
|
public static final byte OPEN = 0x01;
|
||||||
|
public static final byte CLOSE = 0x02;
|
||||||
|
public static final byte STOP = 0x03;
|
||||||
|
public static final byte PERCENT = 0x04;
|
||||||
|
public static final byte DELETE_LIMIT = 0x07;
|
||||||
|
public static final byte DEFAULT = 0x08;
|
||||||
|
public static final byte SET_CONTEXT = 0x09;
|
||||||
|
public static final byte RUN_CONTEXT = 0x0A;
|
||||||
|
public static final byte DEL_CONTEXT = 0x0B;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DataAddress {
|
||||||
|
public static final byte ID_L = 0x00;
|
||||||
|
public static final byte ID_H = 0x01;
|
||||||
|
public static final byte POSITION = 0x02;
|
||||||
|
public static final byte DEFAULT_DIR = 0x03;
|
||||||
|
public static final byte HAND_START = 0x04;
|
||||||
|
public static final byte MODE = 0x05;
|
||||||
|
public static final byte EXT_SWITCH = 0x27;
|
||||||
|
public static final byte EXT_HV_SWITCH = 0x28;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Packet {
|
||||||
|
private static final int HEADER_LENGTH = 5;
|
||||||
|
private static final int CRC16_LENGTH = 2;
|
||||||
|
public static final int MIN_LENGTH = HEADER_LENGTH + CRC16_LENGTH;
|
||||||
|
|
||||||
|
private static final byte START = 0x55;
|
||||||
|
|
||||||
|
private ByteBuffer dataBuffer;
|
||||||
|
private int dataLength; // Packet length without CRC16
|
||||||
|
|
||||||
|
public Packet(byte[] data) {
|
||||||
|
dataBuffer = ByteBuffer.wrap(data);
|
||||||
|
dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
dataLength = data.length - CRC16_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHeader(short device_addr, byte function, byte data_addr, int data_length) {
|
||||||
|
dataLength = HEADER_LENGTH + data_length;
|
||||||
|
|
||||||
|
dataBuffer = ByteBuffer.allocate(dataLength + CRC16_LENGTH);
|
||||||
|
dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
dataBuffer.put(START);
|
||||||
|
dataBuffer.putShort(device_addr);
|
||||||
|
dataBuffer.put(function);
|
||||||
|
dataBuffer.put(data_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCrc16() {
|
||||||
|
dataBuffer.putShort(crc16(dataLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Packet(short device_addr, byte function, byte data_addr) {
|
||||||
|
setHeader(device_addr, function, data_addr, 0);
|
||||||
|
setCrc16();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Packet(short device_addr, byte function, byte data_addr, byte value) {
|
||||||
|
int dataLength = (function == Function.WRITE) ? 2 : 1;
|
||||||
|
|
||||||
|
setHeader(device_addr, function, data_addr, dataLength);
|
||||||
|
if (function == Function.WRITE) {
|
||||||
|
// WRITE command also requires length of data to be written
|
||||||
|
dataBuffer.put((byte) 1);
|
||||||
|
}
|
||||||
|
dataBuffer.put(value);
|
||||||
|
setCrc16();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBuffer() {
|
||||||
|
return dataBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return dataBuffer.get(0) == START && crc16(dataLength) == dataBuffer.getShort(dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getFunction() {
|
||||||
|
return dataBuffer.get(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getDataAddress() {
|
||||||
|
return dataBuffer.get(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getDataLength() {
|
||||||
|
return dataBuffer.get(HEADER_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getData(int offset) {
|
||||||
|
return dataBuffer.get(HEADER_LENGTH + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Herzborg uses modbus variant of CRC16
|
||||||
|
// Code adapted from https://habr.com/ru/post/418209/
|
||||||
|
private short crc16(int length) {
|
||||||
|
int crc = 0xFFFF;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
crc = crc ^ Byte.toUnsignedInt(dataBuffer.get(i));
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
int mask = ((crc & 0x1) != 0) ? 0xA001 : 0x0000;
|
||||||
|
crc = ((crc >> 1) & 0x7FFF) ^ mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (short) crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="herzborg" 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>Herzborg Binding</name>
|
||||||
|
<description>This is the binding for Herzborg smart curtain motors.</description>
|
||||||
|
|
||||||
|
</binding:binding>
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="herzborg"
|
||||||
|
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="serialBus">
|
||||||
|
<label>Herzborg Serial Bus</label>
|
||||||
|
<description>RS485 bus</description>
|
||||||
|
<config-description>
|
||||||
|
<parameter name="port" type="text" required="true">
|
||||||
|
<label>Serial Port</label>
|
||||||
|
<context>serial-port</context>
|
||||||
|
<description>Serial port to use, for example /dev/ttyS0 or COM1</description>
|
||||||
|
<default>/dev/ttyS0</default>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</bridge-type>
|
||||||
|
|
||||||
|
<thing-type id="curtain">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="serialBus"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
<label>Herzborg Curtain Motor</label>
|
||||||
|
<description>Curtain motor</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="position" typeId="position"/>
|
||||||
|
<channel id="mode" typeId="mode"/>
|
||||||
|
<channel id="reverse" typeId="reverse"/>
|
||||||
|
<channel id="handStart" typeId="handStart"/>
|
||||||
|
<channel id="extSwitch" typeId="extSwitch"/>
|
||||||
|
<channel id="hwSwitch" typeId="hwSwitch"/>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="address" type="integer">
|
||||||
|
<label>Address</label>
|
||||||
|
<description>Device address on the bus.</description>
|
||||||
|
<default>65278</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="pollInterval" type="integer" unit="s">
|
||||||
|
<label>Poll Interval</label>
|
||||||
|
<description>Poll interval in seconds</description>
|
||||||
|
<default>1</default>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<channel-type id="position">
|
||||||
|
<item-type>Rollershutter</item-type>
|
||||||
|
<label>Position</label>
|
||||||
|
<description>Curtain position control</description>
|
||||||
|
<category>Blinds</category>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="mode">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Mode</label>
|
||||||
|
<description>Motor mode</description>
|
||||||
|
<state readOnly="true">
|
||||||
|
<options>
|
||||||
|
<option value="0">Stop</option>
|
||||||
|
<option value="1">Open</option>
|
||||||
|
<option value="2">Close</option>
|
||||||
|
<option value="3">Setting</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="reverse" advanced="true">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Reverse Motor</label>
|
||||||
|
<description>Reverse default motor direction</description>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="handStart" advanced="true">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Start By Hand</label>
|
||||||
|
<description>Enable or disable start by hand</description>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="extSwitch" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>External Switch</label>
|
||||||
|
<description>External switch type</description>
|
||||||
|
<state>
|
||||||
|
<options>
|
||||||
|
<option value="1">2-channel biased</option>
|
||||||
|
<option value="2">2-channel rocker</option>
|
||||||
|
<option value="3">DC246</option>
|
||||||
|
<option value="4">single button</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="hwSwitch" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>HV Switch</label>
|
||||||
|
<description>High-voltage switch type (only for EV motor)</description>
|
||||||
|
<state>
|
||||||
|
<options>
|
||||||
|
<option value="0">2-channel biased</option>
|
||||||
|
<option value="1">hotel mode</option>
|
||||||
|
<option value="2">2-channel rocker</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
</thing:thing-descriptions>
|
|
@ -152,6 +152,7 @@
|
||||||
<module>org.openhab.binding.helios</module>
|
<module>org.openhab.binding.helios</module>
|
||||||
<module>org.openhab.binding.heliosventilation</module>
|
<module>org.openhab.binding.heliosventilation</module>
|
||||||
<module>org.openhab.binding.heos</module>
|
<module>org.openhab.binding.heos</module>
|
||||||
|
<module>org.openhab.binding.herzborg</module>
|
||||||
<module>org.openhab.binding.homeconnect</module>
|
<module>org.openhab.binding.homeconnect</module>
|
||||||
<module>org.openhab.binding.homematic</module>
|
<module>org.openhab.binding.homematic</module>
|
||||||
<module>org.openhab.binding.homewizard</module>
|
<module>org.openhab.binding.homewizard</module>
|
||||||
|
|
Loading…
Reference in New Issue