[hue] Changed discovery to mDNS; added HTTPS handling; refactor HTTPClient to use jetty shared client (#11842)

* Changed discovery to MDNS; added HTTPS handling; refactor HTTPClient to use jetty shared client

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2022-10-22 23:39:51 +02:00 committed by GitHub
parent d2efe69a73
commit ee34d92c17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 1158 additions and 1618 deletions

View File

@ -1,15 +1,15 @@
# Philips Hue Binding # Philips Hue Binding
This binding integrates the [Philips Hue Lighting system](https://www.meethue.com). 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) ![Philips Hue](doc/hue.jpg)
## Supported Things ## 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. 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. 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. 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. 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). 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). 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). 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: 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. 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. These sensors save state for rules and calculate what actions to do.
CLIP Sensor set or get by JSON through IP. 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 ## 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 `<openHAB-conf>/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 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 ## 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 In the thing file, this looks e.g. like
``` ```
Bridge hue:bridge:1 [ ipAddress="192.168.0.64" ] 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. 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 <bridgeUID> username`. The generated user name can be found, after pressing the authentication button on the bridge, with the following console command: `hue <bridgeUID> username`.
The user name can be set using the `userName` configuration value, e.g.: 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" ] Bridge hue:bridge:1 [ ipAddress="192.168.0.64", userName="qwertzuiopasdfghjklyxcvbnm1234" ]
``` ```
| Parameter | Description | | Parameter | Description |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ipAddress | Network address of the Hue bridge. **Mandatory** | | 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. | | 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** | | protocol | Protocol to connect to the Hue Bridge ("http" or "https"), default value is "https"). |
| pollingInterval | Seconds between fetching light values from the Hue bridge. Optional, the default value is 10 (min="1", step="1"). | | useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. **Advanced**, default value is `true`. |
| 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. | | 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 ### 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 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 | | 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") | | fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
### Groups ### 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 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 | | 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") | | 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 | | 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_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 | | 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. 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 <bridgeUID> scenes` and `hue <groupThingUID> scenes`. You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.
@ -366,11 +375,3 @@ if (receivedEvent == "1000.0")) {
//do stuff //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.

View File

@ -4,7 +4,7 @@
<feature name="openhab-binding-hue" description="Hue Binding" version="${project.version}"> <feature name="openhab-binding-hue" description="Hue Binding" version="${project.version}">
<feature>openhab-runtime-base</feature> <feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature> <feature>openhab-transport-mdns</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hue/${project.version}</bundle> <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.hue/${project.version}</bundle>
</feature> </feature>
</features> </features>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<AsyncPutParameters> 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<Result> 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<Result> 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;
}
}
}

View File

@ -89,8 +89,6 @@ public class HueBindingConstants {
// Bridge config properties // Bridge config properties
public static final String HOST = "ipAddress"; 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"; public static final String USER_NAME = "userName";
// Thing configuration properties // Thing configuration properties
@ -102,4 +100,11 @@ public class HueBindingConstants {
public static final String GROUP_ID = "groupId"; public static final String GROUP_ID = "groupId";
public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]"; 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";
} }

View File

@ -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";
}

View File

@ -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<List<PortalDiscoveryResult>>() {
}.getType();
private String internalipaddress;
public String getIPAddress() {
return internalipaddress;
}
}

View File

@ -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();
}
}

View File

@ -26,7 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 * @author Jochen Leopold - Initial contribution
*/ */

View File

@ -26,59 +26,16 @@ public class HueBridgeConfig {
public static final String HTTP = "http"; public static final String HTTP = "http";
public static final String HTTPS = "https"; public static final String HTTPS = "https";
private @Nullable String ipAddress; public @Nullable String ipAddress;
private @Nullable Integer port; public @Nullable Integer port;
private String protocol = HTTP; public String protocol = HTTPS;
private @Nullable String userName; public boolean useSelfSignedCertificate = true;
private int pollingInterval = 10; public @Nullable String userName;
private int sensorPollingInterval = 500; public int pollingInterval = 10;
public int sensorPollingInterval = 500;
public @Nullable String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public int getPort() { public int getPort() {
Integer thePort = this.port; Integer thePort = port;
return (thePort != null) ? thePort.intValue() : HTTPS.equals(protocol) ? 443 : 80; 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;
}
} }

View File

@ -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;
}
}

View File

@ -46,7 +46,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
@Activate @Activate
public HueCommandExtension(final @Reference ThingRegistry thingRegistry) { public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
super("hue", "Interact with the hue binding."); super("hue", "Interact with the Hue binding.");
this.thingRegistry = thingRegistry; this.thingRegistry = thingRegistry;
} }
@ -78,7 +78,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
console.println("No handler initialized for the thingUID '" + args[0] + "'"); console.println("No handler initialized for the thingUID '" + args[0] + "'");
printUsage(console); printUsage(console);
} else if (bridgeHandler == null && groupHandler == null) { } 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); printUsage(console);
} else { } else {
switch (args[1]) { switch (args[1]) {
@ -87,7 +87,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
String userName = bridgeHandler.getUserName(); String userName = bridgeHandler.getUserName();
console.println("Your user name is " + (userName != null ? userName : "undefined")); console.println("Your user name is " + (userName != null ? userName : "undefined"));
} else { } else {
console.println("'" + args[0] + "' is not a Hue bridgeUID"); console.println("'" + args[0] + "' is not a Hue BridgeUID");
printUsage(console); printUsage(console);
} }
break; break;

View File

@ -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<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(THING_TYPE_BRIDGE);
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
ThingUID uid = getThingUID(device);
if (uid != null) {
Map<String, Object> 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<String, @Nullable Object> 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;
}
}

View File

@ -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<String, @Nullable Object> 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<ThingTypeUID> 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;
}
}

View File

@ -13,25 +13,21 @@
package org.openhab.binding.hue.internal.discovery; package org.openhab.binding.hue.internal.discovery;
import static org.openhab.binding.hue.internal.HueBindingConstants.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.net.http.HttpUtil; 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.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -42,37 +38,29 @@ import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken; 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'. * provided by Philips'.
* *
* @author Awelkiyar Wehabrebi - Initial contribution * @author Awelkiyar Wehabrebi - Initial contribution
* @author Christoph Knauf - Refactorings * @author Christoph Knauf - Refactorings
* @author Andre Fuechsel - make {@link #startScan()} asynchronous * @author Andre Fuechsel - make {@link #startScan()} asynchronous
*/ */
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.hue") @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
@NonNullByDefault
public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService { public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue"; private static final String MODEL_NAME_PHILIPS_HUE = "\"name\":\"Philips Hue\"";
protected static final String BRIDGE_INDICATOR = "fffe"; protected static final String BRIDGE_INDICATOR = "fffe";
private static final String DISCOVERY_URL = "https://discovery.meethue.com/"; private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
protected static final String LABEL_PATTERN = "Philips Hue (%s)";
protected static final String LABEL_PATTERN = "Philips hue (IP)"; private static final String CONFIG_URL_PATTERN = "http://%s/api/0/config";
private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
private static final int REQUEST_TIMEOUT = 5000; private static final int REQUEST_TIMEOUT = 5000;
private static final int DISCOVERY_TIMEOUT = 10; private static final int DISCOVERY_TIMEOUT = 10;
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class); private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
public HueBridgeNupnpDiscovery() { public HueBridgeNupnpDiscovery() {
super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false); super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
} }
@Override @Override
@ -87,37 +75,25 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
for (BridgeJsonParameters bridge : getBridgeList()) { for (BridgeJsonParameters bridge : getBridgeList()) {
if (isReachableAndValidHueBridge(bridge)) { if (isReachableAndValidHueBridge(bridge)) {
String host = bridge.getInternalIpAddress(); String host = bridge.getInternalIpAddress();
String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10); String serialNumber = bridge.getId().toLowerCase();
serialNumber = serialNumber.toLowerCase();
ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber); ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
DiscoveryResult result = DiscoveryResultBuilder.create(uid) DiscoveryResult result = DiscoveryResultBuilder.create(uid) //
.withProperties(buildProperties(host, serialNumber)) .withProperties(Map.of( //
.withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER) HOST, host, //
Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) //
.withLabel(String.format(LABEL_PATTERN, host)) //
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
.build(); .build();
thingDiscovered(result); 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<String, Object> buildProperties(String host, String serialNumber) {
Map<String, Object> 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. * Checks if the Bridge is a reachable Hue Bridge with a valid id.
* *
* @param bridge the {@link BridgeJsonParameters}s * @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 * BRIDGE_INDICATOR longer then 10
*/ */
private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) { 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); logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
return false; return false;
} }
if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) { if (!BRIDGE_INDICATOR.equals(id.substring(6, 10))) {
logger.debug( logger.debug(
"Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.", "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
id, BRIDGE_INDICATOR); id, BRIDGE_INDICATOR);
return false; return false;
} }
try { try {
description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host)); description = doGetRequest(String.format(CONFIG_URL_PATTERN, host));
} catch (IOException e) { } catch (IOException e) {
logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host); logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
return false; return false;
@ -170,10 +146,10 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
return Objects.requireNonNull(bridgeParameters); return Objects.requireNonNull(bridgeParameters);
} catch (IOException e) { } catch (IOException e) {
logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges"); 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"); logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
} }
return new ArrayList<>(); return List.of();
} }
/** /**

View File

@ -15,7 +15,6 @@ package org.openhab.binding.hue.internal.discovery;
import static org.openhab.binding.hue.internal.HueBindingConstants.*; import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -26,10 +25,10 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.FullGroup; import org.openhab.binding.hue.internal.dto.FullGroup;
import org.openhab.binding.hue.internal.FullHueObject; import org.openhab.binding.hue.internal.dto.FullHueObject;
import org.openhab.binding.hue.internal.FullLight; import org.openhab.binding.hue.internal.dto.FullLight;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.handler.HueBridgeHandler; import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
import org.openhab.binding.hue.internal.handler.HueGroupHandler; import org.openhab.binding.hue.internal.handler.HueGroupHandler;
import org.openhab.binding.hue.internal.handler.HueLightHandler; 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.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; 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.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
@ -53,8 +51,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected * 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. * to a paired Hue Bridge. The default search time for Hue is 60 seconds.
* *
* @author Kai Kreuzer - Initial contribution * @author Kai Kreuzer - Initial contribution
* @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types; * @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 * @author Laurent Garnier - Added support for groups
*/ */
@NonNullByDefault @NonNullByDefault
public class HueDeviceDiscoveryService extends AbstractDiscoveryService public class HueDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
implements DiscoveryService, ThingHandlerService {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
.of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.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(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(), GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(), TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.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 // @formatter:off
private static final Map<String, String> TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries( private static final Map<String, String> TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries(

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.Comparator;
import java.util.regex.Matcher; import java.util.regex.Matcher;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -18,9 +18,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Samuel Leisering - Initial contribution * @author Samuel Leisering - Initial contribution
*/ */
@NonNullByDefault @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, <code>GET</code>ing the Lights always returns {@link FullLight}s instead of * Starting from version 1.11, <code>GET</code>ing the Lights always returns {@link FullLight}s instead of
@ -29,6 +32,6 @@ public class ApiVersionUtils {
* @return * @return
*/ */
public static boolean supportsFullLights(ApiVersion version) { public static boolean supportsFullLights(ApiVersion version) {
return fullLights.compare(version) <= 0; return FULL_LIGHTS.compare(version) <= 0;
} }
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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. * Collection of updates to the bridge configuration.

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -21,8 +21,8 @@ import com.google.gson.Gson;
* @author Samuel Leisering - changed Command visibility to public * @author Samuel Leisering - changed Command visibility to public
*/ */
public class Command { public class Command {
String key; public String key;
Object value; public Object value;
public Command(String key, Object value) { public Command(String key, Object value) {
this.key = key; this.key = key;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.ArrayList;
import java.util.Date; import java.util.Date;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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; import static java.util.stream.Collectors.joining;
@ -26,7 +26,7 @@ import java.util.ArrayList;
*/ */
public class ConfigUpdate { public class ConfigUpdate {
protected final ArrayList<Command> commands = new ArrayList<>(); public final ArrayList<Command> commands = new ArrayList<>();
public ConfigUpdate() { public ConfigUpdate() {
super(); super();

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
class CreateUserRequest { public class CreateUserRequest {
private String username; private String username;
private String devicetype; private String devicetype;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -18,11 +18,10 @@ import java.util.List;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
/** /**
*
* @author Q42 - Initial contribution * @author Q42 - Initial contribution
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding * @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<List<ErrorResponse>>() { public static final Type GSON_TYPE = new TypeToken<List<ErrorResponse>>() {
}.getType(); }.getType();

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.ArrayList;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -40,7 +40,7 @@ public class FullGroup extends Group {
/** /**
* Test constructor * Test constructor
*/ */
FullGroup(String id, String name, String type, State action, List<String> lights, State state) { public FullGroup(String id, String name, String type, State action, List<String> lights, State state) {
super(id, name, type); super(id, name, type);
this.action = action; this.action = action;
this.lights = lights; this.lights = lights;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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; 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; 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 Samuel Leisering - Initial contribution
* @author Christoph Weitkamp - Initial contribution * @author Christoph Weitkamp - Initial contribution
@ -53,7 +53,7 @@ public class FullHueObject extends HueObject {
/** /**
* Set the type of the object. * Set the type of the object.
*/ */
protected void setType(final String type) { public void setType(final String type) {
this.type = type; this.type = type;
} }
@ -73,7 +73,7 @@ public class FullHueObject extends HueObject {
/** /**
* Set the model ID of the object. * Set the model ID of the object.
*/ */
protected void setModelID(final String modelId) { public void setModelID(final String modelId) {
this.modelid = modelId; this.modelid = modelId;
} }
@ -115,7 +115,7 @@ public class FullHueObject extends HueObject {
/** /**
* Sets the unique id of the object. * Sets the unique id of the object.
*/ */
protected void setUniqueID(final String uniqueid) { public void setUniqueID(final String uniqueid) {
this.uniqueid = uniqueid; this.uniqueid = uniqueid;
} }
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.time.Duration; import java.time.Duration;
@ -18,7 +18,6 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.dto.Capabilities;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.Map; import java.util.Map;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
/** /**
* Basic group information. * Basic group information.
@ -24,7 +24,7 @@ public class Group {
private String name; private String name;
private String type; private String type;
Group() { public Group() {
this.id = "0"; this.id = "0";
this.name = "Lightset 0"; this.name = "Lightset 0";
this.type = "LightGroup"; this.type = "LightGroup";
@ -39,11 +39,11 @@ public class Group {
this.type = type; this.type = type;
} }
void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -34,7 +34,7 @@ public class HueObject {
HueObject() { HueObject() {
} }
void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }

View File

@ -10,9 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 * Updates the configuration of a light level sensor

View File

@ -10,13 +10,12 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
/** /**
*
* @author Q42 - Initial contribution * @author Q42 - Initial contribution
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
class NewLightsResponse { public class NewLightsResponse {
public String lastscan; public String lastscan;
} }

View File

@ -10,9 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 * Updates the configuration of a presence sensor

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -51,7 +51,7 @@ public class Scene {
/** /**
* Test constructor * Test constructor
*/ */
Scene(String id, String name, @Nullable String groupId, List<String> lightIds, boolean recycle) { public Scene(String id, String name, @Nullable String groupId, List<String> lightIds, boolean recycle) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.groupId = groupId; this.groupId = groupId;
@ -63,7 +63,7 @@ public class Scene {
return id; return id;
} }
void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@ -30,7 +30,7 @@ public class Schedule {
private String id; private String id;
private String name; private String name;
void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
import java.util.Date; import java.util.Date;

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.List;
@ -21,7 +21,7 @@ import java.util.List;
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
class SearchForLightsRequest { public class SearchForLightsRequest {
private List<String> deviceid; private List<String> deviceid;
public SearchForLightsRequest(List<String> deviceid) { public SearchForLightsRequest(List<String> deviceid) {

View File

@ -10,9 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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. * Collection of updates to the sensor configuration.

View File

@ -10,9 +10,13 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
class SetAttributesRequest { @NonNullByDefault
private String name; public class SetAttributesRequest {
private List<String> lights; private final @Nullable String name;
private final @Nullable List<String> lights;
public SetAttributesRequest(String name) { public SetAttributesRequest(String name) {
this(name, null); this(name, null);
@ -32,7 +37,7 @@ class SetAttributesRequest {
this(null, lights); this(null, lights);
} }
public SetAttributesRequest(String name, List<HueObject> lights) { public SetAttributesRequest(@Nullable String name, @Nullable List<HueObject> lights) {
if (name != null && Util.stringSize(name) > 32) { if (name != null && Util.stringSize(name) > 32) {
throw new IllegalArgumentException("Name can be at most 32 characters long"); throw new IllegalArgumentException("Name can be at most 32 characters long");
} else if (lights != null && (lights.isEmpty() || lights.size() > 16)) { } else if (lights != null && (lights.isEmpty() || lights.size() > 16)) {
@ -40,8 +45,6 @@ class SetAttributesRequest {
} }
this.name = name; this.name = name;
if (lights != null) { this.lights = lights == null ? null : lights.stream().map(l -> l.getId()).collect(Collectors.toList());
this.lights = Util.lightsToIds(lights);
}
} }
} }

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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. * Details of a bridge firmware update.

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
import java.util.Arrays; import java.util.Arrays;
@ -23,14 +23,14 @@ import java.util.Arrays;
*/ */
public class State { public class State {
private boolean on; private boolean on;
int bri; public int bri;
int hue; public int hue;
int sat; public int sat;
private float[] xy; private float[] xy;
int ct; public int ct;
private String alert; private String alert;
private String effect; private String effect;
String colormode; public String colormode;
private boolean reachable; private boolean reachable;
public State() { public State() {

View File

@ -10,11 +10,10 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.dto.State.AlertMode;
import org.openhab.binding.hue.internal.State.Effect; import org.openhab.binding.hue.internal.dto.State.Effect;
import org.openhab.binding.hue.internal.dto.ColorTemperature;
/** /**
* Collection of updates to the state of a light. * Collection of updates to the state of a light.

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -19,11 +19,10 @@ import java.util.Map;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
/** /**
*
* @author Q42 - Initial contribution * @author Q42 - Initial contribution
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding * @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<List<SuccessResponse>>() { public static final Type GSON_TYPE = new TypeToken<List<SuccessResponse>>() {
}.getType(); }.getType();

View File

@ -10,9 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 * Updates the configuration of a temperature sensor

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.hue.internal; package org.openhab.binding.hue.internal.dto;
import java.util.Date; import java.util.Date;

View File

@ -10,11 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -22,12 +20,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
/** /**
*
* @author Q42 - Initial contribution * @author Q42 - Initial contribution
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@NonNullByDefault @NonNullByDefault
class Util { public final class Util {
private Util() { private Util() {
} }
@ -36,28 +34,6 @@ class Util {
return str.getBytes(StandardCharsets.UTF_8).length; return str.getBytes(StandardCharsets.UTF_8).length;
} }
public static List<HueObject> idsToLights(List<String> ids) {
List<HueObject> lights = new ArrayList<>();
for (String id : ids) {
HueObject light = new HueObject();
light.setId(id);
lights.add(light);
}
return lights;
}
public static List<String> lightsToIds(List<HueObject> lights) {
List<String> ids = new ArrayList<>();
for (HueObject light : lights) {
ids.add(light.getId());
}
return ids;
}
public static @Nullable String quickMatch(String needle, String haystack) { public static @Nullable String quickMatch(String needle, String haystack) {
Matcher m = Pattern.compile(needle).matcher(haystack); Matcher m = Pattern.compile(needle).matcher(haystack);
m.find(); m.find();

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; package org.openhab.binding.hue.internal.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Thrown when the API returns an unknown error. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class ApiException extends Exception { public class ApiException extends Exception {
public ApiException() { public ApiException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; 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. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class DeviceOffException extends ApiException { public class DeviceOffException extends ApiException {
public DeviceOffException() { public DeviceOffException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; 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. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class EntityNotAvailableException extends ApiException { public class EntityNotAvailableException extends ApiException {
public EntityNotAvailableException() { public EntityNotAvailableException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; 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. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class GroupTableFullException extends ApiException { public class GroupTableFullException extends ApiException {
public GroupTableFullException() { public GroupTableFullException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; package org.openhab.binding.hue.internal.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Thrown when scheduling an invalid command. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class InvalidCommandException extends ApiException { public class InvalidCommandException extends ApiException {
public InvalidCommandException() { public InvalidCommandException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; 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. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class LinkButtonException extends ApiException { public class LinkButtonException extends ApiException {
public LinkButtonException() { public LinkButtonException() {
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.exceptions; 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. * 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 * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@NonNullByDefault
public class UnauthorizedException extends ApiException { public class UnauthorizedException extends ApiException {
public UnauthorizedException() { public UnauthorizedException() {
} }

View File

@ -10,17 +10,17 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 static org.openhab.binding.hue.internal.HueBindingConstants.*;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.HueBridgeHandler;
import org.openhab.binding.hue.internal.handler.HueGroupHandler; import org.openhab.binding.hue.internal.handler.HueGroupHandler;
import org.openhab.binding.hue.internal.handler.HueLightHandler; 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.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; 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.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
@ -60,22 +61,25 @@ import org.osgi.service.component.annotations.Reference;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
public class HueThingHandlerFactory extends BaseThingHandlerFactory { public class HueThingHandlerFactory extends BaseThingHandlerFactory {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
.of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.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(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(),
PresenceHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(), GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(), TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.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 HueStateDescriptionProvider stateDescriptionProvider;
private final TranslationProvider i18nProvider; private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider; private final LocaleProvider localeProvider;
@Activate @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) { final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.stateDescriptionProvider = stateDescriptionProvider; this.stateDescriptionProvider = stateDescriptionProvider;
this.i18nProvider = i18nProvider; this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider; this.localeProvider = localeProvider;
@ -103,7 +107,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
return super.createThing(thingTypeUID, configuration, hueGroupUID, bridgeUID); 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 @Override
@ -149,7 +153,8 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
@Override @Override
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { 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())) { } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueLightHandler(thing, stateDescriptionProvider); return new HueLightHandler(thing, stateDescriptionProvider);
} else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {

View File

@ -15,8 +15,8 @@ package org.openhab.binding.hue.internal.handler;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullGroup; import org.openhab.binding.hue.internal.dto.FullGroup;
import org.openhab.binding.hue.internal.Scene; 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. * The {@link GroupStatusListener} is notified when a group status has changed or a group has been removed or added.

View File

@ -18,7 +18,6 @@ import static org.openhab.core.thing.Thing.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -33,20 +32,21 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.ApiVersionUtils; import org.eclipse.jetty.client.HttpClient;
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.openhab.binding.hue.internal.config.HueBridgeConfig; 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.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.ApiException;
import org.openhab.binding.hue.internal.exceptions.DeviceOffException; import org.openhab.binding.hue.internal.exceptions.DeviceOffException;
import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException; 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.binding.hue.internal.exceptions.UnauthorizedException;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.status.ConfigStatusMessage; 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.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; 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.HSBType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType; 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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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. * the framework. All {@link HueLightHandler}s use the {@link HueBridgeHandler} to execute the actual commands.
* *
* @author Dennis Nobel - Initial contribution * @author Dennis Nobel - Initial contribution
@ -93,12 +98,13 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE); public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
private static final long BYPASS_MIN_DURATION_BEFORE_CMD = 1500L; 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 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 final Logger logger = LoggerFactory.getLogger(HueBridgeHandler.class);
private @Nullable ServiceRegistration<?> serviceRegistration;
private final HttpClient httpClient;
private final HueStateDescriptionProvider stateDescriptionOptionProvider; private final HueStateDescriptionProvider stateDescriptionOptionProvider;
private final TranslationProvider i18nProvider; private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider; private final LocaleProvider localeProvider;
@ -120,8 +126,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
try { try {
pollingLock.lock(); pollingLock.lock();
if (!lastBridgeConnectionState) { if (!lastBridgeConnectionState) {
// if user is not set in configuration try to create a new user on Hue bridge // if user is not set in configuration try to create a new user on Hue Bridge
if (hueBridgeConfig.getUserName() == null) { if (hueBridgeConfig.userName == null) {
hueBridge.getFullConfig(); hueBridge.getFullConfig();
} }
lastBridgeConnectionState = tryResumeBridgeConnection(); lastBridgeConnectionState = tryResumeBridgeConnection();
@ -132,6 +138,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} }
} }
} catch (ConfigurationException e) {
handleConfigurationFailure(e);
} catch (UnauthorizedException | IllegalStateException e) { } catch (UnauthorizedException | IllegalStateException e) {
if (isReachable(hueBridge.getIPAddress())) { if (isReachable(hueBridge.getIPAddress())) {
lastBridgeConnectionState = false; lastBridgeConnectionState = false;
@ -142,7 +150,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
lastBridgeConnectionState = false; lastBridgeConnectionState = false;
onConnectionLost(); onConnectionLost();
} }
} catch (ApiException | IOException e) { } catch (ApiException | CommunicationException | IOException e) {
if (hueBridge != null && lastBridgeConnectionState) { if (hueBridge != null && lastBridgeConnectionState) {
logger.debug("Connection to Hue Bridge {} lost: {}", hueBridge.getIPAddress(), e.getMessage(), e); logger.debug("Connection to Hue Bridge {} lost: {}", hueBridge.getIPAddress(), e.getMessage(), e);
lastBridgeConnectionState = false; lastBridgeConnectionState = false;
@ -165,7 +173,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
// If there is no connection, this line will fail // If there is no connection, this line will fail
hueBridge.authenticate("invalid"); hueBridge.authenticate("invalid");
} catch (IOException e) { } catch (ConfigurationException | IOException e) {
return false; return false;
} catch (ApiException e) { } catch (ApiException e) {
String message = e.getMessage(); String message = e.getMessage();
@ -407,9 +415,11 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
private List<String> consoleScenesList = new ArrayList<>(); private List<String> consoleScenesList = new ArrayList<>();
public HueBridgeHandler(Bridge bridge, HueStateDescriptionProvider stateDescriptionOptionProvider, public HueBridgeHandler(Bridge bridge, HttpClient httpClient,
TranslationProvider i18nProvider, LocaleProvider localeProvider) { HueStateDescriptionProvider stateDescriptionOptionProvider, TranslationProvider i18nProvider,
LocaleProvider localeProvider) {
super(bridge); super(bridge);
this.httpClient = httpClient;
this.stateDescriptionOptionProvider = stateDescriptionOptionProvider; this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
this.i18nProvider = i18nProvider; this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider; this.localeProvider = localeProvider;
@ -417,7 +427,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(HueDeviceDiscoveryService.class); return Set.of(HueDeviceDiscoveryService.class);
} }
@Override @Override
@ -596,10 +606,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
ScheduledFuture<?> job = lightPollingJob; ScheduledFuture<?> job = lightPollingJob;
if (job == null || job.isCancelled()) { if (job == null || job.isCancelled()) {
long lightPollingInterval; long lightPollingInterval;
int configPollingInterval = hueBridgeConfig.getPollingInterval(); int configPollingInterval = hueBridgeConfig.pollingInterval;
if (configPollingInterval < 1) { if (configPollingInterval < 1) {
lightPollingInterval = TimeUnit.SECONDS.toSeconds(10); 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); lightPollingInterval);
} else { } else {
lightPollingInterval = configPollingInterval; lightPollingInterval = configPollingInterval;
@ -621,12 +631,12 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
private void startSensorPolling() { private void startSensorPolling() {
ScheduledFuture<?> job = sensorPollingJob; ScheduledFuture<?> job = sensorPollingJob;
if (job == null || job.isCancelled()) { if (job == null || job.isCancelled()) {
int configSensorPollingInterval = hueBridgeConfig.getSensorPollingInterval(); int configSensorPollingInterval = hueBridgeConfig.sensorPollingInterval;
if (configSensorPollingInterval > 0) { if (configSensorPollingInterval > 0) {
long sensorPollingInterval; long sensorPollingInterval;
if (configSensorPollingInterval < 50) { if (configSensorPollingInterval < 50) {
sensorPollingInterval = TimeUnit.MILLISECONDS.toMillis(500); 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); sensorPollingInterval);
} else { } else {
sensorPollingInterval = configSensorPollingInterval; sensorPollingInterval = configSensorPollingInterval;
@ -665,7 +675,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
@Override @Override
public void dispose() { public void dispose() {
logger.debug("Handler disposed."); logger.debug("Disposing Hue Bridge handler ...");
Future<?> job = initJob; Future<?> job = initJob;
if (job != null) { if (job != null) {
job.cancel(true); job.cancel(true);
@ -676,46 +686,50 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
if (hueBridge != null) { if (hueBridge != null) {
hueBridge = null; hueBridge = null;
} }
ServiceRegistration<?> localServiceRegistration = serviceRegistration;
if (localServiceRegistration != null) {
// remove trustmanager service
localServiceRegistration.unregister();
serviceRegistration = null;
}
} }
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing hue bridge handler."); logger.debug("Initializing Hue Bridge handler ...");
hueBridgeConfig = getConfigAs(HueBridgeConfig.class); hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
String ip = hueBridgeConfig.getIpAddress(); String ip = hueBridgeConfig.ipAddress;
if (ip == null || ip.isEmpty()) { if (ip == null || ip.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-ip-address"); "@text/offline.conf-error-no-ip-address");
} else { } else {
if (hueBridge == null) { if (hueBridge == null) {
hueBridge = new HueBridge(ip, hueBridgeConfig.getPort(), hueBridgeConfig.getProtocol(), scheduler); if (HueBridgeConfig.HTTPS.equals(hueBridgeConfig.protocol)) {
hueBridge.setTimeout(5000); // 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); 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(); onUpdate();
} }
} }
public @Nullable String getUserName() { public @Nullable String getUserName() {
return hueBridgeConfig == null ? null : hueBridgeConfig.getUserName(); return hueBridgeConfig == null ? null : hueBridgeConfig.userName;
} }
private synchronized void onUpdate() { private synchronized void onUpdate() {
if (hueBridge != null) { startLightPolling();
startLightPolling(); startSensorPolling();
startSensorPolling(); startScenePolling();
startScenePolling();
}
} }
/** /**
@ -761,9 +775,9 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
*/ */
private boolean tryResumeBridgeConnection() throws IOException, ApiException { private boolean tryResumeBridgeConnection() throws IOException, ApiException {
logger.debug("Connection to Hue Bridge {} established.", hueBridge.getIPAddress()); logger.debug("Connection to Hue Bridge {} established.", hueBridge.getIPAddress());
if (hueBridgeConfig.getUserName() == null) { if (hueBridgeConfig.userName == null) {
logger.warn( 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-username"); "@text/offline.conf-error-no-username");
return false; 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 * If there is a user name available, it attempts to re-authenticate. Otherwise new authentication credentials will
* be requested from the bridge. * 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 * @return returns {@code true} if re-authentication was successful, {@code false} otherwise
*/ */
public boolean onNotAuthenticated() { public boolean onNotAuthenticated() {
if (hueBridge == null) { if (hueBridge == null) {
return false; return false;
} }
String userName = hueBridgeConfig.getUserName(); String userName = hueBridgeConfig.userName;
if (userName == null) { if (userName == null) {
createUser(); createUser();
} else { } else {
try { try {
hueBridge.authenticate(userName); hueBridge.authenticate(userName);
return true; return true;
} catch (ConfigurationException e) {
handleConfigurationFailure(e);
} catch (Exception e) { } catch (Exception e) {
logger.trace("", e);
handleAuthenticationFailure(e, userName); handleAuthenticationFailure(e, userName);
} }
} }
@ -813,10 +830,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
} }
private String createUserOnPhysicalBridge() throws IOException, ApiException { private String createUserOnPhysicalBridge() throws IOException, ApiException {
logger.info("Creating new user on Hue bridge {} - please press the pairing button on the bridge.", logger.info("Creating new user on Hue Bridge {} - please press the pairing button on the bridge.",
hueBridgeConfig.getIpAddress()); hueBridgeConfig.ipAddress);
String userName = hueBridge.link(DEVICE_TYPE); 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; return userName;
} }
@ -829,26 +846,32 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
hueBridgeConfig = getConfigAs(HueBridgeConfig.class); hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
logger.trace("Configuration update failed.", 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."); 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) { 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."); logger.warn("Please configure a valid user or remove user from configuration to generate a new one.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-invalid-username"); "@text/offline.conf-error-invalid-username");
} }
private void handleLinkButtonNotPressed(LinkButtonException ex) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-press-pairing-button"); "@text/offline.conf-error-press-pairing-button");
} }
private void handleExceptionWhileCreatingUser(Exception ex) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-creation-username"); "@text/offline.conf-error-creation-username");
} }
@ -1041,10 +1064,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
// Check whether an IP address is provided // Check whether an IP address is provided
hueBridgeConfig = getConfigAs(HueBridgeConfig.class); hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
String ip = hueBridgeConfig.getIpAddress(); String ip = hueBridgeConfig.ipAddress;
if (ip == null || ip.isEmpty()) { if (ip == null || ip.isEmpty()) {
return List.of(ConfigStatusMessage.Builder.error(HOST) return List.of(ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
.withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build()); .withArguments(HOST).build());
} else { } else {
return List.of(); return List.of();
} }

View File

@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.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. * Access to the Hue system for light handlers.

View File

@ -25,11 +25,11 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.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.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
@ -87,7 +87,7 @@ public class HueGroupHandler extends BaseThingHandler implements HueLightActions
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing hue group handler."); logger.debug("Initializing Hue group handler.");
Bridge bridge = getBridge(); Bridge bridge = getBridge();
initializeThing((bridge == null) ? null : bridge.getStatus()); 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) { public void handleCommand(String channel, Command command, long fadeTime) {
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler == null) { 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; return;
} }
FullGroup group = bridgeHandler.getGroupById(groupId); FullGroup group = bridgeHandler.getGroupById(groupId);
if (group == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-wrong-group-id"); "@text/offline.conf-error-wrong-group-id");
return; return;

View File

@ -24,11 +24,11 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.Capabilities;
import org.openhab.binding.hue.internal.dto.ColorTemperature; 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.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
@ -51,7 +51,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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. * command.
* *
* @author Dennis Nobel - Initial contribution * @author Dennis Nobel - Initial contribution
@ -116,7 +116,7 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing hue light handler."); logger.debug("Initializing Hue light handler.");
Bridge bridge = getBridge(); Bridge bridge = getBridge();
initializeThing((bridge == null) ? null : bridge.getStatus()); 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) { public void handleCommand(String channel, Command command, long fadeTime) {
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler == null) { 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; return;
} }
final FullLight light = lastFullLight == null ? bridgeHandler.getLightById(lightId) : lastFullLight; final FullLight light = lastFullLight == null ? bridgeHandler.getLightById(lightId) : lastFullLight;
if (light == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-wrong-light-id"); "@text/offline.conf-error-wrong-light-id");
return; return;

View File

@ -12,8 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.handler; 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.HueBindingConstants.*;
import static org.openhab.binding.hue.internal.dto.FullSensor.*;
import static org.openhab.core.thing.Thing.*; import static org.openhab.core.thing.Thing.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -27,9 +27,9 @@ import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.dto.StateUpdate;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -72,7 +72,7 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing hue sensor handler."); logger.debug("Initializing Hue sensor handler.");
Bridge bridge = getBridge(); Bridge bridge = getBridge();
initializeThing((bridge == null) ? null : bridge.getStatus()); 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) { protected void handleCommand(String channel, Command command) {
HueClient bridgeHandler = getHueClient(); HueClient bridgeHandler = getHueClient();
if (bridgeHandler == null) { 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; return;
} }
final FullSensor sensor = lastFullSensor; final FullSensor sensor = lastFullSensor;
if (sensor == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-wrong-sensor-id"); "@text/offline.conf-error-wrong-sensor-id");
return; return;
@ -206,13 +206,13 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
if (!configUpdate.isEmpty()) { if (!configUpdate.isEmpty()) {
HueClient hueBridge = getHueClient(); HueClient hueBridge = getHueClient();
if (hueBridge == null) { 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; return;
} }
final FullSensor sensor = lastFullSensor; final FullSensor sensor = lastFullSensor;
if (sensor == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-wrong-sensor-id"); "@text/offline.conf-error-wrong-sensor-id");
return; return;

View File

@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.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.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;

View File

@ -13,7 +13,7 @@
package org.openhab.binding.hue.internal.handler; package org.openhab.binding.hue.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault; 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. * The {@link LightStatusListener} is notified when a light status has changed or a light has been removed or added.

View File

@ -13,7 +13,7 @@
package org.openhab.binding.hue.internal.handler; package org.openhab.binding.hue.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault; 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. * The {@link SensorStatusListener} is notified when a sensor status has changed or a sensor has been removed or added.

View File

@ -20,8 +20,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;

View File

@ -21,13 +21,12 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -43,7 +42,7 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault @NonNullByDefault
public class DimmerSwitchHandler extends HueSensorHandler { public class DimmerSwitchHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER_SWITCH); public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DIMMER_SWITCH);
public DimmerSwitchHandler(Thing thing) { public DimmerSwitchHandler(Thing thing) {
super(thing); super(thing);

View File

@ -12,16 +12,15 @@
*/ */
package org.openhab.binding.hue.internal.handler.sensors; 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.HueBindingConstants.*;
import static org.openhab.binding.hue.internal.dto.FullSensor.STATE_PRESENCE;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -35,7 +34,8 @@ import org.openhab.core.thing.ThingTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public class GeofencePresenceHandler extends HueSensorHandler { public class GeofencePresenceHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_GEOFENCE_SENSOR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GEOFENCE_SENSOR);
public GeofencePresenceHandler(Thing thing) { public GeofencePresenceHandler(Thing thing) {
super(thing); super(thing);

View File

@ -12,18 +12,17 @@
*/ */
package org.openhab.binding.hue.internal.handler.sensors; 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.HueBindingConstants.*;
import static org.openhab.binding.hue.internal.dto.FullSensor.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.LightLevelConfigUpdate; import org.openhab.binding.hue.internal.dto.LightLevelConfigUpdate;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -41,7 +40,8 @@ import org.openhab.core.thing.ThingTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public class LightLevelHandler extends HueSensorHandler { public class LightLevelHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_LIGHT_LEVEL_SENSOR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_LIGHT_LEVEL_SENSOR);
public LightLevelHandler(Thing thing) { public LightLevelHandler(Thing thing) {
super(thing); super(thing);

View File

@ -12,17 +12,16 @@
*/ */
package org.openhab.binding.hue.internal.handler.sensors; 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.HueBindingConstants.*;
import static org.openhab.binding.hue.internal.dto.FullSensor.*;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.PresenceConfigUpdate; import org.openhab.binding.hue.internal.dto.PresenceConfigUpdate;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueClient; import org.openhab.binding.hue.internal.handler.HueClient;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
@ -43,7 +42,8 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class PresenceHandler extends HueSensorHandler { public class PresenceHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PRESENCE_SENSOR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR);
private final Logger logger = LoggerFactory.getLogger(PresenceHandler.class); private final Logger logger = LoggerFactory.getLogger(PresenceHandler.class);
@ -55,13 +55,13 @@ public class PresenceHandler extends HueSensorHandler {
public void handleCommand(String channel, Command command) { public void handleCommand(String channel, Command command) {
HueClient hueBridge = getHueClient(); HueClient hueBridge = getHueClient();
if (hueBridge == null) { 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; return;
} }
final FullSensor sensor = lastFullSensor; final FullSensor sensor = lastFullSensor;
if (sensor == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-wrong-sensor-id"); "@text/offline.conf-error-wrong-sensor-id");
return; return;

View File

@ -21,13 +21,12 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@ -42,7 +41,7 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault @NonNullByDefault
public class TapSwitchHandler extends HueSensorHandler { public class TapSwitchHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TAP_SWITCH); public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TAP_SWITCH);
public TapSwitchHandler(Thing thing) { public TapSwitchHandler(Thing thing) {
super(thing); super(thing);

View File

@ -12,18 +12,17 @@
*/ */
package org.openhab.binding.hue.internal.handler.sensors; 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.HueBindingConstants.*;
import static org.openhab.binding.hue.internal.dto.FullSensor.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.hue.internal.FullSensor; import org.openhab.binding.hue.internal.dto.FullSensor;
import org.openhab.binding.hue.internal.SensorConfigUpdate; import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
import org.openhab.binding.hue.internal.TemperatureConfigUpdate; import org.openhab.binding.hue.internal.dto.TemperatureConfigUpdate;
import org.openhab.binding.hue.internal.handler.HueSensorHandler; import org.openhab.binding.hue.internal.handler.HueSensorHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
@ -39,7 +38,8 @@ import org.openhab.core.thing.ThingTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public class TemperatureHandler extends HueSensorHandler { public class TemperatureHandler extends HueSensorHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TEMPERATURE_SENSOR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_TEMPERATURE_SENSOR);
public TemperatureHandler(Thing thing) { public TemperatureHandler(Thing thing) {
super(thing); super(thing);

View File

@ -6,13 +6,4 @@
<name>Hue Binding</name> <name>Hue Binding</name>
<description>The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs.</description> <description>The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs.</description>
<config-description>
<parameter name="removalGracePeriod" type="integer" min="0" step="1" unit="s">
<label>Removal Grace Period</label>
<description>Extra grace period (seconds) that UPnP discovery shall wait before removing a lost Bridge from the
Inbox. Default is 50 seconds.</description>
<default>50</default>
</parameter>
</config-description>
</binding:binding> </binding:binding>

View File

@ -117,7 +117,7 @@
<config-description uri="thing-type:hue:group"> <config-description uri="thing-type:hue:group">
<parameter name="groupId" type="text" required="true"> <parameter name="groupId" type="text" required="true">
<label>Group ID</label> <label>Group ID</label>
<description>The group identifier identifies one certain hue group or room.</description> <description>The group identifier identifies one certain Hue group or room.</description>
</parameter> </parameter>
<parameter name="fadetime" type="integer" min="0" step="100" unit="ms"> <parameter name="fadetime" type="integer" min="0" step="100" unit="ms">
<label>@text/config.fadetime.label</label> <label>@text/config.fadetime.label</label>

View File

@ -3,11 +3,6 @@
binding.hue.name = Hue Binding binding.hue.name = Hue Binding
binding.hue.description = The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs. 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 types
thing-type.hue.0000.label = On/Off Light 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.label = CLIP Generic Flag Sensor
thing-type.hue.0850.description = A generic sensor object for IP sensor use. thing-type.hue.0850.description = A generic sensor object for IP sensor use.
thing-type.hue.bridge.label = Hue Bridge 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.label = Geofence Sensor
thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection. thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection.
thing-type.hue.group.label = Hue Group 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 types config
thing-type.config.hue.bridge.ipAddress.label = Network Address 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.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.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.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.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.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.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.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 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.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.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.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.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.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.label = Sensor Status
config.on.description = Enables or disables the sensor. config.on.description = Enables or disables the sensor.
# config status messages # 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 # thing status descriptions
offline.conf-error-no-ip-address = Cannot connect to Hue bridge. IP address not available in configuration. offline.communication-error = An unexpected exception occurred during execution.
offline.conf-error-no-username = Cannot connect to Hue bridge. User name for authentication not available in configuration. 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-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-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.conf-error-creation-username = Failed to create new user on Hue Bridge.
offline.bridge-connection-lost = Hue bridge connection lost. offline.bridge-connection-lost = Hue Bridge connection lost.
offline.conf-error-no-light-id = Light ID not available in configuration. 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-sensor-id = Sensor ID not available in configuration.
offline.conf-error-no-group-id = Group 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-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-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.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.light-not-reachable = Hue Bridge reports light as not reachable.
offline.sensor-not-reachable = Hue bridge reports sensor as not reachable. offline.sensor-not-reachable = Hue Bridge reports sensor as not reachable.
offline.light-removed = Hue bridge reports light as removed. offline.light-removed = Hue Bridge reports light as removed.
offline.sensor-removed = Hue bridge reports sensor as removed. offline.sensor-removed = Hue Bridge reports sensor as removed.
offline.group-removed = Hue bridge reports group as removed. offline.group-removed = Hue Bridge reports group as removed.
# lightactions # lightactions

View File

@ -6,7 +6,7 @@
<!-- Hue Bridge --> <!-- Hue Bridge -->
<bridge-type id="bridge"> <bridge-type id="bridge">
<label>Hue Bridge</label> <label>Hue Bridge</label>
<description>The Hue bridge represents the Philips Hue bridge.</description> <description>The Hue Bridge represents the Philips Hue Bridge.</description>
<channels> <channels>
<channel id="scene" typeId="scene"/> <channel id="scene" typeId="scene"/>
@ -21,26 +21,41 @@
<parameter name="ipAddress" type="text" required="true"> <parameter name="ipAddress" type="text" required="true">
<context>network-address</context> <context>network-address</context>
<label>Network Address</label> <label>Network Address</label>
<description>Network address of the Hue bridge.</description> <description>Network address of the Hue Bridge.</description>
</parameter> </parameter>
<parameter name="port" type="integer" required="false" min="1" max="65535"> <parameter name="port" type="integer" required="false" min="1" max="65535">
<label>Port</label> <label>Port</label>
<description>Port of the Hue bridge.</description> <description>Port of the Hue Bridge.</description>
</parameter>
<parameter name="protocol" type="text">
<label>Protocol</label>
<description>Protocol to connect to the Hue Bridge (http or https).</description>
<default>https</default>
<options>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</options>
</parameter>
<parameter name="useSelfSignedCertificate" type="boolean">
<label>Use Self-Signed Certificate</label>
<description>Use self-signed certificate for HTTPS connection to Hue Bridge.</description>
<default>true</default>
<advanced>true</advanced>
</parameter> </parameter>
<parameter name="userName" type="text"> <parameter name="userName" type="text">
<context>password</context> <context>password</context>
<label>Username</label> <label>Username</label>
<description>Name of a registered Hue bridge user, that allows to access the API.</description> <description>Name of a registered Hue Bridge user, that allows to access the API.</description>
</parameter> </parameter>
<parameter name="pollingInterval" type="integer" min="1" step="1" unit="s"> <parameter name="pollingInterval" type="integer" min="1" step="1" unit="s">
<label>Polling Interval</label> <label>Polling Interval</label>
<description>Seconds between fetching values from the Hue bridge. Default is 10.</description> <description>Seconds between fetching values from the Hue Bridge. Default is 10.</description>
<default>10</default> <default>10</default>
</parameter> </parameter>
<parameter name="sensorPollingInterval" type="integer" min="0" step="1" unit="ms"> <parameter name="sensorPollingInterval" type="integer" min="0" step="1" unit="ms">
<label>Sensor Polling Interval</label> <label>Sensor Polling Interval</label>
<description>Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for <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 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.</description> sensors. Default is 500.</description>
<default>500</default> <default>500</default>
</parameter> </parameter>

View File

@ -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-----

View File

@ -12,15 +12,16 @@
*/ */
package org.openhab.binding.hue.internal; 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.junit.jupiter.api.Test;
import org.openhab.binding.hue.internal.dto.ApiVersion;
/** /**
*
*
* @author Samuel Leisering - Initial contribution * @author Samuel Leisering - Initial contribution
*/ */
@NonNullByDefault
public class ApiVersionTest { public class ApiVersionTest {
@Test @Test

View File

@ -14,35 +14,47 @@ package org.openhab.binding.hue.internal;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.mock;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors; 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.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.openhab.binding.hue.internal.config.HueBridgeConfig;
import org.openhab.binding.hue.internal.HttpClient.Result; 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.binding.hue.internal.exceptions.ApiException;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.i18n.ConfigurationException;
/** /**
* @author Hengrui Jiang - initial contribution * @author Hengrui Jiang - initial contribution
*/ */
@NonNullByDefault
public class HueBridgeTest { public class HueBridgeTest {
@Test @Test
public void testGetScenesExcludeRecycleScenes() throws IOException, ApiException { public void testGetScenesExcludeRecycleScenes() throws IOException, ApiException {
HttpClient mockHttpClient = Mockito.mock(HttpClient.class); HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
Executors.newScheduledThreadPool(1)) {
HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1), @Override
mockHttpClient); public HueResult get(String address) throws ConfigurationException, CommunicationException {
if ("https://ip:443/api/username/lights".equals(address)) {
List<Scene> testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), true), // return new HueResult("{}", HttpStatus.OK_200);
new Scene("id2", "name2", "group2", Collections.emptyList(), false)); } else if ("https://ip:443/api/username/scenes".equals(address)) {
when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200)); List<Scene> 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<Scene> scenes = hueBridge.getScenes(); List<Scene> scenes = hueBridge.getScenes();
assertThat(scenes.size(), is(1)); assertThat(scenes.size(), is(1));
@ -51,15 +63,22 @@ public class HueBridgeTest {
@Test @Test
public void testGetScenesOrderByGroup() throws IOException, ApiException { public void testGetScenesOrderByGroup() throws IOException, ApiException {
HttpClient mockHttpClient = Mockito.mock(HttpClient.class); HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
Executors.newScheduledThreadPool(1)) {
HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1), @Override
mockHttpClient); public HueResult get(String address) throws ConfigurationException, CommunicationException {
if ("https://ip:443/api/username/lights".equals(address)) {
List<Scene> testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), false), // return new HueResult("{}", HttpStatus.OK_200);
new Scene("id2", "name2", "group2", Collections.emptyList(), false), } else if ("https://ip:443/api/username/scenes".equals(address)) {
new Scene("id3", "name3", "group1", Collections.emptyList(), false)); List<Scene> testScenes = List.of( //
when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200)); 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<Scene> scenes = hueBridge.getScenes(); List<Scene> scenes = hueBridge.getScenes();
assertThat(scenes.size(), is(3)); assertThat(scenes.size(), is(3));
@ -92,8 +111,7 @@ public class HueBridgeTest {
" \"version\": 2,\n" + // " \"version\": 2,\n" + //
" \"group\": \"%s\"\n" + // " \"group\": \"%s\"\n" + //
" }"; " }";
String lights = String.join(",", String lights = scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.joining(","));
scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.toList()));
return String.format(template, scene.getId(), scene.getName(), lights, scene.isRecycle(), scene.getGroupId()); return String.format(template, scene.getId(), scene.getName(), lights, scene.isRecycle(), scene.getGroupId());
} }
} }

View File

@ -17,9 +17,12 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test; 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.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.binding.hue.internal.handler.LightStateConverter;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType; 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 Denis Dudnik - switched to internally integrated source of Jue library
* @author Markus Rathgeb - migrated to plain Java test * @author Markus Rathgeb - migrated to plain Java test
*/ */
@NonNullByDefault
public class LightStateConverterTest { public class LightStateConverterTest {
@Test @Test

View File

@ -18,11 +18,16 @@ import static org.hamcrest.MatcherAssert.assertThat;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test; 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 * @author HJiang - initial contribution
*/ */
@NonNullByDefault
public class SceneTest { public class SceneTest {
private static final State PLACEHOLDER_STATE = new State(); private static final State PLACEHOLDER_STATE = new State();

View File

@ -23,10 +23,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.openhab.binding.hue.internal.FullConfig; import org.openhab.binding.hue.internal.dto.FullConfig;
import org.openhab.binding.hue.internal.FullLight; import org.openhab.binding.hue.internal.dto.FullLight;
import org.openhab.binding.hue.internal.State.ColorMode; import org.openhab.binding.hue.internal.dto.State.ColorMode;
import org.openhab.binding.hue.internal.StateUpdate; import org.openhab.binding.hue.internal.dto.StateUpdate;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;

View File

@ -12,7 +12,8 @@
*/ */
package org.openhab.binding.hue.internal.handler; 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. * 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 Markus Rathgeb - migrated to plain Java test
* @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
*/ */
@NonNullByDefault
public class HueLightState { public class HueLightState {
int brightness = 200; int brightness = 200;

View File

@ -26,8 +26,6 @@ Fragment-Host: org.openhab.binding.hue
jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ 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.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.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.annotation-api;version='[2.0.0,2.0.1)',\
jakarta.inject.jakarta.inject-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)',\ 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.apache.felix.scr;version='[2.1.30,2.1.31)',\
org.osgi.util.function;version='[1.2.0,1.2.1)',\ org.osgi.util.function;version='[1.2.0,1.2.1)',\
org.osgi.util.promise;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)',\ xstream;version='[1.4.19,1.4.20)',\
com.google.gson;version='[2.8.9,2.8.10)',\ 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.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.client;version='[9.4.46,9.4.47)',\
org.eclipse.jetty.http;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)',\ 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.client;version='[9.4.46,9.4.47)',\
org.eclipse.jetty.websocket.common;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.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)',\ 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)',\ biz.aQute.tester.junit-platform;version='[6.3.0,6.3.1)',\
org.openhab.binding.hue;version='[3.4.0,3.4.1)',\ ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
org.openhab.binding.hue.tests;version='[3.4.0,3.4.1)',\ ch.qos.logback.core;version='[1.2.11,1.2.12)'
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)'

View File

@ -15,26 +15,35 @@ package org.openhab.binding.hue.internal;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*; 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.HueBindingConstants.*;
import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; 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.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.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.binding.hue.internal.handler.HueBridgeHandler;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryListener; import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultFlag; import org.openhab.core.config.discovery.DiscoveryResultFlag;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry; import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
@ -54,7 +63,6 @@ import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
*/ */
public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent { public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
protected HueThingHandlerFactory hueThingHandlerFactory;
protected DiscoveryListener discoveryListener; protected DiscoveryListener discoveryListener;
protected ThingRegistry thingRegistry; protected ThingRegistry thingRegistry;
protected Bridge hueBridge; protected Bridge hueBridge;
@ -74,7 +82,8 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, "1.2.3.4"); configuration.put(HOST, "1.2.3.4");
configuration.put(USER_NAME, "testUserName"); 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", hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
configuration); configuration);
@ -150,45 +159,45 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent
} }
@Test @Test
public void startSearchIsCalled() { public void startSearchIsCalled() throws IOException, ApiException {
final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false); 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 @Override
public Result put(String address, String body) throws IOException { public HueResult get(String address) throws CommunicationException {
return new Result("", 200);
}
@Override
public Result get(String address) throws IOException {
if (address.endsWith("testUserName")) { if (address.endsWith("testUserName")) {
String body = "{\"lights\":{}}"; 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")) { } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) {
String body = "{}"; String body = "{}";
return new Result(body, 200); return new HueResult(body, HttpStatus.OK_200);
} else if (address.endsWith("testUserName/config")) { } else if (address.endsWith("testUserName/config")) {
String body = "{ \"apiversion\": \"1.26.0\"}"; String body = "{\"apiversion\": \"1.26.0\"}";
return new Result(body, 200); return new HueResult(body, HttpStatus.OK_200);
} else { } else {
return new Result("", 404); return new HueResult("", HttpStatus.NOT_FOUND_404);
} }
} }
@Override @Override
public Result post(String address, String body) throws IOException { public HueResult post(String address, String body) throws CommunicationException {
if (address.endsWith("lights")) { if (address.endsWith("lights")) {
String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}"; String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
searchHasBeenTriggered.set(true); searchHasBeenTriggered.set(true);
return new Result(bodyReturn, 200); return new HueResult(bodyReturn, HttpStatus.OK_200);
} else { } 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(); ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
waitForAssert(() -> { 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(() -> { waitForAssert(() -> {
try { try {
// mock HttpClient // mock HttpClient
final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge"); final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
hueBridgeField.setAccessible(true); hueBridgeField.setAccessible(true);
hueBridgeField.set(hueBridgeHandler, mockedHueBridge);
final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler); final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
assertThat(hueBridgeValue, is(notNullValue())); 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"); final Field usernameField = HueBridge.class.getDeclaredField("username");
usernameField.setAccessible(true); usernameField.setAccessible(true);
usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME)); usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));

View File

@ -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 {
}

View File

@ -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()));
}
}

View File

@ -37,7 +37,6 @@ import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
/** /**
*
* @author Christoph Knauf - Initial contribution * @author Christoph Knauf - Initial contribution
* @author Markus Rathgeb - migrated to plain Java test * @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 ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
final String ip1 = "192.168.31.17"; final String ip1 = "192.168.31.17";
final String ip2 = "192.168.30.28"; final String ip2 = "192.168.30.28";
final String sn1 = "00178820057f"; final String sn1 = "001788fffe20057f";
final String sn2 = "001788141b41"; final String sn2 = "001788fffe141b41";
final ThingUID BRIDGE_THING_UID_1 = new ThingUID(BRIDGE_THING_TYPE_UID, sn1); 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 ThingUID BRIDGE_THING_UID_2 = new ThingUID(BRIDGE_THING_TYPE_UID, sn2);
final String validBridgeDiscoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + ip1 final String validBridgeDiscoveryResult = "[{\"id\":\"" + sn1 + "\",\"internalipaddress\":" + ip1 + "},{\"id\":\""
+ "},{\"id\":\"001788fffe141b41\",\"internalipaddress\":" + ip2 + "}]"; + sn2 + "\",\"internalipaddress\":" + ip2 + "}]";
String discoveryResult; String discoveryResult;
String expBridgeDescription = "" + // 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\":\"\"}";
"<?xml version=\"1.0\"?>" + //
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">" + //
" <specVersion>" + //
" <major>1</major>" + //
" <minor>0</minor>" + //
" </specVersion>" + //
" <URLBase>http://$IP:80/</URLBase>" + //
" <device>" + //
" <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>" + //
" <friendlyName>Philips hue ($IP)</friendlyName>" + //
" <manufacturer>Royal Philips Electronics</manufacturer>" + //
" <manufacturerURL>http://www.philips.com</manufacturerURL>" + //
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>" + //
"<modelName>Philips hue bridge 2012</modelName>" + //
"<modelNumber>1000000000000</modelNumber>" + //
"<modelURL>http://www.meethue.com</modelURL>" + //
" <serialNumber>93eadbeef13</serialNumber>" + //
" <UDN>uuid:01234567-89ab-cdef-0123-456789abcdef</UDN>" + //
" <serviceList>" + //
" <service>" + //
" <serviceType>(null)</serviceType>" + //
" <serviceId>(null)</serviceId>" + //
" <controlURL>(null)</controlURL>" + //
" <eventSubURL>(null)</eventSubURL>" + //
" <SCPDURL>(null)</SCPDURL>" + //
" </service>" + //
" </serviceList>" + //
" <presentationURL>index.html</presentationURL>" + //
" <iconList>" + //
" <icon>" + //
" <mimetype>image/png</mimetype>" + //
" <height>48</height>" + //
" <width>48</width>" + //
" <depth>24</depth>" + //
" <url>hue_logo_0.png</url>" + //
" </icon>" + //
" <icon>" + //
" <mimetype>image/png</mimetype>" + //
" <height>120</height>" + //
" <width>120</width>" + //
" <depth>24</depth>" + //
" <url>hue_logo_3.png</url>" + //
" </icon>" + //
" </iconList>" + //
" </device>" + //
"</root>";
private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) { private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) {
assertThat(result.getBridgeUID(), nullValue()); 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("ipAddress"), is(expIp));
assertThat(result.getProperties().get("serialNumber"), is(expSn)); assertThat(result.getProperties().get("serialNumber"), is(expSn));
} }
@ -132,9 +85,9 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
if (url.contains("meethue")) { if (url.contains("meethue")) {
return discoveryResult; return discoveryResult;
} else if (url.contains(ip1)) { } else if (url.contains(ip1)) {
return expBridgeDescription.replaceAll("$IP", ip1); return expBridgeDescription.replaceAll("$SN", sn1);
} else if (url.contains(ip2)) { } else if (url.contains(ip2)) {
return expBridgeDescription.replaceAll("$IP", ip2); return expBridgeDescription.replaceAll("$SN", sn2);
} }
throw new IOException(); throw new IOException();
} }

View File

@ -16,19 +16,19 @@ import static org.eclipse.jdt.annotation.Checks.requireNonNull;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*; 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.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.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jetty.client.HttpClient;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent; import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent;
import org.openhab.binding.hue.internal.HueBridge; import org.openhab.binding.hue.internal.config.HueBridgeConfig;
import org.openhab.binding.hue.internal.HueConfigStatusMessage; import org.openhab.binding.hue.internal.connection.HueBridge;
import org.openhab.binding.hue.internal.exceptions.ApiException; import org.openhab.binding.hue.internal.exceptions.ApiException;
import org.openhab.binding.hue.internal.exceptions.LinkButtonException; import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
import org.openhab.binding.hue.internal.exceptions.UnauthorizedException; 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.Configuration;
import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.config.core.status.ConfigStatusMessage;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry; import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
@ -72,18 +73,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void assertThatANewUserIsAddedToConfigIfNotExistingYet() { public void assertThatANewUserIsAddedToConfigIfNotExistingYet() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
hueBridgeHandler.thingUpdated(bridge); hueBridgeHandler.thingUpdated(bridge);
injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { injectBridge(hueBridgeHandler,
@Override new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
public String link(String deviceType) throws IOException, ApiException { @Override
return TEST_USER_NAME; public String link(String deviceType) throws IOException, ApiException {
} return TEST_USER_NAME;
}); }
});
hueBridgeHandler.onNotAuthenticated(); hueBridgeHandler.onNotAuthenticated();
@ -95,17 +97,18 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(USER_NAME, TEST_USER_NAME); configuration.put(USER_NAME, TEST_USER_NAME);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
hueBridgeHandler.thingUpdated(bridge); hueBridgeHandler.thingUpdated(bridge);
injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { injectBridge(hueBridgeHandler,
@Override new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
public void authenticate(String userName) throws IOException, ApiException { @Override
} public void authenticate(String userName) throws IOException, ApiException {
}); }
});
hueBridgeHandler.onNotAuthenticated(); hueBridgeHandler.onNotAuthenticated();
@ -117,18 +120,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(USER_NAME, "notAuthenticatedUser"); configuration.put(USER_NAME, "notAuthenticatedUser");
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
hueBridgeHandler.thingUpdated(bridge); hueBridgeHandler.thingUpdated(bridge);
injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { injectBridge(hueBridgeHandler,
@Override new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
public void authenticate(String userName) throws IOException, ApiException { @Override
throw new UnauthorizedException(); public void authenticate(String userName) throws IOException, ApiException {
} throw new UnauthorizedException();
}); }
});
hueBridgeHandler.onNotAuthenticated(); hueBridgeHandler.onNotAuthenticated();
@ -141,18 +145,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void verifyStatusIfLinkButtonIsNotPressed() { public void verifyStatusIfLinkButtonIsNotPressed() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
hueBridgeHandler.thingUpdated(bridge); hueBridgeHandler.thingUpdated(bridge);
injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { injectBridge(hueBridgeHandler,
@Override new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
public String link(String deviceType) throws IOException, ApiException { @Override
throw new LinkButtonException(); public String link(String deviceType) throws IOException, ApiException {
} throw new LinkButtonException();
}); }
});
hueBridgeHandler.onNotAuthenticated(); hueBridgeHandler.onNotAuthenticated();
@ -165,18 +170,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void verifyStatusIfNewUserCannotBeCreated() { public void verifyStatusIfNewUserCannotBeCreated() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
hueBridgeHandler.thingUpdated(bridge); hueBridgeHandler.thingUpdated(bridge);
injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) { injectBridge(hueBridgeHandler,
@Override new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
public String link(String deviceType) throws IOException, ApiException { @Override
throw new ApiException(); public String link(String deviceType) throws IOException, ApiException {
} throw new ApiException();
}); }
});
hueBridgeHandler.onNotAuthenticated(); hueBridgeHandler.onNotAuthenticated();
@ -190,7 +196,7 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void verifyOfflineIsSetWithoutBridgeOfflineStatus() { public void verifyOfflineIsSetWithoutBridgeOfflineStatus() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, DUMMY_HOST); configuration.put(HOST, DUMMY_HOST);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
@ -206,14 +212,13 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() { public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, null); configuration.put(HOST, null);
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST) ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
.withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build(); .withArguments(HOST).build();
waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next())); waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
} }
@ -222,19 +227,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() { public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.put(HOST, ""); configuration.put(HOST, "");
configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber"); configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
Bridge bridge = createBridgeThing(configuration); Bridge bridge = createBridgeThing(configuration);
HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class); HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST) ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
.withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build(); .withArguments(HOST).build();
waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next())); waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
} }
private Bridge createBridgeThing(Configuration configuration) { private Bridge createBridgeThing(Configuration configuration) {
configuration.put("useSelfSignedCertificate", false);
Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID,
new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration); new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration);