2 * Copyright (c) 2010-2022 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.io.homekit.internal;
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.concurrent.ExecutionException;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.io.console.Console;
22 import org.openhab.core.io.console.ConsoleCommandCompleter;
23 import org.openhab.core.io.console.StringsCompleter;
24 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
25 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
26 import org.openhab.io.homekit.Homekit;
27 import org.openhab.io.homekit.internal.accessories.DummyHomekitAccessory;
28 import org.osgi.service.component.annotations.Component;
29 import org.osgi.service.component.annotations.Reference;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 import io.github.hapjava.services.Service;
36 * Console commands for interacting with the HomeKit integration
38 * @author Andy Lintner - Initial contribution
40 @Component(service = ConsoleCommandExtension.class)
42 public class HomekitCommandExtension extends AbstractConsoleCommandExtension {
43 private static final String SUBCMD_CLEAR_PAIRINGS = "clearPairings";
44 private static final String SUBCMD_LIST_ACCESSORIES = "list";
45 private static final String SUBCMD_PRINT_ACCESSORY = "show";
46 private static final String SUBCMD_ALLOW_UNAUTHENTICATED = "allowUnauthenticated";
47 private static final String SUBCMD_PRUNE_DUMMY_ACCESSORIES = "pruneDummyAccessories";
48 private static final String SUBCMD_LIST_DUMMY_ACCESSORIES = "listDummyAccessories";
49 private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(
50 List.of(SUBCMD_CLEAR_PAIRINGS, SUBCMD_LIST_ACCESSORIES, SUBCMD_PRINT_ACCESSORY,
51 SUBCMD_ALLOW_UNAUTHENTICATED, SUBCMD_PRUNE_DUMMY_ACCESSORIES, SUBCMD_LIST_DUMMY_ACCESSORIES),
54 private class CommandCompleter implements ConsoleCommandCompleter {
55 public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
56 if (cursorArgumentIndex == 0) {
57 boolean result = SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
64 private final Logger logger = LoggerFactory.getLogger(HomekitCommandExtension.class);
66 private @NonNullByDefault({}) Homekit homekit;
68 public HomekitCommandExtension() {
69 super("homekit", "Interact with the HomeKit integration.");
73 public void execute(String[] args, Console console) {
74 if (args.length > 0) {
75 String subCommand = args[0];
77 case SUBCMD_CLEAR_PAIRINGS:
78 clearHomekitPairings(console);
81 case SUBCMD_ALLOW_UNAUTHENTICATED:
82 if (args.length > 1) {
83 boolean allow = Boolean.parseBoolean(args[1]);
84 allowUnauthenticatedHomekitRequests(allow, console);
86 console.println("true/false is required as an argument");
89 case SUBCMD_LIST_ACCESSORIES:
90 listAccessories(console);
92 case SUBCMD_PRINT_ACCESSORY:
93 if (args.length > 1) {
94 printAccessory(args[1], console);
96 console.println("accessory id or name is required as an argument");
99 case SUBCMD_PRUNE_DUMMY_ACCESSORIES:
100 pruneDummyAccessories(console);
102 case SUBCMD_LIST_DUMMY_ACCESSORIES:
103 listDummyAccessories(console);
106 console.println("Unknown command '" + subCommand + "'");
116 public List<String> getUsages() {
117 return Arrays.asList(buildCommandUsage(SUBCMD_LIST_ACCESSORIES, "list all HomeKit accessories"),
118 buildCommandUsage(SUBCMD_PRINT_ACCESSORY + " <accessory id | accessory name>",
119 "print additional details of the accessories which partially match provided ID or name."),
120 buildCommandUsage(SUBCMD_CLEAR_PAIRINGS, "removes all pairings with HomeKit clients."),
121 buildCommandUsage(SUBCMD_ALLOW_UNAUTHENTICATED + " <boolean>",
122 "enables or disables unauthenticated access to facilitate debugging"),
123 buildCommandUsage(SUBCMD_PRUNE_DUMMY_ACCESSORIES,
124 "removes dummy accessories whose items no longer exist."),
125 buildCommandUsage(SUBCMD_LIST_DUMMY_ACCESSORIES,
126 "list dummy accessories whose items no longer exist."));
130 public void setHomekit(Homekit homekit) {
131 this.homekit = homekit;
135 public @Nullable ConsoleCommandCompleter getCompleter() {
136 return new CommandCompleter();
139 private void clearHomekitPairings(Console console) {
140 homekit.clearHomekitPairings();
141 console.println("Cleared HomeKit pairings");
144 private void allowUnauthenticatedHomekitRequests(boolean allow, Console console) {
145 homekit.allowUnauthenticatedRequests(allow);
146 console.println((allow ? "Enabled " : "Disabled ") + "unauthenticated HomeKit access");
149 private void pruneDummyAccessories(Console console) {
150 homekit.pruneDummyAccessories();
151 console.println("Dummy accessories pruned.");
154 private void listAccessories(Console console) {
155 homekit.getAccessories().forEach(v -> {
157 console.println(v.getId() + " " + v.getName().get());
158 } catch (InterruptedException | ExecutionException e) {
159 logger.warn("Cannot list accessories", e);
164 private void listDummyAccessories(Console console) {
165 homekit.getAccessories().forEach(v -> {
167 if (v instanceof DummyHomekitAccessory) {
168 console.println(v.getSerialNumber().get());
170 } catch (InterruptedException | ExecutionException e) {
171 logger.warn("Cannot list accessories", e);
176 private void printService(Console console, Service service, int indent) {
177 console.println(" ".repeat(indent) + "Service Type: " + service.getClass().getSimpleName() + " ("
178 + service.getType() + ")");
179 console.println(" ".repeat(indent + 2) + "Characteristics:");
180 service.getCharacteristics().forEach((c) -> {
183 " ".repeat(indent + 4) + c.getClass().getSimpleName() + ": " + c.toJson(0).get().toString());
184 } catch (InterruptedException | ExecutionException e) {
187 if (service.getLinkedServices().isEmpty()) {
190 console.println(" ".repeat(indent + 2) + "Linked Services:");
191 service.getLinkedServices().forEach((s) -> printService(console, s, indent + 2));
194 private void printAccessory(String id, Console console) {
195 homekit.getAccessories().forEach(v -> {
197 if (("" + v.getId()).contains(id) || ((v.getName().get() != null)
198 && (v.getName().get().toUpperCase().contains(id.toUpperCase())))) {
199 console.println(v.getId() + " " + v.getName().get());
200 console.println("Services:");
201 v.getServices().forEach(s -> printService(console, s, 2));
204 } catch (InterruptedException | ExecutionException e) {
205 logger.warn("Cannot print accessory", e);