Skip to content

Commit

Permalink
dump devices from console
Browse files Browse the repository at this point in the history
Signed-off-by: Mark Herwege <[email protected]>
  • Loading branch information
mherwege committed Aug 30, 2024
1 parent d9a0039 commit 7d1f7df
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public class NikoHomeControlBindingConstants {
public static final String CHANNEL_NOTICE = "notice";

// Bridge config properties
public static final String CONFIG_CONTROLLER_ID = "controller";
public static final String CONFIG_HOST_NAME = "addr";
public static final String CONFIG_PORT = "port";
public static final String CONFIG_REFRESH = "refresh";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nikohomecontrol.internal.console;

import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.BINDING_ID;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlDiscover;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.ConsoleCommandCompleter;
import org.openhab.core.io.console.StringsCompleter;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingStatus;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

/**
* The {@link NikoHomeControlCommandExtension} is responsible for handling console commands
*
* @author Mark Herwege - Initial contribution
*/

@NonNullByDefault
@Component(service = ConsoleCommandExtension.class)
public class NikoHomeControlCommandExtension extends AbstractConsoleCommandExtension
implements ConsoleCommandCompleter {

private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();

private static final String CONTROLLERS = "controllers";
private static final String DEVICELIST = "devicelist";
private static final String DUMP = "dump";

private static final String ROOT_PATH = System.getProperty("user.home") + File.separator + BINDING_ID
+ File.separator + DEVICELIST;

private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CONTROLLERS, DEVICELIST), false);
private static final StringsCompleter DUMP_COMPLETER = new StringsCompleter(List.of(DUMP), false);

private final ThingRegistry thingRegistry;
private final NetworkAddressService networkAddressService;

private List<NikoHomeControlBridgeHandler> bridgeHandlers = List.of();

@Activate
public NikoHomeControlCommandExtension(final @Reference ThingRegistry thingRegistry,
final @Reference NetworkAddressService networkAddressService) {
super("nikohomecontrol", "Interact with the Niko Home Control binding");
this.thingRegistry = thingRegistry;
this.networkAddressService = networkAddressService;
}

@Override
public void execute(String[] args, Console console) {
if ((args.length < 1) || (args.length > 3)) {
console.println("Invalid number of arguments");
printUsage(console);
return;
}

bridgeHandlers = thingRegistry.getAll().stream()
.filter(t -> t.getHandler() instanceof NikoHomeControlBridgeHandler)
.map(b -> ((NikoHomeControlBridgeHandler) b.getHandler())).toList();
Map<String, String> bridgeNhcVersion = bridgeHandlers.stream()
.collect(Collectors.toMap(handler -> handler.getThing().getUID().toString(),
handler -> handler instanceof NikoHomeControlBridgeHandler2 ? "II" : "I"));

switch (args[0].toLowerCase()) {
case CONTROLLERS:
if (args.length > 1) {
console.println("No extra argument allowed after 'controllers'");
printUsage(console);
return;
} else {
Map<String, String> bridgeIds = bridgeHandlers.stream()
.collect(Collectors.toMap(handler -> handler.getThing().getUID().toString(),
handler -> handler.getControllerId().toLowerCase()));
List<String> controllerIds = List.of();
Map<String, String> controllerNhcVersion = Map.of();
try {
String broadcastAddr = networkAddressService.getConfiguredBroadcastAddress();
if (broadcastAddr == null) {
console.println(
"Controller discovery not possible, no broadcast address found, result only contains bridges");
} else {
NikoHomeControlDiscover nhcDiscover;
nhcDiscover = new NikoHomeControlDiscover(broadcastAddr);
controllerIds = nhcDiscover.getNhcBridgeIds().stream().map(String::toLowerCase)
.filter(id -> !bridgeIds.containsValue(id)).toList();
controllerNhcVersion = controllerIds.stream().collect(
Collectors.toMap(Function.identity(), id -> nhcDiscover.isNhcII(id) ? "II" : "I"));
}
} catch (IOException e) {
console.println(
"Controller discovery not possible, network error, result only contains bridges");
}
Map<String, String> nhcVersion = Map.copyOf(controllerNhcVersion);
console.println("Controller ID NHC Version Bridge ID");
bridgeIds.forEach((bridge, id) -> console.printf("%-12s %2s %s%n", id,
bridgeNhcVersion.get(id), bridge));
controllerIds.forEach(id -> console.printf("%-12s %2s%n", id, nhcVersion.get(id)));
}
break;
case DEVICELIST:
if (args.length < 2) {
console.println("No bridge ID provided");
printUsage(console);
return;
}
Optional<NikoHomeControlBridgeHandler> bridgeOptional = bridgeHandlers.stream()
.filter(b -> b.getThing().getUID().toString().toLowerCase().equals(args[1].toLowerCase()))
.findAny();
if (bridgeOptional.isEmpty()) {
console.println("'" + args[1] + "' is not a valid bridge ID");
printUsage(console);
return;
}
if (!"II".equals(bridgeNhcVersion.get(args[1]))) {
console.println("'" + args[1] + "' is not a Niko Home Control II bridge");
printUsage(console);
return;
}
NikoHomeControlBridgeHandler bridgeHandler = bridgeOptional.get();
if (!ThingStatus.ONLINE.equals(bridgeHandler.getThing().getStatus())) {
console.println("Niko Home Control bridge not online, no commands allowed");
return;
}
NikoHomeControlCommunication2 nhcComm = (NikoHomeControlCommunication2) bridgeHandler
.getCommunication();
if (nhcComm != null) {
String devices = prettyJson(nhcComm.getRawDevicesListResponse());
console.println(devices);

if (args.length > 2 && DUMP.equals(args[2])) {
String filename = ROOT_PATH + LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE)
+ ".json";
writeJsonToFile(filename, devices, console);
}
} else {
// cannot happen if thing is online
}
break;
default:
console.println("Command argument '" + args[0] + "' not recognized");
printUsage(console);
}
}

private String prettyJson(String json) {
try {
return GSON.toJson(JsonParser.parseString(json));
} catch (JsonSyntaxException e) {
// Keep the unformatted json if there is a syntax exception
return json;
}
}

private void writeJsonToFile(String filename, String json, Console console) {
try {
JsonElement element = JsonParser.parseString(json);
if (element.isJsonNull() || (element.isJsonArray() && ((JsonArray) element).size() == 0)) {
console.println("Empty device list, nothing to dump");
return;
}
} catch (JsonSyntaxException e) {
// Just continue and write the file with non-valid json anyway
}

// ensure full path exists
File file = new File(filename);
File parentFile = file.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}

final byte[] contents = json.getBytes(StandardCharsets.UTF_8);
try {
Files.write(file.toPath(), contents);
} catch (IOException e) {
console.println("I/O error writing device list to file");
}
}

@Override
public List<String> getUsages() {
return Arrays.asList(new String[] { buildCommandUsage(CONTROLLERS, "list all Niko Home Control Controllers"),
buildCommandUsage(DEVICELIST + " <bridge ID>",
"create device list of Niko Home Control II installation on Controller with provided bridge ID"),
buildCommandUsage(DEVICELIST + " <bridge ID> " + DUMP,
"create device list of Niko Home Control II installation on Controller with provided bridge ID and dump result in a file in your home/nikohomecontrol directory") });
}

@Override
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
if (cursorArgumentIndex <= 0) {
return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
} else if (cursorArgumentIndex == 1) {
return new StringsCompleter(
bridgeHandlers.stream().filter(handler -> handler instanceof NikoHomeControlBridgeHandler2)
.map(handler -> handler.getThing().getUID().toString()).toList(),
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
} else if (cursorArgumentIndex == 2) {
return DUMP_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ private void addNhcIBridge(InetAddress addr, String bridgeId) {
ThingUID uid = new ThingUID(BINDING_ID, "bridge", bridgeId);

DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withRepresentationProperty(CONFIG_HOST_NAME)
.build();
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withProperty(CONFIG_CONTROLLER_ID, bridgeId)
.withRepresentationProperty(CONFIG_HOST_NAME).build();
thingDiscovered(discoveryResult);
}

Expand All @@ -104,8 +104,8 @@ private void addNhcIIBridge(InetAddress addr, String bridgeId) {
ThingUID uid = new ThingUID(BINDING_ID, "bridge2", bridgeId);

DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withRepresentationProperty(CONFIG_HOST_NAME)
.build();
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).withProperty(CONFIG_CONTROLLER_ID, bridgeId)
.withRepresentationProperty(CONFIG_HOST_NAME).build();
thingDiscovered(discoveryResult);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,9 @@ public ZoneId getTimeZone() {
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(NikoHomeControlDiscoveryService.class);
}

public String getControllerId() {
String id = thing.getProperties().get(CONFIG_CONTROLLER_ID);
return id != null ? id : "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication

private volatile String profile = "";

private String rawDevicesListResponse = "";

private volatile @Nullable NhcSystemInfo2 nhcSystemInfo;
private volatile @Nullable NhcTimeInfo2 nhcTimeInfo;

Expand Down Expand Up @@ -270,6 +272,7 @@ private void servicesListRsp(String response) {
}

private void devicesListRsp(String response) {
rawDevicesListResponse = response;
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcDevice2> deviceList = null;
Expand Down Expand Up @@ -1276,6 +1279,10 @@ public String getServices() {
return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
}

public String getRawDevicesListResponse() {
return rawDevicesListResponse;
}

@Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
// do in separate thread as this method needs to return early
Expand Down

0 comments on commit 7d1f7df

Please sign in to comment.