[boschshc] Add command to list SHC device mappings (#15060)

* [boschshc] add command to list Bosch Smart Home Controller devices and mapping to openhab devices and related services

Signed-off-by: Gerd Zanker <gerd.zanker@web.de>
This commit is contained in:
Gerd Zanker 2024-02-26 21:04:04 +01:00 committed by GitHub
parent d56c9e55e2
commit 1cfaf20fdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 555 additions and 13 deletions

View File

@ -0,0 +1,264 @@
/**
* 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.boschshc.internal.console;
import static org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService.DEVICEMODEL_TO_THINGTYPE_MAP;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
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.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Console command to list Bosch SHC devices and openhab support.
* Use the SHC API to get all SHC devices and SHC services
* and tries to lookup openhab devices and implemented service classes.
* Prints each name and looked-up implementation on console.
*
* @author Gerd Zanker - Initial contribution
*/
@NonNullByDefault
@Component(service = ConsoleCommandExtension.class)
public class BoschShcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
static final String SHOW_BINDINGINFO = "showBindingInfo";
static final String SHOW_DEVICES = "showDevices";
static final String SHOW_SERVICES = "showServices";
static final String GET_BRIDGEINFO = "bridgeInfo";
static final String GET_DEVICES = "deviceInfo";
private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(
List.of(SHOW_BINDINGINFO, SHOW_DEVICES, SHOW_SERVICES, GET_BRIDGEINFO, GET_DEVICES), false);
private final ThingRegistry thingRegistry;
@Activate
public BoschShcCommandExtension(final @Reference ThingRegistry thingRegistry) {
super(BoschSHCBindingConstants.BINDING_ID, "Interact with the Bosch Smart Home Controller.");
this.thingRegistry = thingRegistry;
}
/**
* Returns all implemented services of this Bosch SHC binding.
* This list shall contain all available services and needs to be extended when a new service is added.
* A unit tests checks if this list matches with the existing subfolders in
* "src/main/java/org/openhab/binding/boschshc/internal/services".
*/
List<String> getAllBoschShcServices() {
return List.of("airqualitylevel", "batterylevel", "binaryswitch", "bypass", "cameranotification", "childlock",
"communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance", "intrusion", "keypad",
"latestmotion", "multilevelswitch", "powermeter", "powerswitch", "privacymode", "roomclimatecontrol",
"shuttercontact", "shuttercontrol", "silentmode", "smokedetectorcheck", "temperaturelevel", "userstate",
"valvetappet");
}
@Override
public void execute(String[] args, Console console) {
if (args.length == 0) {
printUsage(console);
return;
}
try {
if (GET_BRIDGEINFO.equals(args[0])) {
console.print(buildBridgeInfo());
return;
}
if (GET_DEVICES.equals(args[0])) {
console.print(buildDeviceInfo());
return;
}
if (SHOW_BINDINGINFO.equals(args[0])) {
console.print(buildBindingInfo());
return;
}
if (SHOW_DEVICES.equals(args[0])) {
console.print(buildSupportedDeviceStatus());
return;
}
if (SHOW_SERVICES.equals(args[0])) {
console.print(buildSupportedServiceStatus());
return;
}
} catch (BoschSHCException | ExecutionException | TimeoutException e) {
console.print(String.format("Error %1s%n", e.getMessage()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// unsupported command, print usage
printUsage(console);
}
private List<BridgeHandler> getBridgeHandlers() {
List<BridgeHandler> bridges = new ArrayList<>();
for (Thing thing : thingRegistry.getAll()) {
ThingHandler thingHandler = thing.getHandler();
if (thingHandler instanceof BridgeHandler bridgeHandler) {
bridges.add(bridgeHandler);
}
}
return bridges;
}
String buildBridgeInfo() throws BoschSHCException, InterruptedException, ExecutionException, TimeoutException {
List<BridgeHandler> bridges = getBridgeHandlers();
StringBuilder builder = new StringBuilder();
for (BridgeHandler bridgeHandler : bridges) {
builder.append(String.format("Bridge: %1s%n", bridgeHandler.getThing().getLabel()));
builder.append(String.format(" access possible: %1s%n", bridgeHandler.checkBridgeAccess()));
PublicInformation publicInformation = bridgeHandler.getPublicInformation();
builder.append(String.format(" SHC Generation: %1s%n", publicInformation.shcGeneration));
builder.append(String.format(" IP Address: %1s%n", publicInformation.shcIpAddress));
builder.append(String.format(" API Versions: %1s%n", publicInformation.apiVersions));
builder.append(String.format(" Software Version: %1s%n",
publicInformation.softwareUpdateState.swInstalledVersion));
builder.append(String.format(" Version Update State: %1s%n",
publicInformation.softwareUpdateState.swUpdateState));
builder.append(String.format(" Available Version: %1s%n",
publicInformation.softwareUpdateState.swUpdateAvailableVersion));
builder.append(String.format("%n"));
}
return builder.toString();
}
String buildDeviceInfo() throws InterruptedException {
StringBuilder builder = new StringBuilder();
for (Thing thing : thingRegistry.getAll()) {
ThingHandler thingHandler = thing.getHandler();
if (thingHandler instanceof BridgeHandler bridgeHandler) {
builder.append(String.format("thing: %1s%n", thing.getLabel()));
builder.append(String.format(" thingHandler: %1s%n", thingHandler.getClass().getName()));
builder.append(String.format("bridge access possible: %1s%n", bridgeHandler.checkBridgeAccess()));
List<Device> devices = bridgeHandler.getDevices();
builder.append(String.format("devices (%1d): %n", devices.size()));
for (Device device : devices) {
builder.append(buildDeviceInfo(device));
builder.append(String.format("%n"));
}
}
}
return builder.toString();
}
private String buildDeviceInfo(Device device) {
StringBuilder builder = new StringBuilder();
builder.append(String.format(" deviceID: %1s%n", device.id));
builder.append(String.format(" type: %1s -> ", device.deviceModel));
if (DEVICEMODEL_TO_THINGTYPE_MAP.containsKey(device.deviceModel)) {
builder.append(DEVICEMODEL_TO_THINGTYPE_MAP.get(device.deviceModel).getId());
} else {
builder.append("!UNSUPPORTED!");
}
builder.append(String.format("%n"));
builder.append(buildDeviceServices(device.deviceServiceIds));
return builder.toString();
}
private String buildDeviceServices(List<String> deviceServiceIds) {
StringBuilder builder = new StringBuilder();
List<String> existingServices = getAllBoschShcServices();
for (String serviceName : deviceServiceIds) {
builder.append(String.format(" service: %1s -> ", serviceName));
if (existingServices.stream().anyMatch(s -> s.equals(serviceName.toLowerCase()))) {
for (String existingService : existingServices) {
if (existingService.equals(serviceName.toLowerCase())) {
builder.append(existingService);
}
}
} else {
builder.append("!UNSUPPORTED!");
}
builder.append(String.format("%n"));
}
return builder.toString();
}
String buildBindingInfo() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Bosch SHC Binding%n"));
Bundle bundle = FrameworkUtil.getBundle(getClass());
if (bundle != null) {
builder.append(String.format(" SymbolicName %1s%n", bundle.getSymbolicName()));
builder.append(String.format(" Version %1s%n", bundle.getVersion()));
}
return builder.toString();
}
String buildSupportedDeviceStatus() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Supported Devices (%1d):%n", DEVICEMODEL_TO_THINGTYPE_MAP.size()));
for (Map.Entry<String, ThingTypeUID> entry : DEVICEMODEL_TO_THINGTYPE_MAP.entrySet()) {
builder.append(
String.format(" - %1s = %1s%n", entry.getKey(), DEVICEMODEL_TO_THINGTYPE_MAP.get(entry.getKey())));
}
return builder.toString();
}
String buildSupportedServiceStatus() {
StringBuilder builder = new StringBuilder();
List<String> supportedServices = getAllBoschShcServices();
builder.append(String.format("Supported Services (%1d):%n", supportedServices.size()));
for (String service : supportedServices) {
builder.append(String.format(" - %1s%n", service));
}
return builder.toString();
}
@Override
public List<String> getUsages() {
return List.of(buildCommandUsage(SHOW_BINDINGINFO, "list detailed information about this binding"),
buildCommandUsage(SHOW_DEVICES, "list all devices supported by this binding"),
buildCommandUsage(SHOW_SERVICES, "list all services supported by this binding"),
buildCommandUsage(GET_DEVICES, "get all Bosch SHC devices"),
buildCommandUsage(GET_BRIDGEINFO, "get detailed information from Bosch SHC"));
}
@Override
public @Nullable ConsoleCommandCompleter getCompleter() {
return this;
}
@Override
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
if (cursorArgumentIndex <= 0) {
return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
}
return false;
}
}

View File

@ -39,6 +39,7 @@ import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
@ -432,6 +433,23 @@ public class BridgeHandler extends BaseBridgeHandler {
}
}
/**
* Get public information from Bosch SHC.
*/
public PublicInformation getPublicInformation()
throws InterruptedException, BoschSHCException, ExecutionException, TimeoutException {
@Nullable
BoschHttpClient localHttpClient = this.httpClient;
if (localHttpClient == null) {
throw new BoschSHCException(HTTP_CLIENT_NOT_INITIALIZED);
}
String url = localHttpClient.getPublicInformationUrl();
Request request = localHttpClient.createRequest(url, GET);
return localHttpClient.sendRequest(request, PublicInformation.class, PublicInformation::isValid, null);
}
public boolean registerDiscoveryListener(ThingDiscoveryService listener) {
if (thingDiscoveryService == null) {
thingDiscoveryService = listener;
@ -604,7 +622,7 @@ public class BridgeHandler extends BaseBridgeHandler {
@Nullable
BoschHttpClient localHttpClient = this.httpClient;
if (localHttpClient == null) {
throw new BoschSHCException("HTTP client not initialized");
throw new BoschSHCException(HTTP_CLIENT_NOT_INITIALIZED);
}
String url = localHttpClient.getBoschSmartHomeUrl(String.format("devices/%s", deviceId));
@ -634,7 +652,7 @@ public class BridgeHandler extends BaseBridgeHandler {
@Nullable
BoschHttpClient locaHttpClient = this.httpClient;
if (locaHttpClient == null) {
throw new BoschSHCException("HTTP client not initialized");
throw new BoschSHCException(HTTP_CLIENT_NOT_INITIALIZED);
}
String url = locaHttpClient.getBoschSmartHomeUrl(String.format("userdefinedstates/%s", stateId));

View File

@ -55,7 +55,7 @@ public class Device {
public String status;
public List<String> childDeviceIds;
public static Boolean isValid(Device obj) {
public static boolean isValid(Device obj) {
return obj != null && obj.id != null;
}

View File

@ -42,4 +42,10 @@ public class PublicInformation {
public List<String> apiVersions;
public String shcIpAddress;
public String shcGeneration;
public SoftwareUpdateState softwareUpdateState;
public static boolean isValid(PublicInformation obj) {
return obj != null && obj.shcIpAddress != null && obj.shcGeneration != null && obj.apiVersions != null
&& SoftwareUpdateState.isValid(obj.softwareUpdateState);
}
}

View File

@ -48,17 +48,13 @@ public class Scenario extends BoschSHCServiceState {
return scenario;
}
public static Boolean isValid(Scenario[] scenarios) {
public static boolean isValid(Scenario[] scenarios) {
return Arrays.stream(scenarios).allMatch(scenario -> (scenario.id != null));
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Scenario{");
sb.append("name='").append(name).append("'");
sb.append(", id='").append(id).append("'");
sb.append(", lastTimeTriggered='").append(lastTimeTriggered).append("'");
sb.append('}');
return sb.toString();
return "Scenario{" + "name='" + name + "'" + ", id='" + id + "'" + ", lastTimeTriggered='" + lastTimeTriggered
+ "'" + '}';
}
}

View File

@ -0,0 +1,30 @@
/**
* 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.boschshc.internal.devices.bridge.dto;
/**
* Software Update State is part of PublicInformation.
*
* @author Gerd Zanker - Initial contribution
*/
public class SoftwareUpdateState {
public String swUpdateState;
public String swInstalledVersion;
public String swUpdateAvailableVersion;
public static boolean isValid(SoftwareUpdateState obj) {
return obj != null && obj.swUpdateState != null && obj.swInstalledVersion != null
&& obj.swUpdateAvailableVersion != null;
}
}

View File

@ -31,7 +31,7 @@ public class SubscribeResult {
return this.jsonrpc;
}
public static Boolean isValid(SubscribeResult obj) {
public static boolean isValid(SubscribeResult obj) {
return obj != null && obj.result != null && obj.jsonrpc != null;
}
}

View File

@ -70,7 +70,7 @@ public class UserDefinedState extends BoschSHCServiceState {
+ type + '\'' + '}';
}
public static Boolean isValid(UserDefinedState obj) {
public static boolean isValid(UserDefinedState obj) {
return obj != null && obj.id != null;
}
}

View File

@ -68,7 +68,7 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR);
// @formatter:off
protected static final Map<String, ThingTypeUID> DEVICEMODEL_TO_THINGTYPE_MAP = Map.ofEntries(
public static final Map<String, ThingTypeUID> DEVICEMODEL_TO_THINGTYPE_MAP = Map.ofEntries(
new AbstractMap.SimpleEntry<>("BBL", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL),
new AbstractMap.SimpleEntry<>("TWINGUARD", BoschSHCBindingConstants.THING_TYPE_TWINGUARD),
new AbstractMap.SimpleEntry<>("BSM", BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH),

View File

@ -0,0 +1,228 @@
/**
* 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.boschshc.internal.console;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SoftwareUpdateState;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.core.io.console.Console;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
/**
* Unit tests for Console command to list Bosch SHC devices and openhab support.
*
* @author Gerd Zanker - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
class BoschShcCommandExtensionTest {
private @NonNullByDefault({}) BoschShcCommandExtension fixture;
private @Mock @NonNullByDefault({}) ThingRegistry thingRegistry;
@BeforeEach
void setUp() {
fixture = new BoschShcCommandExtension(thingRegistry);
}
@Test
void execute() {
// only sanity checks, content is tested with the functions called by execute
Console consoleMock = mock(Console.class);
when(thingRegistry.getAll()).thenReturn(Collections.emptyList());
fixture.execute(new String[] {}, consoleMock);
verify(consoleMock, times(5)).printUsage(any());
fixture.execute(new String[] { "" }, consoleMock);
verify(consoleMock, times(10)).printUsage(any());
fixture.execute(new String[] { BoschShcCommandExtension.SHOW_BINDINGINFO }, consoleMock);
verify(consoleMock, atLeastOnce()).print(any());
fixture.execute(new String[] { BoschShcCommandExtension.SHOW_DEVICES }, consoleMock);
verify(consoleMock, atLeastOnce()).print(any());
fixture.execute(new String[] { BoschShcCommandExtension.SHOW_SERVICES }, consoleMock);
verify(consoleMock, atLeastOnce()).print(any());
fixture.execute(new String[] { BoschShcCommandExtension.GET_BRIDGEINFO }, consoleMock);
verify(consoleMock, atLeastOnce()).print(any());
fixture.execute(new String[] { BoschShcCommandExtension.GET_DEVICES }, consoleMock);
verify(consoleMock, atLeastOnce()).print(any());
}
@Test
void getCompleter() {
assertThat(fixture.getCompleter(), is(fixture));
}
@Test
void getUsages() {
List<String> strings = fixture.getUsages();
assertThat(strings.size(), is(5));
assertThat(strings.get(0), is("boschshc showBindingInfo - list detailed information about this binding"));
assertThat(strings.get(1), is("boschshc showDevices - list all devices supported by this binding"));
}
@Test
void complete() {
ArrayList<String> candidates = new ArrayList<>();
assertThat(fixture.complete(new String[] { "" }, 1, 0, candidates), is(false));
assertThat(fixture.complete(new String[] { "" }, 0, 0, candidates), is(true));
// for empty arguments, the completer suggest all usage commands
assertThat(candidates.size(), is(fixture.getUsages().size()));
}
@Test
void printBridgeInfo() throws BoschSHCException, ExecutionException, InterruptedException, TimeoutException {
// no bridge
when(thingRegistry.getAll()).thenReturn(Collections.emptyList());
assertThat(fixture.buildBridgeInfo(), is(""));
// one bridge
PublicInformation publicInformation = new PublicInformation();
publicInformation.shcGeneration = "Gen-T";
publicInformation.shcIpAddress = "1.2.3.4";
publicInformation.softwareUpdateState = new SoftwareUpdateState();
Bridge mockBridge = mock(Bridge.class);
when(mockBridge.getLabel()).thenReturn("TestLabel");
BridgeHandler mockBridgeHandler = mock(BridgeHandler.class);
when(mockBridgeHandler.getThing()).thenReturn(mockBridge);
when(mockBridgeHandler.getPublicInformation()).thenReturn(publicInformation);
Thing mockBridgeThing = mock(Thing.class);
when(mockBridgeThing.getHandler()).thenReturn(mockBridgeHandler);
when(thingRegistry.getAll()).thenReturn(Collections.singletonList(mockBridgeThing));
assertThat(fixture.buildBridgeInfo(),
allOf(containsString("Bridge: TestLabel"), containsString("access possible: false"),
containsString("SHC Generation: Gen-T"), containsString("IP Address: 1.2.3.4")));
// two bridges
PublicInformation publicInformation2 = new PublicInformation();
publicInformation2.shcGeneration = "Gen-U";
publicInformation2.shcIpAddress = "11.22.33.44";
publicInformation2.softwareUpdateState = new SoftwareUpdateState();
Bridge mockBridge2 = mock(Bridge.class);
when(mockBridge2.getLabel()).thenReturn("Bridge 2");
BridgeHandler mockBridgeHandler2 = mock(BridgeHandler.class);
when(mockBridgeHandler2.getThing()).thenReturn(mockBridge2);
when(mockBridgeHandler2.getPublicInformation()).thenReturn(publicInformation2);
Thing mockBridgeThing2 = mock(Thing.class);
when(mockBridgeThing2.getHandler()).thenReturn(mockBridgeHandler2);
when(thingRegistry.getAll()).thenReturn(Arrays.asList(mockBridgeThing, mockBridgeThing2));
assertThat(fixture.buildBridgeInfo(),
allOf(containsString("Bridge: TestLabel"), containsString("access possible: false"),
containsString("SHC Generation: Gen-T"), containsString("IP Address: 1.2.3.4"),
containsString("Bridge: Bridge 2"), containsString("access possible: false"),
containsString("SHC Generation: Gen-U"), containsString("IP Address: 11.22.33.44")));
}
@Test
void printDeviceInfo() throws InterruptedException {
// no bridge
when(thingRegistry.getAll()).thenReturn(Collections.emptyList());
assertThat(fixture.buildDeviceInfo(), is(""));
// One bridge, No device
BridgeHandler mockBridgeHandler = mock(BridgeHandler.class);
Thing mockBridgeThing = mock(Thing.class);
when(mockBridgeThing.getLabel()).thenReturn("TestLabel");
when(mockBridgeThing.getHandler()).thenReturn(mockBridgeHandler);
when(thingRegistry.getAll()).thenReturn(Collections.singletonList(mockBridgeThing));
assertThat(fixture.buildDeviceInfo(), allOf(containsString("thing: TestLabel"), containsString("devices (0)")));
// One bridge, One UNsupported device
Device mockShcDevice = mock(Device.class);
mockShcDevice.deviceModel = "";
mockShcDevice.deviceServiceIds = Collections.emptyList();
when(mockBridgeHandler.getDevices()).thenReturn(List.of(mockShcDevice));
assertThat(fixture.buildDeviceInfo(), allOf(containsString("thing: TestLabel"), containsString("devices (1)"),
containsString("!UNSUPPORTED!")));
// One bridge, One supported device
mockShcDevice.deviceModel = "TWINGUARD";
mockShcDevice.deviceServiceIds = Collections.emptyList();
when(mockBridgeHandler.getDevices()).thenReturn(List.of(mockShcDevice));
assertThat(fixture.buildDeviceInfo(), allOf(containsString("thing: TestLabel"), containsString("devices (1)"),
containsString("TWINGUARD -> twinguard")));
// One bridge, One supported device with services
mockShcDevice.deviceModel = "TWINGUARD";
mockShcDevice.deviceServiceIds = List.of("unknownService", "batterylevel");
when(mockBridgeHandler.getDevices()).thenReturn(List.of(mockShcDevice));
assertThat(fixture.buildDeviceInfo(), allOf(containsString("thing: TestLabel"), containsString("devices (1)"),
containsString("TWINGUARD -> twinguard"), containsString("service: unknownService -> !UNSUPPORTED!"),
containsString("batterylevel -> batterylevel")));
}
@Test
void printBindingInfo() {
assertThat(fixture.buildBindingInfo(), containsString("Bosch SHC Binding"));
}
@Test
void printSupportedDevices() {
assertThat(fixture.buildSupportedDeviceStatus(),
allOf(containsString("Supported Devices"), containsString("BBL = boschshc:shutter-control")));
}
@Test
void printSupportedServices() {
assertThat(fixture.buildSupportedServiceStatus(),
allOf(containsString("Supported Services"), containsString("airqualitylevel")));
}
/**
* The list of services returned by getAllBoschShcServices() shall match
* the implemented services in org.openhab.bindings.boschshc.internal.services.
* Because reflection doesn't return all services classes during runtime
* this test supports consistency between the lists of services and the implemented services.
*/
@Test
void getAllBoschShcServices() throws IOException {
List<String> services = Files
.walk(Paths.get("src/main/java/org/openhab/binding/boschshc/internal/services").toAbsolutePath(), 1)
.filter(Files::isDirectory).map(Path::getFileName).map(Path::toString)
// exclude folders which no service implementation
.filter(name -> !name.equals("dto")).filter(name -> !name.equals("services")).sorted()
.collect(Collectors.toList());
assertThat(services, is(fixture.getAllBoschShcServices()));
}
}