Skip to content

Commit b217ed3

Browse files
committed
implement support for thing actions
So far only transition time for color channels is supported Signed-off-by: Thomas Weißschuh <[email protected]>
1 parent 9667b90 commit b217ed3

20 files changed

+302
-87
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.openhab.binding.zigbee;
2+
3+
import org.eclipse.jdt.annotation.NonNullByDefault;
4+
5+
@NonNullByDefault
6+
public interface ZigBeeCommandParameter<T> {
7+
String getName();
8+
Class<T> getType();
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.openhab.binding.zigbee;
2+
3+
import org.eclipse.jdt.annotation.NonNullByDefault;
4+
import org.openhab.binding.zigbee.internal.ZigBeeCommandParameterImpl;
5+
import org.openhab.binding.zigbee.internal.ZigBeeCommandParametersImpl;
6+
7+
import java.util.Collection;
8+
import java.util.Optional;
9+
10+
@NonNullByDefault
11+
public interface ZigBeeCommandParameters {
12+
static ZigBeeCommandParameters empty() {
13+
return new ZigBeeCommandParametersImpl();
14+
}
15+
16+
<T> ZigBeeCommandParameters add(final ZigBeeCommandParameter<T> param, final T value);
17+
<T> Optional<T> get(final ZigBeeCommandParameter<T> param);
18+
Collection<ZigBeeCommandParameter<?>> setParameters();
19+
20+
ZigBeeCommandParameter<Integer> TRANSITION_TIME =
21+
new ZigBeeCommandParameterImpl<>(Integer.class, "transitionTime");
22+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.openhab.binding.zigbee;
2+
3+
import org.eclipse.jdt.annotation.NonNullByDefault;
4+
import org.eclipse.jdt.annotation.Nullable;
5+
import org.eclipse.smarthome.core.thing.ChannelUID;
6+
import org.eclipse.smarthome.core.thing.binding.ThingActions;
7+
import org.eclipse.smarthome.core.thing.binding.ThingActionsScope;
8+
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
9+
import org.eclipse.smarthome.core.types.Command;
10+
import org.openhab.binding.zigbee.handler.ZigBeeThingHandler;
11+
import org.openhab.core.automation.annotation.ActionInput;
12+
import org.openhab.core.automation.annotation.RuleAction;
13+
14+
import static org.eclipse.jdt.annotation.Checks.requireNonNull;
15+
16+
@SuppressWarnings("unused")
17+
@ThingActionsScope(name="zigbee")
18+
@NonNullByDefault
19+
public final class ZigBeeThingActions implements ThingActions {
20+
private @Nullable ZigBeeThingHandler handler;
21+
22+
@Override
23+
public void setThingHandler(@Nullable final ThingHandler handler) {
24+
this.handler = (ZigBeeThingHandler) handler;
25+
}
26+
27+
@Override
28+
public @Nullable ThingHandler getThingHandler() {
29+
return handler;
30+
}
31+
32+
@RuleAction(label = "sendCommand")
33+
public void sendCommand(
34+
@ActionInput(name = "channelId", required = true) final String channelId,
35+
@ActionInput(name = "command", required = true) final Command command,
36+
@ActionInput(name = "params") @Nullable final ZigBeeCommandParameters params
37+
) {
38+
handleCommand(getChannel(channelId), command, params != null ? params : ZigBeeCommandParameters.empty());
39+
}
40+
41+
private void handleCommand(final ChannelUID channel, final Command command, final ZigBeeCommandParameters params) {
42+
requireNonNull(handler).handleCommand(channel, command, params);
43+
}
44+
45+
private ChannelUID getChannel(final String channelId) {
46+
return new ChannelUID(requireNonNull(handler).getThing().getUID(), channelId);
47+
}
48+
}

org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/converter/ZigBeeBaseChannelConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import java.math.BigDecimal;
1616
import java.math.RoundingMode;
17+
import java.util.Collections;
1718
import java.util.HashMap;
1819
import java.util.List;
1920
import java.util.Map;
@@ -34,6 +35,7 @@
3435
import org.eclipse.smarthome.core.types.State;
3536
import org.eclipse.smarthome.core.types.StateDescription;
3637
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
38+
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
3739
import org.openhab.binding.zigbee.handler.ZigBeeCoordinatorHandler;
3840
import org.openhab.binding.zigbee.handler.ZigBeeThingHandler;
3941
import org.slf4j.Logger;
@@ -251,7 +253,7 @@ public void handleRefresh() {
251253
*
252254
* @param command the {@link Command} to send
253255
*/
254-
public void handleCommand(final Command command) {
256+
public void handleCommand(final Command command, final ZigBeeCommandParameters params) {
255257
// Overridable if a channel can be commanded
256258
}
257259

org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/handler/ZigBeeThingHandler.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
4949
import org.eclipse.smarthome.core.thing.ThingStatusInfo;
5050
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
51+
import org.eclipse.smarthome.core.thing.binding.ThingHandlerService;
5152
import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
5253
import org.eclipse.smarthome.core.thing.binding.firmware.Firmware;
5354
import org.eclipse.smarthome.core.thing.binding.firmware.FirmwareUpdateHandler;
@@ -59,11 +60,15 @@
5960
import org.eclipse.smarthome.core.types.State;
6061
import org.eclipse.smarthome.core.types.StateDescription;
6162
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
63+
import org.openhab.binding.zigbee.ZigBeeCommandParameter;
64+
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
6265
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
6366
import org.openhab.binding.zigbee.converter.ZigBeeChannelConverterFactory;
6467
import org.openhab.binding.zigbee.discovery.ZigBeeNodePropertyDiscoverer;
68+
import org.openhab.binding.zigbee.internal.ZigBeeCommandParametersImpl;
6569
import org.openhab.binding.zigbee.internal.ZigBeeConfigDescriptionParameters;
6670
import org.openhab.binding.zigbee.internal.ZigBeeDeviceConfigHandler;
71+
import org.openhab.binding.zigbee.ZigBeeThingActions;
6772
import org.openhab.binding.zigbee.internal.converter.config.ZclClusterConfigFactory;
6873
import org.openhab.binding.zigbee.internal.converter.config.ZclClusterConfigHandler;
6974
import org.openhab.binding.zigbee.internal.converter.config.ZclReportingConfig;
@@ -694,6 +699,10 @@ public void handleConfigurationUpdate(Map<String, Object> configurationParameter
694699

695700
@Override
696701
public void handleCommand(final ChannelUID channelUID, final Command command) {
702+
handleCommand(channelUID, command, ZigBeeCommandParameters.empty());
703+
}
704+
705+
public void handleCommand(final ChannelUID channelUID, final Command command, final ZigBeeCommandParameters params) {
697706
logger.debug("{}: Command for channel {} --> {} [{}]", nodeIeeeAddress, channelUID, command,
698707
command.getClass().getSimpleName());
699708

@@ -718,7 +727,12 @@ public void run() {
718727
if (command == RefreshType.REFRESH) {
719728
handler.handleRefresh();
720729
} else {
721-
handler.handleCommand(command);
730+
ZigBeeCommandParametersImpl.UsageTracker parameterTracker = new ZigBeeCommandParametersImpl.UsageTracker(params);
731+
handler.handleCommand(command, parameterTracker);
732+
Set<ZigBeeCommandParameter<?>> unusedParams = parameterTracker.unusedParams();
733+
if (!unusedParams.isEmpty() && logger.isWarnEnabled()) {
734+
logger.warn("Handler {} did not use given parameters {}", handler, unusedParams);
735+
}
722736
}
723737
} catch (Exception e) {
724738
logger.debug("{}: Exception sending command to channel {}", nodeIeeeAddress, channelUID, e);
@@ -1006,4 +1020,9 @@ public boolean isUpdateExecutable() {
10061020
// Always allow the firmware to be updated
10071021
return true;
10081022
}
1023+
1024+
@Override
1025+
public Collection<Class<? extends ThingHandlerService>> getServices() {
1026+
return Collections.singleton(ZigBeeThingActions.class);
1027+
}
10091028
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.openhab.binding.zigbee.internal;
2+
3+
import org.eclipse.jdt.annotation.NonNullByDefault;
4+
import org.openhab.binding.zigbee.ZigBeeCommandParameter;
5+
6+
@NonNullByDefault
7+
public final class ZigBeeCommandParameterImpl<T> implements ZigBeeCommandParameter<T> {
8+
9+
private final String name;
10+
private final Class<T> klazz;
11+
12+
public ZigBeeCommandParameterImpl(final Class<T> klazz, final String name) {
13+
this.klazz = klazz;
14+
this.name = name;
15+
}
16+
17+
@Override
18+
public String getName() {
19+
return name;
20+
}
21+
22+
@Override
23+
public Class<T> getType() {
24+
return klazz;
25+
}
26+
27+
@Override
28+
public String toString() {
29+
return name;
30+
}
31+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package org.openhab.binding.zigbee.internal;
2+
3+
import org.eclipse.jdt.annotation.NonNullByDefault;
4+
import org.openhab.binding.zigbee.ZigBeeCommandParameter;
5+
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import java.util.*;
10+
11+
@NonNullByDefault
12+
public class ZigBeeCommandParametersImpl implements ZigBeeCommandParameters {
13+
private static final Logger logger = LoggerFactory.getLogger(ZigBeeCommandParametersImpl.class);
14+
15+
private final Map<ZigBeeCommandParameter<?>, Object> params = new HashMap<>();
16+
17+
@Override
18+
public <T> ZigBeeCommandParameters add(final ZigBeeCommandParameter<T> param, final T value) {
19+
params.put(param, value);
20+
return this;
21+
}
22+
23+
@Override
24+
public <T> Optional<T> get(final ZigBeeCommandParameter<T> param) {
25+
Object v = params.get(param);
26+
if (v == null) {
27+
return Optional.empty();
28+
} else if (!param.getType().isInstance(param)) {
29+
logger.debug("Can not retrieve param {}: object of type {} ({}) can not be casted to {}",
30+
param.getName(), param.getClass(), param, param.getType().getName()
31+
);
32+
return Optional.empty();
33+
} else {
34+
return Optional.of(param.getType().cast(param));
35+
}
36+
}
37+
38+
@Override
39+
public Collection<ZigBeeCommandParameter<?>> setParameters() {
40+
return Collections.unmodifiableSet(params.keySet());
41+
}
42+
43+
public static class UsageTracker implements ZigBeeCommandParameters {
44+
private final Set<ZigBeeCommandParameter<?>> usedParams = new HashSet<>();
45+
private final ZigBeeCommandParameters delegate;
46+
47+
public UsageTracker(ZigBeeCommandParameters delegate) {
48+
this.delegate = delegate;
49+
}
50+
51+
public Set<ZigBeeCommandParameter<?>> unusedParams() {
52+
Set<ZigBeeCommandParameter<?>> unusedParams = new HashSet<>(setParameters());
53+
unusedParams.removeAll(usedParams);
54+
return Collections.unmodifiableSet(unusedParams);
55+
}
56+
57+
@Override
58+
public <T> ZigBeeCommandParameters add(ZigBeeCommandParameter<T> param, T value) {
59+
return delegate.add(param, value);
60+
}
61+
62+
@Override
63+
public <T> Optional<T> get(ZigBeeCommandParameter<T> param) {
64+
usedParams.add(param);
65+
return delegate.get(param);
66+
}
67+
68+
@Override
69+
public Collection<ZigBeeCommandParameter<?>> setParameters() {
70+
return delegate.setParameters();
71+
}
72+
}
73+
}

org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterColorColor.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.eclipse.smarthome.core.types.Command;
3535
import org.eclipse.smarthome.core.types.UnDefType;
3636
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
37+
import org.openhab.binding.zigbee.ZigBeeCommandParameters;
3738
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
3839
import org.openhab.binding.zigbee.internal.converter.config.ZclLevelControlConfig;
3940
import org.slf4j.Logger;
@@ -274,14 +275,14 @@ public void handleRefresh() {
274275
clusterColorControl.getColorMode(0);
275276
}
276277

277-
private void changeOnOff(OnOffType onoff) throws InterruptedException, ExecutionException {
278+
private void changeOnOff(OnOffType onoff, int transitionTime) throws InterruptedException, ExecutionException {
278279
boolean on = onoff == OnOffType.ON;
279280

280281
if (clusterOnOff == null) {
281282
if (clusterLevelControl == null) {
282283
logger.warn("{}: ignoring on/off command", endpoint.getIeeeAddress());
283284
} else {
284-
changeBrightness(on ? PercentType.HUNDRED : PercentType.ZERO);
285+
changeBrightness(on ? PercentType.HUNDRED : PercentType.ZERO, transitionTime);
285286
}
286287
return;
287288
}
@@ -293,12 +294,12 @@ private void changeOnOff(OnOffType onoff) throws InterruptedException, Execution
293294
}
294295
}
295296

296-
private void changeBrightness(PercentType brightness) throws InterruptedException, ExecutionException {
297+
private void changeBrightness(PercentType brightness, int transitionTime) throws InterruptedException, ExecutionException {
297298
if (clusterLevelControl == null) {
298299
if (clusterOnOff == null) {
299300
logger.warn("{}: ignoring brightness command", endpoint.getIeeeAddress());
300301
} else {
301-
changeOnOff(brightness.intValue() == 0 ? OnOffType.OFF : OnOffType.ON);
302+
changeOnOff(brightness.intValue() == 0 ? OnOffType.OFF : OnOffType.ON, transitionTime);
302303
}
303304
return;
304305
}
@@ -309,55 +310,56 @@ private void changeBrightness(PercentType brightness) throws InterruptedExceptio
309310
if (brightness.equals(PercentType.ZERO)) {
310311
clusterOnOff.offCommand();
311312
} else {
312-
clusterLevelControl.moveToLevelWithOnOffCommand(level, configLevelControl.getDefaultTransitionTime())
313+
clusterLevelControl.moveToLevelWithOnOffCommand(level, transitionTime)
313314
.get();
314315
}
315316
} else {
316-
clusterLevelControl.moveToLevelCommand(level, configLevelControl.getDefaultTransitionTime()).get();
317+
clusterLevelControl.moveToLevelCommand(level, transitionTime).get();
317318
}
318319
}
319320

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

324325
clusterColorControl
325-
.moveToHueAndSaturationCommand(hue, saturation, configLevelControl.getDefaultTransitionTime()).get();
326+
.moveToHueAndSaturationCommand(hue, saturation, transitionTime).get();
326327
}
327328

328-
private void changeColorXY(HSBType color) throws InterruptedException, ExecutionException {
329+
private void changeColorXY(HSBType color, int transitionTime) throws InterruptedException, ExecutionException {
329330
PercentType xy[] = color.toXY();
330331

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

336-
clusterColorControl.moveToColorCommand(x, y, configLevelControl.getDefaultTransitionTime()).get();
337+
clusterColorControl.moveToColorCommand(x, y, transitionTime).get();
337338
}
338339

339340
@Override
340-
public void handleCommand(final Command command) {
341+
public void handleCommand(final Command command, final ZigBeeCommandParameters params) {
342+
int transitionTime = params.get(ZigBeeCommandParameters.TRANSITION_TIME).orElseGet(configLevelControl::getDefaultTransitionTime);
341343
try {
342344
if (command instanceof HSBType) {
343345
HSBType color = (HSBType) command;
344346
PercentType brightness = color.getBrightness();
345347

346-
changeBrightness(brightness);
348+
changeBrightness(brightness, transitionTime);
347349

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

352354
if (supportsHue) {
353-
changeColorHueSaturation(color);
355+
changeColorHueSaturation(color, transitionTime);
354356
} else {
355-
changeColorXY(color);
357+
changeColorXY(color, transitionTime);
356358
}
357359
} else if (command instanceof PercentType) {
358-
changeBrightness((PercentType) command);
360+
changeBrightness((PercentType) command, transitionTime);
359361
} else if (command instanceof OnOffType) {
360-
changeOnOff((OnOffType) command);
362+
changeOnOff((OnOffType) command, transitionTime);
361363
}
362364
} catch (InterruptedException | ExecutionException e) {
363365
logger.warn("{}: Exception processing command", endpoint.getIeeeAddress(), e);

0 commit comments

Comments
 (0)