2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.deconz.internal.handler;
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
19 import java.util.stream.Collectors;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.binding.deconz.internal.DeconzDynamicCommandDescriptionProvider;
23 import org.openhab.binding.deconz.internal.Util;
24 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
25 import org.openhab.binding.deconz.internal.dto.GroupAction;
26 import org.openhab.binding.deconz.internal.dto.GroupMessage;
27 import org.openhab.binding.deconz.internal.dto.GroupState;
28 import org.openhab.binding.deconz.internal.dto.Scene;
29 import org.openhab.binding.deconz.internal.types.ResourceType;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.HSBType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.Gson;
47 * This group thing doesn't establish any connections, that is done by the bridge Thing.
49 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
50 * and registers to the websocket connection as a listener.
52 * @author Jan N. Klug - Initial contribution
55 public class GroupThingHandler extends DeconzBaseThingHandler {
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_LIGHTGROUP);
57 private final Logger logger = LoggerFactory.getLogger(GroupThingHandler.class);
58 private final DeconzDynamicCommandDescriptionProvider commandDescriptionProvider;
60 private Map<String, String> scenes = Map.of();
61 private GroupState groupStateCache = new GroupState();
62 private String colorMode = "";
64 public GroupThingHandler(Thing thing, Gson gson,
65 DeconzDynamicCommandDescriptionProvider commandDescriptionProvider) {
66 super(thing, gson, ResourceType.GROUPS);
67 this.commandDescriptionProvider = commandDescriptionProvider;
71 public void initialize() {
72 ThingConfig thingConfig = getConfigAs(ThingConfig.class);
73 colorMode = thingConfig.colormode;
79 public void handleCommand(ChannelUID channelUID, Command command) {
80 String channelId = channelUID.getId();
82 GroupAction newGroupAction = new GroupAction();
86 if (command instanceof RefreshType) {
87 valueUpdated(channelUID.getId(), groupStateCache);
92 if (command instanceof StringType) {
93 newGroupAction.alert = command.toString();
99 if (command instanceof HSBType) {
100 HSBType hsbCommand = (HSBType) command;
101 // XY color is the implicit default: Use XY color mode if i) no color mode is set or ii) if the bulb
102 // is in CT mode or iii) already in XY mode. Only if the bulb is in HS mode, use this one.
103 if ("hs".equals(colorMode)) {
104 newGroupAction.hue = (int) (hsbCommand.getHue().doubleValue() * HUE_FACTOR);
105 newGroupAction.sat = Util.fromPercentType(hsbCommand.getSaturation());
107 PercentType[] xy = hsbCommand.toXY();
109 logger.warn("Failed to convert {} to xy-values", command);
111 newGroupAction.xy = new double[] { xy[0].doubleValue() / 100.0, xy[1].doubleValue() / 100.0 };
113 } else if (command instanceof PercentType) {
114 newGroupAction.bri = Util.fromPercentType((PercentType) command);
115 } else if (command instanceof DecimalType) {
116 newGroupAction.bri = ((DecimalType) command).intValue();
117 } else if (command instanceof OnOffType) {
118 newGroupAction.on = OnOffType.ON.equals(command);
123 case CHANNEL_COLOR_TEMPERATURE:
124 if (command instanceof DecimalType) {
125 int miredValue = Util.kelvinToMired(((DecimalType) command).intValue());
126 newGroupAction.ct = Util.constrainToRange(miredValue, ZCL_CT_MIN, ZCL_CT_MAX);
132 if (command instanceof StringType) {
133 String sceneId = scenes.get(command.toString());
134 if (sceneId != null) {
135 sendCommand(null, command, channelUID, "scenes/" + sceneId + "/recall", null);
137 logger.debug("Ignoring command {} for {}, scene is not found in available scenes: {}", command,
146 Integer bri = newGroupAction.bri;
148 newGroupAction.on = (bri > 0);
151 sendCommand(newGroupAction, command, channelUID, null);
155 protected void processStateResponse(DeconzBaseMessage stateResponse) {
156 if (stateResponse instanceof GroupMessage) {
157 GroupMessage groupMessage = (GroupMessage) stateResponse;
158 scenes = groupMessage.scenes.stream().collect(Collectors.toMap(scene -> scene.name, scene -> scene.id));
159 ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_SCENE);
160 commandDescriptionProvider.setCommandOptions(channelUID,
161 groupMessage.scenes.stream().map(Scene::toCommandOption).collect(Collectors.toList()));
164 messageReceived(config.id, stateResponse);
167 private void valueUpdated(String channelId, GroupState newState) {
170 updateState(channelId, OnOffType.from(newState.all_on));
173 updateState(channelId, OnOffType.from(newState.any_on));
180 public void messageReceived(String sensorID, DeconzBaseMessage message) {
181 if (message instanceof GroupMessage) {
182 GroupMessage groupMessage = (GroupMessage) message;
183 logger.trace("{} received {}", thing.getUID(), groupMessage);
184 GroupState groupState = groupMessage.state;
185 if (groupState != null) {
186 updateStatus(ThingStatus.ONLINE);
187 thing.getChannels().stream().map(c -> c.getUID().getId()).forEach(c -> valueUpdated(c, groupState));
188 groupStateCache = groupState;
190 GroupAction groupAction = groupMessage.action;
191 if (groupAction != null) {
192 if (colorMode.isEmpty()) {
193 String cmode = groupAction.colormode;
194 if (cmode != null && ("hs".equals(cmode) || "xy".equals(cmode))) {
195 // only set the color mode if it is hs or xy, not ct