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.hue.internal.console;
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
17 import java.util.Arrays;
18 import java.util.List;
20 import java.util.Objects;
21 import java.util.Optional;
23 import java.util.TreeMap;
24 import java.util.stream.Collectors;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
29 import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
30 import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference;
31 import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
32 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
33 import org.openhab.binding.hue.internal.exceptions.ApiException;
34 import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
35 import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
36 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
37 import org.openhab.binding.hue.internal.handler.HueGroupHandler;
38 import org.openhab.core.io.console.Console;
39 import org.openhab.core.io.console.ConsoleCommandCompleter;
40 import org.openhab.core.io.console.StringsCompleter;
41 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
42 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingRegistry;
45 import org.openhab.core.thing.ThingUID;
46 import org.openhab.core.thing.binding.ThingHandler;
47 import org.osgi.service.component.annotations.Activate;
48 import org.osgi.service.component.annotations.Component;
49 import org.osgi.service.component.annotations.Reference;
52 * The {@link HueCommandExtension} is responsible for handling console commands
54 * @author Laurent Garnier - Initial contribution
55 * @author Andrew Fiddian-Green - Added CLIP 2 console commands
59 @Component(service = ConsoleCommandExtension.class)
60 public class HueCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
62 private static final String FMT_BRIDGE = "Bridge %s \"Philips Hue Bridge\" [ipAddress=\"%s\", applicationKey=\"%s\"] {";
63 private static final String FMT_THING = " Thing %s %s \"%s\" [resourceId=\"%s\"] // %s idV1:%s";
64 private static final String FMT_COMMENT = " // %s things";
65 private static final String FMT_APPKEY = " - Application key: %s";
66 private static final String FMT_SCENE = " %s '%s'";
68 private static final String USER_NAME = "username";
69 private static final String SCENES = "scenes";
70 private static final String APPLICATION_KEY = "applicationkey";
71 private static final String THINGS = "things";
73 private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(USER_NAME, SCENES), false);
75 private static final StringsCompleter SUBCMD_COMPLETER_2 = new StringsCompleter(
76 List.of(APPLICATION_KEY, THINGS, SCENES), false);
78 private static final StringsCompleter SCENES_COMPLETER = new StringsCompleter(List.of(SCENES), false);
80 private final ThingRegistry thingRegistry;
82 public static final Set<ResourceType> SUPPORTED_RESOURCES = Set.of(ResourceType.DEVICE, ResourceType.ROOM,
83 ResourceType.ZONE, ResourceType.BRIDGE_HOME);
86 public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
87 super("hue", "Interact with the Hue binding.");
88 this.thingRegistry = thingRegistry;
92 public void execute(String[] args, Console console) {
93 if (args.length == 2) {
94 Thing thing = getThing(args[0]);
95 ThingHandler thingHandler = null;
96 HueBridgeHandler bridgeHandler = null;
97 HueGroupHandler groupHandler = null;
98 Clip2BridgeHandler clip2BridgeHandler = null;
100 thingHandler = thing.getHandler();
101 if (thingHandler instanceof Clip2BridgeHandler) {
102 clip2BridgeHandler = (Clip2BridgeHandler) thingHandler;
103 } else if (thingHandler instanceof HueBridgeHandler) {
104 bridgeHandler = (HueBridgeHandler) thingHandler;
105 } else if (thingHandler instanceof HueGroupHandler) {
106 groupHandler = (HueGroupHandler) thingHandler;
110 console.println("Bad thing id '" + args[0] + "'");
111 } else if (thingHandler == null) {
112 console.println("No handler initialized for the thingUID '" + args[0] + "'");
113 } else if (bridgeHandler == null && groupHandler == null && clip2BridgeHandler == null) {
114 console.println("'" + args[0] + "' is neither a Hue BridgeUID nor a Hue groupThingUID");
116 if (bridgeHandler != null) {
119 String userName = bridgeHandler.getUserName();
120 console.println("Your user name is " + (userName != null ? userName : "undefined"));
123 bridgeHandler.listScenesForConsole().forEach(console::println);
126 } else if (groupHandler != null) {
129 groupHandler.listScenesForConsole().forEach(console::println);
132 } else if (clip2BridgeHandler != null) {
133 String applicationKey = clip2BridgeHandler.getApplicationKey();
134 String ipAddress = clip2BridgeHandler.getIpAddress();
135 String exception = "";
138 case APPLICATION_KEY:
139 console.println(String.format(FMT_APPKEY, applicationKey));
143 console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
145 List<Resource> scenes = clip2BridgeHandler
146 .getResources(new ResourceReference().setType(ResourceType.SCENE))
148 if (scenes.isEmpty()) {
149 console.println("no scenes found");
151 scenes.forEach(scene -> console
152 .println(String.format(FMT_SCENE, scene.getId(), scene.getName())));
154 } catch (ApiException | AssetNotLoadedException e) {
155 exception = String.format("%s: '%s'", e.getClass().getName(), e.getMessage());
156 } catch (InterruptedException e) {
158 console.println("}");
159 if (!exception.isBlank()) {
160 console.println(exception);
165 console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
167 for (ResourceType resourceType : SUPPORTED_RESOURCES) {
168 List<Resource> resources;
170 resources = clip2BridgeHandler
171 .getResources(new ResourceReference().setType(resourceType)).getResources();
172 } catch (ApiException | AssetNotLoadedException e) {
173 exception = String.format("%s: '%s'", e.getClass().getName(), e.getMessage());
175 } catch (InterruptedException e) {
178 if (!resources.isEmpty()) {
179 console.println(String.format(FMT_COMMENT, resourceType.toString()));
180 Map<String, String> lines = new TreeMap<>();
182 for (Resource resource : resources) {
183 MetaData metaData = resource.getMetaData();
184 if (Objects.nonNull(metaData)
185 && (metaData.getArchetype() == Archetype.BRIDGE_V2)) {
186 // do not list the bridge itself
189 String resourceId = resource.getId();
190 String idv1 = resource.getIdV1();
191 String thingLabel = resource.getName();
192 String comment = resource.getProductName();
193 String thingType = resourceType.name().toLowerCase();
194 String thingId = resourceId;
196 // special zone 'all lights'
197 if (resource.getType() == ResourceType.BRIDGE_HOME) {
198 thingLabel = clip2BridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
200 thingType = comment.toLowerCase();
203 Optional<Thing> legacyThingOptional = clip2BridgeHandler.getLegacyThing(idv1);
204 if (legacyThingOptional.isPresent()) {
205 Thing legacyThing = legacyThingOptional.get();
206 thingId = legacyThing.getUID().getId();
207 String legacyLabel = legacyThing.getLabel();
208 thingLabel = Objects.nonNull(legacyLabel) ? legacyLabel : thingLabel;
211 lines.put(thingLabel, String.format(FMT_THING, thingType, thingId, thingLabel,
212 resourceId, comment, idv1));
214 lines.entrySet().forEach(entry -> console.println(entry.getValue()));
217 console.println("}");
218 if (!exception.isBlank()) {
219 console.println(exception);
230 public List<String> getUsages() {
231 return Arrays.asList(new String[] { buildCommandUsage("<bridgeUID> " + USER_NAME, "show the user name"),
232 buildCommandUsage("<bridgeUID> " + APPLICATION_KEY, "show the API v2 application key"),
233 buildCommandUsage("<bridgeUID> " + SCENES, "list all the scenes with their id"),
234 buildCommandUsage("<bridgeUID> " + THINGS, "list all the API v2 device/room/zone things with their id"),
235 buildCommandUsage("<groupThingUID> " + SCENES, "list all the scenes from this group with their id") });
239 public @Nullable ConsoleCommandCompleter getCompleter() {
244 public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
245 if (cursorArgumentIndex <= 0) {
246 return new StringsCompleter(thingRegistry.getAll().stream()
247 .filter(t -> THING_TYPE_BRIDGE.equals(t.getThingTypeUID())
248 || THING_TYPE_BRIDGE_API2.equals(t.getThingTypeUID())
249 || THING_TYPE_GROUP.equals(t.getThingTypeUID()))
250 .map(t -> t.getUID().getAsString()).collect(Collectors.toList()), true)
251 .complete(args, cursorArgumentIndex, cursorPosition, candidates);
252 } else if (cursorArgumentIndex == 1) {
253 Thing thing = getThing(args[0]);
254 if (thing != null && (THING_TYPE_BRIDGE.equals(thing.getThingTypeUID()))) {
255 return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
256 } else if (thing != null && (THING_TYPE_BRIDGE_API2.equals(thing.getThingTypeUID()))) {
257 return SUBCMD_COMPLETER_2.complete(args, cursorArgumentIndex, cursorPosition, candidates);
258 } else if (thing != null && THING_TYPE_GROUP.equals(thing.getThingTypeUID())) {
259 return SCENES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
265 private @Nullable Thing getThing(String uid) {
268 ThingUID thingUID = new ThingUID(uid);
269 thing = thingRegistry.get(thingUID);
270 } catch (IllegalArgumentException e) {