]> git.basschouten.com Git - openhab-addons.git/blob
a910dc801de6d460ae826b7ac38f7c4e9ae9d20b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.io.homekit.internal;
14
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.concurrent.ExecutionException;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.io.console.Console;
23 import org.openhab.core.io.console.ConsoleCommandCompleter;
24 import org.openhab.core.io.console.StringsCompleter;
25 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
26 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
27 import org.openhab.io.homekit.Homekit;
28 import org.openhab.io.homekit.internal.accessories.DummyHomekitAccessory;
29 import org.osgi.service.component.annotations.Component;
30 import org.osgi.service.component.annotations.Reference;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import io.github.hapjava.accessories.HomekitAccessory;
35 import io.github.hapjava.services.Service;
36
37 /**
38  * Console commands for interacting with the HomeKit integration
39  *
40  * @author Andy Lintner - Initial contribution
41  */
42 @Component(service = ConsoleCommandExtension.class)
43 @NonNullByDefault
44 public class HomekitCommandExtension extends AbstractConsoleCommandExtension {
45     private static final String SUBCMD_CLEAR_PAIRINGS = "clearPairings";
46     private static final String SUBCMD_LIST_ACCESSORIES = "list";
47     private static final String SUBCMD_PRINT_ACCESSORY = "show";
48     private static final String SUBCMD_ALLOW_UNAUTHENTICATED = "allowUnauthenticated";
49     private static final String SUBCMD_PRUNE_DUMMY_ACCESSORIES = "pruneDummyAccessories";
50     private static final String SUBCMD_LIST_DUMMY_ACCESSORIES = "listDummyAccessories";
51     private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(
52             List.of(SUBCMD_CLEAR_PAIRINGS, SUBCMD_LIST_ACCESSORIES, SUBCMD_PRINT_ACCESSORY,
53                     SUBCMD_ALLOW_UNAUTHENTICATED, SUBCMD_PRUNE_DUMMY_ACCESSORIES, SUBCMD_LIST_DUMMY_ACCESSORIES),
54             false);
55
56     private static final String PARAM_INSTANCE = "--instance";
57     private static final String PARAM_INSTANCE_HELP = " [--instance <instance id>]";
58
59     private class CommandCompleter implements ConsoleCommandCompleter {
60         public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
61             if (cursorArgumentIndex == 0) {
62                 boolean result = SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
63                 return result;
64             }
65             return false;
66         }
67     }
68
69     private final Logger logger = LoggerFactory.getLogger(HomekitCommandExtension.class);
70
71     private @NonNullByDefault({}) Homekit homekit;
72
73     public HomekitCommandExtension() {
74         super("homekit", "Interact with the HomeKit integration.");
75     }
76
77     @Override
78     public void execute(String[] argsArray, Console console) {
79         if (argsArray.length > 0) {
80             List<String> args = Arrays.asList(argsArray);
81             Integer instance = null;
82
83             // capture the common instance argument and take it out of args
84             for (int i = 0; i < args.size() - 1; ++i) {
85                 if (PARAM_INSTANCE.equals(args.get(i))) {
86                     instance = Integer.parseInt(args.get(i + 1));
87                     int instanceCount = homekit.getInstanceCount();
88                     if (instance < 1 || instance > instanceCount) {
89                         console.println("Instance " + args.get(i + 1) + " out of range 1.." + instanceCount);
90                         return;
91                     }
92
93                     List<String> newArgs = args.subList(0, i);
94                     if (i < args.size() - 2) {
95                         newArgs.addAll(args.subList(i + 2, args.size() - 1));
96                     }
97                     args = newArgs;
98                     break;
99                 }
100             }
101
102             String subCommand = args.get(0);
103             switch (subCommand) {
104                 case SUBCMD_CLEAR_PAIRINGS:
105                     if (args.size() != 1) {
106                         console.println("Unknown arguments; not clearing pairings");
107                     } else {
108                         clearHomekitPairings(console, instance);
109                     }
110                     break;
111
112                 case SUBCMD_ALLOW_UNAUTHENTICATED:
113                     if (args.size() > 1) {
114                         boolean allow = Boolean.parseBoolean(args.get(1));
115                         allowUnauthenticatedHomekitRequests(allow, console);
116                     } else {
117                         console.println("true/false is required as an argument");
118                     }
119                     break;
120                 case SUBCMD_LIST_ACCESSORIES:
121                     listAccessories(console, instance);
122                     break;
123                 case SUBCMD_PRINT_ACCESSORY:
124                     if (args.size() > 1) {
125                         printAccessory(args.get(1), console, instance);
126                     } else {
127                         console.println("accessory id or name is required as an argument");
128                     }
129                     break;
130                 case SUBCMD_PRUNE_DUMMY_ACCESSORIES:
131                     if (args.size() != 1) {
132                         console.println("Unknown arguments; not pruning dummy accessories");
133                     } else {
134                         pruneDummyAccessories(console, instance);
135                     }
136                     break;
137                 case SUBCMD_LIST_DUMMY_ACCESSORIES:
138                     listDummyAccessories(console, instance);
139                     break;
140                 default:
141                     console.println("Unknown command '" + subCommand + "'");
142                     printUsage(console);
143                     break;
144             }
145         } else {
146             printUsage(console);
147         }
148     }
149
150     @Override
151     public List<String> getUsages() {
152         return Arrays.asList(
153                 buildCommandUsage(SUBCMD_LIST_ACCESSORIES + PARAM_INSTANCE_HELP,
154                         "list all HomeKit accessories, optionally for a specific instance."),
155                 buildCommandUsage(SUBCMD_PRINT_ACCESSORY + PARAM_INSTANCE_HELP + " <accessory id | accessory name>",
156                         "print additional details of the accessories which partially match provided ID or name, optionally searching a specific instance."),
157                 buildCommandUsage(SUBCMD_CLEAR_PAIRINGS + PARAM_INSTANCE_HELP,
158                         "removes all pairings with HomeKit clients, optionally for a specific instance."),
159                 buildCommandUsage(SUBCMD_ALLOW_UNAUTHENTICATED + " <boolean>",
160                         "enables or disables unauthenticated access to facilitate debugging"),
161                 buildCommandUsage(SUBCMD_PRUNE_DUMMY_ACCESSORIES + PARAM_INSTANCE_HELP,
162                         "removes dummy accessories whose items no longer exist, optionally for a specific instance."),
163                 buildCommandUsage(SUBCMD_LIST_DUMMY_ACCESSORIES + PARAM_INSTANCE_HELP,
164                         "list dummy accessories whose items no longer exist, optionally for a specific instance."));
165     }
166
167     @Reference
168     public void setHomekit(Homekit homekit) {
169         this.homekit = homekit;
170     }
171
172     @Override
173     public @Nullable ConsoleCommandCompleter getCompleter() {
174         return new CommandCompleter();
175     }
176
177     private void clearHomekitPairings(Console console, @Nullable Integer instance) {
178         if (instance != null) {
179             homekit.clearHomekitPairings(instance);
180             console.println("Cleared HomeKit pairings for instance " + instance);
181         } else {
182             homekit.clearHomekitPairings();
183             console.println("Cleared HomeKit pairings");
184         }
185     }
186
187     private void allowUnauthenticatedHomekitRequests(boolean allow, Console console) {
188         homekit.allowUnauthenticatedRequests(allow);
189         console.println((allow ? "Enabled " : "Disabled ") + "unauthenticated HomeKit access");
190     }
191
192     private void pruneDummyAccessories(Console console, @Nullable Integer instance) {
193         if (instance != null) {
194             homekit.pruneDummyAccessories(instance);
195             console.println("Dummy accessories pruned for instance " + instance);
196         } else {
197             homekit.pruneDummyAccessories();
198             console.println("Dummy accessories pruned");
199         }
200     }
201
202     private void listAccessories(Console console, @Nullable Integer instance) {
203         getInstanceAccessories(instance).forEach(v -> {
204             try {
205                 console.println(v.getId() + " " + v.getName().get());
206             } catch (InterruptedException | ExecutionException e) {
207                 logger.warn("Cannot list accessories", e);
208             }
209         });
210     }
211
212     private void listDummyAccessories(Console console, @Nullable Integer instance) {
213         getInstanceAccessories(instance).forEach(v -> {
214             try {
215                 if (v instanceof DummyHomekitAccessory) {
216                     console.println(v.getSerialNumber().get());
217                 }
218             } catch (InterruptedException | ExecutionException e) {
219                 logger.warn("Cannot list accessories", e);
220             }
221         });
222     }
223
224     private void printService(Console console, Service service, int indent) {
225         console.println(" ".repeat(indent) + "Service Type: " + service.getClass().getSimpleName() + " ("
226                 + service.getType() + ")");
227         console.println(" ".repeat(indent + 2) + "Characteristics:");
228         service.getCharacteristics().forEach((c) -> {
229             try {
230                 console.println(
231                         " ".repeat(indent + 4) + c.getClass().getSimpleName() + ": " + c.toJson(0).get().toString());
232             } catch (InterruptedException | ExecutionException e) {
233             }
234         });
235         if (service.getLinkedServices().isEmpty()) {
236             return;
237         }
238         console.println(" ".repeat(indent + 2) + "Linked Services:");
239         service.getLinkedServices().forEach((s) -> printService(console, s, indent + 2));
240     }
241
242     private void printAccessory(String id, Console console, @Nullable Integer instance) {
243         getInstanceAccessories(instance).forEach(v -> {
244             try {
245                 if (("" + v.getId()).contains(id) || ((v.getName().get() != null)
246                         && (v.getName().get().toUpperCase().contains(id.toUpperCase())))) {
247                     console.println(v.getId() + " " + v.getName().get());
248                     console.println("Services:");
249                     v.getServices().forEach(s -> printService(console, s, 2));
250                     console.println("");
251                 }
252             } catch (InterruptedException | ExecutionException e) {
253                 logger.warn("Cannot print accessory", e);
254             }
255         });
256     }
257
258     /**
259      * Get in-scope accessories
260      * 
261      * @param instance if null, means all accessories from all instances
262      */
263     private Collection<HomekitAccessory> getInstanceAccessories(@Nullable Integer instance) {
264         if (instance != null) {
265             return homekit.getAccessories(instance);
266         } else {
267             return homekit.getAccessories();
268         }
269     }
270 }