Skip to content

Commit

Permalink
[lutron] Add sysvar handler for HomeWorks QS (openhab#8132)
Browse files Browse the repository at this point in the history
* Add new sysvar thing
* Add internal support for SYSVAR and SHADEGRP protocol commands
* Enable sysvar monitoring in bridge when needed

Signed-off-by: Bob Adair <[email protected]>
  • Loading branch information
bobadair authored and andrewfg committed Aug 31, 2020
1 parent 90a62e1 commit 24f607e
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 9 deletions.
21 changes: 20 additions & 1 deletion bundles/org.openhab.binding.lutron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This binding currently supports the following thing types:
* **blind** - Lutron venetian blind or horizontal sheer blind [**Experimental**]
* **greenmode** - Green Mode subsystem
* **timeclock** - Scheduling subsystem
* **sysvar** - System state variable (HomeWorks QS only) [**Experimental**]

## Discovery

Expand Down Expand Up @@ -521,6 +522,22 @@ then
end
```

### System State Variables (HomeWorks QS only) [**Experimental**]

HomeWorks QS systems allow for conditional programming logic based on state variables.
The **sysvar** thing allows state variable values to be read and set from openHAB.
This makes sophisticated integration schemes possible.
Each **sysvar** thing represents one system state variable.
It has a single channel *varstate* with type Number and category Number.
Automatic discovery of state variables is not yet supported.
They must be manually configured.

Thing configuration file example:

```
Thing sysvar qsstate [ integrationId=80 ]
```

## Channels

The following is a summary of channels for all RadioRA 2 binding things:
Expand All @@ -544,6 +561,7 @@ The following is a summary of channels for all RadioRA 2 binding things:
| timeclock | execevent | Number | Execute event or monitor events executed |
| timeclock | enableevent | Number | Enable event or monitor events enabled |
| timeclock | disableevent | Number | Disable event or monitor events disabled |
| sysvar | varstate | Number | Get/set system state variable value |

The channels available on each keypad device (i.e. keypad, ttkeypad, intlkeypad, grafikeyekeypad, pico, vcrx, and virtualkeypad) will vary with keypad type and model.
Appropriate channels will be created automatically by the keypad, ttkeypad, intlkeypad, grafikeyekeypad, and pico thing handlers based on the setting of the `model` parameter for those thing types.
Expand All @@ -552,7 +570,7 @@ Appropriate channels will be created automatically by the keypad, ttkeypad, intl

| Thing | Channel | Native Type | Accepts |
|-----------|---------------|--------------|-------------------------------------------------------|
|dimmer |lightlevel |PercentType |OnOffType, PercentType |
|dimmer |lightlevel |PercentType |OnOffType, PercentType (rounded/truncated to integer) |
|switch |switchstatus |OnOffType |OnOffType |
|occ. sensor|occupancystatus|OnOffType |(*readonly*) |
|cco |switchstatus |OnOffType |OnOffType, RefreshType |
Expand All @@ -569,6 +587,7 @@ Appropriate channels will be created automatically by the keypad, ttkeypad, intl
| |execevent |DecimalType |DecimalType |
| |enableevent |DecimalType |DecimalType |
| |disableevent |DecimalType |DecimalType |
|sysvar |varstate |DecimalType |DecimalType (rounded/truncated to integer) |

Most channels receive immediate notifications of device state changes from the Lutron control system.
The only exceptions are **greenmode** *step*, which is periodically polled and accepts REFRESH commands to initiate immediate polling, and **timeclock** *sunrise* and *sunset*, which must be polled daily using REFRESH commands to retrieve current values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class LutronBindingConstants {
public static final ThingTypeUID THING_TYPE_BLIND = new ThingTypeUID(BINDING_ID, "blind");
public static final ThingTypeUID THING_TYPE_PALLADIOMKEYPAD = new ThingTypeUID(BINDING_ID, "palladiomkeypad");
public static final ThingTypeUID THING_TYPE_WCI = new ThingTypeUID(BINDING_ID, "wci");
public static final ThingTypeUID THING_TYPE_SYSVAR = new ThingTypeUID(BINDING_ID, "sysvar");

// List of all Channel ids
public static final String CHANNEL_LIGHTLEVEL = "lightlevel";
Expand All @@ -65,6 +66,7 @@ public class LutronBindingConstants {
public static final String CHANNEL_STEP = "step";
public static final String CHANNEL_BLINDLIFTLEVEL = "blindliftlevel";
public static final String CHANNEL_BLINDTILTLEVEL = "blindtiltlevel";
public static final String CHANNEL_VARSTATE = "varstate";

// Bridge config properties (used by discovery service)
public static final String HOST = "ipAddress";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.openhab.binding.lutron.internal.handler.QSIOHandler;
import org.openhab.binding.lutron.internal.handler.ShadeHandler;
import org.openhab.binding.lutron.internal.handler.SwitchHandler;
import org.openhab.binding.lutron.internal.handler.SysvarHandler;
import org.openhab.binding.lutron.internal.handler.TabletopKeypadHandler;
import org.openhab.binding.lutron.internal.handler.TimeclockHandler;
import org.openhab.binding.lutron.internal.handler.VcrxHandler;
Expand Down Expand Up @@ -97,12 +98,12 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
.unmodifiableSet(Collections.singleton(HwConstants.THING_TYPE_HWDIMMER));

// Other types that can be initiated but not discovered
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_IPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE, PrgConstants.THING_TYPE_GRAFIKEYE,
RadioRAConstants.THING_TYPE_RS232, RadioRAConstants.THING_TYPE_DIMMER,
RadioRAConstants.THING_TYPE_SWITCH, RadioRAConstants.THING_TYPE_PHANTOM,
HwConstants.THING_TYPE_HWSERIALBRIDGE, THING_TYPE_CCO_PULSED, THING_TYPE_CCO_MAINTAINED)
.collect(Collectors.toSet()));
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_IPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE,
PrgConstants.THING_TYPE_GRAFIKEYE, RadioRAConstants.THING_TYPE_RS232,
RadioRAConstants.THING_TYPE_DIMMER, RadioRAConstants.THING_TYPE_SWITCH,
RadioRAConstants.THING_TYPE_PHANTOM, HwConstants.THING_TYPE_HWSERIALBRIDGE, THING_TYPE_CCO_PULSED,
THING_TYPE_CCO_MAINTAINED, THING_TYPE_SYSVAR).collect(Collectors.toSet()));

private final Logger logger = LoggerFactory.getLogger(LutronHandlerFactory.class);

Expand Down Expand Up @@ -186,6 +187,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new QSIOHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_BLIND)) {
return new BlindHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_SYSVAR)) {
return new SysvarHandler(thing);
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_PRGBRIDGE)) {
return new PrgBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_GRAFIKEYE)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* 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.lutron.internal.config;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.SysvarHandler}.
*
* @author Bob Adair - Initial contribution
*/
@NonNullByDefault
public class SysvarConfig {
public int integrationId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -31,6 +32,7 @@
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.types.Command;
import org.openhab.binding.lutron.internal.config.IPBridgeConfig;
import org.openhab.binding.lutron.internal.discovery.LutronDeviceDiscoveryService;
Expand All @@ -51,11 +53,13 @@
*/
public class IPBridgeHandler extends BaseBridgeHandler {
private static final Pattern RESPONSE_REGEX = Pattern
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");

private static final String DB_UPDATE_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";

private static final Integer MONITOR_PROMPT = 12;
private static final Integer MONITOR_SYSVAR = 10;
private static final Integer MONITOR_ENABLE = 1;
private static final Integer MONITOR_DISABLE = 2;

private static final Integer SYSTEM_DBEXPORTDATETIME = 10;
Expand Down Expand Up @@ -91,6 +95,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
private Date lastDbUpdateDate;
private LutronDeviceDiscoveryService discoveryService;

private final AtomicBoolean requireSysvarMonitoring = new AtomicBoolean(false);

public void setDiscoveryService(LutronDeviceDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
Expand Down Expand Up @@ -203,6 +209,10 @@ private synchronized void connect() {
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_PROMPT,
MONITOR_DISABLE));

if (requireSysvarMonitoring.get()) {
setSysvarMonitoring(true);
}

// Check the time device database was last updated. On the initial connect, this will trigger
// a scan for paired devices.
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSTEM, -1, SYSTEM_DBEXPORTDATETIME));
Expand Down Expand Up @@ -443,6 +453,22 @@ private void scanForDevices() {
}
}

private void setSysvarMonitoring(boolean enable) {
Integer setting = (enable) ? MONITOR_ENABLE : MONITOR_DISABLE;
sendCommand(
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_SYSVAR, setting));
}

@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
// enable sysvar monitoring the first time a sysvar child thing initializes
if (childHandler instanceof SysvarHandler) {
if (requireSysvarMonitoring.compareAndSet(false, true)) {
setSysvarMonitoring(true);
}
}
}

@Override
public void thingUpdated(Thing thing) {
IPBridgeConfig newConfig = thing.getConfiguration().as(IPBridgeConfig.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
* Base type for all Lutron thing handlers.
*
* @author Allan Tong - Initial contribution
* @author Bob Adair - Added methods for status and state management
* @author Bob Adair - Added additional commands and methods for status and state management
*
*/
@NonNullByDefault
public abstract class LutronHandler extends BaseThingHandler {
Expand Down Expand Up @@ -108,6 +109,16 @@ protected void greenMode(Object... parameters) {
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MODE, getIntegrationId(), parameters));
}

protected void sysvar(Object... parameters) {
sendCommand(
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
}

protected void shadegrp(Object... parameters) {
sendCommand(
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
}

protected void queryOutput(Object... parameters) {
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
}
Expand All @@ -124,4 +135,13 @@ protected void queryTimeclock(Object... parameters) {
protected void queryGreenMode(Object... parameters) {
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.MODE, getIntegrationId(), parameters));
}

protected void querySysvar(Object... parameters) {
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
}

protected void queryShadegrp(Object... parameters) {
sendCommand(
new LutronCommand(LutronOperation.QUERY, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* 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.lutron.internal.handler;

import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_VARSTATE;

import java.math.BigDecimal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.types.Command;
import org.openhab.binding.lutron.internal.config.SysvarConfig;
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Handler responsible for getting/setting sysvar state for HomeWorks QS
*
* @author Bob Adair - Initial contribution
*
*/
@NonNullByDefault
public class SysvarHandler extends LutronHandler {
private static final Integer ACTION_GETSETSYSVAR = 1;

private final Logger logger = LoggerFactory.getLogger(SysvarHandler.class);

private @Nullable SysvarConfig config;
private int integrationId;

public SysvarHandler(Thing thing) {
super(thing);
}

@Override
public int getIntegrationId() {
SysvarConfig config = this.config;
if (config != null) {
return config.integrationId;
} else {
throw new IllegalStateException("handler not initialized");
}
}

@Override
public void initialize() {
SysvarConfig config = getThing().getConfiguration().as(SysvarConfig.class);
this.config = config;
if (config.integrationId <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
} else {
integrationId = config.integrationId;
logger.debug("Initializing Sysvar handler for integration ID {}", integrationId);
initDeviceState();
}
}

@Override
protected void initDeviceState() {
logger.debug("Initializing handler state for sysvar id {}", integrationId);
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
querySysvar(ACTION_GETSETSYSVAR); // handleUpdate() will set thing status to online when response arrives
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}

@Override
public void channelLinked(ChannelUID channelUID) {
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
// Refresh state when new item is linked.
querySysvar(ACTION_GETSETSYSVAR);
}
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
if (command instanceof Number) {
int state = ((Number) command).intValue();
sysvar(ACTION_GETSETSYSVAR, state);
}
}
}

@Override
public void handleUpdate(LutronCommandType type, String... parameters) {
if (type == LutronCommandType.SYSVAR && parameters.length > 1
&& ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
BigDecimal state = new BigDecimal(parameters[1]);
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
updateStatus(ThingStatus.ONLINE);
}
updateState(CHANNEL_VARSTATE, new DecimalType(state));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* Type of command in the Lutron integration protocol.
*
* @author Allan Tong - Initial contribution
* @author Bob Adair - Added additional commands
*
*/
public enum LutronCommandType {
Expand All @@ -25,6 +26,8 @@ public enum LutronCommandType {
MODE,
MONITORING,
OUTPUT,
SHADEGRP,
SYSTEM,
SYSVAR,
TIMECLOCK,
}
Loading

0 comments on commit 24f607e

Please sign in to comment.