Skip to content

Commit

Permalink
implement support for thing actions
Browse files Browse the repository at this point in the history
So far only transition time for color channels is supported

Signed-off-by: Thomas Weißschuh <thomas@weissschuh.net>
  • Loading branch information
t-8ch committed Dec 26, 2019
1 parent 9667b90 commit b217ed3
Show file tree
Hide file tree
Showing 20 changed files with 302 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.openhab.binding.zigbee;

import org.eclipse.jdt.annotation.NonNullByDefault;

@NonNullByDefault
public interface ZigBeeCommandParameter<T> {
String getName();
Class<T> getType();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.openhab.binding.zigbee;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.zigbee.internal.ZigBeeCommandParameterImpl;
import org.openhab.binding.zigbee.internal.ZigBeeCommandParametersImpl;

import java.util.Collection;
import java.util.Optional;

@NonNullByDefault
public interface ZigBeeCommandParameters {
static ZigBeeCommandParameters empty() {
return new ZigBeeCommandParametersImpl();
}

<T> ZigBeeCommandParameters add(final ZigBeeCommandParameter<T> param, final T value);
<T> Optional<T> get(final ZigBeeCommandParameter<T> param);
Collection<ZigBeeCommandParameter<?>> setParameters();

ZigBeeCommandParameter<Integer> TRANSITION_TIME =
new ZigBeeCommandParameterImpl<>(Integer.class, "transitionTime");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.openhab.binding.zigbee;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.ChannelUID;
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.eclipse.smarthome.core.types.Command;
import org.openhab.binding.zigbee.handler.ZigBeeThingHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;

import static org.eclipse.jdt.annotation.Checks.requireNonNull;

@SuppressWarnings("unused")
@ThingActionsScope(name="zigbee")
@NonNullByDefault
public final class ZigBeeThingActions implements ThingActions {
private @Nullable ZigBeeThingHandler handler;

@Override
public void setThingHandler(@Nullable final ThingHandler handler) {
this.handler = (ZigBeeThingHandler) handler;
}

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

@RuleAction(label = "sendCommand")
public void sendCommand(
@ActionInput(name = "channelId", required = true) final String channelId,
@ActionInput(name = "command", required = true) final Command command,
@ActionInput(name = "params") @Nullable final ZigBeeCommandParameters params
) {
handleCommand(getChannel(channelId), command, params != null ? params : ZigBeeCommandParameters.empty());
}

private void handleCommand(final ChannelUID channel, final Command command, final ZigBeeCommandParameters params) {
requireNonNull(handler).handleCommand(channel, command, params);
}

private ChannelUID getChannel(final String channelId) {
return new ChannelUID(requireNonNull(handler).getThing().getUID(), channelId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -34,6 +35,7 @@
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.StateDescription;
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
import org.openhab.binding.zigbee.handler.ZigBeeCoordinatorHandler;
import org.openhab.binding.zigbee.handler.ZigBeeThingHandler;
import org.slf4j.Logger;
Expand Down Expand Up @@ -251,7 +253,7 @@ public void handleRefresh() {
*
* @param command the {@link Command} to send
*/
public void handleCommand(final Command command) {
public void handleCommand(final Command command, final ZigBeeCommandParameters params) {
// Overridable if a channel can be commanded
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.ThingStatusInfo;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandlerService;
import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
import org.eclipse.smarthome.core.thing.binding.firmware.Firmware;
import org.eclipse.smarthome.core.thing.binding.firmware.FirmwareUpdateHandler;
Expand All @@ -59,11 +60,15 @@
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.StateDescription;
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
import org.openhab.binding.zigbee.ZigBeeCommandParameter;
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
import org.openhab.binding.zigbee.converter.ZigBeeChannelConverterFactory;
import org.openhab.binding.zigbee.discovery.ZigBeeNodePropertyDiscoverer;
import org.openhab.binding.zigbee.internal.ZigBeeCommandParametersImpl;
import org.openhab.binding.zigbee.internal.ZigBeeConfigDescriptionParameters;
import org.openhab.binding.zigbee.internal.ZigBeeDeviceConfigHandler;
import org.openhab.binding.zigbee.ZigBeeThingActions;
import org.openhab.binding.zigbee.internal.converter.config.ZclClusterConfigFactory;
import org.openhab.binding.zigbee.internal.converter.config.ZclClusterConfigHandler;
import org.openhab.binding.zigbee.internal.converter.config.ZclReportingConfig;
Expand Down Expand Up @@ -694,6 +699,10 @@ public void handleConfigurationUpdate(Map<String, Object> configurationParameter

@Override
public void handleCommand(final ChannelUID channelUID, final Command command) {
handleCommand(channelUID, command, ZigBeeCommandParameters.empty());
}

public void handleCommand(final ChannelUID channelUID, final Command command, final ZigBeeCommandParameters params) {
logger.debug("{}: Command for channel {} --> {} [{}]", nodeIeeeAddress, channelUID, command,
command.getClass().getSimpleName());

Expand All @@ -718,7 +727,12 @@ public void run() {
if (command == RefreshType.REFRESH) {
handler.handleRefresh();
} else {
handler.handleCommand(command);
ZigBeeCommandParametersImpl.UsageTracker parameterTracker = new ZigBeeCommandParametersImpl.UsageTracker(params);
handler.handleCommand(command, parameterTracker);
Set<ZigBeeCommandParameter<?>> unusedParams = parameterTracker.unusedParams();
if (!unusedParams.isEmpty() && logger.isWarnEnabled()) {
logger.warn("Handler {} did not use given parameters {}", handler, unusedParams);
}
}
} catch (Exception e) {
logger.debug("{}: Exception sending command to channel {}", nodeIeeeAddress, channelUID, e);
Expand Down Expand Up @@ -1006,4 +1020,9 @@ public boolean isUpdateExecutable() {
// Always allow the firmware to be updated
return true;
}

@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ZigBeeThingActions.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.openhab.binding.zigbee.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.zigbee.ZigBeeCommandParameter;

@NonNullByDefault
public final class ZigBeeCommandParameterImpl<T> implements ZigBeeCommandParameter<T> {

private final String name;
private final Class<T> klazz;

public ZigBeeCommandParameterImpl(final Class<T> klazz, final String name) {
this.klazz = klazz;
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public Class<T> getType() {
return klazz;
}

@Override
public String toString() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.openhab.binding.zigbee.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.zigbee.ZigBeeCommandParameter;
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

@NonNullByDefault
public class ZigBeeCommandParametersImpl implements ZigBeeCommandParameters {
private static final Logger logger = LoggerFactory.getLogger(ZigBeeCommandParametersImpl.class);

private final Map<ZigBeeCommandParameter<?>, Object> params = new HashMap<>();

@Override
public <T> ZigBeeCommandParameters add(final ZigBeeCommandParameter<T> param, final T value) {
params.put(param, value);
return this;
}

@Override
public <T> Optional<T> get(final ZigBeeCommandParameter<T> param) {
Object v = params.get(param);
if (v == null) {
return Optional.empty();
} else if (!param.getType().isInstance(param)) {
logger.debug("Can not retrieve param {}: object of type {} ({}) can not be casted to {}",
param.getName(), param.getClass(), param, param.getType().getName()
);
return Optional.empty();
} else {
return Optional.of(param.getType().cast(param));
}
}

@Override
public Collection<ZigBeeCommandParameter<?>> setParameters() {
return Collections.unmodifiableSet(params.keySet());
}

public static class UsageTracker implements ZigBeeCommandParameters {
private final Set<ZigBeeCommandParameter<?>> usedParams = new HashSet<>();
private final ZigBeeCommandParameters delegate;

public UsageTracker(ZigBeeCommandParameters delegate) {
this.delegate = delegate;
}

public Set<ZigBeeCommandParameter<?>> unusedParams() {
Set<ZigBeeCommandParameter<?>> unusedParams = new HashSet<>(setParameters());
unusedParams.removeAll(usedParams);
return Collections.unmodifiableSet(unusedParams);
}

@Override
public <T> ZigBeeCommandParameters add(ZigBeeCommandParameter<T> param, T value) {
return delegate.add(param, value);
}

@Override
public <T> Optional<T> get(ZigBeeCommandParameter<T> param) {
usedParams.add(param);
return delegate.get(param);
}

@Override
public Collection<ZigBeeCommandParameter<?>> setParameters() {
return delegate.setParameters();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.UnDefType;
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
import org.openhab.binding.zigbee.internal.converter.config.ZclLevelControlConfig;
import org.slf4j.Logger;
Expand Down Expand Up @@ -274,14 +275,14 @@ public void handleRefresh() {
clusterColorControl.getColorMode(0);
}

private void changeOnOff(OnOffType onoff) throws InterruptedException, ExecutionException {
private void changeOnOff(OnOffType onoff, int transitionTime) throws InterruptedException, ExecutionException {
boolean on = onoff == OnOffType.ON;

if (clusterOnOff == null) {
if (clusterLevelControl == null) {
logger.warn("{}: ignoring on/off command", endpoint.getIeeeAddress());
} else {
changeBrightness(on ? PercentType.HUNDRED : PercentType.ZERO);
changeBrightness(on ? PercentType.HUNDRED : PercentType.ZERO, transitionTime);
}
return;
}
Expand All @@ -293,12 +294,12 @@ private void changeOnOff(OnOffType onoff) throws InterruptedException, Execution
}
}

private void changeBrightness(PercentType brightness) throws InterruptedException, ExecutionException {
private void changeBrightness(PercentType brightness, int transitionTime) throws InterruptedException, ExecutionException {
if (clusterLevelControl == null) {
if (clusterOnOff == null) {
logger.warn("{}: ignoring brightness command", endpoint.getIeeeAddress());
} else {
changeOnOff(brightness.intValue() == 0 ? OnOffType.OFF : OnOffType.ON);
changeOnOff(brightness.intValue() == 0 ? OnOffType.OFF : OnOffType.ON, transitionTime);
}
return;
}
Expand All @@ -309,55 +310,56 @@ private void changeBrightness(PercentType brightness) throws InterruptedExceptio
if (brightness.equals(PercentType.ZERO)) {
clusterOnOff.offCommand();
} else {
clusterLevelControl.moveToLevelWithOnOffCommand(level, configLevelControl.getDefaultTransitionTime())
clusterLevelControl.moveToLevelWithOnOffCommand(level, transitionTime)
.get();
}
} else {
clusterLevelControl.moveToLevelCommand(level, configLevelControl.getDefaultTransitionTime()).get();
clusterLevelControl.moveToLevelCommand(level, transitionTime).get();
}
}

private void changeColorHueSaturation(HSBType color) throws InterruptedException, ExecutionException {
private void changeColorHueSaturation(HSBType color, int transitionTime) throws InterruptedException, ExecutionException {
int hue = (int) (color.getHue().floatValue() * 254.0f / 360.0f + 0.5f);
int saturation = percentToLevel(color.getSaturation());

clusterColorControl
.moveToHueAndSaturationCommand(hue, saturation, configLevelControl.getDefaultTransitionTime()).get();
.moveToHueAndSaturationCommand(hue, saturation, transitionTime).get();
}

private void changeColorXY(HSBType color) throws InterruptedException, ExecutionException {
private void changeColorXY(HSBType color, int transitionTime) throws InterruptedException, ExecutionException {
PercentType xy[] = color.toXY();

logger.debug("{}: Change Color HSV ({}, {}, {}) -> XY ({}, {})", endpoint.getIeeeAddress(), color.getHue(),
color.getSaturation(), color.getBrightness(), xy[0], xy[1]);
int x = (int) (xy[0].floatValue() / 100.0f * 65536.0f + 0.5f); // up to 65279
int y = (int) (xy[1].floatValue() / 100.0f * 65536.0f + 0.5f); // up to 65279

clusterColorControl.moveToColorCommand(x, y, configLevelControl.getDefaultTransitionTime()).get();
clusterColorControl.moveToColorCommand(x, y, transitionTime).get();
}

@Override
public void handleCommand(final Command command) {
public void handleCommand(final Command command, final ZigBeeCommandParameters params) {
int transitionTime = params.get(ZigBeeCommandParameters.TRANSITION_TIME).orElseGet(configLevelControl::getDefaultTransitionTime);
try {
if (command instanceof HSBType) {
HSBType color = (HSBType) command;
PercentType brightness = color.getBrightness();

changeBrightness(brightness);
changeBrightness(brightness, transitionTime);

if (delayedColorChange && brightness.intValue() != lastHSB.getBrightness().intValue()) {
Thread.sleep(1100);
}

if (supportsHue) {
changeColorHueSaturation(color);
changeColorHueSaturation(color, transitionTime);
} else {
changeColorXY(color);
changeColorXY(color, transitionTime);
}
} else if (command instanceof PercentType) {
changeBrightness((PercentType) command);
changeBrightness((PercentType) command, transitionTime);
} else if (command instanceof OnOffType) {
changeOnOff((OnOffType) command);
changeOnOff((OnOffType) command, transitionTime);
}
} catch (InterruptedException | ExecutionException e) {
logger.warn("{}: Exception processing command", endpoint.getIeeeAddress(), e);
Expand Down
Loading

0 comments on commit b217ed3

Please sign in to comment.