Skip to content

Commit

Permalink
[lcn] Add shutter positioning/angle, operating hours counters, tunabl…
Browse files Browse the repository at this point in the history
…e white, regulator mode, beeping (openhab#13056)

* [lcn] Add shutter positioning/angle, operating hours counters, tunable white, regulator mode, beeping

Also, fix possible race condition during connecting, when the PC coupler is not connected to the LCN data wire.
Replace discontinued LCN-PKE by LCN-VISU.

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
  • Loading branch information
fwolter authored and psmedley committed Feb 23, 2023
1 parent ef8300b commit 9b2b9eb
Show file tree
Hide file tree
Showing 37 changed files with 1,268 additions and 126 deletions.
79 changes: 62 additions & 17 deletions bundles/org.openhab.binding.lcn/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.lcn.internal;

import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -39,9 +41,12 @@ public class LcnBindingConstants {
public static final ThingTypeUID THING_TYPE_MODULE = new ThingTypeUID(BINDING_ID, "module");
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
/** Regex for address in PCK protocol */
public static final String ADDRESS_REGEX = "[:=%]M(?<segId>\\d{3})(?<modId>\\d{3})";
public static final String ADDRESS_WITHOUT_PREFIX = "M(?<segId>\\d{3})(?<modId>\\d{3})";
public static final String ADDRESS_REGEX = "[:=%]" + ADDRESS_WITHOUT_PREFIX;
public static final Pattern MEASUREMENT_PATTERN_BEFORE_2013 = Pattern
.compile(LcnBindingConstants.ADDRESS_REGEX + "\\.(?<value>\\d{5})");
/** LCN coding for ACK */
public static final int CODE_ACK = -1;
public static final Collection<String> ALLOWED_BEEP_TONALITIES = Set.of("N", "S", "1", "2", "3", "4", "5", "6",
"7");
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
@NonNullByDefault
public class LcnModuleActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(LcnModuleActions.class);
private static final int MAX_BEEP_VOLUME = 100;
private static final int MAX_BEEP_COUNT = 50;
private static final int DYN_TEXT_CHUNK_COUNT = 5;
private static final int DYN_TEXT_HEADER_LENGTH = 6;
private static final int DYN_TEXT_CHUNK_LENGTH = 12;
Expand Down Expand Up @@ -173,6 +175,42 @@ public void startRelayTimer(
}
}

/**
* Let the beeper connected to the LCN module beep.
*
* @param volume sound volume in percent. Can be null. Then, the last volume is used.
* @param tonality N=normal, S=special, 1-7 tonalities 1-7. Can be null. Then, normal tonality is used.
* @param count number of beeps. Can be null. Then, number of beeps is one.
*/
@RuleAction(label = "let the module's beeper beep", description = "Lets the beeper connected to the LCN module beep")
public void beep(
@ActionInput(name = "volume", required = false, type = "java.lang.Double", label = "Sound Volume", description = "The sound volume in percent.") @Nullable Double soundVolume,
@ActionInput(name = "tonality", required = false, type = "java.lang.String", label = "Tonality", description = "Tonality (N, S, 1-7)") @Nullable String tonality,
@ActionInput(name = "count", required = false, type = "java.lang.Integer", label = "Count", description = "Number of beeps") @Nullable Integer count) {
try {
if (soundVolume != null) {
if (soundVolume < 0) {
throw new LcnException("Volume cannot be negative: " + soundVolume);
}
getHandler().sendPck(PckGenerator.setBeepVolume(Math.min(soundVolume, MAX_BEEP_VOLUME)));
}

Integer localCount = count;
if (localCount == null) {
localCount = 1;
}

String filteredTonality = LcnBindingConstants.ALLOWED_BEEP_TONALITIES.stream() //
.filter(t -> t.equals(tonality)) //
.findAny() //
.orElse("N");

getHandler().sendPck(PckGenerator.beep(filteredTonality, Math.min(localCount, MAX_BEEP_COUNT)));
} catch (LcnException e) {
logger.warn("Could not send beep command: {}", e.getMessage());
}
}

/** Static alias to support the old DSL rules engine and make the action available there. */
public static void hitKey(ThingActions actions, @Nullable String table, int key, @Nullable String action) {
((LcnModuleActions) actions).hitKey(table, key, action);
Expand All @@ -193,6 +231,11 @@ public static void startRelayTimer(ThingActions actions, int relaynumber, double
((LcnModuleActions) actions).startRelayTimer(relaynumber, duration);
}

/** Static alias to support the old DSL rules engine and make the action available there. */
public static void beep(ThingActions actions, Double soundVolume, String tonality, Integer count) {
((LcnModuleActions) actions).beep(soundVolume, tonality, count);
}

private LcnModuleHandler getHandler() throws LcnException {
LcnModuleHandler localModuleHandler = moduleHandler;
if (localModuleHandler != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public void handleCommand(ChannelUID channelUid, Command command) {
} else if (command instanceof PercentType) {
subHandler.handleCommandPercent((PercentType) command, channelGroup, channelUid.getIdWithoutGroup());
} else if (command instanceof StringType) {
subHandler.handleCommandString((StringType) command, number.get());
subHandler.handleCommandString((StringType) command, number.orElse(0));
} else if (command instanceof DecimalType) {
DecimalType decimalType = (DecimalType) command;
DecimalType nativeValue = getConverter(channelUid).onCommandFromItem(decimalType.doubleValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void handleCommand(ChannelUID channelUID, Command command) {
public synchronized void initialize() {
PckGatewayConfiguration localConfig = config = getConfigAs(PckGatewayConfiguration.class);

String errorMessage = "Could not connect to LCN-PCHK/PKE: " + localConfig.getHostname() + ": ";
String errorMessage = "Could not connect to LCN-PCHK/VISU: " + localConfig.getHostname() + ": ";

try {
OutputPortDimMode dimMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
import org.openhab.binding.lcn.internal.subhandler.LcnModuleKeyLockTableSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleLedSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleLogicSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleOperatingHoursCounterSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleOutputSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRelaySubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterOutputSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterRelaySubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterRelayPositionSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterRelaySlatAngleSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRvarLockSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRvarModeSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleRvarSetpointSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleS0CounterSubHandler;
import org.openhab.binding.lcn.internal.subhandler.LcnModuleThresholdSubHandler;
Expand All @@ -44,12 +47,14 @@ public enum LcnChannelGroup {
OUTPUT(4, LcnModuleOutputSubHandler::new),
ROLLERSHUTTEROUTPUT(1, LcnModuleRollershutterOutputSubHandler::new),
RELAY(8, LcnModuleRelaySubHandler::new),
ROLLERSHUTTERRELAY(4, LcnModuleRollershutterRelaySubHandler::new),
ROLLERSHUTTERRELAY(4, LcnModuleRollershutterRelayPositionSubHandler::new),
ROLLERSHUTTERRELAYSLAT(4, LcnModuleRollershutterRelaySlatAngleSubHandler::new),
LED(12, LcnModuleLedSubHandler::new),
LOGIC(4, LcnModuleLogicSubHandler::new),
BINARYSENSOR(8, LcnModuleBinarySensorSubHandler::new),
VARIABLE(12, LcnModuleVariableSubHandler::new),
RVARSETPOINT(2, LcnModuleRvarSetpointSubHandler::new),
RVARMODE(2, LcnModuleRvarModeSubHandler::new),
RVARLOCK(2, LcnModuleRvarLockSubHandler::new),
THRESHOLDREGISTER1(5, LcnModuleThresholdSubHandler::new),
THRESHOLDREGISTER2(4, LcnModuleThresholdSubHandler::new),
Expand All @@ -61,6 +66,7 @@ public enum LcnChannelGroup {
KEYLOCKTABLEC(8, LcnModuleKeyLockTableSubHandler::new),
KEYLOCKTABLED(8, LcnModuleKeyLockTableSubHandler::new),
CODE(0, LcnModuleCodeSubHandler::new),
OPERATINGHOURS(0, LcnModuleOperatingHoursCounterSubHandler::new),
HOSTCOMMAND(0, LcnModuleHostCommandSubHandler::new);

private int count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public final class LcnDefs {
public static final String LCNCONNSTATE_CONNECTED = "$io:#LCN:connected";
/** LCN-PK/PKU is disconnected. */
public static final String LCNCONNSTATE_DISCONNECTED = "$io:#LCN:disconnected";
/** LCN-PCHK/PKE has not enough licenses to handle this connection. */
/** LCN-PCHK/VISU has not enough licenses to handle this connection. */
public static final String INSUFFICIENT_LICENSES = "$err:(license?)";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ public static String dimOutput(int outputId, double percent, int rampMs) throws
}
}

/**
* Generates a command for setting the tunable white mode.
*
* @param mode 0..2
* @return the PCK command (without address header) as text
* @throws LcnException if out of range
*/
public static String setTunableWhiteMode(int mode) throws LcnException {
if (mode < 0 || mode > 2) {
throw new LcnException();
}

return String.format("AW%d", mode);
}

/**
* Generates a dim command for all output-ports.
*
Expand Down Expand Up @@ -332,6 +347,42 @@ public static String controlRelays(LcnDefs.RelayStateModifier[] states) throws L
return ret.toString();
}

/**
* Generates a command to control the position of roller shutters on relays.
*
* @param motorNumber of the roller shutter (0-based)
* @param percent of the entire roller shutter height
* @return the PCK command (without address header) as text
* @throws LcnException if out of range
*/
public static String controlShutterPosition(int motorNumber, int percent) throws LcnException {
return controlShutter(motorNumber, percent, "JH");
}

/**
* Generates a command to control the slat angle of roller shutters on relays.
*
* @param motorNumber of the roller shutter (0-based)
* @param percent of the slat angle
* @return the PCK command (without address header) as text
* @throws LcnException if out of range
*/
public static String controlShutterSlatAngle(int motorNumber, int percent) throws LcnException {
return controlShutter(motorNumber, percent, "JW");
}

private static String controlShutter(int motorNumber, int percent, String command) throws LcnException {
if (motorNumber < 0 || motorNumber >= 4) {
throw new LcnException("Roller shutter (relay) motor number out of range: " + motorNumber);
}

if (percent < 0 || percent > 100) {
throw new LcnException("Roller shutter (relay) position/angle out of range (percent): " + percent);
}

return String.format("%s%03d%03d", command, percent, 1 << motorNumber);
}

/**
* Generates a binary-sensors status request.
*
Expand Down Expand Up @@ -365,6 +416,30 @@ public static String setSetpointAbsolute(int number, int value) {
return String.format("X2%03d%03d%03d", 30, b1, b2);
}

/**
* Generates a command to change the regulator mode.
*
* @param number regulator number 0..1
* @param cooling true=cooling, false=heating
* @return the PCK command (without address header) as text
* @throws LcnException
*/
public static String setRVarMode(int number, boolean cooling) throws LcnException {
String regulator;
switch (number) {
case 0:
regulator = "A";
break;
case 1:
regulator = "B";
break;
default:
throw new LcnException();
}

return "RE" + regulator + "T" + (cooling ? "C" : "H");
}

/**
* Generates a command to change the value of a variable.
*
Expand Down Expand Up @@ -751,6 +826,41 @@ public static String startRelayTimer(int relayNumber, double duration) throws Lc
return command.toString();
}

/**
* Generates a command to set the beeping sound volume.
*
* @param volume the sound volume
* @return the PCK command (without address header) as text
* @throws LcnException if out of range
*/
public static String setBeepVolume(double volume) throws LcnException {
if (volume < 0 || volume > 100) {
throw new LcnException();
}

return String.format("PIV%03d", Math.round(volume));
}

/**
* Generates a command to let the beeper connected to the LCN module beep.
*
* @param volume the sound volume
* @return the PCK command (without address header) as text
* @throws LcnException if out of range
*/
public static String beep(String tonality, int count) throws LcnException {
LcnBindingConstants.ALLOWED_BEEP_TONALITIES.stream() //
.filter(t -> t.equals(tonality)) //
.findAny() //
.orElseThrow(LcnException::new);

if (count < 0) {
throw new LcnException();
}

return String.format("PI%s%d", tonality, Math.min(count, 50));
}

/**
* Generates a null command, used for broadcast messages.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void shutdownFinally() {
*/
protected void parseLcnBusDiconnectMessage(String pck) {
if (pck.equals(LcnDefs.LCNCONNSTATE_DISCONNECTED)) {
connection.getCallback().onOffline("LCN bus not connected to LCN-PCHK/PKE");
connection.getCallback().onOffline("LCN-PCHK/VISU not connected to LCN data wire");
nextState(ConnectionStateWaitForLcnBusConnectedAfterDisconnected::new);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public synchronized void handleConnectionFailed(@Nullable Throwable e) {
*
* @param data the PCK message
*/
public void onInputReceived(String data) {
public synchronized void onInputReceived(String data) {
AbstractConnectionState localState = state;
if (localState != null) {
localState.onPckMessageReceived(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void onPckMessageReceived(String data) {
switch (data) {
case LcnDefs.LCNCONNSTATE_DISCONNECTED:
cancelLegacyTimer();
connection.getCallback().onOffline("LCN bus not connected to LCN-PCHK/PKE");
connection.getCallback().onOffline("LCN-PCHK/VISU not connected to LCN data wire");
break;
case LcnDefs.LCNCONNSTATE_CONNECTED:
cancelLegacyTimer();
Expand All @@ -61,7 +61,7 @@ public void onPckMessageReceived(String data) {
case LcnDefs.INSUFFICIENT_LICENSES:
cancelLegacyTimer();
handleConnectionFailed(
new LcnException("LCN-PCHK/PKE has not enough licenses to handle this connection"));
new LcnException("LCN-PCHK/VISU has not enough licenses to handle this connection"));
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lcn.internal.LcnBindingConstants;
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.LcnDefs.RelayStateModifier;
import org.openhab.binding.lcn.internal.common.LcnException;
import org.openhab.binding.lcn.internal.common.PckGenerator;
import org.openhab.binding.lcn.internal.connection.ModInfo;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;

Expand All @@ -34,8 +36,13 @@
* @author Fabian Wolter - Initial contribution
*/
@NonNullByDefault
public class LcnModuleRollershutterRelaySubHandler extends AbstractLcnModuleSubHandler {
public LcnModuleRollershutterRelaySubHandler(LcnModuleHandler handler, ModInfo info) {
public abstract class AbstractLcnModuleRollershutterRelaySubHandler extends AbstractLcnModuleSubHandler {
private static final String POSITION = "P";
private static final String ANGLE = "W";
private static final Pattern PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + //
"(?<type>[" + POSITION + "|" + ANGLE + "])(?<shutterNumber>\\d)(?<percent>\\d{3})");

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

Expand Down Expand Up @@ -68,11 +75,21 @@ public void handleCommandStopMove(StopMoveType command, LcnChannelGroup channelG

@Override
public void handleStatusMessage(Matcher matcher) {
// status messages of roller shutters on relays are handled in the relay sub handler
int shutterNumber = Integer.parseInt(matcher.group("shutterNumber")) - 1;
int percent = Integer.parseInt(matcher.group("percent"));

LcnChannelGroup group;
if (POSITION.equals(matcher.group("type"))) {
group = LcnChannelGroup.ROLLERSHUTTERRELAY;
} else {
group = LcnChannelGroup.ROLLERSHUTTERRELAYSLAT;
}

fireUpdate(group, shutterNumber, new PercentType(percent));
}

@Override
public Collection<Pattern> getPckStatusMessagePatterns() {
return Collections.emptyList();
return Collections.singleton(PATTERN);
}
}
Loading

0 comments on commit 9b2b9eb

Please sign in to comment.