]> git.basschouten.com Git - openhab-addons.git/blob
5d13c84ef4d563fe25ce484d17e604ae3b169438
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hue.internal.console;
14
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.TreeMap;
24 import java.util.stream.Collectors;
25
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;
50
51 /**
52  * The {@link HueCommandExtension} is responsible for handling console commands
53  *
54  * @author Laurent Garnier - Initial contribution
55  * @author Andrew Fiddian-Green - Added CLIP 2 console commands
56  */
57
58 @NonNullByDefault
59 @Component(service = ConsoleCommandExtension.class)
60 public class HueCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
61
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'";
67
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";
72
73     private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(USER_NAME, SCENES), false);
74
75     private static final StringsCompleter SUBCMD_COMPLETER_2 = new StringsCompleter(
76             List.of(APPLICATION_KEY, THINGS, SCENES), false);
77
78     private static final StringsCompleter SCENES_COMPLETER = new StringsCompleter(List.of(SCENES), false);
79
80     private final ThingRegistry thingRegistry;
81
82     public static final Set<ResourceType> SUPPORTED_RESOURCES = Set.of(ResourceType.DEVICE, ResourceType.ROOM,
83             ResourceType.ZONE, ResourceType.BRIDGE_HOME);
84
85     @Activate
86     public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
87         super("hue", "Interact with the Hue binding.");
88         this.thingRegistry = thingRegistry;
89     }
90
91     @Override
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;
99             if (thing != 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;
107                 }
108             }
109             if (thing == null) {
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");
115             } else {
116                 if (bridgeHandler != null) {
117                     switch (args[1]) {
118                         case USER_NAME:
119                             String userName = bridgeHandler.getUserName();
120                             console.println("Your user name is " + (userName != null ? userName : "undefined"));
121                             return;
122                         case SCENES:
123                             bridgeHandler.listScenesForConsole().forEach(console::println);
124                             return;
125                     }
126                 } else if (groupHandler != null) {
127                     switch (args[1]) {
128                         case SCENES:
129                             groupHandler.listScenesForConsole().forEach(console::println);
130                             return;
131                     }
132                 } else if (clip2BridgeHandler != null) {
133                     String applicationKey = clip2BridgeHandler.getApplicationKey();
134                     String ipAddress = clip2BridgeHandler.getIpAddress();
135                     String exception = "";
136
137                     switch (args[1]) {
138                         case APPLICATION_KEY:
139                             console.println(String.format(FMT_APPKEY, applicationKey));
140                             return;
141
142                         case SCENES:
143                             console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
144                             try {
145                                 List<Resource> scenes = clip2BridgeHandler
146                                         .getResources(new ResourceReference().setType(ResourceType.SCENE))
147                                         .getResources();
148                                 if (scenes.isEmpty()) {
149                                     console.println("no scenes found");
150                                 } else {
151                                     scenes.forEach(scene -> console
152                                             .println(String.format(FMT_SCENE, scene.getId(), scene.getName())));
153                                 }
154                             } catch (ApiException | AssetNotLoadedException e) {
155                                 exception = String.format("%s: '%s'", e.getClass().getName(), e.getMessage());
156                             } catch (InterruptedException e) {
157                             }
158                             console.println("}");
159                             if (!exception.isBlank()) {
160                                 console.println(exception);
161                             }
162                             return;
163
164                         case THINGS:
165                             console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
166
167                             for (ResourceType resourceType : SUPPORTED_RESOURCES) {
168                                 List<Resource> resources;
169                                 try {
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());
174                                     break;
175                                 } catch (InterruptedException e) {
176                                     break;
177                                 }
178                                 if (!resources.isEmpty()) {
179                                     console.println(String.format(FMT_COMMENT, resourceType.toString()));
180                                     Map<String, String> lines = new TreeMap<>();
181
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
187                                             continue;
188                                         }
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;
195
196                                         // special zone 'all lights'
197                                         if (resource.getType() == ResourceType.BRIDGE_HOME) {
198                                             thingLabel = clip2BridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
199                                             comment = "Zone";
200                                             thingType = comment.toLowerCase();
201                                         }
202
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;
209                                         }
210
211                                         lines.put(thingLabel, String.format(FMT_THING, thingType, thingId, thingLabel,
212                                                 resourceId, comment, idv1));
213                                     }
214                                     lines.entrySet().forEach(entry -> console.println(entry.getValue()));
215                                 }
216                             }
217                             console.println("}");
218                             if (!exception.isBlank()) {
219                                 console.println(exception);
220                             }
221                             return;
222                     }
223                 }
224             }
225         }
226         printUsage(console);
227     }
228
229     @Override
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") });
236     }
237
238     @Override
239     public @Nullable ConsoleCommandCompleter getCompleter() {
240         return this;
241     }
242
243     @Override
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);
260             }
261         }
262         return false;
263     }
264
265     private @Nullable Thing getThing(String uid) {
266         Thing thing = null;
267         try {
268             ThingUID thingUID = new ThingUID(uid);
269             thing = thingRegistry.get(thingUID);
270         } catch (IllegalArgumentException e) {
271             thing = null;
272         }
273         return thing;
274     }
275 }