Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lcn] Add support for commands from LCN to openHAB #8284

Merged
merged 3 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion bundles/org.openhab.binding.lcn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,11 @@ If a special command is needed, the [Hit Key](#hit-key) action (German: "Sende T
| Send Keys | Sende Tasten | N/A | N/A | N/A | Action: "hitKey": Hits a key of a key table in an LCN module. Can be used to execute commands, not supported by this binding. |
| Dimmer Output Control Multiple | Mehrere Ausgänge steuern | output | 1-4 | Dimmer, Switch | Control multiple outputs simultaneously. See below. |
| Transponder | Transponder | code#transponder | | Trigger | Receive transponder messages |
| Fingerprint | Fingerprint | code#fingerprint | | Trigger | Receive fingerprint code messages |
| Fingerprint | Fingerprint | code#fingerprint | | Trigger | Receive fingerprint code messages |
| Remote Control | Fernbedienung | code#remotecontrolkey | | Trigger | Receive commands from remote control |
| Access Control | Zutrittskontrolle | code#remotecontrolcode | | Trigger | Receive serial numbers from remote control |
| Remote Control Battery Low | Fernbedienung Batterie schwach | code#remotecontrolbatterylow | | Trigger | Triggered when the sending remote control has a low battery |
| Host Command (Send Keys) | Kommando an Host (Sende Tasten) | hostcommand#sendKeys | - | Trigger | Receive *send keys* command from LCN module |
| Status Message | Statusmeldungen | - | - | - | Automatically done by openHAB Binding |
| Audio Beep | Audio Piepen | - | - | - | Not implemented |
| Audio LCN-MRS | Audio LCN-MRS | - | - | - | Not implemented |
Expand Down Expand Up @@ -310,6 +311,35 @@ then
end
```

### Command from an LCN Module to openHAB

LCN modules can send commands to openHAB, e.g. by pressing a physical LCN key.
The command must be programmed into the LCN module by the programming software LCN-PRO.
Only the *send keys* command (German: "Sende Tasten") is supported.

Program a command to a key of an LCN module via LCN-PRO.
When LCN-PRO asks you for the target address, don't select any module, but manually enter the PCK host ID, configured within PCHK (default: 4).
Select the *send keys* command and "A-C (former command)", as PCHK 3.2.2 only supports the old command.
Then, select any key(s) you want to send to openHAB. These can be freely chosen, as they are only evaluated by openHAB.

![Screenshot, showing the send keys command](doc/host_command_send_keys.png)

The following rule can be used to trigger any action:

```
rule "Module 12 sent A1 Hit"
when
Channel "lcn:module:b827ebfea4bb:S000M012:hostcommand#sendKeys" triggered "A1:HIT"
then
M10_Relay7.sendCommand(ON)
end
```

`A1` is the key of the *send keys* command, programmed by LCN-PRO.
After the colon, the LCN "hit type" follows: HIT, MAKE or BREAK (German: kurz, lang, los)

If multiple keys or key tables are programmed in a single "send keys" command, multiple triggers will be executed.

### Remote Control

To evaluate commands from LCN remote controls (e.g. LCN-RT or LCN-RT16), the module's I-port behavior must be configured as "IR access control" within *LCN-PRO*:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion bundles/org.openhab.binding.lcn/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?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">
<?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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.openhab.binding.lcn.internal.subhandler.AbstractLcnModuleSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleBinarySensorSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleCodeSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleHostCommandSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleKeyLockTableSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleLedSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleLogicSubHandler;
Expand Down Expand Up @@ -59,7 +60,8 @@ public enum LcnChannelGroup {
KEYLOCKTABLEB(8, LcnModuleKeyLockTableSubHandler::new),
KEYLOCKTABLEC(8, LcnModuleKeyLockTableSubHandler::new),
KEYLOCKTABLED(8, LcnModuleKeyLockTableSubHandler::new),
CODE(0, LcnModuleCodeSubHandler::new);
CODE(0, LcnModuleCodeSubHandler::new),
HOSTCOMMAND(0, LcnModuleHostCommandSubHandler::new);

private int count;
private BiFunction<LcnModuleHandler, ModInfo, ? extends AbstractLcnModuleSubHandler> handlerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.NoSuchElementException;

import org.eclipse.jdt.annotation.NonNullByDefault;

Expand All @@ -31,6 +33,10 @@ public final class LcnDefs {
public static final int THRESHOLD_REGISTER_COUNT = 4;
/** Number of key tables of an LCN module. */
public static final int KEY_TABLE_COUNT = 4;
/** Number of key tables of an LCN module before firmware 0C030C0. */
public static final int KEY_TABLE_COUNT_UNTIL_0C030C0 = 3;
/** Number of keys per table of an LCN module */
public static final int KEY_COUNT = 8;
/** Number of thresholds before LCN module firmware version 2013 */
public static final int THRESHOLD_COUNT_BEFORE_2013 = 5;
/**
Expand Down Expand Up @@ -114,10 +120,25 @@ public enum RelVarRef {

/** Command types used when sending LCN keys. */
public enum SendKeyCommand {
HIT,
MAKE,
BREAK,
DONTSEND
DONTSEND(0),
HIT(1),
MAKE(2),
BREAK(3);

private int id;

SendKeyCommand(int id) {
this.id = id;
}

public int getId() {
return id;
}

public static SendKeyCommand get(int id) {
return Arrays.stream(values()).filter(v -> v.getId() == id).findAny()
.orElseThrow(NoSuchElementException::new);
}
}

/** Key-lock modifiers used in LCN commands. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* 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.lcn.internal.subhandler;

import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lcn.internal.LcnModuleHandler;
import org.openhab.binding.lcn.internal.common.LcnChannelGroup;
import org.openhab.binding.lcn.internal.common.LcnDefs;
import org.openhab.binding.lcn.internal.common.LcnException;
import org.openhab.binding.lcn.internal.connection.ModInfo;

/**
* Handles 'send key' commands sent to this PCK host.
*
* @author Fabian Wolter - Initial contribution
*/
@NonNullByDefault
public class LcnModuleHostCommandSubHandler extends AbstractLcnModuleSubHandler {
private static final Pattern SEND_KEY_PATTERN = Pattern
.compile("\\+M(?<hostId>\\d{3})(?<segId>\\d{3})(?<modId>\\d{3})\\.STH(?<byte0>\\d{3})(?<byte1>\\d{3})");

public LcnModuleHostCommandSubHandler(LcnModuleHandler handler, ModInfo info) {
super(handler, info);
}

@Override
public void handleRefresh(LcnChannelGroup channelGroup, int number) {
// nothing
}

@Override
public void handleStatusMessage(Matcher matcher) throws LcnException {
int keyTableAndActionMask = Integer.parseInt(matcher.group("byte0"));
int keyNumberMask = Integer.parseInt(matcher.group("byte1"));

if ((keyTableAndActionMask & (1 << 6)) == 0) {
return;
}

// PCHK 3.22 supports only the old 'send key' command with key tables A-C
for (int keyTableNumber = 0; keyTableNumber < LcnDefs.KEY_TABLE_COUNT_UNTIL_0C030C0; keyTableNumber++) {
String keyTableName = LcnDefs.KeyTable.values()[keyTableNumber].name();

for (int keyNumber = 0; keyNumber < LcnDefs.KEY_COUNT; keyNumber++) {
int actionRaw = (keyTableAndActionMask >> (keyTableNumber * 2)) & 3;

if (actionRaw > LcnDefs.SendKeyCommand.DONTSEND.getId()
&& actionRaw <= LcnDefs.SendKeyCommand.BREAK.getId()
&& ((1 << keyNumber) & keyNumberMask) != 0) {
String actionName = LcnDefs.SendKeyCommand.get(actionRaw).name();

handler.triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys",
keyTableName + (keyNumber + 1) + ":" + actionName);
}
}
}
}

@Override
public Collection<Pattern> getPckStatusMessagePatterns() {
return Collections.singleton(SEND_KEY_PATTERN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,5 @@ channel-group-type.lcn.codes.channel.fingerprint.label = Fingerprint-Code
channel-group-type.lcn.codes.channel.remotecontrolkey.label = Fernbedienung Tasten
channel-group-type.lcn.codes.channel.remotecontrolcode.label = Fernbedienung Tasten mit Zutrittscode
channel-group-type.lcn.codes.channel.remotecontrolbatterylow.label = Fernbedienung Batterie leer
channel-group-type.lcn.hostcommands.label = Kommandos an Host (openHAB)
channel-group-type.lcn.hostcommands.channel.sendKeys.label = Sende Tasten
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<channel-group typeId="keyslocktablec" id="keylocktablec"/>
<channel-group typeId="keyslocktabled" id="keylocktabled"/>
<channel-group typeId="codes" id="code"/>
<channel-group typeId="hostcommands" id="hostcommand"/>
</channel-groups>
<properties>
<property name="serialNumber"/>
Expand Down Expand Up @@ -658,4 +659,94 @@
<label>Remote Control Low Battery</label>
<event/>
</channel-type>

<channel-group-type id="hostcommands">
<label>Host Command (to openHAB)</label>
<channels>
<channel typeId="sendKeys" id="sendKeys"/>
</channels>
</channel-group-type>

<channel-type id="sendKeys" advanced="true">
<kind>trigger</kind>
<label>Send Keys</label>
<event>
<options>
<option value="A1:HIT">A1 Hit</option>
<option value="A1:MAKE">A1 Make</option>
<option value="A1:BREAK">A1 Break</option>
<option value="A2:HIT">A2 Hit</option>
<option value="A2:MAKE">A2 Make</option>
<option value="A2:BREAK">A2 Break</option>
<option value="A3:HIT">A3 Hit</option>
<option value="A3:MAKE">A3 Make</option>
<option value="A3:BREAK">A3 Break</option>
<option value="A4:HIT">A4 Hit</option>
<option value="A4:MAKE">A4 Make</option>
<option value="A4:BREAK">A4 Break</option>
<option value="A5:HIT">A5 Hit</option>
<option value="A5:MAKE">A5 Make</option>
<option value="A5:BREAK">A5 Break</option>
<option value="A6:HIT">A6 Hit</option>
<option value="A6:MAKE">A6 Make</option>
<option value="A6:BREAK">A6 Break</option>
<option value="A7:HIT">A7 Hit</option>
<option value="A7:MAKE">A7 Make</option>
<option value="A7:BREAK">A7 Break</option>
<option value="A8:HIT">A8 Hit</option>
<option value="A8:MAKE">A8 Make</option>
<option value="A8:BREAK">A8 Break</option>

<option value="B1:HIT">B1 Hit</option>
<option value="B1:MAKE">B1 Make</option>
<option value="B1:BREAK">B1 Break</option>
<option value="B2:HIT">B2 Hit</option>
<option value="B2:MAKE">B2 Make</option>
<option value="B2:BREAK">B2 Break</option>
<option value="B3:HIT">B3 Hit</option>
<option value="B3:MAKE">B3 Make</option>
<option value="B3:BREAK">B3 Break</option>
<option value="B4:HIT">B4 Hit</option>
<option value="B4:MAKE">B4 Make</option>
<option value="B4:BREAK">B4 Break</option>
<option value="B5:HIT">B5 Hit</option>
<option value="B5:MAKE">B5 Make</option>
<option value="B5:BREAK">B5 Break</option>
<option value="B6:HIT">B6 Hit</option>
<option value="B6:MAKE">B6 Make</option>
<option value="B6:BREAK">B6 Break</option>
<option value="B7:HIT">B7 Hit</option>
<option value="B7:MAKE">B7 Make</option>
<option value="B7:BREAK">B7 Break</option>
<option value="B8:HIT">B8 Hit</option>
<option value="B8:MAKE">B8 Make</option>
<option value="B8:BREAK">B8 Break</option>

<option value="C1:HIT">C1 Hit</option>
<option value="C1:MAKE">C1 Make</option>
<option value="C1:BREAK">C1 Break</option>
<option value="C2:HIT">C2 Hit</option>
<option value="C2:MAKE">C2 Make</option>
<option value="C2:BREAK">C2 Break</option>
<option value="C3:HIT">C3 Hit</option>
<option value="C3:MAKE">C3 Make</option>
<option value="C3:BREAK">C3 Break</option>
<option value="C4:HIT">C4 Hit</option>
<option value="C4:MAKE">C4 Make</option>
<option value="C4:BREAK">C4 Break</option>
<option value="C5:HIT">C5 Hit</option>
<option value="C5:MAKE">C5 Make</option>
<option value="C5:BREAK">C5 Break</option>
<option value="C6:HIT">C6 Hit</option>
<option value="C6:MAKE">C6 Make</option>
<option value="C6:BREAK">C6 Break</option>
<option value="C7:HIT">C7 Hit</option>
<option value="C7:MAKE">C7 Make</option>
<option value="C7:BREAK">C7 Break</option>
<option value="C8:HIT">C8 Hit</option>
<option value="C8:MAKE">C8 Make</option>
<option value="C8:BREAK">C8 Break</option>
</options>
</event>
</channel-type>
</thing:thing-descriptions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* 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.lcn.internal.subhandler;

import static org.mockito.Mockito.verify;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.lcn.internal.common.LcnChannelGroup;

/**
* Test class.
*
* @author Fabian Wolter - Initial contribution
*/
@NonNullByDefault
public class LcnModuleHostCommandSubHandlerTest extends AbstractTestLcnModuleSubHandler {
private @NonNullByDefault({}) LcnModuleHostCommandSubHandler subHandler;

@Override
@Before
public void setUp() {
super.setUp();

subHandler = new LcnModuleHostCommandSubHandler(handler, info);
}

@Test
public void testA1Hit() {
subHandler.tryParse("+M004000005.STH065001");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "A1:HIT");
}

@Test
public void testA1Make() {
subHandler.tryParse("+M004000005.STH066001");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "A1:MAKE");
}

@Test
public void testC8Break() {
subHandler.tryParse("+M004000005.STH112128");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "C8:BREAK");
}

@Test
public void testC1Hit() {
subHandler.tryParse("+M004000005.STH080001");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "C1:HIT");
}

@Test
public void testMultiple() {
subHandler.tryParse("+M004000005.STH121034");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "A2:HIT");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "A6:HIT");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "B2:MAKE");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "B6:MAKE");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "C2:BREAK");
verify(handler).triggerChannel(LcnChannelGroup.HOSTCOMMAND, "sendKeys", "C6:BREAK");
}
}