Skip to content

Commit

Permalink
[lutron] Add setLevel thing action to dimmer (openhab#8153)
Browse files Browse the repository at this point in the history
* [lutron] Workaround for thing actions bug
* [lutron] Fix NPE
* Fix NPE when setting fadeInTime and fadeOutTime variables prior to handler initialization

Also-by: Austin Guiswite <[email protected]>
Signed-off-by: Bob Adair <[email protected]>
  • Loading branch information
bobadair authored and andrewfg committed Aug 31, 2020
1 parent fe96f88 commit c9e4ec8
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 5 deletions.
33 changes: 31 additions & 2 deletions bundles/org.openhab.binding.lutron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ Bridge lutron:ipbridge:radiora2 [ ipAddress="192.168.1.2", user="lutron", passwo

### Dimmers

Dimmers can optionally be configured to specify a fade in and fade out time in seconds using the `fadeInTime` and `fadeOutTime` parameters.
Dimmers can optionally be configured to specify a default fade in and fade out time in seconds using the `fadeInTime` and `fadeOutTime` parameters.
These are used for ON and OFF commands, respectively, and default to 1 second if not set.
Commands using a specific percent value will use a default fade time of 0.25 seconds.

A **dimmer** thing has a single channel *lightlevel* with type Dimmer and category DimmableLight.

Thing configuration file example:
Expand All @@ -113,6 +116,19 @@ Thing configuration file example:
Thing dimmer livingroom [ integrationId=8, fadeInTime=0.5, fadeOutTime=5 ]
```

The **dimmer** thing supports the thing action `setLevel(Double level, Double fadeTime, Double delayTime)` for automation rules.

The parameters are:

* `level` The new light level to set (0-100)
* `fadeTime` The time in seconds over which the dimmer should fade to the new level
* `delayTime` The time in seconds to delay before starting to fade to the new level

The fadeTime and delayTime parameters are significant to 2 digits after the decimal point (i.e. to hundredths of a second), but some Lutron systems may round the time to the nearest 0.25 seconds when processing the command.
Times of 100 seconds or more will be rounded to the nearest integer value.

See below for an example rule using thing actions.

### Switches

Switches take no additional parameters besides `integrationId`.
Expand Down Expand Up @@ -594,7 +610,7 @@ The only exceptions are **greenmode** *step*, which is periodically polled and a
Many other channels accept REFRESH commands to initiate a poll, but sending one should not normally be necessary.


## RadioRA 2 Configuration File Example
## RadioRA 2/HomeWorks QS Configuration File Examples:

demo.things:

Expand Down Expand Up @@ -634,6 +650,19 @@ Rollershutter Lib_Shade1 "Shade 1" { channel="lutron:shade:radiora2:
```

dimmerAction.rules:

```
rule "Test dimmer action"
when
Item TestSwitch received command ON
then
val actions = getActions("lutron","lutron:dimmer:radiora2:lrtable")
actions.setLevel(100, 5.5, 0)
end
```


# Lutron RadioRA (Classic) Binding

This binding integrates with the legacy Lutron RadioRA (Classic) lighting system.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* 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.action;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.binding.ThingActions;
import org.eclipse.smarthome.core.thing.binding.ThingActionsScope;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.openhab.binding.lutron.internal.handler.DimmerHandler;
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link DimmerActions} defines thing actions for DimmerHandler.
*
* @author Bob Adair - Initial contribution
*/
@ThingActionsScope(name = "lutron")
@NonNullByDefault
public class DimmerActions implements ThingActions, IDimmerActions {
private final Logger logger = LoggerFactory.getLogger(DimmerActions.class);

private @Nullable DimmerHandler handler;

public DimmerActions() {
logger.trace("Lutron Dimmer actions service created");
}

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof DimmerHandler) {
this.handler = (DimmerHandler) handler;
}
}

@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}

/**
* The setLevel dimmer thing action
*/
@Override
@RuleAction(label = "setLevel", description = "Send set level command with fade and delay times")
public void setLevel(
@ActionInput(name = "level", label = "Dimmer Level", description = "New dimmer level (0-100)") @Nullable Double level,
@ActionInput(name = "fadeTime", label = "Fade Time", description = "Time to fade to new level (seconds)") @Nullable Double fadeTime,
@ActionInput(name = "delayTime", label = "Delay Time", description = "Delay before starting fade (seconds)") @Nullable Double delayTime) {
DimmerHandler dimmerHandler = handler;
if (dimmerHandler == null) {
logger.debug("Handler not set for Dimmer thing actions.");
return;
}
if (level == null) {
logger.debug("Ignoring setLevel command due to null level value.");
return;
}
if (fadeTime == null) {
logger.debug("Ignoring setLevel command due to null value for fadeTime.");
return;
}
if (delayTime == null) {
logger.debug("Ignoring setLevel command due to null value for delayTime.");
return;
}

Double lightLevel = level;
if (lightLevel > 100.0) {
lightLevel = 100.0;
} else if (lightLevel < 0.0) {
lightLevel = 0.0;
}
try {
dimmerHandler.setLightLevel(new BigDecimal(lightLevel).setScale(2, BigDecimal.ROUND_HALF_UP),
new LutronDuration(fadeTime), new LutronDuration(delayTime));
} catch (IllegalArgumentException e) {
logger.debug("Ignoring setLevel command due to illegal argument exception: {}", e.getMessage());
}
}

/**
* Static setLevel method for Rules DSL backward compatibility
*/
public static void setLevel(@Nullable ThingActions actions, @Nullable Double level, @Nullable Double fadeTime,
@Nullable Double delayTime) {
invokeMethodOf(actions).setLevel(level, fadeTime, delayTime); // Replace when core issue #1536 is fixed
}

/**
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
* resolved.
*/
private static IDimmerActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(DimmerActions.class.getName())) {
if (actions instanceof IDimmerActions) {
return (IDimmerActions) actions;
} else {
return (IDimmerActions) Proxy.newProxyInstance(IDimmerActions.class.getClassLoader(),
new Class[] { IDimmerActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of DimmerActions");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 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.action;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* The {@link IDimmerActions} interface defines the interface for all thing actions supported by the dimmer thing.
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
* resolved.
*
* @author Bob Adair - Initial contribution
*
*/
@NonNullByDefault
public interface IDimmerActions {

public void setLevel(@Nullable Double level, @Nullable Double fadeTime, @Nullable Double delayTime);

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_LIGHTLEVEL;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;

import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.library.types.PercentType;
Expand All @@ -23,9 +25,12 @@
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.thing.binding.ThingHandlerService;
import org.eclipse.smarthome.core.types.Command;
import org.openhab.binding.lutron.action.DimmerActions;
import org.openhab.binding.lutron.internal.config.DimmerConfig;
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -41,11 +46,18 @@ public class DimmerHandler extends LutronHandler {
private final Logger logger = LoggerFactory.getLogger(DimmerHandler.class);

private DimmerConfig config;
private LutronDuration fadeInTime;
private LutronDuration fadeOutTime;

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

@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(DimmerActions.class);
}

@Override
public int getIntegrationId() {
if (config == null) {
Expand All @@ -62,6 +74,8 @@ public void initialize() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
return;
}
fadeInTime = new LutronDuration(config.fadeInTime);
fadeOutTime = new LutronDuration(config.fadeOutTime);
logger.debug("Initializing Dimmer handler for integration ID {}", getIntegrationId());

initDeviceState();
Expand Down Expand Up @@ -94,16 +108,20 @@ public void handleCommand(ChannelUID channelUID, Command command) {
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
if (command instanceof Number) {
int level = ((Number) command).intValue();

output(ACTION_ZONELEVEL, level, 0.25);
} else if (command.equals(OnOffType.ON)) {
output(ACTION_ZONELEVEL, 100, this.config.fadeInTime);
output(ACTION_ZONELEVEL, 100, fadeInTime);
} else if (command.equals(OnOffType.OFF)) {
output(ACTION_ZONELEVEL, 0, this.config.fadeOutTime);
output(ACTION_ZONELEVEL, 0, fadeOutTime);
}
}
}

public void setLightLevel(BigDecimal level, LutronDuration fade, LutronDuration delay) {
int intLevel = level.intValue();
output(ACTION_ZONELEVEL, intLevel, fade, delay);
}

@Override
public void handleUpdate(LutronCommandType type, String... parameters) {
if (type == LutronCommandType.OUTPUT && parameters.length > 1
Expand Down
Loading

0 comments on commit c9e4ec8

Please sign in to comment.