diff --git a/bundles/org.openhab.binding.hue/README.md b/bundles/org.openhab.binding.hue/README.md index b50629a1e..c33c61f4e 100644 --- a/bundles/org.openhab.binding.hue/README.md +++ b/bundles/org.openhab.binding.hue/README.md @@ -1,15 +1,15 @@ # Philips Hue Binding This binding integrates the [Philips Hue Lighting system](https://www.meethue.com). -The integration happens through the Hue bridge, which acts as an IP gateway to the ZigBee devices. +The integration happens through the Hue Bridge, which acts as an IP gateway to the ZigBee devices. ![Philips Hue](doc/hue.jpg) ## Supported Things -The Hue bridge is required as a "bridge" for accessing any other Hue device. +The Hue Bridge is required as a "bridge" for accessing any other Hue device. It supports the ZigBee LightLink protocol as well as the upwards compatible ZigBee 3.0 protocol. -There are two types of Hue bridges, generally referred to as v1 (the rounded version) and v2 (the squared version). +There are two types of Hue Bridges, generally referred to as v1 (the rounded version) and v2 (the squared version). Only noticeable difference between the two generation of bridges is the added support for Apple HomeKit in v2. Both bridges are fully supported by this binding. @@ -17,7 +17,7 @@ Almost all available Hue devices are supported by this binding. This includes not only the "Friends of Hue", but also products like the LivingWhites adapter. Additionally, it is possible to use OSRAM Lightify devices as well as other ZigBee LightLink compatible products, including the IKEA TRÅDFRI lights (when updated). Beside bulbs and luminaires the Hue binding also supports some ZigBee sensors. Currently only Hue specific sensors are tested successfully (Hue Motion Sensor and Hue Dimmer Switch). -Please note that the devices need to be registered with the Hue bridge before it is possible for this binding to use them. +Please note that the devices need to be registered with the Hue Bridge before it is possible for this binding to use them. The Hue binding supports all seven types of lighting devices defined for ZigBee LightLink ([see page 24, table 2](https://www.nxp.com/docs/en/user-guide/JN-UG-3091.pdf). These are: @@ -65,28 +65,35 @@ They are presented by the following ZigBee Device ID and _Thing type_: The Hue Dimmer Switch has 4 buttons and registers as a Non-Colour Controller switch, while the Hue Tap (also 4 buttons) registers as a Non-Colour Scene Controller in accordance with the ZLL standard. -Also, Hue bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor. +Also, Hue Bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor. These sensors save state for rules and calculate what actions to do. CLIP Sensor set or get by JSON through IP. -Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue bridge. +Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue Bridge. ## Discovery -The Hue bridge is discovered through UPnP in the local network. +The Hue Bridge is discovered through mDNS in the local network. +Auto-discovery is enabled by default. +To disable it, you can add the following line to `/services/runtime.cfg`: + +``` +discovery.hue:background=false +``` + Once it is added as a Thing, its authentication button (in the middle) needs to be pressed in order to authorize the binding to access it. -Once the binding is authorized, it automatically reads all devices and groups that are set up on the Hue bridge and puts them into the Inbox. +Once the binding is authorized, it automatically reads all devices and groups that are set up on the Hue Bridge and puts them into the Inbox. ## Thing Configuration -The Hue bridge requires the IP address as a configuration value in order for the binding to know where to access it. +The Hue Bridge requires the IP address as a configuration value in order for the binding to know where to access it. In the thing file, this looks e.g. like ``` Bridge hue:bridge:1 [ ipAddress="192.168.0.64" ] ``` -A user to authenticate against the Hue bridge is automatically generated. +A user to authenticate against the Hue Bridge is automatically generated. Please note that the generated user name cannot be written automatically to the `.thing` file, and has to be set manually. The generated user name can be found, after pressing the authentication button on the bridge, with the following console command: `hue username`. The user name can be set using the `userName` configuration value, e.g.: @@ -95,17 +102,19 @@ The user name can be set using the `userName` configuration value, e.g.: Bridge hue:bridge:1 [ ipAddress="192.168.0.64", userName="qwertzuiopasdfghjklyxcvbnm1234" ] ``` -| Parameter | Description | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ipAddress | Network address of the Hue bridge. **Mandatory** | -| port | Port of the Hue bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined. | -| userName | Name of a registered Hue bridge user, that allows to access the API. **Mandatory** | -| pollingInterval | Seconds between fetching light values from the Hue bridge. Optional, the default value is 10 (min="1", step="1"). | -| sensorPollingInterval | Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. | +| Parameter | Description | +|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ipAddress | Network address of the Hue Bridge. **Mandatory**. | +| port | Port of the Hue Bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined. | +| protocol | Protocol to connect to the Hue Bridge ("http" or "https"), default value is "https"). | +| useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. **Advanced**, default value is `true`. | +| userName | Name of a registered Hue Bridge user, that allows to access the API. **Mandatory** | +| pollingInterval | Seconds between fetching light values from the Hue Bridge. Optional, the default value is 10 (min="1", step="1"). | +| sensorPollingInterval | Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. | ### Devices -The devices are identified by the number that the Hue bridge assigns to them (also shown in the Hue App as an identifier). +The devices are identified by the number that the Hue Bridge assigns to them (also shown in the Hue App as an identifier). Thus, all it needs for manual configuration is this single value like ``` @@ -130,13 +139,13 @@ The following device types also have an optional configuration value to specify | Parameter | Description | |-----------|-------------------------------------------------------------------------------| -| lightId | Number of the device provided by the Hue bridge. **Mandatory** | +| lightId | Number of the device provided by the Hue Bridge. **Mandatory** | | fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") | ### Groups -The groups are identified by the number that the Hue bridge assigns to them. +The groups are identified by the number that the Hue Bridge assigns to them. Thus, all it needs for manual configuration is this single value like ``` @@ -149,7 +158,7 @@ The group type also have an optional configuration value to specify the fade tim | Parameter | Description | |-----------|-------------------------------------------------------------------------------| -| groupId | Number of the group provided by the Hue bridge. **Mandatory** | +| groupId | Number of the group provided by the Hue Bridge. **Mandatory** | | fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") | @@ -179,7 +188,7 @@ The devices support some of the following channels: | last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302 | | battery_level | Number | This channel shows the battery level. | 0820, 0106, 0107, 0302 | | battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 | -| scene | String | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue bridge. | bridge, group | +| scene | String | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue Bridge. | bridge, group | To load a hue scene inside a rule for example, the ID of the scene will be required. You can list all the scene IDs with the following console commands: `hue scenes` and `hue scenes`. @@ -366,11 +375,3 @@ if (receivedEvent == "1000.0")) { //do stuff } ``` - -### UPnP Discovery: Inbox 'Grace Period' - -The Hue Bridge can sometimes be late in sending its UPnP 'ssdp:alive' notifications even though it has not really gone offline. -This means that the Hue Bridge could be repeatedly removed from, and (re)added to, the InBox. -Which would lead to confusion in the UI, and repeated logger messages. -To prevent this, the binding tells the OpenHAB core to wait for a further period of time ('grace period') before actually removing the Bridge from the Inbox. -The 'grace period' has a default value of 50 seconds, but it can be fine tuned in the main UI via Settings | Bindings | Hue | Configure. diff --git a/bundles/org.openhab.binding.hue/src/main/feature/feature.xml b/bundles/org.openhab.binding.hue/src/main/feature/feature.xml index abc121f42..ba4db3d38 100644 --- a/bundles/org.openhab.binding.hue/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.hue/src/main/feature/feature.xml @@ -4,7 +4,7 @@ openhab-runtime-base - openhab-transport-upnp + openhab-transport-mdns mvn:org.openhab.addons.bundles/org.openhab.binding.hue/${project.version} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java deleted file mode 100644 index d0c78db9e..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import java.util.Date; - -/** - * - * @author Q42 - Initial contribution - * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding - */ -@SuppressWarnings("unused") -class CreateScheduleRequest { - private String name; - private String description; - private ScheduleCommand command; - private Date time; - - public CreateScheduleRequest(String name, String description, ScheduleCommand command, Date time) { - if (name != null && Util.stringSize(name) > 32) { - throw new IllegalArgumentException("Schedule name can be at most 32 characters long"); - } - - if (description != null && Util.stringSize(description) > 64) { - throw new IllegalArgumentException("Schedule description can be at most 64 characters long"); - } - - if (command == null) { - throw new IllegalArgumentException("No schedule command specified"); - } - - this.name = name; - this.description = description; - this.command = command; - this.time = time; - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java deleted file mode 100644 index d714863fe..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import java.util.Date; - -/** - * Detailed schedule information. - * - * @author Q42 - Initial contribution - * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding - */ -public class FullSchedule extends Schedule { - private String description; - private ScheduleCommand command; // Not really appropriate for exposure - private Date time; - - /** - * Returns the description of the schedule. - * - * @return description - */ - public String getDescription() { - return description; - } - - /** - * Returns the scheduled command. - * - * @return command - */ - public ScheduleCommand getCommand() { - return command; - } - - /** - * Returns the time for which the command is scheduled to be ran. - * - * @return scheduled time - */ - public Date getTime() { - return time; - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java deleted file mode 100644 index 06629ef09..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.LinkedList; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author Q42 - Initial contribution - * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding - */ -@NonNullByDefault -public class HttpClient { - private int timeout = 1000; - private final Logger logger = LoggerFactory.getLogger(HttpClient.class); - private final LinkedList commandsQueue = new LinkedList<>(); - private @Nullable Future job; - - @SuppressWarnings({ "null", "unused" }) - private void executeCommands() { - while (true) { - try { - long delayTime = 0; - synchronized (commandsQueue) { - AsyncPutParameters payloadCallbackPair = commandsQueue.poll(); - if (payloadCallbackPair != null) { - logger.debug("Async sending put to address: {} delay: {} body: {}", payloadCallbackPair.address, - payloadCallbackPair.delay, payloadCallbackPair.body); - try { - Result result = put(payloadCallbackPair.address, payloadCallbackPair.body); - payloadCallbackPair.future.complete(result); - } catch (IOException e) { - payloadCallbackPair.future.completeExceptionally(e); - } - delayTime = payloadCallbackPair.delay; - } else { - return; - } - } - Thread.sleep(delayTime); - } catch (InterruptedException e) { - logger.debug("commandExecutorThread was interrupted", e); - } - } - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public Result get(String address) throws IOException { - return doNetwork(address, "GET"); - } - - public Result post(String address, String body) throws IOException { - return doNetwork(address, "POST", body); - } - - public Result put(String address, String body) throws IOException { - return doNetwork(address, "PUT", body); - } - - public CompletableFuture putAsync(String address, String body, long delay, - ScheduledExecutorService scheduler) { - AsyncPutParameters asyncPutParameters = new AsyncPutParameters(address, body, delay); - - synchronized (commandsQueue) { - if (commandsQueue.isEmpty()) { - commandsQueue.offer(asyncPutParameters); - Future localJob = job; - if (localJob == null || localJob.isDone()) { - job = scheduler.submit(this::executeCommands); - } - } else { - commandsQueue.offer(asyncPutParameters); - } - } - - return asyncPutParameters.future; - } - - public Result delete(String address) throws IOException { - return doNetwork(address, "DELETE"); - } - - protected Result doNetwork(String address, String requestMethod) throws IOException { - return doNetwork(address, requestMethod, null); - } - - protected Result doNetwork(String address, String requestMethod, @Nullable String body) throws IOException { - HttpURLConnection conn = (HttpURLConnection) new URL(address).openConnection(); - try { - conn.setRequestMethod(requestMethod); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setConnectTimeout(timeout); - conn.setReadTimeout(timeout); - - if (body != null && !"".equals(body)) { - conn.setDoOutput(true); - try (Writer out = new OutputStreamWriter(conn.getOutputStream())) { - out.write(body); - } - } - - try (InputStream in = conn.getInputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream()) { - byte[] buffer = new byte[1024]; - int length; - while ((length = in.read(buffer)) != -1) { - result.write(buffer, 0, length); - } - return new Result(result.toString(StandardCharsets.UTF_8.name()), conn.getResponseCode()); - } - } finally { - conn.disconnect(); - } - } - - public static class Result { - private final String body; - private final int responseCode; - - public Result(String body, int responseCode) { - this.body = body; - this.responseCode = responseCode; - } - - public String getBody() { - return body; - } - - public int getResponseCode() { - return responseCode; - } - } - - public final class AsyncPutParameters { - public final String address; - public final String body; - public final CompletableFuture future; - public final long delay; - - public AsyncPutParameters(String address, String body, long delay) { - this.address = address; - this.body = body; - this.future = new CompletableFuture<>(); - this.delay = delay; - } - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java index f331488a8..b0416a584 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java @@ -89,8 +89,6 @@ public class HueBindingConstants { // Bridge config properties public static final String HOST = "ipAddress"; - public static final String PORT = "port"; - public static final String PROTOCOL = "protocol"; public static final String USER_NAME = "userName"; // Thing configuration properties @@ -102,4 +100,11 @@ public class HueBindingConstants { public static final String GROUP_ID = "groupId"; public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]"; + + // + public static final String TEXT_OFFLINE_COMMUNICATION_ERROR = "@text/offline.communication-error"; + public static final String TEXT_OFFLINE_CONFIGURATION_ERROR_INVALID_SSL_CERIFICATE = "@text/offline.conf-error-invalid-ssl-certificate"; + + // Config status messages + public static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration"; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java deleted file mode 100644 index 78e7f5fc6..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import org.openhab.core.config.core.status.ConfigStatusMessage; - -/** - * The {@link HueConfigStatusMessage} defines - * the keys to be used for {@link ConfigStatusMessage}s. - * - * @author Alexander Kostadinov - Initial contribution - * @author Kai Kreuzer - Changed from enum to interface - * - */ -public interface HueConfigStatusMessage { - - static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration"; -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java deleted file mode 100644 index 1622061e3..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import java.lang.reflect.Type; -import java.util.List; - -import com.google.gson.reflect.TypeToken; - -/** - * - * @author Q42 - Initial contribution - * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding - */ -class PortalDiscoveryResult { - public static final Type GSON_TYPE = new TypeToken>() { - }.getType(); - - private String internalipaddress; - - public String getIPAddress() { - return internalipaddress; - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java deleted file mode 100644 index 7809968c7..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -import com.google.gson.JsonElement; - -/** - * Information about a scheduled command. - * - * @author Q42 - Initial contribution - * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding - */ -public class ScheduleCommand { - private String address; - private String method; - private JsonElement body; - - ScheduleCommand(String address, String method, JsonElement body) { - this.address = address; - this.method = method; - this.body = body; - } - - /** - * Returns the relative request url. - * - * @return request url - */ - public String getAddress() { - return address; - } - - /** - * Returns the request method. - * Can be GET, PUT, POST or DELETE. - * - * @return request method - */ - public String getMethod() { - return method; - } - - /** - * Returns the request body. - * - * @return request body - */ - public String getBody() { - return body.toString(); - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/action/LightActions.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/action/LightActions.java index 91fc89aad..fa0295fac 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/action/LightActions.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/action/LightActions.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link LightActions} defines {@link ThingActions} for the hue lights. + * The {@link LightActions} defines {@link ThingActions} for the Hue lights. * * @author Jochen Leopold - Initial contribution */ diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/config/HueBridgeConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/config/HueBridgeConfig.java index e7dd794fa..77ab7c25d 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/config/HueBridgeConfig.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/config/HueBridgeConfig.java @@ -26,59 +26,16 @@ public class HueBridgeConfig { public static final String HTTP = "http"; public static final String HTTPS = "https"; - private @Nullable String ipAddress; - private @Nullable Integer port; - private String protocol = HTTP; - private @Nullable String userName; - private int pollingInterval = 10; - private int sensorPollingInterval = 500; - - public @Nullable String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } + public @Nullable String ipAddress; + public @Nullable Integer port; + public String protocol = HTTPS; + public boolean useSelfSignedCertificate = true; + public @Nullable String userName; + public int pollingInterval = 10; + public int sensorPollingInterval = 500; public int getPort() { - Integer thePort = this.port; + Integer thePort = port; return (thePort != null) ? thePort.intValue() : HTTPS.equals(protocol) ? 443 : 80; } - - public void setPort(int port) { - this.port = port; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - public @Nullable String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public int getPollingInterval() { - return pollingInterval; - } - - public void setPollingInterval(int pollingInterval) { - this.pollingInterval = pollingInterval; - } - - public int getSensorPollingInterval() { - return sensorPollingInterval; - } - - public void setSensorPollingInterval(int sensorPollingInterval) { - this.sensorPollingInterval = sensorPollingInterval; - } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java similarity index 59% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java index e681c6f56..532acb130 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.connection; + +import static org.openhab.binding.hue.internal.HueBindingConstants.*; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; import java.net.URI; import java.net.URISyntaxException; @@ -25,16 +26,50 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import javax.net.ssl.SSLHandshakeException; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.HttpClient.Result; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.hue.internal.dto.ApiVersion; +import org.openhab.binding.hue.internal.dto.ApiVersionUtils; +import org.openhab.binding.hue.internal.dto.Config; +import org.openhab.binding.hue.internal.dto.ConfigUpdate; +import org.openhab.binding.hue.internal.dto.CreateUserRequest; +import org.openhab.binding.hue.internal.dto.ErrorResponse; +import org.openhab.binding.hue.internal.dto.FullConfig; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.FullHueObject; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.Group; +import org.openhab.binding.hue.internal.dto.HueObject; +import org.openhab.binding.hue.internal.dto.NewLightsResponse; +import org.openhab.binding.hue.internal.dto.Scene; +import org.openhab.binding.hue.internal.dto.Schedule; +import org.openhab.binding.hue.internal.dto.ScheduleUpdate; +import org.openhab.binding.hue.internal.dto.SearchForLightsRequest; +import org.openhab.binding.hue.internal.dto.SetAttributesRequest; +import org.openhab.binding.hue.internal.dto.StateUpdate; +import org.openhab.binding.hue.internal.dto.SuccessResponse; +import org.openhab.binding.hue.internal.dto.Util; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.DeviceOffException; import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException; @@ -42,17 +77,17 @@ import org.openhab.binding.hue.internal.exceptions.GroupTableFullException; import org.openhab.binding.hue.internal.exceptions.InvalidCommandException; import org.openhab.binding.hue.internal.exceptions.LinkButtonException; import org.openhab.binding.hue.internal.exceptions.UnauthorizedException; +import org.openhab.core.i18n.CommunicationException; +import org.openhab.core.i18n.ConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; /** - * Representation of a connection with a Hue bridge. + * Representation of a connection with a Hue Bridge. * * @author Q42 - Initial contribution * @author Andre Fuechsel - search for lights with given serial number added @@ -67,25 +102,31 @@ public class HueBridge { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + private final HttpClient httpClient; private final String ip; private final String baseUrl; private @Nullable String username; + private long timeout = TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS); private final Gson gson = new GsonBuilder().setDateFormat(DATE_FORMAT).create(); - private HttpClient http = new HttpClient(); + + private final LinkedList commandsQueue = new LinkedList<>(); + private @Nullable Future job; private final ScheduledExecutorService scheduler; - @Nullable - private Config cachedConfig; + private @Nullable Config cachedConfig; /** * Connect with a bridge as a new user. * + * @param httpClient instance of the Jetty shared client * @param ip ip address of bridge * @param port port of bridge * @param protocol protocol to connect to the bridge + * @param scheduler the ExecutorService to schedule commands */ - public HueBridge(String ip, int port, String protocol, ScheduledExecutorService scheduler) { + public HueBridge(HttpClient httpClient, String ip, int port, String protocol, ScheduledExecutorService scheduler) { + this.httpClient = httpClient; this.ip = ip; String baseUrl; try { @@ -106,35 +147,27 @@ public class HueBridge { * Use the ip only constructor and authenticate() function if * you don't want to connect right now. * + * @param httpClient instance of the Jetty shared client * @param ip ip address of bridge * @param port port of bridge * @param protocol protocol to connect to the bridge * @param username username to authenticate with + * @param scheduler the ExecutorService to schedule commands */ - public HueBridge(String ip, int port, String protocol, String username, ScheduledExecutorService scheduler) - throws IOException, ApiException { - this(ip, port, protocol, scheduler); + public HueBridge(HttpClient httpClient, String ip, int port, String protocol, String username, + ScheduledExecutorService scheduler) + throws IOException, ApiException, ConfigurationException, UnauthorizedException { + this(httpClient, ip, port, protocol, scheduler); authenticate(username); } - /** - * Test constructor - */ - HueBridge(String ip, String baseUrl, String username, ScheduledExecutorService scheduler, HttpClient http) { - this.ip = ip; - this.baseUrl = baseUrl; - this.username = username; - this.scheduler = scheduler; - this.http = http; - } - /** * Set the connect and read timeout for HTTP requests. * * @param timeout timeout in milliseconds or 0 for indefinitely */ - public void setTimeout(int timeout) { - http.setTimeout(timeout); + public void setTimeout(long timeout) { + this.timeout = timeout; } /** @@ -160,11 +193,11 @@ public class HueBridge { * @throws ApiException */ private Config getCachedConfig() throws IOException, ApiException { - if (this.cachedConfig == null) { - this.cachedConfig = getConfig(); + if (cachedConfig == null) { + cachedConfig = getConfig(); } - return Objects.requireNonNull(this.cachedConfig); + return Objects.requireNonNull(cachedConfig); } /** @@ -211,26 +244,21 @@ public class HueBridge { return getTypedLights(gsonType); } - private List getTypedLights(Type gsonType) throws IOException, ApiException { + private List getTypedLights(Type gsonType) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("lights")); + HueResult result = get(getRelativeURL("lights")); handleErrors(result); - Map lightMap = safeFromJson(result.getBody(), gsonType); - ArrayList lightList = new ArrayList<>(); - - for (String id : lightMap.keySet()) { - @Nullable - T light = lightMap.get(id); - if (light != null) { - light.setId(id); - lightList.add(light); - } - } - - return lightList; + Map lightMap = safeFromJson(result.body, gsonType); + List lights = new ArrayList<>(); + lightMap.forEach((id, light) -> { + light.setId(id); + lights.add(light); + }); + return lights; } /** @@ -239,23 +267,21 @@ public class HueBridge { * @return list of sensors * @throws UnauthorizedException thrown if the user no longer exists */ - public List getSensors() throws IOException, ApiException { + public List getSensors() + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("sensors")); + HueResult result = get(getRelativeURL("sensors")); handleErrors(result); - Map sensorMap = safeFromJson(result.getBody(), FullSensor.GSON_TYPE); - ArrayList sensorList = new ArrayList<>(); - - for (String id : sensorMap.keySet()) { - FullSensor sensor = sensorMap.get(id); + Map sensorMap = safeFromJson(result.body, FullSensor.GSON_TYPE); + List sensors = new ArrayList<>(); + sensorMap.forEach((id, sensor) -> { sensor.setId(id); - sensorList.add(sensor); - } - - return sensorList; + sensors.add(sensor); + }); + return sensors; } /** @@ -266,14 +292,15 @@ public class HueBridge { * @return last search time * @throws UnauthorizedException thrown if the user no longer exists */ - public @Nullable Date getLastSearch() throws IOException, ApiException { + public @Nullable Date getLastSearch() + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("lights/new")); + HueResult result = get(getRelativeURL("lights/new")); handleErrors(result); - String lastScan = safeFromJson(result.getBody(), NewLightsResponse.class).lastscan; + String lastScan = safeFromJson(result.body, NewLightsResponse.class).lastscan; switch (lastScan) { case "none": @@ -295,10 +322,10 @@ public class HueBridge { * * @throws UnauthorizedException thrown if the user no longer exists */ - public void startSearch() throws IOException, ApiException { + public void startSearch() throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.post(getRelativeURL("lights"), ""); + HueResult result = post(getRelativeURL("lights"), ""); handleErrors(result); } @@ -310,11 +337,11 @@ public class HueBridge { * @param serialNumbers list of serial numbers * @throws UnauthorizedException thrown if the user no longer exists */ - public void startSearch(List serialNumbers) throws IOException, ApiException { + public void startSearch(List serialNumbers) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = gson.toJson(new SearchForLightsRequest(serialNumbers)); - Result result = http.post(getRelativeURL("lights"), body); + HueResult result = post(getRelativeURL("lights"), gson.toJson(new SearchForLightsRequest(serialNumbers))); handleErrors(result); } @@ -327,14 +354,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if a light with the given id doesn't exist */ - public FullHueObject getLight(HueObject light) throws IOException, ApiException { + public FullHueObject getLight(HueObject light) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("lights/" + enc(light.getId()))); + HueResult result = get(getRelativeURL("lights/" + enc(light.getId()))); handleErrors(result); - FullHueObject fullLight = safeFromJson(result.getBody(), FullLight.class); + FullHueObject fullLight = safeFromJson(result.body, FullLight.class); fullLight.setId(light.getId()); return fullLight; } @@ -349,15 +377,16 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified light no longer exists */ - public String setLightName(HueObject light, String name) throws IOException, ApiException { + public String setLightName(HueObject light, String name) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = gson.toJson(new SetAttributesRequest(name)); - Result result = http.put(getRelativeURL("lights/" + enc(light.getId())), body); + HueResult result = put(getRelativeURL("lights/" + enc(light.getId())), + gson.toJson(new SetAttributesRequest(name))); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); String lightName = (String) response.success.get("/lights/" + enc(light.getId()) + "/name"); @@ -377,12 +406,11 @@ public class HueBridge { * @throws DeviceOffException thrown if the specified light is turned off * @throws IOException if the bridge cannot be reached */ - public CompletableFuture setLightState(FullLight light, StateUpdate update) { + public CompletableFuture setLightState(FullLight light, StateUpdate update) { requireAuthentication(); - String body = update.toJson(); - return http.putAsync(getRelativeURL("lights/" + enc(light.getId()) + "/state"), body, update.getMessageDelay(), - scheduler); + return putAsync(getRelativeURL("lights/" + enc(light.getId()) + "/state"), update.toJson(), + update.getMessageDelay()); } /** @@ -395,12 +423,11 @@ public class HueBridge { * @throws DeviceOffException thrown if the specified sensor is turned off * @throws IOException if the bridge cannot be reached */ - public CompletableFuture setSensorState(FullSensor sensor, StateUpdate update) { + public CompletableFuture setSensorState(FullSensor sensor, StateUpdate update) { requireAuthentication(); - String body = update.toJson(); - return http.putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/state"), body, - update.getMessageDelay(), scheduler); + return putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/state"), update.toJson(), + update.getMessageDelay()); } /** @@ -412,12 +439,11 @@ public class HueBridge { * @throws EntityNotAvailableException thrown if the specified sensor no longer exists * @throws IOException if the bridge cannot be reached */ - public CompletableFuture updateSensorConfig(FullSensor sensor, ConfigUpdate update) { + public CompletableFuture updateSensorConfig(FullSensor sensor, ConfigUpdate update) { requireAuthentication(); - String body = update.toJson(); - return http.putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/config"), body, - update.getMessageDelay(), scheduler); + return putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/config"), update.toJson(), + update.getMessageDelay()); } /** @@ -435,20 +461,20 @@ public class HueBridge { * @return list of groups * @throws UnauthorizedException thrown if the user no longer exists */ - public List getGroups() throws IOException, ApiException { + public List getGroups() + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("groups")); + HueResult result = get(getRelativeURL("groups")); handleErrors(result); - Map groupMap = safeFromJson(result.getBody(), FullGroup.GSON_TYPE); - ArrayList groupList = new ArrayList<>(); - + Map groupMap = safeFromJson(result.body, FullGroup.GSON_TYPE); + List groups = new ArrayList<>(); if (groupMap.get("0") == null) { // Group 0 is not returned, we create it as in fact it exists try { - groupList.add(getGroup(getAllGroup())); + groups.add(getGroup(getAllGroup())); } catch (FileNotFoundException e) { // We need a special exception handling here to further support deCONZ REST API. On deCONZ group "0" may // not exist and the APIs will return a different HTTP status code if requesting a non existing group @@ -457,13 +483,11 @@ public class HueBridge { logger.debug("Cannot find AllGroup with id \"0\" on Hue Bridge. Skipping it."); } } - groupMap.forEach((id, group) -> { group.setId(id); - groupList.add(group); + groups.add(group); }); - - return groupList; + return groups; } /** @@ -478,15 +502,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws GroupTableFullException thrown if the group limit has been reached */ - public Group createGroup(List lights) throws IOException, ApiException { + public Group createGroup(List lights) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = gson.toJson(new SetAttributesRequest(lights)); - Result result = http.post(getRelativeURL("groups"), body); + HueResult result = post(getRelativeURL("groups"), gson.toJson(new SetAttributesRequest(lights))); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); Group group = new Group(); @@ -508,15 +532,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws GroupTableFullException thrown if the group limit has been reached */ - public Group createGroup(String name, List lights) throws IOException, ApiException { + public Group createGroup(String name, List lights) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = gson.toJson(new SetAttributesRequest(name, lights)); - Result result = http.post(getRelativeURL("groups"), body); + HueResult result = post(getRelativeURL("groups"), gson.toJson(new SetAttributesRequest(name, lights))); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); Group group = new Group(); @@ -533,14 +557,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if a group with the given id doesn't exist */ - public FullGroup getGroup(Group group) throws IOException, ApiException { + public FullGroup getGroup(Group group) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("groups/" + enc(group.getId()))); + HueResult result = get(getRelativeURL("groups/" + enc(group.getId()))); handleErrors(result); - FullGroup fullGroup = safeFromJson(result.getBody(), FullGroup.class); + FullGroup fullGroup = safeFromJson(result.body, FullGroup.class); fullGroup.setId(group.getId()); return fullGroup; } @@ -555,19 +580,20 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified group no longer exists */ - public String setGroupName(Group group, String name) throws IOException, ApiException { + public String setGroupName(Group group, String name) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); if (!group.isModifiable()) { throw new IllegalArgumentException("Group cannot be modified"); } - String body = gson.toJson(new SetAttributesRequest(name)); - Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body); + HueResult result = put(getRelativeURL("groups/" + enc(group.getId())), + gson.toJson(new SetAttributesRequest(name))); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name"); @@ -585,15 +611,16 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified group no longer exists */ - public void setGroupLights(Group group, List lights) throws IOException, ApiException { + public void setGroupLights(Group group, List lights) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); if (!group.isModifiable()) { throw new IllegalArgumentException("Group cannot be modified"); } - String body = gson.toJson(new SetAttributesRequest(lights)); - Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body); + HueResult result = put(getRelativeURL("groups/" + enc(group.getId())), + gson.toJson(new SetAttributesRequest(lights))); handleErrors(result); } @@ -609,19 +636,19 @@ public class HueBridge { * @throws EntityNotAvailableException thrown if the specified group no longer exists */ public String setGroupAttributes(Group group, String name, List lights) - throws IOException, ApiException { + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); if (!group.isModifiable()) { throw new IllegalArgumentException("Group cannot be modified"); } - String body = gson.toJson(new SetAttributesRequest(name, lights)); - Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body); + HueResult result = put(getRelativeURL("groups/" + enc(group.getId())), + gson.toJson(new SetAttributesRequest(name, lights))); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name"); @@ -639,12 +666,11 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified group no longer exists */ - public CompletableFuture setGroupState(Group group, StateUpdate update) { + public CompletableFuture setGroupState(Group group, StateUpdate update) { requireAuthentication(); - String body = update.toJson(); - return http.putAsync(getRelativeURL("groups/" + enc(group.getId()) + "/action"), body, update.getMessageDelay(), - scheduler); + return putAsync(getRelativeURL("groups/" + enc(group.getId()) + "/action"), update.toJson(), + update.getMessageDelay()); } /** @@ -654,14 +680,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified group no longer exists */ - public void deleteGroup(Group group) throws IOException, ApiException { + public void deleteGroup(Group group) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); if (!group.isModifiable()) { throw new IllegalArgumentException("Group cannot be modified"); } - Result result = http.delete(getRelativeURL("groups/" + enc(group.getId()))); + HueResult result = delete(getRelativeURL("groups/" + enc(group.getId()))); handleErrors(result); } @@ -672,109 +699,21 @@ public class HueBridge { * @return schedules * @throws UnauthorizedException thrown if the user no longer exists */ - public List getSchedules() throws IOException, ApiException { + public List getSchedules() + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("schedules")); + HueResult result = get(getRelativeURL("schedules")); handleErrors(result); - Map scheduleMap = safeFromJson(result.getBody(), Schedule.GSON_TYPE); - - ArrayList scheduleList = new ArrayList<>(); - - for (String id : scheduleMap.keySet()) { - Schedule schedule = scheduleMap.get(id); + Map scheduleMap = safeFromJson(result.body, Schedule.GSON_TYPE); + List schedules = new ArrayList<>(); + scheduleMap.forEach((id, schedule) -> { schedule.setId(id); - scheduleList.add(schedule); - } - - return scheduleList; - } - - /** - * Schedules a new command to be run at the specified time. - * To select the command for the new schedule, simply run it - * as you normally would in the callback. Instead of it running - * immediately, it will be scheduled to run at the specified time. - * It will automatically fail with an IOException, because there - * will be no response. Note that GET methods cannot be scheduled, - * so those will still run and return results immediately. - * - * @param time time to run command - * @param callback callback in which the command is specified - * @throws UnauthorizedException thrown if the user no longer exists - * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid - */ - public void createSchedule(Date time, ScheduleCallback callback) throws IOException, ApiException { - createSchedule(null, null, time, callback); - } - - /** - * Schedules a new command to be run at the specified time. - * To select the command for the new schedule, simply run it - * as you normally would in the callback. Instead of it running - * immediately, it will be scheduled to run at the specified time. - * It will automatically fail with an IOException, because there - * will be no response. Note that GET methods cannot be scheduled, - * so those will still run and return results immediately. - * - * @param name name [0..32] - * @param time time to run command - * @param callback callback in which the command is specified - * @throws UnauthorizedException thrown if the user no longer exists - * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid - */ - public void createSchedule(String name, Date time, ScheduleCallback callback) throws IOException, ApiException { - createSchedule(name, null, time, callback); - } - - /** - * Schedules a new command to be run at the specified time. - * To select the command for the new schedule, simply run it - * as you normally would in the callback. Instead of it running - * immediately, it will be scheduled to run at the specified time. - * It will automatically fail with an IOException, because there - * will be no response. Note that GET methods cannot be scheduled, - * so those will still run and return results immediately. - * - * @param name name [0..32] - * @param description description [0..64] - * @param time time to run command - * @param callback callback in which the command is specified - * @throws UnauthorizedException thrown if the user no longer exists - * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid - */ - public void createSchedule(@Nullable String name, @Nullable String description, Date time, - ScheduleCallback callback) throws IOException, ApiException { - requireAuthentication(); - - handleCommandCallback(callback); - - String body = gson.toJson(new CreateScheduleRequest(name, description, scheduleCommand, time)); - Result result = http.post(getRelativeURL("schedules"), body); - - handleErrors(result); - } - - /** - * Returns detailed information for the given schedule. - * - * @param schedule schedule - * @return detailed schedule information - * @throws UnauthorizedException thrown if the user no longer exists - * @throws EntityNotAvailableException thrown if the specified schedule no longer exists - */ - public FullSchedule getSchedule(Schedule schedule) throws IOException, ApiException { - requireAuthentication(); - - Result result = http.get(getRelativeURL("schedules/" + enc(schedule.getId()))); - - handleErrors(result); - - FullSchedule fullSchedule = safeFromJson(result.getBody(), FullSchedule.class); - fullSchedule.setId(schedule.getId()); - return fullSchedule; + schedules.add(schedule); + }); + return schedules; } /** @@ -785,91 +724,15 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified schedule no longer exists */ - public void setSchedule(Schedule schedule, ScheduleUpdate update) throws IOException, ApiException { + public void setSchedule(Schedule schedule, ScheduleUpdate update) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = update.toJson(); - Result result = http.put(getRelativeURL("schedules/" + enc(schedule.getId())), body); + HueResult result = put(getRelativeURL("schedules/" + enc(schedule.getId())), update.toJson()); handleErrors(result); } - /** - * Changes the command of a schedule. - * - * @param schedule schedule - * @param callback callback for new command - * @see #createSchedule(String, String, Date, ScheduleCallback) - * @throws UnauthorizedException thrown if the user no longer exists - * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid - */ - public void setScheduleCommand(Schedule schedule, ScheduleCallback callback) throws IOException, ApiException { - requireAuthentication(); - - handleCommandCallback(callback); - - String body = gson.toJson(new CreateScheduleRequest(null, null, scheduleCommand, null)); - Result result = http.put(getRelativeURL("schedules/" + enc(schedule.getId())), body); - - handleErrors(result); - } - - /** - * Callback to specify a schedule command. - */ - public interface ScheduleCallback { - /** - * Run the command you want to schedule as if you're executing - * it normally. The request will automatically fail to produce - * a result by throwing an IOException. Requests that only - * get data (e.g. getGroups) will still execute immediately, - * because those cannot be scheduled. - * - * @param bridge this bridge for convenience - * @throws IOException always thrown right after executing a command - */ - public void onScheduleCommand(HueBridge bridge) throws IOException, ApiException; - } - - private @Nullable ScheduleCommand scheduleCommand = null; - - private @Nullable ScheduleCommand handleCommandCallback(ScheduleCallback callback) throws ApiException { - // Temporarily reroute requests to a fake HTTP client - HttpClient realClient = http; - http = new HttpClient() { - @Override - protected Result doNetwork(String address, String requestMethod, @Nullable String body) throws IOException { - // GET requests cannot be scheduled, so will continue working normally for convenience - if ("GET".equals(requestMethod)) { - return super.doNetwork(address, requestMethod, body); - } else { - String extractedAddress = Util.quickMatch("^http://[^/]+(.+)$", address); - JsonElement commandBody = body == null ? null : JsonParser.parseString(body); - scheduleCommand = new ScheduleCommand(extractedAddress, requestMethod, commandBody); - - // Return a fake result that will cause an exception and the callback to end - return new Result("", 405); - } - } - }; - - // Run command - try { - scheduleCommand = null; - callback.onScheduleCommand(this); - } catch (IOException | RuntimeException e) { - // Command will automatically fail to return a result because of deferred execution - } - if (scheduleCommand != null && Util.stringSize(scheduleCommand.getBody()) > 90) { - throw new InvalidCommandException("Commmand body is larger than 90 bytes"); - } - - // Restore HTTP client - http = realClient; - - return scheduleCommand; - } - /** * Delete a schedule. * @@ -877,10 +740,11 @@ public class HueBridge { * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the schedule no longer exists */ - public void deleteSchedule(Schedule schedule) throws IOException, ApiException { + public void deleteSchedule(Schedule schedule) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.delete(getRelativeURL("schedules/" + enc(schedule.getId()))); + HueResult result = delete(getRelativeURL("schedules/" + enc(schedule.getId()))); handleErrors(result); } @@ -890,13 +754,14 @@ public class HueBridge { * * @return all scenes that can be activated */ - public List getScenes() throws IOException, ApiException { + public List getScenes() throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("scenes")); + HueResult result = get(getRelativeURL("scenes")); + handleErrors(result); - Map sceneMap = safeFromJson(result.getBody(), Scene.GSON_TYPE); + Map sceneMap = safeFromJson(result.body, Scene.GSON_TYPE); return sceneMap.entrySet().stream()// .map(e -> { e.getValue().setId(e.getKey()); @@ -913,7 +778,7 @@ public class HueBridge { * @param id the scene to be activated * @throws IOException if the bridge cannot be reached */ - public CompletableFuture recallScene(String id) { + public CompletableFuture recallScene(String id) { Group allLightsGroup = new Group(); return setGroupState(allLightsGroup, new StateUpdate().setScene(id)); } @@ -925,12 +790,16 @@ public class HueBridge { * is thrown and the internal username is not changed. * * @param username username to authenticate + * @throws ConfigurationException thrown on ssl failure * @throws UnauthorizedException thrown if authentication failed */ - public void authenticate(String username) throws IOException, ApiException { + public void authenticate(String username) + throws IOException, ApiException, ConfigurationException, UnauthorizedException { try { this.username = username; getLights(); + } catch (ConfigurationException e) { + throw e; } catch (Exception e) { this.username = null; throw new UnauthorizedException(e.toString()); @@ -944,7 +813,8 @@ public class HueBridge { * @param devicetype identifier of application [0..40] * @throws LinkButtonException thrown if the bridge button has not been pressed */ - public void link(String username, String devicetype) throws IOException, ApiException { + public void link(String username, String devicetype) + throws IOException, ApiException, ConfigurationException, CommunicationException { this.username = link(new CreateUserRequest(username, devicetype)); } @@ -956,21 +826,22 @@ public class HueBridge { * @param devicetype identifier of application [0..40] * @throws LinkButtonException thrown if the bridge button has not been pressed */ - public String link(String devicetype) throws IOException, ApiException { + public String link(String devicetype) + throws IOException, ApiException, ConfigurationException, CommunicationException { return (this.username = link(new CreateUserRequest(devicetype))); } - private String link(CreateUserRequest request) throws IOException, ApiException { + private String link(CreateUserRequest request) + throws IOException, ApiException, ConfigurationException, CommunicationException { if (this.username != null) { throw new IllegalStateException("already linked"); } - String body = gson.toJson(request); - Result result = http.post(getRelativeURL(""), body); + HueResult result = post(getRelativeURL(""), gson.toJson(request)); handleErrors(result); - List entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE); + List entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE); SuccessResponse response = entries.get(0); String username = (String) response.success.get("username"); @@ -987,14 +858,14 @@ public class HueBridge { * @return bridge configuration * @throws UnauthorizedException thrown if the user no longer exists */ - public Config getConfig() throws IOException, ApiException { + public Config getConfig() throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("config")); + HueResult result = get(getRelativeURL("config")); handleErrors(result); - return safeFromJson(result.getBody(), Config.class); + return safeFromJson(result.body, Config.class); } /** @@ -1003,11 +874,11 @@ public class HueBridge { * @param update changes to the configuration * @throws UnauthorizedException thrown if the user no longer exists */ - public void setConfig(ConfigUpdate update) throws IOException, ApiException { + public void setConfig(ConfigUpdate update) + throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - String body = update.toJson(); - Result result = http.put(getRelativeURL("config"), body); + HueResult result = put(getRelativeURL("config"), update.toJson()); handleErrors(result); } @@ -1017,10 +888,10 @@ public class HueBridge { * * @throws UnauthorizedException thrown if the user no longer exists */ - public void unlink() throws IOException, ApiException { + public void unlink() throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.delete(getRelativeURL("config/whitelist/" + enc(username))); + HueResult result = delete(getRelativeURL("config/whitelist/" + enc(username))); handleErrors(result); } @@ -1034,14 +905,14 @@ public class HueBridge { * @return full bridge configuration * @throws UnauthorizedException thrown if the user no longer exists */ - public FullConfig getFullConfig() throws IOException, ApiException { + public FullConfig getFullConfig() throws IOException, ApiException, ConfigurationException, CommunicationException { requireAuthentication(); - Result result = http.get(getRelativeURL("")); + HueResult result = get(getRelativeURL("")); handleErrors(result); - FullConfig fullConfig = gson.fromJson(result.getBody(), FullConfig.class); + FullConfig fullConfig = gson.fromJson(result.body, FullConfig.class); return Objects.requireNonNull(fullConfig); } @@ -1070,12 +941,12 @@ public class HueBridge { } // Used as assert in all requests to elegantly catch common errors - public void handleErrors(Result result) throws IOException, ApiException { - if (result.getResponseCode() != 200) { + public void handleErrors(HueResult result) throws IOException, ApiException { + if (result.responseCode != HttpStatus.OK_200) { throw new IOException(); } else { try { - List errors = gson.fromJson(result.getBody(), ErrorResponse.GSON_TYPE); + List errors = gson.fromJson(result.body, ErrorResponse.GSON_TYPE); if (errors == null) { return; } @@ -1111,15 +982,7 @@ public class HueBridge { // UTF-8 URL encode private String enc(@Nullable String str) { - if (str != null) { - try { - return URLEncoder.encode(str, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new UnsupportedOperationException("UTF-8 not supported"); - } - } else { - return ""; - } + return str == null ? "" : URLEncoder.encode(str, StandardCharsets.UTF_8); } private String getRelativeURL(String path) { @@ -1129,4 +992,133 @@ public class HueBridge { } return path.isEmpty() ? relativeUrl : relativeUrl + "/" + path; } + + public HueResult get(String address) throws ConfigurationException, CommunicationException { + return doNetwork(address, HttpMethod.GET); + } + + public HueResult post(String address, String body) throws ConfigurationException, CommunicationException { + return doNetwork(address, HttpMethod.POST, body); + } + + public HueResult put(String address, String body) throws ConfigurationException, CommunicationException { + return doNetwork(address, HttpMethod.PUT, body); + } + + public HueResult delete(String address) throws ConfigurationException, CommunicationException { + return doNetwork(address, HttpMethod.DELETE); + } + + private HueResult doNetwork(String address, HttpMethod requestMethod) + throws ConfigurationException, CommunicationException { + return doNetwork(address, requestMethod, null); + } + + private HueResult doNetwork(String address, HttpMethod requestMethod, @Nullable String body) + throws ConfigurationException, CommunicationException { + logger.trace("Hue request: {} - URL = '{}'", requestMethod, address); + try { + final Request request = httpClient.newRequest(address).method(requestMethod).timeout(timeout, + TimeUnit.MILLISECONDS); + + if (body != null) { + logger.trace("Hue request body: '{}'", body); + request.content(new StringContentProvider(body), "application/json"); + } + + final ContentResponse contentResponse = request.send(); + + final int httpStatus = contentResponse.getStatus(); + final String content = contentResponse.getContentAsString(); + logger.trace("Hue response: status = {}, content = '{}'", httpStatus, content); + return new HueResult(content, httpStatus); + } catch (ExecutionException e) { + String message = e.getMessage(); + if (e.getCause() instanceof SSLHandshakeException) { + logger.debug("SSLHandshakeException occurred during execution: {}", message, e); + throw new ConfigurationException(TEXT_OFFLINE_CONFIGURATION_ERROR_INVALID_SSL_CERIFICATE, e.getCause()); + } else { + logger.debug("ExecutionException occurred during execution: {}", message, e); + throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message, + e.getCause()); + } + } catch (TimeoutException e) { + String message = e.getMessage(); + logger.debug("TimeoutException occurred during execution: {}", message, e); + throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + String message = e.getMessage(); + logger.debug("InterruptedException occurred during execution: {}", message, e); + throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message); + } + } + + private CompletableFuture putAsync(String address, String body, long delay) { + AsyncPutParameters asyncPutParameters = new AsyncPutParameters(address, body, delay); + synchronized (commandsQueue) { + if (commandsQueue.isEmpty()) { + commandsQueue.offer(asyncPutParameters); + Future localJob = job; + if (localJob == null || localJob.isDone()) { + job = scheduler.submit(this::executeCommands); + } + } else { + commandsQueue.offer(asyncPutParameters); + } + } + return asyncPutParameters.future; + } + + private void executeCommands() { + while (true) { + try { + long delayTime = 0; + synchronized (commandsQueue) { + AsyncPutParameters payloadCallbackPair = commandsQueue.poll(); + if (payloadCallbackPair != null) { + logger.debug("Async sending put to address: {} delay: {} body: {}", payloadCallbackPair.address, + payloadCallbackPair.delay, payloadCallbackPair.body); + try { + HueResult result = doNetwork(payloadCallbackPair.address, HttpMethod.PUT, + payloadCallbackPair.body); + payloadCallbackPair.future.complete(result); + } catch (ConfigurationException | CommunicationException e) { + payloadCallbackPair.future.completeExceptionally(e); + } + delayTime = payloadCallbackPair.delay; + } else { + return; + } + } + Thread.sleep(delayTime); + } catch (InterruptedException e) { + logger.debug("commandExecutorThread was interrupted", e); + } + } + } + + public static class HueResult { + public final String body; + public final int responseCode; + + public HueResult(String body, int responseCode) { + this.body = body; + this.responseCode = responseCode; + } + } + + public final class AsyncPutParameters { + public final String address; + public final String body; + public final CompletableFuture future; + public final long delay; + + public AsyncPutParameters(String address, String body, long delay) { + this.address = address; + this.body = body; + this.future = new CompletableFuture<>(); + this.delay = delay; + } + } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java new file mode 100644 index 000000000..5fa2820ed --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2010-2022 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.hue.internal.connection; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; + +import javax.net.ssl.X509ExtendedTrustManager; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.io.net.http.PEMTrustManager; +import org.openhab.core.io.net.http.PEMTrustManager.CertificateInstantiationException; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; +import org.openhab.core.io.net.http.TrustAllTrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides a {@link PEMTrustManager} to allow secure connections to any Hue Bridge. + * + * @author Christoph Weitkamp - Initial Contribution + */ +@NonNullByDefault +public class HueTlsTrustManagerProvider implements TlsTrustManagerProvider { + + private static final String PEM_FILENAME = "huebridge_cacert.pem"; + private final String hostname; + private final boolean useSelfSignedCertificate; + + private final Logger logger = LoggerFactory.getLogger(HueTlsTrustManagerProvider.class); + + public HueTlsTrustManagerProvider(String hostname, boolean useSelfSignedCertificate) { + this.hostname = hostname; + this.useSelfSignedCertificate = useSelfSignedCertificate; + } + + @Override + public String getHostName() { + return hostname; + } + + @Override + public X509ExtendedTrustManager getTrustManager() { + try { + if (useSelfSignedCertificate) { + logger.trace("Use self-signed certificate downloaded from Hue Bridge."); + // use self-signed certificate downloaded from Hue Bridge + return PEMTrustManager.getInstanceFromServer("https://" + getHostName()); + } else { + logger.trace("Use Signify private CA Certificate for Hue Bridges from resources."); + // use Signify private CA Certificate for Hue Bridges from resources + return getInstanceFromResource(PEM_FILENAME); + } + } catch (CertificateException | MalformedURLException e) { + logger.error("An unexpected exception occurred - returning a TrustAllTrustManager: {}", e.getMessage(), e); + } + return TrustAllTrustManager.getInstance(); + } + + /** + * Creates a {@link PEMTrustManager} instance by reading the PEM certificate from the given file. + * This is useful if you have a private CA Certificate stored in a file. + * + * @param fileName name to the PEM file located in the resources folder + * @return a {@link PEMTrustManager} instance + * @throws CertificateInstantiationException + */ + private PEMTrustManager getInstanceFromResource(String fileName) throws CertificateException { + String pemCert = readPEMCertificateStringFromResource(fileName); + if (pemCert != null) { + return new PEMTrustManager(pemCert); + } + throw new CertificateInstantiationException( + String.format("Certificate resource '%s' not found or not accessible.", fileName)); + } + + private @Nullable String readPEMCertificateStringFromResource(String fileName) { + URL resource = Thread.currentThread().getContextClassLoader().getResource(fileName); + if (resource != null) { + try (InputStream certInputStream = resource.openStream()) { + return new String(certInputStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + logger.error("An unexpected exception occurred: ", e); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java index 8e5fe3c3f..a1ee6c759 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java @@ -46,7 +46,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension { @Activate public HueCommandExtension(final @Reference ThingRegistry thingRegistry) { - super("hue", "Interact with the hue binding."); + super("hue", "Interact with the Hue binding."); this.thingRegistry = thingRegistry; } @@ -78,7 +78,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension { console.println("No handler initialized for the thingUID '" + args[0] + "'"); printUsage(console); } else if (bridgeHandler == null && groupHandler == null) { - console.println("'" + args[0] + "' is neither a Hue bridgeUID nor a Hue groupThingUID"); + console.println("'" + args[0] + "' is neither a Hue BridgeUID nor a Hue groupThingUID"); printUsage(console); } else { switch (args[1]) { @@ -87,7 +87,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension { String userName = bridgeHandler.getUserName(); console.println("Your user name is " + (userName != null ? userName : "undefined")); } else { - console.println("'" + args[0] + "' is not a Hue bridgeUID"); + console.println("'" + args[0] + "' is not a Hue BridgeUID"); printUsage(console); } break; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java deleted file mode 100644 index fb40cdadc..000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal.discovery; - -import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER; - -import java.io.IOException; -import java.util.Collections; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.jupnp.model.meta.DeviceDetails; -import org.jupnp.model.meta.ModelDetails; -import org.jupnp.model.meta.RemoteDevice; -import org.openhab.binding.hue.internal.HueBindingConstants; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant; -import org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.service.cm.Configuration; -import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link HueBridgeDiscoveryParticipant} is responsible for discovering new and - * removed hue bridges. It uses the central {@link UpnpDiscoveryService}. - * - * @author Kai Kreuzer - Initial contribution - * @author Thomas Höfer - Added representation - */ -@NonNullByDefault -@Component(service = UpnpDiscoveryParticipant.class) -public class HueBridgeDiscoveryParticipant implements UpnpDiscoveryParticipant { - - private final Logger logger = LoggerFactory.getLogger(HueBridgeDiscoveryParticipant.class); - - // Hue bridges have maxAge 100 seconds, so set the default grace period to half of that - private long removalGracePeriodSeconds = 50; - - private final ConfigurationAdmin configAdmin; - - @Activate - public HueBridgeDiscoveryParticipant(final @Reference ConfigurationAdmin configAdmin) { - this.configAdmin = configAdmin; - } - - @Override - public Set getSupportedThingTypeUIDs() { - return Collections.singleton(THING_TYPE_BRIDGE); - } - - @Override - public @Nullable DiscoveryResult createResult(RemoteDevice device) { - ThingUID uid = getThingUID(device); - if (uid != null) { - Map properties = new HashMap<>(); - properties.put(HOST, device.getDetails().getBaseURL().getHost()); - properties.put(PORT, device.getDetails().getBaseURL().getPort()); - properties.put(PROTOCOL, device.getDetails().getBaseURL().getProtocol()); - String serialNumber = device.getDetails().getSerialNumber(); - DiscoveryResult result; - if (serialNumber != null && !serialNumber.isBlank()) { - properties.put(PROPERTY_SERIAL_NUMBER, serialNumber.toLowerCase()); - - result = DiscoveryResultBuilder.create(uid).withProperties(properties) - .withLabel(device.getDetails().getFriendlyName()) - .withRepresentationProperty(PROPERTY_SERIAL_NUMBER).build(); - } else { - result = DiscoveryResultBuilder.create(uid).withProperties(properties) - .withLabel(device.getDetails().getFriendlyName()).build(); - } - return result; - } else { - return null; - } - } - - @Override - public @Nullable ThingUID getThingUID(RemoteDevice device) { - DeviceDetails details = device.getDetails(); - if (details != null) { - ModelDetails modelDetails = details.getModelDetails(); - String serialNumber = details.getSerialNumber(); - if (modelDetails != null && serialNumber != null && !serialNumber.isBlank()) { - String modelName = modelDetails.getModelName(); - if (modelName != null) { - if (modelName.startsWith("Philips hue bridge")) { - return new ThingUID(THING_TYPE_BRIDGE, serialNumber.toLowerCase()); - } - } - } - } - return null; - } - - @Override - public long getRemovalGracePeriodSeconds(RemoteDevice device) { - try { - Configuration conf = configAdmin.getConfiguration("binding.hue"); - Dictionary properties = conf.getProperties(); - if (properties != null) { - Object property = properties.get(HueBindingConstants.REMOVAL_GRACE_PERIOD); - if (property != null) { - removalGracePeriodSeconds = Long.parseLong(property.toString()); - } - } - } catch (IOException | IllegalStateException | NumberFormatException e) { - // fall through to pre-initialised (default) value - } - logger.trace("getRemovalGracePeriodSeconds={}", removalGracePeriodSeconds); - return removalGracePeriodSeconds; - } -} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java new file mode 100644 index 000000000..2014c0e38 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2010-2022 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.hue.internal.discovery; + +import static org.openhab.binding.hue.internal.HueBindingConstants.*; + +import java.util.Dictionary; +import java.util.Map; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hue.internal.handler.HueBridgeHandler; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link HueBridgeMDNSDiscoveryParticipant} is responsible for discovering new and removed Hue Bridges. It uses the + * central {@link MDNSDiscoveryService}. + * + * @author Kai Kreuzer - Initial contribution + * @author Thomas Höfer - Added representation + * @author Christoph Weitkamp - Change discovery protocol to mDNS + */ +@Component(configurationPid = "discovery.hue") +@NonNullByDefault +public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant { + + private static final String SERVICE_TYPE = "_hue._tcp.local."; + private static final String MDNS_PROPERTY_BRIDGE_ID = "bridgeid"; + private static final String MDNS_PROPERTY_MODEL_ID = "modelid"; + + private static final String CONFIG_PROPERTY_REMOVAL_GRACE_PERIOD = "removalGracePeriod"; + + private final Logger logger = LoggerFactory.getLogger(HueBridgeMDNSDiscoveryParticipant.class); + + private long removalGracePeriod = 0L; + + private boolean isAutoDiscoveryEnabled = true; + + @Activate + protected void activate(ComponentContext componentContext) { + activateOrModifyService(componentContext); + } + + @Modified + protected void modified(ComponentContext componentContext) { + activateOrModifyService(componentContext); + } + + private void activateOrModifyService(ComponentContext componentContext) { + Dictionary properties = componentContext.getProperties(); + String autoDiscoveryPropertyValue = (String) properties + .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); + if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) { + isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue); + } + String removalGracePeriodPropertyValue = (String) properties.get(CONFIG_PROPERTY_REMOVAL_GRACE_PERIOD); + if (removalGracePeriodPropertyValue != null && !removalGracePeriodPropertyValue.isBlank()) { + try { + removalGracePeriod = Long.parseLong(removalGracePeriodPropertyValue); + } catch (NumberFormatException e) { + logger.warn("Configuration property '{}' has invalid value: {}", CONFIG_PROPERTY_REMOVAL_GRACE_PERIOD, + removalGracePeriodPropertyValue); + } + } + } + + @Override + public Set getSupportedThingTypeUIDs() { + return HueBridgeHandler.SUPPORTED_THING_TYPES; + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo service) { + if (isAutoDiscoveryEnabled) { + ThingUID uid = getThingUID(service); + if (uid != null) { + String host = service.getHostAddresses()[0]; + String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID); + String friendlyName = String.format("%s (%s)", service.getName(), host); + return DiscoveryResultBuilder.create(uid) // + .withProperties(Map.of( // + HOST, host, // + Thing.PROPERTY_MODEL_ID, service.getPropertyString(MDNS_PROPERTY_MODEL_ID), // + Thing.PROPERTY_SERIAL_NUMBER, id.toLowerCase())) // + .withLabel(friendlyName) // + .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) // + .withTTL(120L) // + .build(); + } + } + return null; + } + + @Override + public @Nullable ThingUID getThingUID(ServiceInfo service) { + String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID); + if (id != null && !id.isBlank()) { + return new ThingUID(THING_TYPE_BRIDGE, id.toLowerCase()); + } + return null; + } + + @Override + public long getRemovalGracePeriodSeconds(ServiceInfo service) { + return removalGracePeriod; + } +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscovery.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscovery.java index f01d6c857..ce3ce1652 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscovery.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscovery.java @@ -13,25 +13,21 @@ package org.openhab.binding.hue.internal.discovery; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingUID; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; @@ -42,37 +38,29 @@ import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; /** - * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service + * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new Hue Bridges. It uses the 'NUPnP service * provided by Philips'. * * @author Awelkiyar Wehabrebi - Initial contribution * @author Christoph Knauf - Refactorings * @author Andre Fuechsel - make {@link #startScan()} asynchronous */ -@NonNullByDefault @Component(service = DiscoveryService.class, configurationPid = "discovery.hue") +@NonNullByDefault public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService { - private static final String MODEL_NAME_PHILIPS_HUE = "Philips hue"; - + private static final String MODEL_NAME_PHILIPS_HUE = "\"name\":\"Philips Hue\""; protected static final String BRIDGE_INDICATOR = "fffe"; - private static final String DISCOVERY_URL = "https://discovery.meethue.com/"; - - protected static final String LABEL_PATTERN = "Philips hue (IP)"; - - private static final String DESC_URL_PATTERN = "http://HOST/description.xml"; - + protected static final String LABEL_PATTERN = "Philips Hue (%s)"; + private static final String CONFIG_URL_PATTERN = "http://%s/api/0/config"; private static final int REQUEST_TIMEOUT = 5000; - private static final int DISCOVERY_TIMEOUT = 10; - private static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE); - private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class); public HueBridgeNupnpDiscovery() { - super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false); + super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false); } @Override @@ -87,37 +75,25 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService { for (BridgeJsonParameters bridge : getBridgeList()) { if (isReachableAndValidHueBridge(bridge)) { String host = bridge.getInternalIpAddress(); - String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10); - serialNumber = serialNumber.toLowerCase(); + String serialNumber = bridge.getId().toLowerCase(); ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber); - DiscoveryResult result = DiscoveryResultBuilder.create(uid) - .withProperties(buildProperties(host, serialNumber)) - .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER) + DiscoveryResult result = DiscoveryResultBuilder.create(uid) // + .withProperties(Map.of( // + HOST, host, // + Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) // + .withLabel(String.format(LABEL_PATTERN, host)) // + .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) // .build(); thingDiscovered(result); } } } - /** - * Builds the bridge properties. - * - * @param host the ip of the bridge - * @param serialNumber the id of the bridge - * @return the bridge properties - */ - private Map buildProperties(String host, String serialNumber) { - Map properties = new HashMap<>(2); - properties.put(HOST, host); - properties.put(PROPERTY_SERIAL_NUMBER, serialNumber); - return properties; - } - /** * Checks if the Bridge is a reachable Hue Bridge with a valid id. * * @param bridge the {@link BridgeJsonParameters}s - * @return true if Bridge is a reachable Hue Bridge with a id containing + * @return true if Hue Bridge is a reachable Hue Bridge with a id containing * BRIDGE_INDICATOR longer then 10 */ private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) { @@ -136,14 +112,14 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService { logger.debug("Bridge not discovered: id {} is shorter then 10.", id); return false; } - if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) { + if (!BRIDGE_INDICATOR.equals(id.substring(6, 10))) { logger.debug( "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.", id, BRIDGE_INDICATOR); return false; } try { - description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host)); + description = doGetRequest(String.format(CONFIG_URL_PATTERN, host)); } catch (IOException e) { logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host); return false; @@ -170,10 +146,10 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService { return Objects.requireNonNull(bridgeParameters); } catch (IOException e) { logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges"); - } catch (JsonParseException je) { + } catch (JsonParseException e) { logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges"); } - return new ArrayList<>(); + return List.of(); } /** diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java index 19f9543d5..0e7009e7e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java @@ -15,7 +15,6 @@ package org.openhab.binding.hue.internal.discovery; import static org.openhab.binding.hue.internal.HueBindingConstants.*; import java.util.AbstractMap.SimpleEntry; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -26,10 +25,10 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.FullGroup; -import org.openhab.binding.hue.internal.FullHueObject; -import org.openhab.binding.hue.internal.FullLight; -import org.openhab.binding.hue.internal.FullSensor; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.FullHueObject; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.FullSensor; import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.binding.hue.internal.handler.HueGroupHandler; import org.openhab.binding.hue.internal.handler.HueLightHandler; @@ -43,7 +42,6 @@ import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; @@ -53,8 +51,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected - * to a paired hue bridge. The default search time for hue is 60 seconds. + * The {@link HueBridgeServiceTracker} tracks for Hue lights, sensors and groups which are connected + * to a paired Hue Bridge. The default search time for Hue is 60 seconds. * * @author Kai Kreuzer - Initial contribution * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types; @@ -67,16 +65,15 @@ import org.slf4j.LoggerFactory; * @author Laurent Garnier - Added support for groups */ @NonNullByDefault -public class HueDeviceDiscoveryService extends AbstractDiscoveryService - implements DiscoveryService, ThingHandlerService { +public class HueDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { - public static final Set SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream + public static final Set SUPPORTED_THING_TYPES = Stream .of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(), GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(), TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(), ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream()) - .flatMap(i -> i).collect(Collectors.toSet())); + .flatMap(i -> i).collect(Collectors.toUnmodifiableSet()); // @formatter:off private static final Map TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries( diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java index eeac4ff39..831fe8f0e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.Comparator; import java.util.regex.Matcher; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java similarity index 76% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java index 6baa3657e..70fd84bd2 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -18,9 +18,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; * @author Samuel Leisering - Initial contribution */ @NonNullByDefault -public class ApiVersionUtils { +public final class ApiVersionUtils { - private static ApiVersion fullLights = new ApiVersion(1, 11, 0); + private static final ApiVersion FULL_LIGHTS = new ApiVersion(1, 11, 0); + + ApiVersionUtils() { + } /** * Starting from version 1.11, GETing the Lights always returns {@link FullLight}s instead of @@ -29,6 +32,6 @@ public class ApiVersionUtils { * @return */ public static boolean supportsFullLights(ApiVersion version) { - return fullLights.compare(version) <= 0; + return FULL_LIGHTS.compare(version) <= 0; } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java index af84e4c46..d300844ca 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; /** * Collection of updates to the bridge configuration. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java index 67bc44088..0014046d1 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import com.google.gson.Gson; @@ -21,8 +21,8 @@ import com.google.gson.Gson; * @author Samuel Leisering - changed Command visibility to public */ public class Command { - String key; - Object value; + public String key; + public Object value; public Command(String key, Object value) { this.key = key; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java similarity index 98% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java index ecf481bfb..8c68bf0d6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.ArrayList; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java index d118c818d..e2453e591 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import static java.util.stream.Collectors.joining; @@ -26,7 +26,7 @@ import java.util.ArrayList; */ public class ConfigUpdate { - protected final ArrayList commands = new ArrayList<>(); + public final ArrayList commands = new ArrayList<>(); public ConfigUpdate() { super(); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java index 1233571d4..a763d17e0 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; /** * @@ -18,7 +18,7 @@ package org.openhab.binding.hue.internal; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("unused") -class CreateUserRequest { +public class CreateUserRequest { private String username; private String devicetype; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java similarity index 94% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java index 78d1d3646..82f4d3655 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.List; @@ -18,11 +18,10 @@ import java.util.List; import com.google.gson.reflect.TypeToken; /** - * * @author Q42 - Initial contribution * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ -class ErrorResponse { +public class ErrorResponse { public static final Type GSON_TYPE = new TypeToken>() { }.getType(); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java index 82d988bae..0d4ee7c00 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.ArrayList; import java.util.List; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java index 98ebaa5a7..1d98cc196 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.List; @@ -40,7 +40,7 @@ public class FullGroup extends Group { /** * Test constructor */ - FullGroup(String id, String name, String type, State action, List lights, State state) { + public FullGroup(String id, String name, String type, State action, List lights, State state) { super(id, name, type); this.action = action; this.lights = lights; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java index da91179ba..224f0373f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import static org.openhab.binding.hue.internal.HueBindingConstants.NORMALIZE_ID_REGEX; @@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.Nullable; import com.google.gson.annotations.SerializedName; /** - * Detailed information about an object on the hue bridge + * Detailed information about an object on the Hue Bridge * * @author Samuel Leisering - Initial contribution * @author Christoph Weitkamp - Initial contribution @@ -53,7 +53,7 @@ public class FullHueObject extends HueObject { /** * Set the type of the object. */ - protected void setType(final String type) { + public void setType(final String type) { this.type = type; } @@ -73,7 +73,7 @@ public class FullHueObject extends HueObject { /** * Set the model ID of the object. */ - protected void setModelID(final String modelId) { + public void setModelID(final String modelId) { this.modelid = modelId; } @@ -115,7 +115,7 @@ public class FullHueObject extends HueObject { /** * Sets the unique id of the object. */ - protected void setUniqueID(final String uniqueid) { + public void setUniqueID(final String uniqueid) { this.uniqueid = uniqueid; } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java index ea3a69bfb..13b152815 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.time.Duration; @@ -18,7 +18,6 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.dto.Capabilities; import com.google.gson.reflect.TypeToken; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java index 27ac4ffaa..867c47c2f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.Map; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java index 84bc63a55..5c49d775e 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; /** * Basic group information. @@ -24,7 +24,7 @@ public class Group { private String name; private String type; - Group() { + public Group() { this.id = "0"; this.name = "Lightset 0"; this.type = "LightGroup"; @@ -39,11 +39,11 @@ public class Group { this.type = type; } - void setName(String name) { + public void setName(String name) { this.name = name; } - void setId(String id) { + public void setId(String id) { this.id = id; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java similarity index 93% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java index 96eb38c40..82b8a5e02 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.Map; @@ -34,7 +34,7 @@ public class HueObject { HueObject() { } - void setId(String id) { + public void setId(String id) { this.id = id; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java index 586a7d547..5586d0839 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; -import static org.openhab.binding.hue.internal.FullSensor.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; /** * Updates the configuration of a light level sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java similarity index 87% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java index 047c038e8..37606af00 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java @@ -10,13 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; /** - * * @author Q42 - Initial contribution * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ -class NewLightsResponse { +public class NewLightsResponse { public String lastscan; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java similarity index 89% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java index 51d86bd04..c6cdf6565 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; -import static org.openhab.binding.hue.internal.FullSensor.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; /** * Updates the configuration of a presence sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java similarity index 95% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java index c252bc138..86d74b122 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.List; @@ -51,7 +51,7 @@ public class Scene { /** * Test constructor */ - Scene(String id, String name, @Nullable String groupId, List lightIds, boolean recycle) { + public Scene(String id, String name, @Nullable String groupId, List lightIds, boolean recycle) { this.id = id; this.name = name; this.groupId = groupId; @@ -63,7 +63,7 @@ public class Scene { return id; } - void setId(String id) { + public void setId(String id) { this.id = id; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java index 229908515..9f467ecd1 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.Map; @@ -30,7 +30,7 @@ public class Schedule { private String id; private String name; - void setId(String id) { + public void setId(String id) { this.id = id; } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java index f0de942f9..1c890ab39 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java index 04c2a7383..0a7a496a7 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.List; @@ -21,7 +21,7 @@ import java.util.List; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("unused") -class SearchForLightsRequest { +public class SearchForLightsRequest { private List deviceid; public SearchForLightsRequest(List deviceid) { diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java similarity index 85% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java index bb7153a14..294a8d31c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; -import static org.openhab.binding.hue.internal.FullSensor.CONFIG_ON; +import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_ON; /** * Collection of updates to the sensor configuration. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java similarity index 68% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java index bebe904a3..91a7bf517 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java @@ -10,9 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * @@ -20,9 +24,10 @@ import java.util.List; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("unused") -class SetAttributesRequest { - private String name; - private List lights; +@NonNullByDefault +public class SetAttributesRequest { + private final @Nullable String name; + private final @Nullable List lights; public SetAttributesRequest(String name) { this(name, null); @@ -32,7 +37,7 @@ class SetAttributesRequest { this(null, lights); } - public SetAttributesRequest(String name, List lights) { + public SetAttributesRequest(@Nullable String name, @Nullable List lights) { if (name != null && Util.stringSize(name) > 32) { throw new IllegalArgumentException("Name can be at most 32 characters long"); } else if (lights != null && (lights.isEmpty() || lights.size() > 16)) { @@ -40,8 +45,6 @@ class SetAttributesRequest { } this.name = name; - if (lights != null) { - this.lights = Util.lightsToIds(lights); - } + this.lights = lights == null ? null : lights.stream().map(l -> l.getId()).collect(Collectors.toList()); } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java index a72ce1c21..2a216e5c8 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; /** * Details of a bridge firmware update. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java similarity index 97% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java index e639d2f65..38e4f6c30 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.Arrays; @@ -23,14 +23,14 @@ import java.util.Arrays; */ public class State { private boolean on; - int bri; - int hue; - int sat; + public int bri; + public int hue; + public int sat; private float[] xy; - int ct; + public int ct; private String alert; private String effect; - String colormode; + public String colormode; private boolean reachable; public State() { diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java index 50331ba5f..d991c882b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java @@ -10,11 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; -import org.openhab.binding.hue.internal.State.AlertMode; -import org.openhab.binding.hue.internal.State.Effect; -import org.openhab.binding.hue.internal.dto.ColorTemperature; +import org.openhab.binding.hue.internal.dto.State.AlertMode; +import org.openhab.binding.hue.internal.dto.State.Effect; /** * Collection of updates to the state of a light. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java similarity index 91% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java index 1ba1e969d..e0cc18a23 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.lang.reflect.Type; import java.util.List; @@ -19,11 +19,10 @@ import java.util.Map; import com.google.gson.reflect.TypeToken; /** - * * @author Q42 - Initial contribution * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ -class SuccessResponse { +public class SuccessResponse { public static final Type GSON_TYPE = new TypeToken>() { }.getType(); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java similarity index 84% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java index becda09b1..017dee9db 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; -import static org.openhab.binding.hue.internal.FullSensor.CONFIG_LED_INDICATION; +import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_LED_INDICATION; /** * Updates the configuration of a temperature sensor diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java similarity index 96% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java index 936d06c4f..30c375f0f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.util.Date; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java similarity index 65% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java index 7b4dc1b87..1eced1454 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java @@ -10,11 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.dto; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -22,12 +20,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** - * * @author Q42 - Initial contribution * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @NonNullByDefault -class Util { +public final class Util { + private Util() { } @@ -36,28 +34,6 @@ class Util { return str.getBytes(StandardCharsets.UTF_8).length; } - public static List idsToLights(List ids) { - List lights = new ArrayList<>(); - - for (String id : ids) { - HueObject light = new HueObject(); - light.setId(id); - lights.add(light); - } - - return lights; - } - - public static List lightsToIds(List lights) { - List ids = new ArrayList<>(); - - for (HueObject light : lights) { - ids.add(light.getId()); - } - - return ids; - } - public static @Nullable String quickMatch(String needle, String haystack) { Matcher m = Pattern.compile(needle).matcher(haystack); m.find(); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/ApiException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/ApiException.java index b678b7beb..10fb3d954 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/ApiException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/ApiException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when the API returns an unknown error. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class ApiException extends Exception { public ApiException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/DeviceOffException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/DeviceOffException.java index 42eb58996..1a377c47a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/DeviceOffException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/DeviceOffException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when trying to change the state of a light that is off. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class DeviceOffException extends ApiException { public DeviceOffException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/EntityNotAvailableException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/EntityNotAvailableException.java index 339e09df1..628e61bd3 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/EntityNotAvailableException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/EntityNotAvailableException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when operating on a group, light or user that doesn't exist. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class EntityNotAvailableException extends ApiException { public EntityNotAvailableException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/GroupTableFullException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/GroupTableFullException.java index 698d1cda6..1d761d3ce 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/GroupTableFullException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/GroupTableFullException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when adding more than 16 groups (excluding all lights group) to a bridge. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class GroupTableFullException extends ApiException { public GroupTableFullException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/InvalidCommandException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/InvalidCommandException.java index 1f61e138f..259e26fe8 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/InvalidCommandException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/InvalidCommandException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when scheduling an invalid command. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class InvalidCommandException extends ApiException { public InvalidCommandException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/LinkButtonException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/LinkButtonException.java index 78ff7d516..7ff36c1d9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/LinkButtonException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/LinkButtonException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown if the link button hasn't been pressed in the last 30 seconds. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class LinkButtonException extends ApiException { public LinkButtonException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/UnauthorizedException.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/UnauthorizedException.java index a105d6552..ab025f683 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/UnauthorizedException.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/UnauthorizedException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.hue.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Thrown when the specified user is no longer whitelisted on the bridge. * @@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions; * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding */ @SuppressWarnings("serial") +@NonNullByDefault public class UnauthorizedException extends ApiException { public UnauthorizedException() { } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java similarity index 92% rename from bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java rename to bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java index 27f584e28..d2783c02b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java @@ -10,17 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.hue.internal; +package org.openhab.binding.hue.internal.factory; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.binding.hue.internal.handler.HueGroupHandler; import org.openhab.binding.hue.internal.handler.HueLightHandler; @@ -35,6 +35,7 @@ import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -60,22 +61,25 @@ import org.osgi.service.component.annotations.Reference; @Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue") public class HueThingHandlerFactory extends BaseThingHandlerFactory { - public static final Set SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream + public static final Set SUPPORTED_THING_TYPES = Stream .of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(), GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(), TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(), ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream()) - .flatMap(i -> i).collect(Collectors.toSet())); + .flatMap(i -> i).collect(Collectors.toUnmodifiableSet()); + private final HttpClient httpClient; private final HueStateDescriptionProvider stateDescriptionProvider; private final TranslationProvider i18nProvider; private final LocaleProvider localeProvider; @Activate - public HueThingHandlerFactory(final @Reference HueStateDescriptionProvider stateDescriptionProvider, + public HueThingHandlerFactory(final @Reference HttpClientFactory httpClientFactory, + final @Reference HueStateDescriptionProvider stateDescriptionProvider, final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) { + this.httpClient = httpClientFactory.getCommonHttpClient(); this.stateDescriptionProvider = stateDescriptionProvider; this.i18nProvider = i18nProvider; this.localeProvider = localeProvider; @@ -103,7 +107,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory { return super.createThing(thingTypeUID, configuration, hueGroupUID, bridgeUID); } - throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the hue binding."); + throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the Hue binding."); } @Override @@ -149,7 +153,8 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory { @Override protected @Nullable ThingHandler createHandler(Thing thing) { if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { - return new HueBridgeHandler((Bridge) thing, stateDescriptionProvider, i18nProvider, localeProvider); + return new HueBridgeHandler((Bridge) thing, httpClient, stateDescriptionProvider, i18nProvider, + localeProvider); } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { return new HueLightHandler(thing, stateDescriptionProvider); } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java index cfbf26a62..2494c7fb3 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java @@ -15,8 +15,8 @@ package org.openhab.binding.hue.internal.handler; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullGroup; -import org.openhab.binding.hue.internal.Scene; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.Scene; /** * The {@link GroupStatusListener} is notified when a group status has changed or a group has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java index 3257fae32..ade906cdb 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java @@ -18,7 +18,6 @@ import static org.openhab.core.thing.Thing.*; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,20 +32,21 @@ import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.ApiVersionUtils; -import org.openhab.binding.hue.internal.Config; -import org.openhab.binding.hue.internal.ConfigUpdate; -import org.openhab.binding.hue.internal.FullConfig; -import org.openhab.binding.hue.internal.FullGroup; -import org.openhab.binding.hue.internal.FullLight; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.HueBridge; -import org.openhab.binding.hue.internal.HueConfigStatusMessage; -import org.openhab.binding.hue.internal.Scene; -import org.openhab.binding.hue.internal.State; -import org.openhab.binding.hue.internal.StateUpdate; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.hue.internal.config.HueBridgeConfig; +import org.openhab.binding.hue.internal.connection.HueBridge; +import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; +import org.openhab.binding.hue.internal.dto.ApiVersionUtils; +import org.openhab.binding.hue.internal.dto.Config; +import org.openhab.binding.hue.internal.dto.ConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullConfig; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.Scene; +import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.DeviceOffException; import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException; @@ -54,8 +54,11 @@ import org.openhab.binding.hue.internal.exceptions.LinkButtonException; import org.openhab.binding.hue.internal.exceptions.UnauthorizedException; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.i18n.CommunicationException; +import org.openhab.core.i18n.ConfigurationException; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; @@ -68,11 +71,13 @@ import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * {@link HueBridgeHandler} is the handler for a hue bridge and connects it to + * {@link HueBridgeHandler} is the handler for a Hue Bridge and connects it to * the framework. All {@link HueLightHandler}s use the {@link HueBridgeHandler} to execute the actual commands. * * @author Dennis Nobel - Initial contribution @@ -93,12 +98,13 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE); private static final long BYPASS_MIN_DURATION_BEFORE_CMD = 1500L; - - private static final String DEVICE_TYPE = "EclipseSmartHome"; - private static final long SCENE_POLLING_INTERVAL = TimeUnit.SECONDS.convert(10, TimeUnit.MINUTES); + private static final String DEVICE_TYPE = "openHAB"; + private final Logger logger = LoggerFactory.getLogger(HueBridgeHandler.class); + private @Nullable ServiceRegistration serviceRegistration; + private final HttpClient httpClient; private final HueStateDescriptionProvider stateDescriptionOptionProvider; private final TranslationProvider i18nProvider; private final LocaleProvider localeProvider; @@ -120,8 +126,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl try { pollingLock.lock(); if (!lastBridgeConnectionState) { - // if user is not set in configuration try to create a new user on Hue bridge - if (hueBridgeConfig.getUserName() == null) { + // if user is not set in configuration try to create a new user on Hue Bridge + if (hueBridgeConfig.userName == null) { hueBridge.getFullConfig(); } lastBridgeConnectionState = tryResumeBridgeConnection(); @@ -132,6 +138,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl updateStatus(ThingStatus.ONLINE); } } + } catch (ConfigurationException e) { + handleConfigurationFailure(e); } catch (UnauthorizedException | IllegalStateException e) { if (isReachable(hueBridge.getIPAddress())) { lastBridgeConnectionState = false; @@ -142,7 +150,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl lastBridgeConnectionState = false; onConnectionLost(); } - } catch (ApiException | IOException e) { + } catch (ApiException | CommunicationException | IOException e) { if (hueBridge != null && lastBridgeConnectionState) { logger.debug("Connection to Hue Bridge {} lost: {}", hueBridge.getIPAddress(), e.getMessage(), e); lastBridgeConnectionState = false; @@ -165,7 +173,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl // If there is no connection, this line will fail hueBridge.authenticate("invalid"); - } catch (IOException e) { + } catch (ConfigurationException | IOException e) { return false; } catch (ApiException e) { String message = e.getMessage(); @@ -407,9 +415,11 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl private List consoleScenesList = new ArrayList<>(); - public HueBridgeHandler(Bridge bridge, HueStateDescriptionProvider stateDescriptionOptionProvider, - TranslationProvider i18nProvider, LocaleProvider localeProvider) { + public HueBridgeHandler(Bridge bridge, HttpClient httpClient, + HueStateDescriptionProvider stateDescriptionOptionProvider, TranslationProvider i18nProvider, + LocaleProvider localeProvider) { super(bridge); + this.httpClient = httpClient; this.stateDescriptionOptionProvider = stateDescriptionOptionProvider; this.i18nProvider = i18nProvider; this.localeProvider = localeProvider; @@ -417,7 +427,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl @Override public Collection> getServices() { - return Collections.singleton(HueDeviceDiscoveryService.class); + return Set.of(HueDeviceDiscoveryService.class); } @Override @@ -596,10 +606,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl ScheduledFuture job = lightPollingJob; if (job == null || job.isCancelled()) { long lightPollingInterval; - int configPollingInterval = hueBridgeConfig.getPollingInterval(); + int configPollingInterval = hueBridgeConfig.pollingInterval; if (configPollingInterval < 1) { lightPollingInterval = TimeUnit.SECONDS.toSeconds(10); - logger.info("Wrong configuration value for polling interval. Using default value: {}s", + logger.warn("Wrong configuration value for polling interval. Using default value: {}s", lightPollingInterval); } else { lightPollingInterval = configPollingInterval; @@ -621,12 +631,12 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl private void startSensorPolling() { ScheduledFuture job = sensorPollingJob; if (job == null || job.isCancelled()) { - int configSensorPollingInterval = hueBridgeConfig.getSensorPollingInterval(); + int configSensorPollingInterval = hueBridgeConfig.sensorPollingInterval; if (configSensorPollingInterval > 0) { long sensorPollingInterval; if (configSensorPollingInterval < 50) { sensorPollingInterval = TimeUnit.MILLISECONDS.toMillis(500); - logger.info("Wrong configuration value for sensor polling interval. Using default value: {}ms", + logger.warn("Wrong configuration value for sensor polling interval. Using default value: {}ms", sensorPollingInterval); } else { sensorPollingInterval = configSensorPollingInterval; @@ -665,7 +675,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl @Override public void dispose() { - logger.debug("Handler disposed."); + logger.debug("Disposing Hue Bridge handler ..."); Future job = initJob; if (job != null) { job.cancel(true); @@ -676,46 +686,50 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl if (hueBridge != null) { hueBridge = null; } + ServiceRegistration localServiceRegistration = serviceRegistration; + if (localServiceRegistration != null) { + // remove trustmanager service + localServiceRegistration.unregister(); + serviceRegistration = null; + } } @Override public void initialize() { - logger.debug("Initializing hue bridge handler."); + logger.debug("Initializing Hue Bridge handler ..."); hueBridgeConfig = getConfigAs(HueBridgeConfig.class); - String ip = hueBridgeConfig.getIpAddress(); + String ip = hueBridgeConfig.ipAddress; if (ip == null || ip.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-no-ip-address"); } else { if (hueBridge == null) { - hueBridge = new HueBridge(ip, hueBridgeConfig.getPort(), hueBridgeConfig.getProtocol(), scheduler); - hueBridge.setTimeout(5000); + if (HueBridgeConfig.HTTPS.equals(hueBridgeConfig.protocol)) { + // register trustmanager service + HueTlsTrustManagerProvider tlsTrustManagerProvider = new HueTlsTrustManagerProvider( + ip + ":" + hueBridgeConfig.getPort(), hueBridgeConfig.useSelfSignedCertificate); + serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext() + .registerService(TlsTrustManagerProvider.class.getName(), tlsTrustManagerProvider, null); + } + + hueBridge = new HueBridge(httpClient, ip, hueBridgeConfig.getPort(), hueBridgeConfig.protocol, + scheduler); updateStatus(ThingStatus.UNKNOWN); - - // Try a first connection that will fail, then try to authenticate, - // and finally change the bridge status to ONLINE - initJob = scheduler.submit(new PollingRunnable() { - @Override - protected void doConnectedRun() throws IOException, ApiException { - } - }); } onUpdate(); } } public @Nullable String getUserName() { - return hueBridgeConfig == null ? null : hueBridgeConfig.getUserName(); + return hueBridgeConfig == null ? null : hueBridgeConfig.userName; } private synchronized void onUpdate() { - if (hueBridge != null) { - startLightPolling(); - startSensorPolling(); - startScenePolling(); - } + startLightPolling(); + startSensorPolling(); + startScenePolling(); } /** @@ -761,9 +775,9 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl */ private boolean tryResumeBridgeConnection() throws IOException, ApiException { logger.debug("Connection to Hue Bridge {} established.", hueBridge.getIPAddress()); - if (hueBridgeConfig.getUserName() == null) { + if (hueBridgeConfig.userName == null) { logger.warn( - "User name for Hue bridge authentication not available in configuration. Setting ThingStatus to OFFLINE."); + "User name for Hue Bridge authentication not available in configuration. Setting ThingStatus to OFFLINE."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-no-username"); return false; @@ -780,21 +794,24 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl * If there is a user name available, it attempts to re-authenticate. Otherwise new authentication credentials will * be requested from the bridge. * - * @param bridge the hue bridge the connection is not authorized + * @param bridge the Hue Bridge the connection is not authorized * @return returns {@code true} if re-authentication was successful, {@code false} otherwise */ public boolean onNotAuthenticated() { if (hueBridge == null) { return false; } - String userName = hueBridgeConfig.getUserName(); + String userName = hueBridgeConfig.userName; if (userName == null) { createUser(); } else { try { hueBridge.authenticate(userName); return true; + } catch (ConfigurationException e) { + handleConfigurationFailure(e); } catch (Exception e) { + logger.trace("", e); handleAuthenticationFailure(e, userName); } } @@ -813,10 +830,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl } private String createUserOnPhysicalBridge() throws IOException, ApiException { - logger.info("Creating new user on Hue bridge {} - please press the pairing button on the bridge.", - hueBridgeConfig.getIpAddress()); + logger.info("Creating new user on Hue Bridge {} - please press the pairing button on the bridge.", + hueBridgeConfig.ipAddress); String userName = hueBridge.link(DEVICE_TYPE); - logger.info("User has been successfully added to Hue bridge."); + logger.info("User has been successfully added to Hue Bridge."); return userName; } @@ -829,26 +846,32 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl hueBridgeConfig = getConfigAs(HueBridgeConfig.class); } catch (IllegalStateException e) { logger.trace("Configuration update failed.", e); - logger.warn("Unable to update configuration of Hue bridge."); + logger.warn("Unable to update configuration of Hue Bridge."); logger.warn("Please configure the user name manually."); } } + private void handleConfigurationFailure(ConfigurationException ex) { + logger.warn( + "Invalid certificate for secured connection. You might want to enable the \"Use Self-Signed Certificate\" configuration."); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getRawMessage()); + } + private void handleAuthenticationFailure(Exception ex, String userName) { - logger.warn("User is not authenticated on Hue bridge {}", hueBridgeConfig.getIpAddress()); + logger.warn("User is not authenticated on Hue Bridge {}", hueBridgeConfig.ipAddress); logger.warn("Please configure a valid user or remove user from configuration to generate a new one."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-invalid-username"); } private void handleLinkButtonNotPressed(LinkButtonException ex) { - logger.debug("Failed creating new user on Hue bridge: {}", ex.getMessage()); + logger.debug("Failed creating new user on Hue Bridge: {}", ex.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-press-pairing-button"); } private void handleExceptionWhileCreatingUser(Exception ex) { - logger.warn("Failed creating new user on Hue bridge", ex); + logger.warn("Failed creating new user on Hue Bridge", ex); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-creation-username"); } @@ -1041,10 +1064,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl // Check whether an IP address is provided hueBridgeConfig = getConfigAs(HueBridgeConfig.class); - String ip = hueBridgeConfig.getIpAddress(); + String ip = hueBridgeConfig.ipAddress; if (ip == null || ip.isEmpty()) { - return List.of(ConfigStatusMessage.Builder.error(HOST) - .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build()); + return List.of(ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING) + .withArguments(HOST).build()); } else { return List.of(); } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java index 6d6dad65e..898730ce2 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java @@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.ConfigUpdate; -import org.openhab.binding.hue.internal.FullGroup; -import org.openhab.binding.hue.internal.FullLight; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; +import org.openhab.binding.hue.internal.dto.ConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.StateUpdate; /** * Access to the Hue system for light handlers. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java index f8860bde8..8d917fe3c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java @@ -25,11 +25,11 @@ import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.FullGroup; -import org.openhab.binding.hue.internal.Scene; -import org.openhab.binding.hue.internal.State; -import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.dto.ColorTemperature; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.Scene; +import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; @@ -87,7 +87,7 @@ public class HueGroupHandler extends BaseThingHandler implements HueLightActions @Override public void initialize() { - logger.debug("Initializing hue group handler."); + logger.debug("Initializing Hue group handler."); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); } @@ -173,13 +173,13 @@ public class HueGroupHandler extends BaseThingHandler implements HueLightActions public void handleCommand(String channel, Command command, long fadeTime) { HueClient bridgeHandler = getHueClient(); if (bridgeHandler == null) { - logger.debug("hue bridge handler not found. Cannot handle command without bridge."); + logger.debug("Hue Bridge handler not found. Cannot handle command without bridge."); return; } FullGroup group = bridgeHandler.getGroupById(groupId); if (group == null) { - logger.debug("hue group not known on bridge. Cannot handle command."); + logger.debug("Hue group not known on bridge. Cannot handle command."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-wrong-group-id"); return; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java index cf890b5bb..6b145ff72 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java @@ -24,11 +24,11 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.FullLight; -import org.openhab.binding.hue.internal.State; -import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.dto.Capabilities; import org.openhab.binding.hue.internal.dto.ColorTemperature; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; @@ -51,7 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * {@link HueLightHandler} is the handler for a hue light. It uses the {@link HueClient} to execute the actual + * {@link HueLightHandler} is the handler for a Hue light. It uses the {@link HueClient} to execute the actual * command. * * @author Dennis Nobel - Initial contribution @@ -116,7 +116,7 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions @Override public void initialize() { - logger.debug("Initializing hue light handler."); + logger.debug("Initializing Hue light handler."); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); } @@ -234,13 +234,13 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions public void handleCommand(String channel, Command command, long fadeTime) { HueClient bridgeHandler = getHueClient(); if (bridgeHandler == null) { - logger.warn("hue bridge handler not found. Cannot handle command without bridge."); + logger.warn("Hue Bridge handler not found. Cannot handle command without bridge."); return; } final FullLight light = lastFullLight == null ? bridgeHandler.getLightById(lightId) : lastFullLight; if (light == null) { - logger.debug("hue light not known on bridge. Cannot handle command."); + logger.debug("Hue light not known on bridge. Cannot handle command."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-wrong-light-id"); return; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java index 9669c1b1e..8189bea1a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java @@ -12,8 +12,8 @@ */ package org.openhab.binding.hue.internal.handler; -import static org.openhab.binding.hue.internal.FullSensor.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; import static org.openhab.core.thing.Thing.*; import java.time.LocalDateTime; @@ -27,9 +27,9 @@ import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; -import org.openhab.binding.hue.internal.StateUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; @@ -72,7 +72,7 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso @Override public void initialize() { - logger.debug("Initializing hue sensor handler."); + logger.debug("Initializing Hue sensor handler."); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); } @@ -167,13 +167,13 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso protected void handleCommand(String channel, Command command) { HueClient bridgeHandler = getHueClient(); if (bridgeHandler == null) { - logger.warn("hue bridge handler not found. Cannot handle command without bridge."); + logger.warn("Hue Bridge handler not found. Cannot handle command without bridge."); return; } final FullSensor sensor = lastFullSensor; if (sensor == null) { - logger.debug("hue sensor not known on bridge. Cannot handle command."); + logger.debug("Hue sensor not known on bridge. Cannot handle command."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-wrong-sensor-id"); return; @@ -206,13 +206,13 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso if (!configUpdate.isEmpty()) { HueClient hueBridge = getHueClient(); if (hueBridge == null) { - logger.warn("hue bridge handler not found. Cannot handle configuration update without bridge."); + logger.warn("Hue Bridge handler not found. Cannot handle configuration update without bridge."); return; } final FullSensor sensor = lastFullSensor; if (sensor == null) { - logger.debug("hue sensor not known on bridge. Cannot handle configuration update."); + logger.debug("Hue sensor not known on bridge. Cannot handle configuration update."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-wrong-sensor-id"); return; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java index 559c9adc8..1212573e6 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java @@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.hue.internal.State; -import org.openhab.binding.hue.internal.State.AlertMode; -import org.openhab.binding.hue.internal.State.ColorMode; -import org.openhab.binding.hue.internal.State.Effect; -import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.dto.ColorTemperature; +import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.dto.State.AlertMode; +import org.openhab.binding.hue.internal.dto.State.ColorMode; +import org.openhab.binding.hue.internal.dto.State.Effect; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.IncreaseDecreaseType; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java index 2f0d82626..c7a3effce 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullLight; +import org.openhab.binding.hue.internal.dto.FullLight; /** * The {@link LightStatusListener} is notified when a light status has changed or a light has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java index e763d1163..42da8c0c9 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java @@ -13,7 +13,7 @@ package org.openhab.binding.hue.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; +import org.openhab.binding.hue.internal.dto.FullSensor; /** * The {@link SensorStatusListener} is notified when a sensor status has changed or a sensor has been removed or added. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java index 0dd736c71..866dd1e3b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java @@ -20,8 +20,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Thing; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java index baf0f8bed..0b676faa7 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java @@ -21,13 +21,12 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; @@ -43,7 +42,7 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class DimmerSwitchHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER_SWITCH); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DIMMER_SWITCH); public DimmerSwitchHandler(Thing thing) { super(thing); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java index fa7312097..0b20d019f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java @@ -12,16 +12,15 @@ */ package org.openhab.binding.hue.internal.handler.sensors; -import static org.openhab.binding.hue.internal.FullSensor.STATE_PRESENCE; import static org.openhab.binding.hue.internal.HueBindingConstants.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.STATE_PRESENCE; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.OnOffType; @@ -35,7 +34,8 @@ import org.openhab.core.thing.ThingTypeUID; */ @NonNullByDefault public class GeofencePresenceHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_GEOFENCE_SENSOR); + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GEOFENCE_SENSOR); public GeofencePresenceHandler(Thing thing) { super(thing); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java index afadf2de3..8ccfef70f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java @@ -12,18 +12,17 @@ */ package org.openhab.binding.hue.internal.handler.sensors; -import static org.openhab.binding.hue.internal.FullSensor.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; import java.math.BigDecimal; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.LightLevelConfigUpdate; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.LightLevelConfigUpdate; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; @@ -41,7 +40,8 @@ import org.openhab.core.thing.ThingTypeUID; */ @NonNullByDefault public class LightLevelHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_LIGHT_LEVEL_SENSOR); + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_LIGHT_LEVEL_SENSOR); public LightLevelHandler(Thing thing) { super(thing); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java index 338e9deb6..7e0d1b16b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java @@ -12,17 +12,16 @@ */ package org.openhab.binding.hue.internal.handler.sensors; -import static org.openhab.binding.hue.internal.FullSensor.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.PresenceConfigUpdate; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.PresenceConfigUpdate; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueClient; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; @@ -43,7 +42,8 @@ import org.slf4j.LoggerFactory; */ @NonNullByDefault public class PresenceHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PRESENCE_SENSOR); + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR); private final Logger logger = LoggerFactory.getLogger(PresenceHandler.class); @@ -55,13 +55,13 @@ public class PresenceHandler extends HueSensorHandler { public void handleCommand(String channel, Command command) { HueClient hueBridge = getHueClient(); if (hueBridge == null) { - logger.warn("hue bridge handler not found. Cannot handle command without bridge."); + logger.warn("Hue Bridge handler not found. Cannot handle command without bridge."); return; } final FullSensor sensor = lastFullSensor; if (sensor == null) { - logger.debug("hue sensor not known on bridge. Cannot handle command."); + logger.debug("Hue sensor not known on bridge. Cannot handle command."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-wrong-sensor-id"); return; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java index 579e49a24..369b4dd0f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java @@ -21,13 +21,12 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; @@ -42,7 +41,7 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class TapSwitchHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TAP_SWITCH); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TAP_SWITCH); public TapSwitchHandler(Thing thing) { super(thing); diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java index 0c21cfe2e..c84e6298a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java @@ -12,18 +12,17 @@ */ package org.openhab.binding.hue.internal.handler.sensors; -import static org.openhab.binding.hue.internal.FullSensor.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*; +import static org.openhab.binding.hue.internal.dto.FullSensor.*; import java.math.BigDecimal; -import java.util.Collections; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.hue.internal.FullSensor; -import org.openhab.binding.hue.internal.SensorConfigUpdate; -import org.openhab.binding.hue.internal.TemperatureConfigUpdate; +import org.openhab.binding.hue.internal.dto.FullSensor; +import org.openhab.binding.hue.internal.dto.SensorConfigUpdate; +import org.openhab.binding.hue.internal.dto.TemperatureConfigUpdate; import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.QuantityType; @@ -39,7 +38,8 @@ import org.openhab.core.thing.ThingTypeUID; */ @NonNullByDefault public class TemperatureHandler extends HueSensorHandler { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TEMPERATURE_SENSOR); + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEMPERATURE_SENSOR); public TemperatureHandler(Thing thing) { super(thing); diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/binding/binding.xml index 87ae19a6a..722437d1e 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/binding/binding.xml @@ -6,13 +6,4 @@ Hue Binding The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs. - - - - Extra grace period (seconds) that UPnP discovery shall wait before removing a lost Bridge from the - Inbox. Default is 50 seconds. - 50 - - - diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/config/config.xml index bddda1e9b..ad9069f64 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/config/config.xml @@ -117,7 +117,7 @@ - The group identifier identifies one certain hue group or room. + The group identifier identifies one certain Hue group or room. diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties index 49e40b300..2b2af1df1 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties @@ -3,11 +3,6 @@ binding.hue.name = Hue Binding binding.hue.description = The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs. -# binding config - -binding.config.hue.removalGracePeriod.label = Removal Grace Period -binding.config.hue.removalGracePeriod.description = Extra grace period (seconds) that UPnP discovery shall wait before removing a lost Bridge from the Inbox. Default is 50 seconds. - # thing types thing-type.hue.0000.label = On/Off Light @@ -39,7 +34,7 @@ thing-type.hue.0840.description = A generic sensor object for IP sensor use. thing-type.hue.0850.label = CLIP Generic Flag Sensor thing-type.hue.0850.description = A generic sensor object for IP sensor use. thing-type.hue.bridge.label = Hue Bridge -thing-type.hue.bridge.description = The Hue bridge represents the Philips Hue bridge. +thing-type.hue.bridge.description = The Hue Bridge represents the Philips Hue Bridge. thing-type.hue.geofencesensor.label = Geofence Sensor thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection. thing-type.hue.group.label = Hue Group @@ -48,17 +43,23 @@ thing-type.hue.group.description = A group of lights or a room that could be swi # thing types config thing-type.config.hue.bridge.ipAddress.label = Network Address -thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue bridge. +thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue Bridge. thing-type.config.hue.bridge.pollingInterval.label = Polling Interval -thing-type.config.hue.bridge.pollingInterval.description = Seconds between fetching values from the Hue bridge. Default is 10. +thing-type.config.hue.bridge.pollingInterval.description = Seconds between fetching values from the Hue Bridge. Default is 10. thing-type.config.hue.bridge.port.label = Port -thing-type.config.hue.bridge.port.description = Port of the Hue bridge. +thing-type.config.hue.bridge.port.description = Port of the Hue Bridge. +thing-type.config.hue.bridge.protocol.label = Protocol +thing-type.config.hue.bridge.protocol.description = Protocol to connect to the Hue Bridge (http or https). +thing-type.config.hue.bridge.protocol.option.http = HTTP +thing-type.config.hue.bridge.protocol.option.https = HTTPS thing-type.config.hue.bridge.sensorPollingInterval.label = Sensor Polling Interval -thing-type.config.hue.bridge.sensorPollingInterval.description = Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the Hue bridge. Use 0 to disable the polling for sensors. Default is 500. +thing-type.config.hue.bridge.sensorPollingInterval.description = Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the Hue Bridge. Use 0 to disable the polling for sensors. Default is 500. +thing-type.config.hue.bridge.useSelfSignedCertificate.label = Use Self-Signed Certificate +thing-type.config.hue.bridge.useSelfSignedCertificate.description = Use self-signed certificate for HTTPS connection to Hue Bridge. thing-type.config.hue.bridge.userName.label = Username -thing-type.config.hue.bridge.userName.description = Name of a registered Hue bridge user, that allows to access the API. +thing-type.config.hue.bridge.userName.description = Name of a registered Hue Bridge user, that allows to access the API. thing-type.config.hue.group.groupId.label = Group ID -thing-type.config.hue.group.groupId.description = The group identifier identifies one certain hue group or room. +thing-type.config.hue.group.groupId.description = The group identifier identifies one certain Hue group or room. thing-type.config.hue.lightlevelsensor.tholddark.label = Threshold Dark thing-type.config.hue.lightlevelsensor.tholddark.description = Threshold the user configured to be used in rules to determine insufficient light level (ie below threshold). Default value 16000. thing-type.config.hue.lightlevelsensor.tholdoffset.label = Threshold Offset @@ -132,37 +133,39 @@ config.fadetime.description = Fade time in milliseconds for changing values. config.ledindication.label = LED Indication config.ledindication.description = Turns device LED during normal operation on or off. Devices might still indicate exceptional operation (Reset, SW Update, Battery Low). config.lightId.label = Light ID -config.lightId.description = The light identifier that is used within the hue bridge. +config.lightId.description = The light identifier that is used within the Hue Bridge. config.plugId.label = Plug ID -config.plugId.description = The plug identifier that is used within the hue bridge. +config.plugId.description = The plug identifier that is used within the Hue Bridge. config.sensorId.label = Sensor ID -config.sensorId.description = The sensor identifier that is used within the hue bridge. +config.sensorId.description = The sensor identifier that is used within the Hue Bridge. config.on.label = Sensor Status config.on.description = Enables or disables the sensor. # config status messages -config-status.error.missing-ip-address-configuration = No IP address for the Hue bridge has been provided. +config-status.error.missing-ip-address-configuration = No IP address for the Hue Bridge has been provided. # thing status descriptions -offline.conf-error-no-ip-address = Cannot connect to Hue bridge. IP address not available in configuration. -offline.conf-error-no-username = Cannot connect to Hue bridge. User name for authentication not available in configuration. +offline.communication-error = An unexpected exception occurred during execution. +offline.conf-error-invalid-ssl-certificate = Invalid certificate for secured connection. You might want to enable the "Use Self-Signed Certificate" configuration. +offline.conf-error-no-ip-address = Cannot connect to Hue Bridge. IP address not available in configuration. +offline.conf-error-no-username = Cannot connect to Hue Bridge. User name for authentication not available in configuration. offline.conf-error-invalid-username = Authentication failed. Remove user name from configuration to generate a new one. -offline.conf-error-press-pairing-button = Not authenticated. Press pairing button on the Hue bridge or set a valid user name in configuration. -offline.conf-error-creation-username = Failed to create new user on Hue bridge. -offline.bridge-connection-lost = Hue bridge connection lost. +offline.conf-error-press-pairing-button = Not authenticated. Press pairing button on the Hue Bridge or set a valid user name in configuration. +offline.conf-error-creation-username = Failed to create new user on Hue Bridge. +offline.bridge-connection-lost = Hue Bridge connection lost. offline.conf-error-no-light-id = Light ID not available in configuration. offline.conf-error-no-sensor-id = Sensor ID not available in configuration. offline.conf-error-no-group-id = Group ID not available in configuration. -offline.conf-error-wrong-light-id = No light with given ID available on Hue bridge. -offline.conf-error-wrong-sensor-id = No sensor with given ID available on Hue bridge. -offline.conf-error-wrong-group-id = No group with given ID available on Hue bridge. -offline.light-not-reachable = Hue bridge reports light as not reachable. -offline.sensor-not-reachable = Hue bridge reports sensor as not reachable. -offline.light-removed = Hue bridge reports light as removed. -offline.sensor-removed = Hue bridge reports sensor as removed. -offline.group-removed = Hue bridge reports group as removed. +offline.conf-error-wrong-light-id = No light with given ID available on Hue Bridge. +offline.conf-error-wrong-sensor-id = No sensor with given ID available on Hue Bridge. +offline.conf-error-wrong-group-id = No group with given ID available on Hue Bridge. +offline.light-not-reachable = Hue Bridge reports light as not reachable. +offline.sensor-not-reachable = Hue Bridge reports sensor as not reachable. +offline.light-removed = Hue Bridge reports light as removed. +offline.sensor-removed = Hue Bridge reports sensor as removed. +offline.group-removed = Hue Bridge reports group as removed. # lightactions diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/bridge.xml index a062d93fa..e44c6f356 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/bridge.xml @@ -6,7 +6,7 @@ - The Hue bridge represents the Philips Hue bridge. + The Hue Bridge represents the Philips Hue Bridge. @@ -21,26 +21,41 @@ network-address - Network address of the Hue bridge. + Network address of the Hue Bridge. - Port of the Hue bridge. + Port of the Hue Bridge. + + + + Protocol to connect to the Hue Bridge (http or https). + https + + + + + + + + Use self-signed certificate for HTTPS connection to Hue Bridge. + true + true password - Name of a registered Hue bridge user, that allows to access the API. + Name of a registered Hue Bridge user, that allows to access the API. - Seconds between fetching values from the Hue bridge. Default is 10. + Seconds between fetching values from the Hue Bridge. Default is 10. 10 - Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for - the sensor values, but a too low value can cause congestion on the Hue bridge. Use 0 to disable the polling for + Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for + the sensor values, but a too low value can cause congestion on the Hue Bridge. Use 0 to disable the polling for sensors. Default is 500. 500 diff --git a/bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem b/bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem new file mode 100644 index 000000000..1cef2d7e1 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICMjCCAdigAwIBAgIUO7FSLbaxikuXAljzVaurLXWmFw4wCgYIKoZIzj0EAwIw +OTELMAkGA1UEBhMCTkwxFDASBgNVBAoMC1BoaWxpcHMgSHVlMRQwEgYDVQQDDAty +b290LWJyaWRnZTAiGA8yMDE3MDEwMTAwMDAwMFoYDzIwMzgwMTE5MDMxNDA3WjA5 +MQswCQYDVQQGEwJOTDEUMBIGA1UECgwLUGhpbGlwcyBIdWUxFDASBgNVBAMMC3Jv +b3QtYnJpZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjNw2tx2AplOf9x86 +aTdvEcL1FU65QDxziKvBpW9XXSIcibAeQiKxegpq8Exbr9v6LBnYbna2VcaK0G22 +jOKkTqOBuTCBtjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNV +HQ4EFgQUZ2ONTFrDT6o8ItRnKfqWKnHFGmQwdAYDVR0jBG0wa4AUZ2ONTFrDT6o8 +ItRnKfqWKnHFGmShPaQ7MDkxCzAJBgNVBAYTAk5MMRQwEgYDVQQKDAtQaGlsaXBz +IEh1ZTEUMBIGA1UEAwwLcm9vdC1icmlkZ2WCFDuxUi22sYpLlwJY81Wrqy11phcO +MAoGCCqGSM49BAMCA0gAMEUCIEBYYEOsa07TH7E5MJnGw557lVkORgit2Rm1h3B2 +sFgDAiEA1Fj/C3AN5psFMjo0//mrQebo0eKd3aWRx+pQY08mk48= +-----END CERTIFICATE----- diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java index 5b6e8cc50..4ce38deb6 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java @@ -12,15 +12,16 @@ */ package org.openhab.binding.hue.internal; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.binding.hue.internal.dto.ApiVersion; /** - * - * * @author Samuel Leisering - Initial contribution */ +@NonNullByDefault public class ApiVersionTest { @Test diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java index 6886e5706..2a47fb8cc 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java @@ -14,35 +14,47 @@ package org.openhab.binding.hue.internal; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpStatus; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.openhab.binding.hue.internal.HttpClient.Result; +import org.openhab.binding.hue.internal.config.HueBridgeConfig; +import org.openhab.binding.hue.internal.connection.HueBridge; +import org.openhab.binding.hue.internal.dto.Scene; import org.openhab.binding.hue.internal.exceptions.ApiException; +import org.openhab.core.i18n.CommunicationException; +import org.openhab.core.i18n.ConfigurationException; /** * @author Hengrui Jiang - initial contribution */ +@NonNullByDefault public class HueBridgeTest { @Test public void testGetScenesExcludeRecycleScenes() throws IOException, ApiException { - HttpClient mockHttpClient = Mockito.mock(HttpClient.class); - - HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1), - mockHttpClient); - - List testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), true), // - new Scene("id2", "name2", "group2", Collections.emptyList(), false)); - when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200)); + HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username", + Executors.newScheduledThreadPool(1)) { + @Override + public HueResult get(String address) throws ConfigurationException, CommunicationException { + if ("https://ip:443/api/username/lights".equals(address)) { + return new HueResult("{}", HttpStatus.OK_200); + } else if ("https://ip:443/api/username/scenes".equals(address)) { + List testScenes = List.of( // + new Scene("id1", "name1", "group1", List.of(), true), // + new Scene("id2", "name2", "group2", List.of(), false)); + return new HueResult(createMockResponse(testScenes), HttpStatus.OK_200); + } + return super.get(address); + } + }; List scenes = hueBridge.getScenes(); assertThat(scenes.size(), is(1)); @@ -51,15 +63,22 @@ public class HueBridgeTest { @Test public void testGetScenesOrderByGroup() throws IOException, ApiException { - HttpClient mockHttpClient = Mockito.mock(HttpClient.class); - - HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1), - mockHttpClient); - - List testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), false), // - new Scene("id2", "name2", "group2", Collections.emptyList(), false), - new Scene("id3", "name3", "group1", Collections.emptyList(), false)); - when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200)); + HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username", + Executors.newScheduledThreadPool(1)) { + @Override + public HueResult get(String address) throws ConfigurationException, CommunicationException { + if ("https://ip:443/api/username/lights".equals(address)) { + return new HueResult("{}", HttpStatus.OK_200); + } else if ("https://ip:443/api/username/scenes".equals(address)) { + List testScenes = List.of( // + new Scene("id1", "name1", "group1", List.of(), false), // + new Scene("id2", "name2", "group2", List.of(), false), // + new Scene("id3", "name3", "group1", List.of(), false)); + return new HueResult(createMockResponse(testScenes), HttpStatus.OK_200); + } + return super.get(address); + } + }; List scenes = hueBridge.getScenes(); assertThat(scenes.size(), is(3)); @@ -92,8 +111,7 @@ public class HueBridgeTest { " \"version\": 2,\n" + // " \"group\": \"%s\"\n" + // " }"; - String lights = String.join(",", - scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.toList())); + String lights = scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.joining(",")); return String.format(template, scene.getId(), scene.getName(), lights, scene.isRecycle(), scene.getGroupId()); } } diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java index a96baf3e8..424877446 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java @@ -17,9 +17,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.hue.internal.State.ColorMode; import org.openhab.binding.hue.internal.dto.ColorTemperature; +import org.openhab.binding.hue.internal.dto.State; +import org.openhab.binding.hue.internal.dto.State.ColorMode; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.binding.hue.internal.handler.LightStateConverter; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; @@ -31,6 +34,7 @@ import org.openhab.core.library.types.PercentType; * @author Denis Dudnik - switched to internally integrated source of Jue library * @author Markus Rathgeb - migrated to plain Java test */ +@NonNullByDefault public class LightStateConverterTest { @Test diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java index 44725d6a9..e6fdc1fbb 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java @@ -18,11 +18,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.util.Arrays; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.binding.hue.internal.dto.FullGroup; +import org.openhab.binding.hue.internal.dto.Scene; +import org.openhab.binding.hue.internal.dto.State; /** * @author HJiang - initial contribution */ +@NonNullByDefault public class SceneTest { private static final State PLACEHOLDER_STATE = new State(); diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java index 7e212306b..10ca508ba 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java @@ -23,10 +23,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.openhab.binding.hue.internal.FullConfig; -import org.openhab.binding.hue.internal.FullLight; -import org.openhab.binding.hue.internal.State.ColorMode; -import org.openhab.binding.hue.internal.StateUpdate; +import org.openhab.binding.hue.internal.dto.FullConfig; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.dto.State.ColorMode; +import org.openhab.binding.hue.internal.dto.StateUpdate; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java index 5b570413f..8915ea1e5 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.hue.internal.handler; -import org.openhab.binding.hue.internal.State.ColorMode; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.hue.internal.dto.State.ColorMode; /** * Builder for the current state of a hue light. @@ -22,6 +23,7 @@ import org.openhab.binding.hue.internal.State.ColorMode; * @author Markus Rathgeb - migrated to plain Java test * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only */ +@NonNullByDefault public class HueLightState { int brightness = 200; diff --git a/itests/org.openhab.binding.hue.tests/itest.bndrun b/itests/org.openhab.binding.hue.tests/itest.bndrun index 5afe76fb0..8ddabc812 100644 --- a/itests/org.openhab.binding.hue.tests/itest.bndrun +++ b/itests/org.openhab.binding.hue.tests/itest.bndrun @@ -26,8 +26,6 @@ Fragment-Host: org.openhab.binding.hue jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\ org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ - org.objectweb.asm.commons;version='[9.0.0,9.0.1)',\ - org.objectweb.asm.tree;version='[9.0.0,9.0.1)',\ jakarta.annotation-api;version='[2.0.0,2.0.1)',\ jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\ javax.measure.unit-api;version='[2.1.2,2.1.3)',\ @@ -45,12 +43,21 @@ Fragment-Host: org.openhab.binding.hue org.apache.felix.scr;version='[2.1.30,2.1.31)',\ org.osgi.util.function;version='[1.2.0,1.2.1)',\ org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + org.openhab.binding.hue;version='[3.4.0,3.4.1)',\ + org.openhab.binding.hue.tests;version='[3.4.0,3.4.1)',\ + org.openhab.core;version='[3.4.0,3.4.1)',\ + org.openhab.core.binding.xml;version='[3.4.0,3.4.1)',\ + org.openhab.core.config.core;version='[3.4.0,3.4.1)',\ + org.openhab.core.config.discovery;version='[3.4.0,3.4.1)',\ + org.openhab.core.config.xml;version='[3.4.0,3.4.1)',\ + org.openhab.core.io.console;version='[3.4.0,3.4.1)',\ + org.openhab.core.io.net;version='[3.4.0,3.4.1)',\ + org.openhab.core.test;version='[3.4.0,3.4.1)',\ + org.openhab.core.thing;version='[3.4.0,3.4.1)',\ + org.openhab.core.thing.xml;version='[3.4.0,3.4.1)',\ xstream;version='[1.4.19,1.4.20)',\ com.google.gson;version='[2.8.9,2.8.10)',\ - org.objectweb.asm;version='[9.2.0,9.2.1)',\ org.apache.felix.configadmin;version='[1.9.24,1.9.25)',\ - org.apache.xbean.bundleutils;version='[4.21.0,4.21.1)',\ - org.apache.xbean.finder;version='[4.21.0,4.21.1)',\ org.eclipse.jetty.client;version='[9.4.46,9.4.47)',\ org.eclipse.jetty.http;version='[9.4.46,9.4.47)',\ org.eclipse.jetty.io;version='[9.4.46,9.4.47)',\ @@ -63,22 +70,14 @@ Fragment-Host: org.openhab.binding.hue org.eclipse.jetty.websocket.client;version='[9.4.46,9.4.47)',\ org.eclipse.jetty.websocket.common;version='[9.4.46,9.4.47)',\ org.ops4j.pax.logging.pax-logging-api;version='[2.0.16,2.0.17)',\ - org.ops4j.pax.web.pax-web-api;version='[7.3.25,7.3.26)',\ - org.jupnp;version='[2.6.1,2.6.2)',\ - ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ - ch.qos.logback.core;version='[1.2.11,1.2.12)',\ org.eclipse.jdt.annotation;version='[2.2.100,2.2.101)',\ + javax.jmdns;version='[3.5.8,3.5.9)',\ + net.bytebuddy.byte-buddy;version='[1.12.1,1.12.2)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.1,1.12.2)',\ + org.mockito.mockito-core;version='[4.1.0,4.1.1)',\ + org.objenesis;version='[3.2.0,3.2.1)',\ + org.openhab.core.config.discovery.mdns;version='[3.4.0,3.4.1)',\ + org.openhab.core.io.transport.mdns;version='[3.4.0,3.4.1)',\ biz.aQute.tester.junit-platform;version='[6.3.0,6.3.1)',\ - org.openhab.binding.hue;version='[3.4.0,3.4.1)',\ - org.openhab.binding.hue.tests;version='[3.4.0,3.4.1)',\ - org.openhab.core;version='[3.4.0,3.4.1)',\ - org.openhab.core.binding.xml;version='[3.4.0,3.4.1)',\ - org.openhab.core.config.core;version='[3.4.0,3.4.1)',\ - org.openhab.core.config.discovery;version='[3.4.0,3.4.1)',\ - org.openhab.core.config.discovery.upnp;version='[3.4.0,3.4.1)',\ - org.openhab.core.config.xml;version='[3.4.0,3.4.1)',\ - org.openhab.core.io.console;version='[3.4.0,3.4.1)',\ - org.openhab.core.io.net;version='[3.4.0,3.4.1)',\ - org.openhab.core.test;version='[3.4.0,3.4.1)',\ - org.openhab.core.thing;version='[3.4.0,3.4.1)',\ - org.openhab.core.thing.xml;version='[3.4.0,3.4.1)' + ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ + ch.qos.logback.core;version='[1.2.11,1.2.12)' diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java index 3ff98df72..53306c45d 100644 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java +++ b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java @@ -15,26 +15,35 @@ package org.openhab.binding.hue.internal; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER; import java.io.IOException; import java.lang.reflect.Field; import java.util.Collection; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.binding.hue.internal.config.HueBridgeConfig; +import org.openhab.binding.hue.internal.connection.HueBridge; import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService; +import org.openhab.binding.hue.internal.dto.FullLight; +import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultFlag; import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.i18n.CommunicationException; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingRegistry; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; @@ -54,7 +63,6 @@ import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; */ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent { - protected HueThingHandlerFactory hueThingHandlerFactory; protected DiscoveryListener discoveryListener; protected ThingRegistry thingRegistry; protected Bridge hueBridge; @@ -74,7 +82,8 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent Configuration configuration = new Configuration(); configuration.put(HOST, "1.2.3.4"); configuration.put(USER_NAME, "testUserName"); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put("useSelfSignedCertificate", false); hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge", configuration); @@ -150,45 +159,45 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent } @Test - public void startSearchIsCalled() { + public void startSearchIsCalled() throws IOException, ApiException { final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false); - MockedHttpClient mockedHttpClient = new MockedHttpClient() { - + HueBridge mockedHueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username", + Executors.newScheduledThreadPool(1)) { @Override - public Result put(String address, String body) throws IOException { - return new Result("", 200); - } - - @Override - public Result get(String address) throws IOException { + public HueResult get(String address) throws CommunicationException { if (address.endsWith("testUserName")) { String body = "{\"lights\":{}}"; - return new Result(body, 200); + return new HueResult(body, HttpStatus.OK_200); } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) { String body = "{}"; - return new Result(body, 200); + return new HueResult(body, HttpStatus.OK_200); } else if (address.endsWith("testUserName/config")) { - String body = "{ \"apiversion\": \"1.26.0\"}"; - return new Result(body, 200); + String body = "{\"apiversion\": \"1.26.0\"}"; + return new HueResult(body, HttpStatus.OK_200); } else { - return new Result("", 404); + return new HueResult("", HttpStatus.NOT_FOUND_404); } } @Override - public Result post(String address, String body) throws IOException { + public HueResult post(String address, String body) throws CommunicationException { if (address.endsWith("lights")) { String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}"; searchHasBeenTriggered.set(true); - return new Result(bodyReturn, 200); + return new HueResult(bodyReturn, HttpStatus.OK_200); } else { - return new Result("", 404); + return new HueResult("", HttpStatus.NOT_FOUND_404); } } + + @Override + public HueResult put(String address, String body) throws CommunicationException { + return new HueResult("", HttpStatus.OK_200); + } }; - installHttpClientMock(hueBridgeHandler, mockedHttpClient); + installHttpClientMock(hueBridgeHandler, mockedHueBridge); ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build(); waitForAssert(() -> { @@ -201,19 +210,17 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent }); } - private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, MockedHttpClient mockedHttpClient) { + private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, HueBridge mockedHueBridge) { waitForAssert(() -> { try { // mock HttpClient final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge"); hueBridgeField.setAccessible(true); + hueBridgeField.set(hueBridgeHandler, mockedHueBridge); + final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler); assertThat(hueBridgeValue, is(notNullValue())); - final Field httpClientField = HueBridge.class.getDeclaredField("http"); - httpClientField.setAccessible(true); - httpClientField.set(hueBridgeValue, mockedHttpClient); - final Field usernameField = HueBridge.class.getDeclaredField("username"); usernameField.setAccessible(true); usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME)); diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java deleted file mode 100644 index e1f9f3f26..000000000 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal; - -/** - * @author Denis Dudnik - Initial contribution - */ -public class MockedHttpClient extends HttpClient { - -} diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java deleted file mode 100644 index d405dfb81..000000000 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.hue.internal.discovery; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER; - -import java.net.MalformedURLException; -import java.net.URL; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.jupnp.model.ValidationException; -import org.jupnp.model.meta.DeviceDetails; -import org.jupnp.model.meta.ManufacturerDetails; -import org.jupnp.model.meta.ModelDetails; -import org.jupnp.model.meta.RemoteDevice; -import org.jupnp.model.meta.RemoteDeviceIdentity; -import org.jupnp.model.meta.RemoteService; -import org.jupnp.model.types.DeviceType; -import org.jupnp.model.types.UDN; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultFlag; -import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant; -import org.openhab.core.test.java.JavaOSGiTest; -import org.openhab.core.thing.ThingUID; - -/** - * Tests for {@link org.openhab.binding.hue.internal.discovery.HueBridgeDiscoveryParticipant}. - * - * @author Kai Kreuzer - Initial contribution - * @author Thomas Höfer - Added representation - * @author Markus Rathgeb - migrated to plain Java test - */ -public class HueBridgeDiscoveryParticipantOSGITest extends JavaOSGiTest { - - UpnpDiscoveryParticipant discoveryParticipant; - - RemoteDevice hueDevice; - RemoteDevice otherDevice; - - @BeforeEach - public void setUp() { - discoveryParticipant = getService(UpnpDiscoveryParticipant.class, HueBridgeDiscoveryParticipant.class); - assertThat(discoveryParticipant, is(notNullValue())); - - try { - final RemoteService remoteService = null; - - hueDevice = new RemoteDevice( - new RemoteDeviceIdentity(new UDN("123"), 60, new URL("http://hue"), null, null), - new DeviceType("namespace", "type"), - new DeviceDetails(new URL("http://1.2.3.4/"), "Hue Bridge", new ManufacturerDetails("Philips"), - new ModelDetails("Philips hue bridge"), "serial123", "upc", null), - remoteService); - - otherDevice = new RemoteDevice( - new RemoteDeviceIdentity(new UDN("567"), 60, new URL("http://acme"), null, null), - new DeviceType("namespace", "type"), new DeviceDetails("Some Device", - new ManufacturerDetails("Taiwan"), new ModelDetails("$%&/"), "serial567", "upc"), - remoteService); - } catch (final ValidationException | MalformedURLException ex) { - fail("Internal test error."); - } - } - - @AfterEach - public void cleanUp() { - } - - @Test - public void correctSupportedTypes() { - assertThat(discoveryParticipant.getSupportedThingTypeUIDs().size(), is(1)); - assertThat(discoveryParticipant.getSupportedThingTypeUIDs().iterator().next(), is(THING_TYPE_BRIDGE)); - } - - @Test - public void correctThingUID() { - assertThat(discoveryParticipant.getThingUID(hueDevice), is(new ThingUID("hue:bridge:serial123"))); - } - - @Test - public void validDiscoveryResult() { - final DiscoveryResult result = discoveryParticipant.createResult(hueDevice); - assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW)); - assertThat(result.getThingUID(), is(new ThingUID("hue:bridge:serial123"))); - assertThat(result.getThingTypeUID(), is(THING_TYPE_BRIDGE)); - assertThat(result.getBridgeUID(), is(nullValue())); - assertThat(result.getProperties().get(HOST), is("1.2.3.4")); - assertThat(result.getProperties().get(PROPERTY_SERIAL_NUMBER), is("serial123")); - assertThat(result.getRepresentationProperty(), is(PROPERTY_SERIAL_NUMBER)); - } - - @Test - public void noThingUIDForUnknownDevice() { - assertThat(discoveryParticipant.getThingUID(otherDevice), is(nullValue())); - } - - @Test - public void noDiscoveryResultForUnknownDevice() { - assertThat(discoveryParticipant.createResult(otherDevice), is(nullValue())); - } -} diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscoveryOSGITest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscoveryOSGITest.java index 747b94b2b..d649a0f42 100644 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscoveryOSGITest.java +++ b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscoveryOSGITest.java @@ -37,7 +37,6 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; /** - * * @author Christoph Knauf - Initial contribution * @author Markus Rathgeb - migrated to plain Java test */ @@ -51,64 +50,18 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest { final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge"); final String ip1 = "192.168.31.17"; final String ip2 = "192.168.30.28"; - final String sn1 = "00178820057f"; - final String sn2 = "001788141b41"; + final String sn1 = "001788fffe20057f"; + final String sn2 = "001788fffe141b41"; final ThingUID BRIDGE_THING_UID_1 = new ThingUID(BRIDGE_THING_TYPE_UID, sn1); final ThingUID BRIDGE_THING_UID_2 = new ThingUID(BRIDGE_THING_TYPE_UID, sn2); - final String validBridgeDiscoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + ip1 - + "},{\"id\":\"001788fffe141b41\",\"internalipaddress\":" + ip2 + "}]"; + final String validBridgeDiscoveryResult = "[{\"id\":\"" + sn1 + "\",\"internalipaddress\":" + ip1 + "},{\"id\":\"" + + sn2 + "\",\"internalipaddress\":" + ip2 + "}]"; String discoveryResult; - String expBridgeDescription = "" + // - "" + // - "" + // - " " + // - " 1" + // - " 0" + // - " " + // - " http://$IP:80/" + // - " " + // - " urn:schemas-upnp-org:device:Basic:1" + // - " Philips hue ($IP)" + // - " Royal Philips Electronics" + // - " http://www.philips.com" + // - "Philips hue Personal Wireless Lighting" + // - "Philips hue bridge 2012" + // - "1000000000000" + // - "http://www.meethue.com" + // - " 93eadbeef13" + // - " uuid:01234567-89ab-cdef-0123-456789abcdef" + // - " " + // - " " + // - " (null)" + // - " (null)" + // - " (null)" + // - " (null)" + // - " (null)" + // - " " + // - " " + // - " index.html" + // - " " + // - " " + // - " image/png" + // - " 48" + // - " 48" + // - " 24" + // - " hue_logo_0.png" + // - " " + // - " " + // - " image/png" + // - " 120" + // - " 120" + // - " 24" + // - " hue_logo_3.png" + // - " " + // - " " + // - " " + // - ""; + String expBridgeDescription = "{\"name\":\"Philips Hue\",\"datastoreversion\":\"113\",\"swversion\":\"1948086000\",\"apiversion\":\"1.48.0\",\"mac\":\"00:11:22:33:44\",\"bridgeid\":\"$SN\",\"factorynew\":false,\"replacesbridgeid\":null,\"modelid\":\"BSB002\",\"starterkitid\":\"\"}"; private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) { assertThat(result.getBridgeUID(), nullValue()); - assertThat(result.getLabel(), is(HueBridgeNupnpDiscovery.LABEL_PATTERN.replace("IP", expIp))); + assertThat(result.getLabel(), is(String.format(HueBridgeNupnpDiscovery.LABEL_PATTERN, expIp))); assertThat(result.getProperties().get("ipAddress"), is(expIp)); assertThat(result.getProperties().get("serialNumber"), is(expSn)); } @@ -132,9 +85,9 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest { if (url.contains("meethue")) { return discoveryResult; } else if (url.contains(ip1)) { - return expBridgeDescription.replaceAll("$IP", ip1); + return expBridgeDescription.replaceAll("$SN", sn1); } else if (url.contains(ip2)) { - return expBridgeDescription.replaceAll("$IP", ip2); + return expBridgeDescription.replaceAll("$SN", sn2); } throw new IOException(); } diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandlerOSGiTest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandlerOSGiTest.java index 8fe44ae26..6af842998 100644 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandlerOSGiTest.java +++ b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandlerOSGiTest.java @@ -16,19 +16,19 @@ import static org.eclipse.jdt.annotation.Checks.requireNonNull; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; import static org.openhab.binding.hue.internal.HueBindingConstants.*; -import static org.openhab.binding.hue.internal.config.HueBridgeConfig.HTTP; -import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER; import java.io.IOException; import java.lang.reflect.Field; import java.util.concurrent.ScheduledExecutorService; +import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent; -import org.openhab.binding.hue.internal.HueBridge; -import org.openhab.binding.hue.internal.HueConfigStatusMessage; +import org.openhab.binding.hue.internal.config.HueBridgeConfig; +import org.openhab.binding.hue.internal.connection.HueBridge; import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.LinkButtonException; import org.openhab.binding.hue.internal.exceptions.UnauthorizedException; @@ -36,6 +36,7 @@ import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingRegistry; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; @@ -72,18 +73,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void assertThatANewUserIsAddedToConfigIfNotExistingYet() { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); hueBridgeHandler.thingUpdated(bridge); - injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { - @Override - public String link(String deviceType) throws IOException, ApiException { - return TEST_USER_NAME; - } - }); + injectBridge(hueBridgeHandler, + new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) { + @Override + public String link(String deviceType) throws IOException, ApiException { + return TEST_USER_NAME; + } + }); hueBridgeHandler.onNotAuthenticated(); @@ -95,17 +97,18 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); configuration.put(USER_NAME, TEST_USER_NAME); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); hueBridgeHandler.thingUpdated(bridge); - injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { - @Override - public void authenticate(String userName) throws IOException, ApiException { - } - }); + injectBridge(hueBridgeHandler, + new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) { + @Override + public void authenticate(String userName) throws IOException, ApiException { + } + }); hueBridgeHandler.onNotAuthenticated(); @@ -117,18 +120,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); configuration.put(USER_NAME, "notAuthenticatedUser"); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); hueBridgeHandler.thingUpdated(bridge); - injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { - @Override - public void authenticate(String userName) throws IOException, ApiException { - throw new UnauthorizedException(); - } - }); + injectBridge(hueBridgeHandler, + new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) { + @Override + public void authenticate(String userName) throws IOException, ApiException { + throw new UnauthorizedException(); + } + }); hueBridgeHandler.onNotAuthenticated(); @@ -141,18 +145,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void verifyStatusIfLinkButtonIsNotPressed() { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); hueBridgeHandler.thingUpdated(bridge); - injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { - @Override - public String link(String deviceType) throws IOException, ApiException { - throw new LinkButtonException(); - } - }); + injectBridge(hueBridgeHandler, + new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) { + @Override + public String link(String deviceType) throws IOException, ApiException { + throw new LinkButtonException(); + } + }); hueBridgeHandler.onNotAuthenticated(); @@ -165,18 +170,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void verifyStatusIfNewUserCannotBeCreated() { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); hueBridgeHandler.thingUpdated(bridge); - injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { - @Override - public String link(String deviceType) throws IOException, ApiException { - throw new ApiException(); - } - }); + injectBridge(hueBridgeHandler, + new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) { + @Override + public String link(String deviceType) throws IOException, ApiException { + throw new ApiException(); + } + }); hueBridgeHandler.onNotAuthenticated(); @@ -190,7 +196,7 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void verifyOfflineIsSetWithoutBridgeOfflineStatus() { Configuration configuration = new Configuration(); configuration.put(HOST, DUMMY_HOST); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); @@ -206,14 +212,13 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() { Configuration configuration = new Configuration(); configuration.put(HOST, null); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); - + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); - ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST) - .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build(); + ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING) + .withArguments(HOST).build(); waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next())); } @@ -222,19 +227,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent { public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() { Configuration configuration = new Configuration(); configuration.put(HOST, ""); - configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); - + configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber"); Bridge bridge = createBridgeThing(configuration); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); - ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST) - .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build(); + ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING) + .withArguments(HOST).build(); waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next())); } private Bridge createBridgeThing(Configuration configuration) { + configuration.put("useSelfSignedCertificate", false); Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration);