]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hue] Changed discovery to mDNS; added HTTPS handling; refactor HTTPClient to use...
authorChristoph Weitkamp <github@christophweitkamp.de>
Sat, 22 Oct 2022 21:39:51 +0000 (23:39 +0200)
committerGitHub <noreply@github.com>
Sat, 22 Oct 2022 21:39:51 +0000 (23:39 +0200)
* Changed discovery to MDNS; added HTTPS handling; refactor HTTPClient to use jetty shared client

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
123 files changed:
bundles/org.openhab.binding.hue/README.md
bundles/org.openhab.binding.hue/src/main/feature/feature.xml
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/action/LightActions.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/config/HueBridgeConfig.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/console/HueCommandExtension.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscovery.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/ApiException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/DeviceOffException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/EntityNotAvailableException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/GroupTableFullException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/InvalidCommandException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/LinkButtonException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/exceptions/UnauthorizedException.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/GroupStatusListener.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueGroupHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueLightHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueSensorHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStateConverter.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/LightStatusListener.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/SensorStatusListener.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/ClipHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/DimmerSwitchHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/GeofencePresenceHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/LightLevelHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/PresenceHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TapSwitchHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/sensors/TemperatureHandler.java
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/binding/binding.xml
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/bridge.xml
bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/ApiVersionTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/HueBridgeTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/LightStateConverterTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/SceneTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightHandlerTest.java
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/handler/HueLightState.java
itests/org.openhab.binding.hue.tests/itest.bndrun
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java [deleted file]
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java [deleted file]
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeNupnpDiscoveryOSGITest.java
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandlerOSGiTest.java

index b50629a1ec6486f1c12aa434968e5a8b5f1f8614..c33c61f4e843e3fbc4a942ea967b7416c706975e 100644 (file)
@@ -1,15 +1,15 @@
 # Philips Hue Binding
 
 This binding integrates the [Philips Hue Lighting system](https://www.meethue.com).
-The integration happens through the Hue bridge, which acts as an IP gateway to the ZigBee devices.
+The integration happens through the Hue Bridge, which acts as an IP gateway to the ZigBee devices.
 
 ![Philips Hue](doc/hue.jpg)
 
 ## Supported Things
 
-The Hue bridge is required as a "bridge" for accessing any other Hue device.
+The Hue Bridge is required as a "bridge" for accessing any other Hue device.
 It supports the ZigBee LightLink protocol as well as the upwards compatible ZigBee 3.0 protocol.
-There are two types of Hue bridges, generally referred to as v1 (the rounded version) and v2 (the squared version).
+There are two types of Hue Bridges, generally referred to as v1 (the rounded version) and v2 (the squared version).
 Only noticeable difference between the two generation of bridges is the added support for Apple HomeKit in v2.
 Both bridges are fully supported by this binding.
 
@@ -17,7 +17,7 @@ Almost all available Hue devices are supported by this binding.
 This includes not only the "Friends of Hue", but also products like the LivingWhites adapter.
 Additionally, it is possible to use OSRAM Lightify devices as well as other ZigBee LightLink compatible products, including the IKEA TRÃ…DFRI lights (when updated). 
 Beside bulbs and luminaires the Hue binding also supports some ZigBee sensors. Currently only Hue specific sensors are tested successfully (Hue Motion Sensor and Hue Dimmer Switch).
-Please note that the devices need to be registered with the Hue bridge before it is possible for this binding to use them.
+Please note that the devices need to be registered with the Hue Bridge before it is possible for this binding to use them.
 
 The Hue binding supports all seven types of lighting devices defined for ZigBee LightLink ([see page 24, table 2](https://www.nxp.com/docs/en/user-guide/JN-UG-3091.pdf).
 These are:
@@ -65,28 +65,35 @@ They are presented by the following ZigBee Device ID and _Thing type_:
 
 The Hue Dimmer Switch has 4 buttons and registers as a Non-Colour Controller switch, while the Hue Tap (also 4 buttons) registers as a Non-Colour Scene Controller in accordance with the ZLL standard.
 
-Also, Hue bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor.
+Also, Hue Bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor.
 These sensors save state for rules and calculate what actions to do.
 CLIP Sensor set or get by JSON through IP.
 
-Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue bridge.
+Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue Bridge.
 
 ## Discovery
 
-The Hue bridge is discovered through UPnP in the local network.
+The Hue Bridge is discovered through mDNS in the local network.
+Auto-discovery is enabled by default.
+To disable it, you can add the following line to `<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 the binding is authorized, it automatically reads all devices and groups that are set up on the Hue bridge and puts them into the Inbox.
+Once the binding is authorized, it automatically reads all devices and groups that are set up on the Hue Bridge and puts them into the Inbox.
 
 ## Thing Configuration
 
-The Hue bridge requires the IP address as a configuration value in order for the binding to know where to access it.
+The Hue Bridge requires the IP address as a configuration value in order for the binding to know where to access it.
 In the thing file, this looks e.g. like
 
 ```
 Bridge hue:bridge:1 [ ipAddress="192.168.0.64" ]
 ```
 
-A user to authenticate against the Hue bridge is automatically generated.
+A user to authenticate against the Hue Bridge is automatically generated.
 Please note that the generated user name cannot be written automatically to the `.thing` file, and has to be set manually.
 The generated user name can be found, after pressing the authentication button on the bridge, with the following console command: `hue <bridgeUID> username`.
 The user name can be set using the `userName` configuration value, e.g.:
@@ -95,17 +102,19 @@ The user name can be set using the `userName` configuration value, e.g.:
 Bridge hue:bridge:1 [ ipAddress="192.168.0.64", userName="qwertzuiopasdfghjklyxcvbnm1234" ]
 ```
 
-| Parameter             | Description                                                                                                                                                                                                                              |
-|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| ipAddress             | Network address of the Hue bridge. **Mandatory**                                                                                                                                                                                         |
-| port                  |  Port of the Hue bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined.                                                                                                                            |
-| userName              | Name of a registered Hue bridge user, that allows to access the API. **Mandatory**                                                                                                                                                       |
-| pollingInterval       | Seconds between fetching light values from the Hue bridge. Optional, the default value is 10 (min="1", step="1").                                                                                                                        |
-| sensorPollingInterval | Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. |
+| Parameter                | Description                                                                                                                                                                                                                                                                                                                   |
+|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| ipAddress                | Network address of the Hue Bridge. **Mandatory**.                                                                                                                                                                                                                                                                             |
+| port                     | Port of the Hue Bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined.                                                                                                                                                                                                                  |
+| protocol                 | Protocol to connect to the Hue Bridge ("http" or "https"), default value is "https").                                                                                                                                                                                                                                         |
+| useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. **Advanced**, default value is `true`.                                                                                                                                                                                                                        |
+| userName                 | Name of a registered Hue Bridge user, that allows to access the API. **Mandatory**                                                                                                                                                                                                                                            |
+| pollingInterval          | Seconds between fetching light values from the Hue Bridge. Optional, the default value is 10 (min="1", step="1").                                                                                                                                                                                                             |
+| sensorPollingInterval    | Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. |
 
 ### Devices
 
-The devices are identified by the number that the Hue bridge assigns to them (also shown in the Hue App as an identifier).
+The devices are identified by the number that the Hue Bridge assigns to them (also shown in the Hue App as an identifier).
 Thus, all it needs for manual configuration is this single value like
 
 ```
@@ -130,13 +139,13 @@ The following device types also have an optional configuration value to specify
 
 | Parameter | Description                                                                   |
 |-----------|-------------------------------------------------------------------------------|
-| lightId   | Number of the device provided by the Hue bridge. **Mandatory**                |
+| lightId   | Number of the device provided by the Hue Bridge. **Mandatory**                |
 | fadetime  | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
 
 
 ### Groups
 
-The groups are identified by the number that the Hue bridge assigns to them.
+The groups are identified by the number that the Hue Bridge assigns to them.
 Thus, all it needs for manual configuration is this single value like
 
 ```
@@ -149,7 +158,7 @@ The group type also have an optional configuration value to specify the fade tim
 
 | Parameter | Description                                                                   |
 |-----------|-------------------------------------------------------------------------------|
-| groupId   | Number of the group provided by the Hue bridge. **Mandatory**                 |
+| groupId   | Number of the group provided by the Hue Bridge. **Mandatory**                 |
 | fadetime  | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
 
 
@@ -179,7 +188,7 @@ The devices support some of the following channels:
 | last_updated          | DateTime           | This channel the date and time when the sensor was last updated.                                                                        | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
 | battery_level         | Number             | This channel shows the battery level.                                                                                                   | 0820, 0106, 0107, 0302                   |
 | battery_low           | Switch             | This channel indicates whether the battery is low or not.                                                                               | 0820, 0106, 0107, 0302                   |
-| scene                 | String             | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue bridge.                   | bridge, group                            |
+| scene                 | String             | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue Bridge.                   | bridge, group                            |
 
 To load a hue scene inside a rule for example, the ID of the scene will be required.
 You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.
@@ -366,11 +375,3 @@ if (receivedEvent == "1000.0")) {
     //do stuff
 }       
 ```
-
-### UPnP Discovery: Inbox 'Grace Period'
-
-The Hue Bridge can sometimes be late in sending its UPnP 'ssdp:alive' notifications even though it has not really gone offline.
-This means that the Hue Bridge could be repeatedly removed from, and (re)added to, the InBox.
-Which would lead to confusion in the UI, and repeated logger messages.
-To prevent this, the binding tells the OpenHAB core to wait for a further period of time ('grace period') before actually removing the Bridge from the Inbox.
-The 'grace period' has a default value of 50 seconds, but it can be fine tuned in the main UI via Settings | Bindings | Hue | Configure.
index abc121f42ed8b90e57fb70053afbdc79ada544a6..ba4db3d38fbb25c4f6e37e6eea19082e9b8406f8 100644 (file)
@@ -4,7 +4,7 @@
 
        <feature name="openhab-binding-hue" description="Hue Binding" version="${project.version}">
                <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>
        </feature>
 </features>
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersion.java
deleted file mode 100644 (file)
index eeac4ff..0000000
+++ /dev/null
@@ -1,131 +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.Comparator;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Represents the version of the API of the form 1.0 or 1.2.1
- *
- * @author Samuel Leisering - Initial contribution
- */
-public class ApiVersion {
-    private final int major;
-    private final int minor;
-    private final int micro;
-
-    private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)(\\.([0-9]+))?$");
-
-    public ApiVersion(int major, int minor, int micro) {
-        this.major = major;
-        this.minor = minor;
-        this.micro = micro;
-    }
-
-    public static ApiVersion of(String version) {
-        Matcher matcher = VERSION_PATTERN.matcher(version);
-        if (matcher.matches()) {
-            int major = Integer.parseInt(matcher.group(1));
-            int minor = Integer.parseInt(matcher.group(2));
-            String microString = matcher.group(4);
-            int micro = Integer.parseInt(microString == null ? "0" : microString);
-
-            ApiVersion apiVersion = new ApiVersion(major, minor, micro);
-            return apiVersion;
-        }
-
-        throw new IllegalArgumentException("Version \"" + version + "\" is not valid");
-    }
-
-    /**
-     * returns the major version part of the version
-     *
-     * @return the major part of the version
-     */
-    public int getMajor() {
-        return major;
-    }
-
-    /**
-     * returns the minor version part of the version
-     *
-     * @return the minor part of the version
-     */
-    public int getMinor() {
-        return minor;
-    }
-
-    /**
-     * returns the micro version part of the version
-     *
-     * @return the micro part of the version
-     */
-    public int getMicro() {
-        return micro;
-    }
-
-    /**
-     * compare API versions according to {@link Comparator#compare(Object, Object)}
-     *
-     * @param other
-     * @return
-     */
-    public int compare(ApiVersion other) {
-        int c = Integer.compare(major, other.major);
-        if (c == 0) {
-            c = Integer.compare(minor, other.minor);
-            if (c == 0) {
-                c = Integer.compare(micro, other.micro);
-            }
-        }
-        return c;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + major;
-        result = prime * result + micro;
-        result = prime * result + minor;
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        ApiVersion other = (ApiVersion) obj;
-        if (major != other.major) {
-            return false;
-        }
-        if (micro != other.micro) {
-            return false;
-        }
-        return minor == other.minor;
-    }
-
-    @Override
-    public String toString() {
-        return major + "." + minor + "." + micro;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ApiVersionUtils.java
deleted file mode 100644 (file)
index 6baa365..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * @author Samuel Leisering - Initial contribution
- */
-@NonNullByDefault
-public class ApiVersionUtils {
-
-    private static ApiVersion fullLights = new ApiVersion(1, 11, 0);
-
-    /**
-     * Starting from version 1.11, <code>GET</code>ing the Lights always returns {@link FullLight}s instead of
-     * {@link HueObject}s.
-     *
-     * @return
-     */
-    public static boolean supportsFullLights(ApiVersion version) {
-        return fullLights.compare(version) <= 0;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/BridgeConfigUpdate.java
deleted file mode 100644 (file)
index af84e4c..0000000
+++ /dev/null
@@ -1,122 +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;
-
-/**
- * Collection of updates to the bridge configuration.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - added Sensor support
- */
-public class BridgeConfigUpdate extends ConfigUpdate {
-    /**
-     * Set the port of the proxy or null if there is no proxy.
-     *
-     * @param port port for proxy
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setProxyPort(Integer port) {
-        if (port != null && port < 0) {
-            throw new IllegalArgumentException("Invalid value for port");
-        }
-
-        commands.add(new Command("proxyport", port == null ? 0 : port));
-        return this;
-    }
-
-    /**
-     * Set the name of the bridge, which also functions as the UPnP name.
-     *
-     * @param name new name [4..16]
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setName(String name) {
-        if (Util.stringSize(name) < 4 || Util.stringSize(name) > 16) {
-            throw new IllegalArgumentException("Bridge name must be between 4 and 16 characters long");
-        }
-
-        commands.add(new Command("name", name));
-        return this;
-    }
-
-    /**
-     * Set the address of the proxy or null if there is no proxy.
-     *
-     * @param ip ip of proxy
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setProxyAddress(String ip) {
-        if (ip != null && Util.stringSize(ip) > 40) {
-            throw new IllegalArgumentException("Bridge proxy address can be at most 40 characters long");
-        }
-
-        commands.add(new Command("proxyaddress", ip == null ? "none" : ip));
-        return this;
-    }
-
-    /**
-     * Set whether the link button has been pressed within the last 30 seconds or not.
-     *
-     * @param pressed true for pressed, false for not pressed
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setLinkButton(boolean pressed) {
-        commands.add(new Command("linkbutton", pressed));
-        return this;
-    }
-
-    /**
-     * Set the IP address of the bridge.
-     *
-     * @param ip ip address of bridge
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setIPAddress(String ip) {
-        commands.add(new Command("ipaddress", ip));
-        return this;
-    }
-
-    /**
-     * Set the network mask of the bridge.
-     *
-     * @param netmask network mask
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setNetworkMask(String netmask) {
-        commands.add(new Command("netmask", netmask));
-        return this;
-    }
-
-    /**
-     * Set the gateway address of the bridge.
-     *
-     * @param ip gateway address
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setGateway(String ip) {
-        commands.add(new Command("gateway", ip));
-        return this;
-    }
-
-    /**
-     * Set whether the bridge uses DHCP to get an ip address or not.
-     *
-     * @param enabled dhcp enabled
-     * @return this object for chaining calls
-     */
-    public BridgeConfigUpdate setDHCP(boolean enabled) {
-        commands.add(new Command("dhcp", enabled));
-        return this;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Command.java
deleted file mode 100644 (file)
index 67bc440..0000000
+++ /dev/null
@@ -1,35 +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.Gson;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Samuel Leisering - changed Command visibility to public
- */
-public class Command {
-    String key;
-    Object value;
-
-    public Command(String key, Object value) {
-        this.key = key;
-        this.value = value;
-    }
-
-    String toJson() {
-        return "\"" + key + "\":" + new Gson().toJson(value);
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Config.java
deleted file mode 100644 (file)
index ecf481b..0000000
+++ /dev/null
@@ -1,199 +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.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Detailed bridge info available if authenticated.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - added API-Version
- */
-public class Config {
-    private String name;
-    private String swversion;
-    private String apiversion;
-    private String bridgeid;
-    private String mac;
-    private String modelid;
-    private boolean dhcp;
-    private String ipaddress;
-    private String netmask;
-    private String gateway;
-    private String proxyaddress;
-    private int proxyport;
-    private Date UTC;
-    private boolean linkbutton;
-    private Map<String, User> whitelist;
-    private SoftwareUpdate swupdate;
-
-    Config() {
-    }
-
-    /**
-     * Returns the name.
-     *
-     * @return name of the bridge
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns the version of the software.
-     *
-     * @return version of software on the bridge
-     */
-    public String getSoftwareVersion() {
-        return swversion;
-    }
-
-    /**
-     * Returns the bridge id
-     *
-     * @return bridge id
-     */
-    public String getBridgeId() {
-        return bridgeid;
-    }
-
-    /**
-     * Returns the MAC address.
-     *
-     * @return mac address of bridge
-     */
-    public String getMACAddress() {
-        return mac;
-    }
-
-    /**
-     * Returns the model id
-     *
-     * @return model id
-     */
-    public String getModelId() {
-        return modelid;
-    }
-
-    /**
-     * Returns if the current IP address was obtained with DHCP.
-     *
-     * @return true if the current IP address was obtained with DHCP, false otherwise.
-     */
-    public boolean isDHCPEnabled() {
-        return dhcp;
-    }
-
-    /**
-     * Returns the IP address.
-     *
-     * @return ip address of bridge
-     */
-    public String getIPAddress() {
-        return ipaddress;
-    }
-
-    /**
-     * Returns the network mask.
-     *
-     * @return network mask
-     */
-    public String getNetworkMask() {
-        return netmask;
-    }
-
-    /**
-     * Returns the IP address of the gateway.
-     *
-     * @return ip address of gateway
-     */
-    public String getGateway() {
-        return gateway;
-    }
-
-    /**
-     * Returns the IP address of the proxy or null if there is none.
-     *
-     * @return ip address of proxy or null
-     */
-    public String getProxyAddress() {
-        return "none".equals(proxyaddress) ? null : proxyaddress;
-    }
-
-    /**
-     * Returns the port of the proxy or null if there is none.
-     *
-     * @return port of proxy or null
-     */
-    public Integer getProxyPort() {
-        return "none".equals(proxyaddress) ? null : proxyport;
-    }
-
-    /**
-     * Returns the time on the bridge.
-     *
-     * @return time on the bridge
-     */
-    public Date getUTCTime() {
-        return UTC;
-    }
-
-    /**
-     * Returns if the link button has been pressed within the last 30 seconds.
-     *
-     * @return true if the link button has been pressed within the last 30 seconds, false otherwise
-     */
-    public boolean isLinkButtonPressed() {
-        return linkbutton;
-    }
-
-    /**
-     * Returns the list of whitelisted users.
-     *
-     * @return list of whitelisted users
-     */
-    public List<User> getWhitelist() {
-        ArrayList<User> usersList = new ArrayList<>();
-
-        usersList.addAll(whitelist.values());
-
-        return usersList;
-    }
-
-    /**
-     * Returns information about a bridge firmware update.
-     *
-     * @return bridge firmware update info
-     */
-    public SoftwareUpdate getSoftwareUpdate() {
-        return swupdate;
-    }
-
-    /**
-     * Returns the current API-Version of the Bridge. This always returns <code>1.0</code>
-     * for bridges with version less than <code>1.2.1</code>, which introduces this call.
-     *
-     * @return
-     */
-    public String getApiVersion() {
-        if (apiversion == null) {
-            return "1.0";
-        }
-        return apiversion;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ConfigUpdate.java
deleted file mode 100644 (file)
index d118c81..0000000
+++ /dev/null
@@ -1,50 +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 static java.util.stream.Collectors.joining;
-
-import java.util.ArrayList;
-
-/**
- * Collection of updates
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - Added support for sensor API
- * @author Christoph Weitkamp - Added support for sensor API
- */
-public class ConfigUpdate {
-
-    protected final ArrayList<Command> commands = new ArrayList<>();
-
-    public ConfigUpdate() {
-        super();
-    }
-
-    public boolean isEmpty() {
-        return commands.isEmpty();
-    }
-
-    public String toJson() {
-        return commands.stream().map(c -> c.toJson()).collect(joining(",", "{", "}"));
-    }
-
-    /**
-     * Returns the message delay recommended by Philips
-     * Regarding to this article: https://developers.meethue.com/documentation/hue-system-performance
-     */
-    public long getMessageDelay() {
-        return commands.size() * 40L;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateScheduleRequest.java
deleted file mode 100644 (file)
index d0c78db..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.util.Date;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@SuppressWarnings("unused")
-class CreateScheduleRequest {
-    private String name;
-    private String description;
-    private ScheduleCommand command;
-    private Date time;
-
-    public CreateScheduleRequest(String name, String description, ScheduleCommand command, Date time) {
-        if (name != null && Util.stringSize(name) > 32) {
-            throw new IllegalArgumentException("Schedule name can be at most 32 characters long");
-        }
-
-        if (description != null && Util.stringSize(description) > 64) {
-            throw new IllegalArgumentException("Schedule description can be at most 64 characters long");
-        }
-
-        if (command == null) {
-            throw new IllegalArgumentException("No schedule command specified");
-        }
-
-        this.name = name;
-        this.description = description;
-        this.command = command;
-        this.time = time;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/CreateUserRequest.java
deleted file mode 100644 (file)
index 1233571..0000000
+++ /dev/null
@@ -1,45 +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 Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@SuppressWarnings("unused")
-class CreateUserRequest {
-    private String username;
-    private String devicetype;
-
-    public CreateUserRequest(String username, String devicetype) {
-        if (Util.stringSize(devicetype) > 40) {
-            throw new IllegalArgumentException("Device type can be at most 40 characters long");
-        }
-
-        if (Util.stringSize(username) < 10 || Util.stringSize(username) > 40) {
-            throw new IllegalArgumentException("Username must be between 10 and 40 characters long");
-        }
-
-        this.username = username;
-        this.devicetype = devicetype;
-    }
-
-    public CreateUserRequest(String devicetype) {
-        if (Util.stringSize(devicetype) > 40) {
-            throw new IllegalArgumentException("Device type can be at most 40 characters long");
-        }
-
-        this.devicetype = devicetype;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ErrorResponse.java
deleted file mode 100644 (file)
index 78d1d36..0000000
+++ /dev/null
@@ -1,57 +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 ErrorResponse {
-    public static final Type GSON_TYPE = new TypeToken<List<ErrorResponse>>() {
-    }.getType();
-
-    public class Error {
-        private Integer type;
-        private String address;
-        private String description;
-    }
-
-    private Error error;
-
-    public Integer getType() {
-        if (error == null) {
-            return null;
-        }
-        return error.type;
-    }
-
-    public String getAddress() {
-        if (error == null) {
-            return null;
-        }
-        return error.address;
-    }
-
-    public String getDescription() {
-        if (error == null) {
-            return null;
-        }
-        return error.description;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullConfig.java
deleted file mode 100644 (file)
index 82d988b..0000000
+++ /dev/null
@@ -1,73 +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.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Container for all data on a bridge.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class FullConfig {
-    private Map<String, FullLight> lights;
-    private Map<String, FullGroup> groups;
-    private Config config;
-
-    /**
-     * Returns detailed information about all lights known to the bridge.
-     *
-     * @return detailed lights list
-     */
-    public List<FullLight> getLights() {
-        ArrayList<FullLight> lightsList = new ArrayList<>();
-
-        for (String id : lights.keySet()) {
-            FullLight light = lights.get(id);
-            light.setId(id);
-            lightsList.add(light);
-        }
-
-        return lightsList;
-    }
-
-    /**
-     * Returns detailed information about all groups on the bridge.
-     *
-     * @return detailed groups list
-     */
-    public List<FullGroup> getGroups() {
-        ArrayList<FullGroup> groupsList = new ArrayList<>();
-
-        for (String id : groups.keySet()) {
-            FullGroup group = groups.get(id);
-            group.setId(id);
-            groupsList.add(group);
-        }
-
-        return groupsList;
-    }
-
-    /**
-     * Returns bridge configuration.
-     * Use HueBridge.getConfig() if you only need this.
-     *
-     * @return bridge configuration
-     */
-    public Config getConfig() {
-        return config;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullGroup.java
deleted file mode 100644 (file)
index 98ebaa5..0000000
+++ /dev/null
@@ -1,81 +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 java.util.Map;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Detailed group information.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Laurent Garnier - field state added
- */
-public class FullGroup extends Group {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, FullGroup>>() {
-    }.getType();
-
-    private State action;
-    private List<String> lights;
-    private State groupState; // Will not be set by hue API
-
-    FullGroup() {
-        super();
-    }
-
-    /**
-     * Test constructor
-     */
-    FullGroup(String id, String name, String type, State action, List<String> lights, State state) {
-        super(id, name, type);
-        this.action = action;
-        this.lights = lights;
-        this.groupState = state;
-    }
-
-    /**
-     * Returns the last sent state update to the group.
-     * This does not have to reflect the current state of the group.
-     *
-     * @return last state update
-     */
-    public State getAction() {
-        return action;
-    }
-
-    /**
-     * Returns a list of the lights in the group.
-     *
-     * @return lights in the group
-     */
-    public List<String> getLightIds() {
-        return lights;
-    }
-
-    /**
-     * Returns the current state of the group.
-     *
-     * @return current state
-     */
-    public State getState() {
-        return groupState;
-    }
-
-    public void setState(State state) {
-        this.groupState = state;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java
deleted file mode 100644 (file)
index da91179..0000000
+++ /dev/null
@@ -1,121 +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 static org.openhab.binding.hue.internal.HueBindingConstants.NORMALIZE_ID_REGEX;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * Detailed information about an object on the hue bridge
- *
- * @author Samuel Leisering - Initial contribution
- * @author Christoph Weitkamp - Initial contribution
- */
-@NonNullByDefault
-public class FullHueObject extends HueObject {
-
-    private @NonNullByDefault({}) String type;
-    private @Nullable String modelid;
-    @SerializedName("manufacturername")
-    private @NonNullByDefault({}) String manufacturerName;
-    @SerializedName("productname")
-    private @NonNullByDefault({}) String productName;
-    private @Nullable String swversion;
-    private @Nullable String uniqueid;
-
-    public FullHueObject() {
-        super();
-    }
-
-    /**
-     * Returns the type of the object.
-     *
-     * @return type
-     */
-    public String getType() {
-        return type;
-    }
-
-    /**
-     * Set the type of the object.
-     */
-    protected void setType(final String type) {
-        this.type = type;
-    }
-
-    /**
-     * Returns the model ID of the object.
-     *
-     * @return model id
-     */
-    public @Nullable String getModelID() {
-        return modelid;
-    }
-
-    public @Nullable String getNormalizedModelID() {
-        return modelid != null ? modelid.replaceAll(NORMALIZE_ID_REGEX, "_") : modelid;
-    }
-
-    /**
-     * Set the model ID of the object.
-     */
-    protected void setModelID(final String modelId) {
-        this.modelid = modelId;
-    }
-
-    public String getManufacturerName() {
-        return manufacturerName;
-    }
-
-    public void setManufacturerName(String manufacturerName) {
-        this.manufacturerName = manufacturerName;
-    }
-
-    public String getProductName() {
-        return productName;
-    }
-
-    public void setProductName(String productName) {
-        this.productName = productName;
-    }
-
-    /**
-     * Returns the software version of the object.
-     *
-     * @return software version
-     */
-    public @Nullable String getSoftwareVersion() {
-        return swversion;
-    }
-
-    /**
-     * Returns the unique id of the object. The unique is the MAC address of the device with a unique endpoint id in the
-     * form: AA:BB:CC:DD:EE:FF:00:11-XX
-     *
-     * @return the unique id, can be null for some virtual types like the daylight sensor
-     */
-    public @Nullable String getUniqueID() {
-        return uniqueid;
-    }
-
-    /**
-     * Sets the unique id of the object.
-     */
-    protected void setUniqueID(final String uniqueid) {
-        this.uniqueid = uniqueid;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java
deleted file mode 100644 (file)
index ea3a69b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.lang.reflect.Type;
-import java.time.Duration;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.dto.Capabilities;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Detailed light information.
- *
- * @author Q42 - Initial contribution
- * @author Thomas Höfer - added unique id and changed range check for brightness and saturation
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Samuel Leisering - added GSon Type to FullLight, refactored content to {@link FullHueObject}
- */
-@NonNullByDefault
-public class FullLight extends FullHueObject {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, FullLight>>() {
-    }.getType();
-
-    public @Nullable Capabilities capabilities;
-    private @NonNullByDefault({}) State state;
-    private final long fadetime = 400; // milliseconds
-
-    /**
-     * Returns the current state of the light.
-     *
-     * @return current state
-     */
-    public State getState() {
-        return state;
-    }
-
-    public Duration getFadeTime() {
-        return Duration.ofMillis(fadetime);
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSchedule.java
deleted file mode 100644 (file)
index d714863..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.util.Date;
-
-/**
- * Detailed schedule information.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class FullSchedule extends Schedule {
-    private String description;
-    private ScheduleCommand command; // Not really appropriate for exposure
-    private Date time;
-
-    /**
-     * Returns the description of the schedule.
-     *
-     * @return description
-     */
-    public String getDescription() {
-        return description;
-    }
-
-    /**
-     * Returns the scheduled command.
-     *
-     * @return command
-     */
-    public ScheduleCommand getCommand() {
-        return command;
-    }
-
-    /**
-     * Returns the time for which the command is scheduled to be ran.
-     *
-     * @return scheduled time
-     */
-    public Date getTime() {
-        return time;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java
deleted file mode 100644 (file)
index 27ac4ff..0000000
+++ /dev/null
@@ -1,64 +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.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Detailed sensor information
- *
- * @author Samuel Leisering - Initial contribution
- * @author Christoph Weitkamp - Initial contribution
- */
-@NonNullByDefault
-public class FullSensor extends FullHueObject {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, FullSensor>>() {
-    }.getType();
-
-    public static final String STATE_LAST_UPDATED = "lastupdated";
-    public static final String STATE_BUTTON_EVENT = "buttonevent";
-    public static final String STATE_PRESENCE = "presence";
-    public static final String STATE_TEMPERATURE = "temperature";
-    public static final String STATE_LIGHT_LEVEL = "lightlevel";
-    public static final String STATE_DARK = "dark";
-    public static final String STATE_DAYLIGHT = "daylight";
-    public static final String STATE_STATUS = "status";
-    public static final String STATE_FLAG = "flag";
-
-    public static final String CONFIG_REACHABLE = "reachable";
-    public static final String CONFIG_BATTERY = "battery";
-    public static final String CONFIG_ON = "on";
-    public static final String CONFIG_LED_INDICATION = "ledindication";
-
-    public static final String CONFIG_PRESENCE_SENSITIVITY = "sensitivity";
-    public static final String CONFIG_PRESENCE_SENSITIVITY_MAX = "sensitivitymax";
-
-    public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_DARK = "tholddark";
-    public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_OFFSET = "tholdoffset";
-
-    private @NonNullByDefault({}) Map<String, Object> state;
-    private @NonNullByDefault({}) Map<String, Object> config;
-
-    public Map<String, Object> getState() {
-        return state;
-    }
-
-    public Map<String, Object> getConfig() {
-        return config;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Group.java
deleted file mode 100644 (file)
index 84bc63a..0000000
+++ /dev/null
@@ -1,90 +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;
-
-/**
- * Basic group information.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Laurent Garnier - field type added
- */
-public class Group {
-    private String id;
-    private String name;
-    private String type;
-
-    Group() {
-        this.id = "0";
-        this.name = "Lightset 0";
-        this.type = "LightGroup";
-    }
-
-    /**
-     * Test constructor
-     */
-    Group(String id, String name, String type) {
-        this.id = id;
-        this.name = name;
-        this.type = type;
-    }
-
-    void setName(String name) {
-        this.name = name;
-    }
-
-    void setId(String id) {
-        this.id = id;
-    }
-
-    void setType(String type) {
-        this.type = type;
-    }
-
-    /**
-     * Returns if the group can be modified.
-     * Currently only returns false for the all lights pseudo group.
-     *
-     * @return modifiability of group
-     */
-    public boolean isModifiable() {
-        return !"0".equals(id);
-    }
-
-    /**
-     * Returns the id of the group.
-     *
-     * @return id
-     */
-    public String getId() {
-        return id;
-    }
-
-    /**
-     * Returns the name of the group.
-     *
-     * @return name
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns the tyoe of the group.
-     *
-     * @return type
-     */
-    public String getType() {
-        return type;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HttpClient.java
deleted file mode 100644 (file)
index 06629ef..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedList;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@NonNullByDefault
-public class HttpClient {
-    private int timeout = 1000;
-    private final Logger logger = LoggerFactory.getLogger(HttpClient.class);
-    private final LinkedList<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;
-        }
-    }
-}
index f331488a89d80d46715038908eb6b84534bde5d3..b0416a584ef63b3d2be7a3273fd804d28dfd3bb6 100644 (file)
@@ -89,8 +89,6 @@ public class HueBindingConstants {
 
     // Bridge config properties
     public static final String HOST = "ipAddress";
-    public static final String PORT = "port";
-    public static final String PROTOCOL = "protocol";
     public static final String USER_NAME = "userName";
 
     // Thing configuration properties
@@ -102,4 +100,11 @@ public class HueBindingConstants {
     public static final String GROUP_ID = "groupId";
 
     public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
+
+    //
+    public static final String TEXT_OFFLINE_COMMUNICATION_ERROR = "@text/offline.communication-error";
+    public static final String TEXT_OFFLINE_CONFIGURATION_ERROR_INVALID_SSL_CERIFICATE = "@text/offline.conf-error-invalid-ssl-certificate";
+
+    // Config status messages
+    public static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration";
 }
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java
deleted file mode 100644 (file)
index e681c6f..0000000
+++ /dev/null
@@ -1,1132 +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.FileNotFoundException;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Type;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.stream.Collectors;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.HttpClient.Result;
-import org.openhab.binding.hue.internal.exceptions.ApiException;
-import org.openhab.binding.hue.internal.exceptions.DeviceOffException;
-import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException;
-import org.openhab.binding.hue.internal.exceptions.GroupTableFullException;
-import org.openhab.binding.hue.internal.exceptions.InvalidCommandException;
-import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
-import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-
-/**
- * Representation of a connection with a Hue bridge.
- *
- * @author Q42 - Initial contribution
- * @author Andre Fuechsel - search for lights with given serial number added
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - added cached config and API-Version
- * @author Laurent Garnier - change the return type of getGroups
- */
-@NonNullByDefault
-public class HueBridge {
-
-    private final Logger logger = LoggerFactory.getLogger(HueBridge.class);
-
-    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
-
-    private final String ip;
-    private final String baseUrl;
-    private @Nullable String username;
-
-    private final Gson gson = new GsonBuilder().setDateFormat(DATE_FORMAT).create();
-    private HttpClient http = new HttpClient();
-    private final ScheduledExecutorService scheduler;
-
-    @Nullable
-    private Config cachedConfig;
-
-    /**
-     * Connect with a bridge as a new user.
-     *
-     * @param ip ip address of bridge
-     * @param port port of bridge
-     * @param protocol protocol to connect to the bridge
-     */
-    public HueBridge(String ip, int port, String protocol, ScheduledExecutorService scheduler) {
-        this.ip = ip;
-        String baseUrl;
-        try {
-            URI uri = new URI(protocol, null, ip, port, "/api", null, null);
-            baseUrl = uri.toString();
-        } catch (URISyntaxException e) {
-            logger.error("exception during constructing URI protocol={}, host={}, port={}", protocol, ip, port, e);
-            baseUrl = protocol + "://" + ip + ":" + port + "/api";
-        }
-        this.baseUrl = baseUrl;
-        this.scheduler = scheduler;
-    }
-
-    /**
-     * Connect with a bridge as an existing user.
-     *
-     * The username is verified by requesting the list of lights.
-     * Use the ip only constructor and authenticate() function if
-     * you don't want to connect right now.
-     *
-     * @param ip ip address of bridge
-     * @param port port of bridge
-     * @param protocol protocol to connect to the bridge
-     * @param username username to authenticate with
-     */
-    public HueBridge(String ip, int port, String protocol, String username, ScheduledExecutorService scheduler)
-            throws IOException, ApiException {
-        this(ip, port, protocol, scheduler);
-        authenticate(username);
-    }
-
-    /**
-     * Test constructor
-     */
-    HueBridge(String ip, String baseUrl, String username, ScheduledExecutorService scheduler, HttpClient http) {
-        this.ip = ip;
-        this.baseUrl = baseUrl;
-        this.username = username;
-        this.scheduler = scheduler;
-        this.http = http;
-    }
-
-    /**
-     * Set the connect and read timeout for HTTP requests.
-     *
-     * @param timeout timeout in milliseconds or 0 for indefinitely
-     */
-    public void setTimeout(int timeout) {
-        http.setTimeout(timeout);
-    }
-
-    /**
-     * Returns the IP address of the bridge.
-     *
-     * @return ip address of bridge
-     */
-    public String getIPAddress() {
-        return ip;
-    }
-
-    public ApiVersion getVersion() throws IOException, ApiException {
-        Config c = getCachedConfig();
-        return ApiVersion.of(c.getApiVersion());
-    }
-
-    /**
-     * Returns a cached version of the basic {@link Config} mostly immutable configuration.
-     * This can be used to reduce load on the bridge.
-     *
-     * @return The {@link Config} of the Hue Bridge, loaded and cached lazily on the first call
-     * @throws IOException
-     * @throws ApiException
-     */
-    private Config getCachedConfig() throws IOException, ApiException {
-        if (this.cachedConfig == null) {
-            this.cachedConfig = getConfig();
-        }
-
-        return Objects.requireNonNull(this.cachedConfig);
-    }
-
-    /**
-     * Returns the username currently authenticated with or null if there isn't one.
-     *
-     * @return username or null
-     */
-    public @Nullable String getUsername() {
-        return username;
-    }
-
-    /**
-     * Returns if authentication was successful on the bridge.
-     *
-     * @return true if authenticated on the bridge, false otherwise
-     */
-    public boolean isAuthenticated() {
-        return getUsername() != null;
-    }
-
-    /**
-     * Returns a list of lights known to the bridge.
-     *
-     * @return list of known lights as {@link FullLight}s
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public List<FullLight> getFullLights() throws IOException, ApiException {
-        if (ApiVersionUtils.supportsFullLights(getVersion())) {
-            Type gsonType = FullLight.GSON_TYPE;
-            return getTypedLights(gsonType);
-        } else {
-            return getFullConfig().getLights();
-        }
-    }
-
-    /**
-     * Returns a list of lights known to the bridge.
-     *
-     * @return list of known lights
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public List<HueObject> getLights() throws IOException, ApiException {
-        Type gsonType = HueObject.GSON_TYPE;
-        return getTypedLights(gsonType);
-    }
-
-    private <T extends HueObject> List<T> getTypedLights(Type gsonType) throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("lights"));
-
-        handleErrors(result);
-
-        Map<String, T> lightMap = safeFromJson(result.getBody(), gsonType);
-        ArrayList<T> lightList = new ArrayList<>();
-
-        for (String id : lightMap.keySet()) {
-            @Nullable
-            T light = lightMap.get(id);
-            if (light != null) {
-                light.setId(id);
-                lightList.add(light);
-            }
-        }
-
-        return lightList;
-    }
-
-    /**
-     * Returns a list of sensors known to the bridge
-     *
-     * @return list of sensors
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public List<FullSensor> getSensors() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("sensors"));
-
-        handleErrors(result);
-
-        Map<String, FullSensor> sensorMap = safeFromJson(result.getBody(), FullSensor.GSON_TYPE);
-        ArrayList<FullSensor> sensorList = new ArrayList<>();
-
-        for (String id : sensorMap.keySet()) {
-            FullSensor sensor = sensorMap.get(id);
-            sensor.setId(id);
-            sensorList.add(sensor);
-        }
-
-        return sensorList;
-    }
-
-    /**
-     * Returns the last time a search for new lights was started.
-     * If a search is currently running, the current time will be
-     * returned or null if a search has never been started.
-     *
-     * @return last search time
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public @Nullable Date getLastSearch() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("lights/new"));
-
-        handleErrors(result);
-
-        String lastScan = safeFromJson(result.getBody(), NewLightsResponse.class).lastscan;
-
-        switch (lastScan) {
-            case "none":
-                return null;
-            case "active":
-                return new Date();
-            default:
-                try {
-                    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(lastScan);
-                } catch (ParseException e) {
-                    return null;
-                }
-        }
-    }
-
-    /**
-     * Start searching for new lights for 1 minute.
-     * A maximum amount of 15 new lights will be added.
-     *
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public void startSearch() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.post(getRelativeURL("lights"), "");
-
-        handleErrors(result);
-    }
-
-    /**
-     * Start searching for new lights with given serial numbers for 1 minute.
-     * A maximum amount of 15 new lights will be added.
-     *
-     * @param serialNumbers list of serial numbers
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public void startSearch(List<String> serialNumbers) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = gson.toJson(new SearchForLightsRequest(serialNumbers));
-        Result result = http.post(getRelativeURL("lights"), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Returns detailed information for the given light.
-     *
-     * @param light light
-     * @return detailed light information
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if a light with the given id doesn't exist
-     */
-    public FullHueObject getLight(HueObject light) throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("lights/" + enc(light.getId())));
-
-        handleErrors(result);
-
-        FullHueObject fullLight = safeFromJson(result.getBody(), FullLight.class);
-        fullLight.setId(light.getId());
-        return fullLight;
-    }
-
-    /**
-     * Changes the name of the light and returns the new name.
-     * A number will be appended to duplicate names, which may result in a new name exceeding 32 characters.
-     *
-     * @param light light
-     * @param name new name [0..32]
-     * @return new name
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified light no longer exists
-     */
-    public String setLightName(HueObject light, String name) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = gson.toJson(new SetAttributesRequest(name));
-        Result result = http.put(getRelativeURL("lights/" + enc(light.getId())), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        String lightName = (String) response.success.get("/lights/" + enc(light.getId()) + "/name");
-        if (lightName == null) {
-            throw new ApiException("Response didn't contain light name.");
-        }
-        return lightName;
-    }
-
-    /**
-     * Changes the state of a light.
-     *
-     * @param light light
-     * @param update changes to the state
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified light no longer exists
-     * @throws DeviceOffException thrown if the specified light is turned off
-     * @throws IOException if the bridge cannot be reached
-     */
-    public CompletableFuture<Result> setLightState(FullLight light, StateUpdate update) {
-        requireAuthentication();
-
-        String body = update.toJson();
-        return http.putAsync(getRelativeURL("lights/" + enc(light.getId()) + "/state"), body, update.getMessageDelay(),
-                scheduler);
-    }
-
-    /**
-     * Changes the state of a clip sensor.
-     *
-     * @param sensor sensor
-     * @param update changes to the state
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified sensor no longer exists
-     * @throws DeviceOffException thrown if the specified sensor is turned off
-     * @throws IOException if the bridge cannot be reached
-     */
-    public CompletableFuture<Result> setSensorState(FullSensor sensor, StateUpdate update) {
-        requireAuthentication();
-
-        String body = update.toJson();
-        return http.putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/state"), body,
-                update.getMessageDelay(), scheduler);
-    }
-
-    /**
-     * Changes the config of a sensor.
-     *
-     * @param sensor sensor
-     * @param update changes to the config
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified sensor no longer exists
-     * @throws IOException if the bridge cannot be reached
-     */
-    public CompletableFuture<Result> updateSensorConfig(FullSensor sensor, ConfigUpdate update) {
-        requireAuthentication();
-
-        String body = update.toJson();
-        return http.putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/config"), body,
-                update.getMessageDelay(), scheduler);
-    }
-
-    /**
-     * Returns a group object representing all lights.
-     *
-     * @return all lights pseudo group
-     */
-    public Group getAllGroup() {
-        return new Group();
-    }
-
-    /**
-     * Returns the list of groups, including the unmodifiable all lights group.
-     *
-     * @return list of groups
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public List<FullGroup> getGroups() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("groups"));
-
-        handleErrors(result);
-
-        Map<String, FullGroup> groupMap = safeFromJson(result.getBody(), FullGroup.GSON_TYPE);
-        ArrayList<FullGroup> groupList = new ArrayList<>();
-
-        if (groupMap.get("0") == null) {
-            // Group 0 is not returned, we create it as in fact it exists
-            try {
-                groupList.add(getGroup(getAllGroup()));
-            } catch (FileNotFoundException e) {
-                // We need a special exception handling here to further support deCONZ REST API. On deCONZ group "0" may
-                // not exist and the APIs will return a different HTTP status code if requesting a non existing group
-                // (Hue: 200, deCONZ: 404).
-                // see https://github.com/openhab/openhab-addons/issues/9175
-                logger.debug("Cannot find AllGroup with id \"0\" on Hue Bridge. Skipping it.");
-            }
-        }
-
-        groupMap.forEach((id, group) -> {
-            group.setId(id);
-            groupList.add(group);
-        });
-
-        return groupList;
-    }
-
-    /**
-     * Creates a new group and returns it.
-     * Due to API limitations, the name of the returned object
-     * will simply be "Group". The bridge will append a number to this
-     * name if it's a duplicate. To get the final name, call getGroup
-     * with the returned object.
-     *
-     * @param lights lights in group
-     * @return object representing new group
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws GroupTableFullException thrown if the group limit has been reached
-     */
-    public Group createGroup(List<HueObject> lights) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = gson.toJson(new SetAttributesRequest(lights));
-        Result result = http.post(getRelativeURL("groups"), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        Group group = new Group();
-        group.setName("Group");
-        group.setId(Util.quickMatch("^/groups/([0-9]+)$", (String) response.success.values().toArray()[0]));
-        return group;
-    }
-
-    /**
-     * Creates a new group and returns it.
-     * Due to API limitations, the name of the returned object
-     * will simply be the same as the name parameter. The bridge will
-     * append a number to the name if it's a duplicate. To get the final
-     * name, call getGroup with the returned object.
-     *
-     * @param name new group name
-     * @param lights lights in group
-     * @return object representing new group
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws GroupTableFullException thrown if the group limit has been reached
-     */
-    public Group createGroup(String name, List<HueObject> lights) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = gson.toJson(new SetAttributesRequest(name, lights));
-        Result result = http.post(getRelativeURL("groups"), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        Group group = new Group();
-        group.setName(name);
-        group.setId(Util.quickMatch("^/groups/([0-9]+)$", (String) response.success.values().toArray()[0]));
-        return group;
-    }
-
-    /**
-     * Returns detailed information for the given group.
-     *
-     * @param group group
-     * @return detailed group information
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if a group with the given id doesn't exist
-     */
-    public FullGroup getGroup(Group group) throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("groups/" + enc(group.getId())));
-
-        handleErrors(result);
-
-        FullGroup fullGroup = safeFromJson(result.getBody(), FullGroup.class);
-        fullGroup.setId(group.getId());
-        return fullGroup;
-    }
-
-    /**
-     * Changes the name of the group and returns the new name.
-     * A number will be appended to duplicate names, which may result in a new name exceeding 32 characters.
-     *
-     * @param group group
-     * @param name new name [0..32]
-     * @return new name
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified group no longer exists
-     */
-    public String setGroupName(Group group, String name) throws IOException, ApiException {
-        requireAuthentication();
-
-        if (!group.isModifiable()) {
-            throw new IllegalArgumentException("Group cannot be modified");
-        }
-
-        String body = gson.toJson(new SetAttributesRequest(name));
-        Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name");
-        if (groupName == null) {
-            throw new ApiException("Response didn't contain group name.");
-        }
-        return groupName;
-    }
-
-    /**
-     * Changes the lights in the group.
-     *
-     * @param group group
-     * @param lights new lights [1..16]
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified group no longer exists
-     */
-    public void setGroupLights(Group group, List<HueObject> lights) throws IOException, ApiException {
-        requireAuthentication();
-
-        if (!group.isModifiable()) {
-            throw new IllegalArgumentException("Group cannot be modified");
-        }
-
-        String body = gson.toJson(new SetAttributesRequest(lights));
-        Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Changes the name and the lights of a group and returns the new name.
-     *
-     * @param group group
-     * @param name new name [0..32]
-     * @param lights [1..16]
-     * @return new name
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified group no longer exists
-     */
-    public String setGroupAttributes(Group group, String name, List<HueObject> lights)
-            throws IOException, ApiException {
-        requireAuthentication();
-
-        if (!group.isModifiable()) {
-            throw new IllegalArgumentException("Group cannot be modified");
-        }
-
-        String body = gson.toJson(new SetAttributesRequest(name, lights));
-        Result result = http.put(getRelativeURL("groups/" + enc(group.getId())), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name");
-        if (groupName == null) {
-            throw new ApiException("Response didn't contain group name.");
-        }
-        return groupName;
-    }
-
-    /**
-     * Changes the state of a group.
-     *
-     * @param group group
-     * @param update changes to the state
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified group no longer exists
-     */
-    public CompletableFuture<Result> setGroupState(Group group, StateUpdate update) {
-        requireAuthentication();
-
-        String body = update.toJson();
-        return http.putAsync(getRelativeURL("groups/" + enc(group.getId()) + "/action"), body, update.getMessageDelay(),
-                scheduler);
-    }
-
-    /**
-     * Delete a group.
-     *
-     * @param group group
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified group no longer exists
-     */
-    public void deleteGroup(Group group) throws IOException, ApiException {
-        requireAuthentication();
-
-        if (!group.isModifiable()) {
-            throw new IllegalArgumentException("Group cannot be modified");
-        }
-
-        Result result = http.delete(getRelativeURL("groups/" + enc(group.getId())));
-
-        handleErrors(result);
-    }
-
-    /**
-     * Returns a list of schedules on the bridge.
-     *
-     * @return schedules
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public List<Schedule> getSchedules() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("schedules"));
-
-        handleErrors(result);
-
-        Map<String, Schedule> scheduleMap = safeFromJson(result.getBody(), Schedule.GSON_TYPE);
-
-        ArrayList<Schedule> scheduleList = new ArrayList<>();
-
-        for (String id : scheduleMap.keySet()) {
-            Schedule schedule = scheduleMap.get(id);
-            schedule.setId(id);
-            scheduleList.add(schedule);
-        }
-
-        return scheduleList;
-    }
-
-    /**
-     * Schedules a new command to be run at the specified time.
-     * To select the command for the new schedule, simply run it
-     * as you normally would in the callback. Instead of it running
-     * immediately, it will be scheduled to run at the specified time.
-     * It will automatically fail with an IOException, because there
-     * will be no response. Note that GET methods cannot be scheduled,
-     * so those will still run and return results immediately.
-     *
-     * @param time time to run command
-     * @param callback callback in which the command is specified
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid
-     */
-    public void createSchedule(Date time, ScheduleCallback callback) throws IOException, ApiException {
-        createSchedule(null, null, time, callback);
-    }
-
-    /**
-     * Schedules a new command to be run at the specified time.
-     * To select the command for the new schedule, simply run it
-     * as you normally would in the callback. Instead of it running
-     * immediately, it will be scheduled to run at the specified time.
-     * It will automatically fail with an IOException, because there
-     * will be no response. Note that GET methods cannot be scheduled,
-     * so those will still run and return results immediately.
-     *
-     * @param name name [0..32]
-     * @param time time to run command
-     * @param callback callback in which the command is specified
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid
-     */
-    public void createSchedule(String name, Date time, ScheduleCallback callback) throws IOException, ApiException {
-        createSchedule(name, null, time, callback);
-    }
-
-    /**
-     * Schedules a new command to be run at the specified time.
-     * To select the command for the new schedule, simply run it
-     * as you normally would in the callback. Instead of it running
-     * immediately, it will be scheduled to run at the specified time.
-     * It will automatically fail with an IOException, because there
-     * will be no response. Note that GET methods cannot be scheduled,
-     * so those will still run and return results immediately.
-     *
-     * @param name name [0..32]
-     * @param description description [0..64]
-     * @param time time to run command
-     * @param callback callback in which the command is specified
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid
-     */
-    public void createSchedule(@Nullable String name, @Nullable String description, Date time,
-            ScheduleCallback callback) throws IOException, ApiException {
-        requireAuthentication();
-
-        handleCommandCallback(callback);
-
-        String body = gson.toJson(new CreateScheduleRequest(name, description, scheduleCommand, time));
-        Result result = http.post(getRelativeURL("schedules"), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Returns detailed information for the given schedule.
-     *
-     * @param schedule schedule
-     * @return detailed schedule information
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified schedule no longer exists
-     */
-    public FullSchedule getSchedule(Schedule schedule) throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("schedules/" + enc(schedule.getId())));
-
-        handleErrors(result);
-
-        FullSchedule fullSchedule = safeFromJson(result.getBody(), FullSchedule.class);
-        fullSchedule.setId(schedule.getId());
-        return fullSchedule;
-    }
-
-    /**
-     * Changes a schedule.
-     *
-     * @param schedule schedule
-     * @param update changes
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the specified schedule no longer exists
-     */
-    public void setSchedule(Schedule schedule, ScheduleUpdate update) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = update.toJson();
-        Result result = http.put(getRelativeURL("schedules/" + enc(schedule.getId())), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Changes the command of a schedule.
-     *
-     * @param schedule schedule
-     * @param callback callback for new command
-     * @see #createSchedule(String, String, Date, ScheduleCallback)
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws InvalidCommandException thrown if the scheduled command is larger than 90 bytes or otherwise invalid
-     */
-    public void setScheduleCommand(Schedule schedule, ScheduleCallback callback) throws IOException, ApiException {
-        requireAuthentication();
-
-        handleCommandCallback(callback);
-
-        String body = gson.toJson(new CreateScheduleRequest(null, null, scheduleCommand, null));
-        Result result = http.put(getRelativeURL("schedules/" + enc(schedule.getId())), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Callback to specify a schedule command.
-     */
-    public interface ScheduleCallback {
-        /**
-         * Run the command you want to schedule as if you're executing
-         * it normally. The request will automatically fail to produce
-         * a result by throwing an IOException. Requests that only
-         * get data (e.g. getGroups) will still execute immediately,
-         * because those cannot be scheduled.
-         *
-         * @param bridge this bridge for convenience
-         * @throws IOException always thrown right after executing a command
-         */
-        public void onScheduleCommand(HueBridge bridge) throws IOException, ApiException;
-    }
-
-    private @Nullable ScheduleCommand scheduleCommand = null;
-
-    private @Nullable ScheduleCommand handleCommandCallback(ScheduleCallback callback) throws ApiException {
-        // Temporarily reroute requests to a fake HTTP client
-        HttpClient realClient = http;
-        http = new HttpClient() {
-            @Override
-            protected Result doNetwork(String address, String requestMethod, @Nullable String body) throws IOException {
-                // GET requests cannot be scheduled, so will continue working normally for convenience
-                if ("GET".equals(requestMethod)) {
-                    return super.doNetwork(address, requestMethod, body);
-                } else {
-                    String extractedAddress = Util.quickMatch("^http://[^/]+(.+)$", address);
-                    JsonElement commandBody = body == null ? null : JsonParser.parseString(body);
-                    scheduleCommand = new ScheduleCommand(extractedAddress, requestMethod, commandBody);
-
-                    // Return a fake result that will cause an exception and the callback to end
-                    return new Result("", 405);
-                }
-            }
-        };
-
-        // Run command
-        try {
-            scheduleCommand = null;
-            callback.onScheduleCommand(this);
-        } catch (IOException | RuntimeException e) {
-            // Command will automatically fail to return a result because of deferred execution
-        }
-        if (scheduleCommand != null && Util.stringSize(scheduleCommand.getBody()) > 90) {
-            throw new InvalidCommandException("Commmand body is larger than 90 bytes");
-        }
-
-        // Restore HTTP client
-        http = realClient;
-
-        return scheduleCommand;
-    }
-
-    /**
-     * Delete a schedule.
-     *
-     * @param schedule schedule
-     * @throws UnauthorizedException thrown if the user no longer exists
-     * @throws EntityNotAvailableException thrown if the schedule no longer exists
-     */
-    public void deleteSchedule(Schedule schedule) throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.delete(getRelativeURL("schedules/" + enc(schedule.getId())));
-
-        handleErrors(result);
-    }
-
-    /**
-     * Returns the list of scenes that are not recyclable.
-     *
-     * @return all scenes that can be activated
-     */
-    public List<Scene> getScenes() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("scenes"));
-        handleErrors(result);
-
-        Map<String, Scene> sceneMap = safeFromJson(result.getBody(), Scene.GSON_TYPE);
-        return sceneMap.entrySet().stream()//
-                .map(e -> {
-                    e.getValue().setId(e.getKey());
-                    return e.getValue();
-                })//
-                .filter(scene -> !scene.isRecycle())//
-                .sorted(Comparator.comparing(Scene::extractKeyForComparator))//
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Activate scene to all lights that belong to the scene.
-     *
-     * @param id the scene to be activated
-     * @throws IOException if the bridge cannot be reached
-     */
-    public CompletableFuture<Result> recallScene(String id) {
-        Group allLightsGroup = new Group();
-        return setGroupState(allLightsGroup, new StateUpdate().setScene(id));
-    }
-
-    /**
-     * Authenticate on the bridge as the specified user.
-     * This function verifies that the specified username is valid and will use
-     * it for subsequent requests if it is, otherwise an UnauthorizedException
-     * is thrown and the internal username is not changed.
-     *
-     * @param username username to authenticate
-     * @throws UnauthorizedException thrown if authentication failed
-     */
-    public void authenticate(String username) throws IOException, ApiException {
-        try {
-            this.username = username;
-            getLights();
-        } catch (Exception e) {
-            this.username = null;
-            throw new UnauthorizedException(e.toString());
-        }
-    }
-
-    /**
-     * Link with bridge using the specified username and device type.
-     *
-     * @param username username for new user [10..40]
-     * @param devicetype identifier of application [0..40]
-     * @throws LinkButtonException thrown if the bridge button has not been pressed
-     */
-    public void link(String username, String devicetype) throws IOException, ApiException {
-        this.username = link(new CreateUserRequest(username, devicetype));
-    }
-
-    /**
-     * Link with bridge using the specified device type. A random valid username will be generated by the bridge and
-     * returned.
-     *
-     * @return new random username generated by bridge
-     * @param devicetype identifier of application [0..40]
-     * @throws LinkButtonException thrown if the bridge button has not been pressed
-     */
-    public String link(String devicetype) throws IOException, ApiException {
-        return (this.username = link(new CreateUserRequest(devicetype)));
-    }
-
-    private String link(CreateUserRequest request) throws IOException, ApiException {
-        if (this.username != null) {
-            throw new IllegalStateException("already linked");
-        }
-
-        String body = gson.toJson(request);
-        Result result = http.post(getRelativeURL(""), body);
-
-        handleErrors(result);
-
-        List<SuccessResponse> entries = safeFromJson(result.getBody(), SuccessResponse.GSON_TYPE);
-        SuccessResponse response = entries.get(0);
-
-        String username = (String) response.success.get("username");
-        if (username == null) {
-            throw new ApiException("Response didn't contain username");
-        }
-        return username;
-    }
-
-    /**
-     * Returns bridge configuration.
-     *
-     * @see Config
-     * @return bridge configuration
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public Config getConfig() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL("config"));
-
-        handleErrors(result);
-
-        return safeFromJson(result.getBody(), Config.class);
-    }
-
-    /**
-     * Change the configuration of the bridge.
-     *
-     * @param update changes to the configuration
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public void setConfig(ConfigUpdate update) throws IOException, ApiException {
-        requireAuthentication();
-
-        String body = update.toJson();
-        Result result = http.put(getRelativeURL("config"), body);
-
-        handleErrors(result);
-    }
-
-    /**
-     * Unlink the current user from the bridge.
-     *
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public void unlink() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.delete(getRelativeURL("config/whitelist/" + enc(username)));
-
-        handleErrors(result);
-    }
-
-    /**
-     * Returns the entire bridge configuration.
-     * This request is rather resource intensive for the bridge,
-     * don't use it more often than necessary. Prefer using requests for
-     * specific information your app needs.
-     *
-     * @return full bridge configuration
-     * @throws UnauthorizedException thrown if the user no longer exists
-     */
-    public FullConfig getFullConfig() throws IOException, ApiException {
-        requireAuthentication();
-
-        Result result = http.get(getRelativeURL(""));
-
-        handleErrors(result);
-
-        FullConfig fullConfig = gson.fromJson(result.getBody(), FullConfig.class);
-        return Objects.requireNonNull(fullConfig);
-    }
-
-    // Used as assert in requests that require authentication
-    private void requireAuthentication() {
-        if (this.username == null) {
-            throw new IllegalStateException("linking is required before interacting with the bridge");
-        }
-    }
-
-    // Methods that convert gson exceptions into ApiExceptions
-    private <T> T safeFromJson(String json, Type typeOfT) throws ApiException {
-        try {
-            return gson.fromJson(json, typeOfT);
-        } catch (JsonParseException e) {
-            throw new ApiException("API returned unexpected result: " + e.getMessage());
-        }
-    }
-
-    private <T> T safeFromJson(String json, Class<T> classOfT) throws ApiException {
-        try {
-            return gson.fromJson(json, classOfT);
-        } catch (JsonParseException e) {
-            throw new ApiException("API returned unexpected result: " + e.getMessage());
-        }
-    }
-
-    // Used as assert in all requests to elegantly catch common errors
-    public void handleErrors(Result result) throws IOException, ApiException {
-        if (result.getResponseCode() != 200) {
-            throw new IOException();
-        } else {
-            try {
-                List<ErrorResponse> errors = gson.fromJson(result.getBody(), ErrorResponse.GSON_TYPE);
-                if (errors == null) {
-                    return;
-                }
-
-                for (ErrorResponse error : errors) {
-                    if (error.getType() == null) {
-                        continue;
-                    }
-
-                    switch (error.getType()) {
-                        case 1:
-                            username = null;
-                            throw new UnauthorizedException(error.getDescription());
-                        case 3:
-                            throw new EntityNotAvailableException(error.getDescription());
-                        case 7:
-                            throw new InvalidCommandException(error.getDescription());
-                        case 101:
-                            throw new LinkButtonException(error.getDescription());
-                        case 201:
-                            throw new DeviceOffException(error.getDescription());
-                        case 301:
-                            throw new GroupTableFullException(error.getDescription());
-                        default:
-                            throw new ApiException(error.getDescription());
-                    }
-                }
-            } catch (JsonParseException e) {
-                // Not an error
-            }
-        }
-    }
-
-    // UTF-8 URL encode
-    private String enc(@Nullable String str) {
-        if (str != null) {
-            try {
-                return URLEncoder.encode(str, StandardCharsets.UTF_8.name());
-            } catch (UnsupportedEncodingException e) {
-                throw new UnsupportedOperationException("UTF-8 not supported");
-            }
-        } else {
-            return "";
-        }
-    }
-
-    private String getRelativeURL(String path) {
-        String relativeUrl = baseUrl;
-        if (username != null) {
-            relativeUrl += "/" + enc(username);
-        }
-        return path.isEmpty() ? relativeUrl : relativeUrl + "/" + path;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueConfigStatusMessage.java
deleted file mode 100644 (file)
index 78e7f5f..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import org.openhab.core.config.core.status.ConfigStatusMessage;
-
-/**
- * The {@link HueConfigStatusMessage} defines
- * the keys to be used for {@link ConfigStatusMessage}s.
- *
- * @author Alexander Kostadinov - Initial contribution
- * @author Kai Kreuzer - Changed from enum to interface
- *
- */
-public interface HueConfigStatusMessage {
-
-    static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration";
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java
deleted file mode 100644 (file)
index 96eb38c..0000000
+++ /dev/null
@@ -1,58 +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.Map;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Basic hue object information.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Samuel Leisering - introduced Sensor support, renamed supertype to HueObject
- */
-public class HueObject {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, HueObject>>() {
-    }.getType();
-
-    private String id;
-    private String name;
-
-    HueObject() {
-    }
-
-    void setId(String id) {
-        this.id = id;
-    }
-
-    /**
-     * Returns the id of the light.
-     *
-     * @return id
-     */
-    public String getId() {
-        return id;
-    }
-
-    /**
-     * Returns the name of the light.
-     *
-     * @return name
-     */
-    public String getName() {
-        return name;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java
deleted file mode 100644 (file)
index 27f584e..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
-import org.openhab.binding.hue.internal.handler.HueGroupHandler;
-import org.openhab.binding.hue.internal.handler.HueLightHandler;
-import org.openhab.binding.hue.internal.handler.HueStateDescriptionProvider;
-import org.openhab.binding.hue.internal.handler.sensors.ClipHandler;
-import org.openhab.binding.hue.internal.handler.sensors.DimmerSwitchHandler;
-import org.openhab.binding.hue.internal.handler.sensors.GeofencePresenceHandler;
-import org.openhab.binding.hue.internal.handler.sensors.LightLevelHandler;
-import org.openhab.binding.hue.internal.handler.sensors.PresenceHandler;
-import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
-import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.BaseThingHandlerFactory;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * {@link HueThingHandlerFactory} is a factory for {@link HueBridgeHandler}s.
- *
- * @author Dennis Nobel - Initial contribution of hue binding
- * @author Kai Kreuzer - added supportsThingType method
- * @author Andre Fuechsel - implemented to use one discovery service per bridge
- * @author Samuel Leisering - Added support for sensor API
- * @author Christoph Weitkamp - Added support for sensor API
- * @author Laurent Garnier - Added support for groups
- */
-@NonNullByDefault
-@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
-public class HueThingHandlerFactory extends BaseThingHandlerFactory {
-
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream
-            .of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(),
-                    DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(),
-                    PresenceHandler.SUPPORTED_THING_TYPES.stream(),
-                    GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
-                    TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
-                    ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
-            .flatMap(i -> i).collect(Collectors.toSet()));
-
-    private final HueStateDescriptionProvider stateDescriptionProvider;
-    private final TranslationProvider i18nProvider;
-    private final LocaleProvider localeProvider;
-
-    @Activate
-    public HueThingHandlerFactory(final @Reference HueStateDescriptionProvider stateDescriptionProvider,
-            final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) {
-        this.stateDescriptionProvider = stateDescriptionProvider;
-        this.i18nProvider = i18nProvider;
-        this.localeProvider = localeProvider;
-    }
-
-    @Override
-    public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
-            @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
-        if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
-            return super.createThing(thingTypeUID, configuration, thingUID, null);
-        } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
-            ThingUID hueLightUID = getLightUID(thingTypeUID, thingUID, configuration, bridgeUID);
-            return super.createThing(thingTypeUID, configuration, hueLightUID, bridgeUID);
-        } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || PresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || TemperatureHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || LightLevelHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
-                || ClipHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
-            ThingUID hueSensorUID = getSensorUID(thingTypeUID, thingUID, configuration, bridgeUID);
-            return super.createThing(thingTypeUID, configuration, hueSensorUID, bridgeUID);
-        } else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
-            ThingUID hueGroupUID = getGroupUID(thingTypeUID, thingUID, configuration, bridgeUID);
-            return super.createThing(thingTypeUID, configuration, hueGroupUID, bridgeUID);
-        }
-
-        throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the hue binding.");
-    }
-
-    @Override
-    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return SUPPORTED_THING_TYPES.contains(thingTypeUID);
-    }
-
-    private ThingUID getLightUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
-            @Nullable ThingUID bridgeUID) {
-        if (thingUID != null) {
-            return thingUID;
-        } else {
-            return getThingUID(thingTypeUID, configuration.get(LIGHT_ID).toString(), bridgeUID);
-        }
-    }
-
-    private ThingUID getSensorUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
-            @Nullable ThingUID bridgeUID) {
-        if (thingUID != null) {
-            return thingUID;
-        } else {
-            return getThingUID(thingTypeUID, configuration.get(SENSOR_ID).toString(), bridgeUID);
-        }
-    }
-
-    private ThingUID getGroupUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
-            @Nullable ThingUID bridgeUID) {
-        if (thingUID != null) {
-            return thingUID;
-        } else {
-            return getThingUID(thingTypeUID, configuration.get(GROUP_ID).toString(), bridgeUID);
-        }
-    }
-
-    private ThingUID getThingUID(ThingTypeUID thingTypeUID, String id, @Nullable ThingUID bridgeUID) {
-        if (bridgeUID != null) {
-            return new ThingUID(thingTypeUID, id, bridgeUID.getId());
-        } else {
-            return new ThingUID(thingTypeUID, id);
-        }
-    }
-
-    @Override
-    protected @Nullable ThingHandler createHandler(Thing thing) {
-        if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new HueBridgeHandler((Bridge) thing, stateDescriptionProvider, i18nProvider, localeProvider);
-        } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new HueLightHandler(thing, stateDescriptionProvider);
-        } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new DimmerSwitchHandler(thing);
-        } else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new TapSwitchHandler(thing);
-        } else if (PresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new PresenceHandler(thing);
-        } else if (GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new GeofencePresenceHandler(thing);
-        } else if (TemperatureHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new TemperatureHandler(thing);
-        } else if (LightLevelHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new LightLevelHandler(thing);
-        } else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new ClipHandler(thing);
-        } else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
-            return new HueGroupHandler(thing, stateDescriptionProvider);
-        } else {
-            return null;
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/LightLevelConfigUpdate.java
deleted file mode 100644 (file)
index 586a7d5..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import static org.openhab.binding.hue.internal.FullSensor.*;
-
-/**
- * Updates the configuration of a light level sensor
- *
- * @author Samuel Leisering - Initial contribution
- * @author Christoph Weitkamp - Initial contribution
- */
-public class LightLevelConfigUpdate extends SensorConfigUpdate {
-    /**
-     *
-     * @param onOff
-     */
-    public void setLedIndication(boolean onOff) {
-        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
-    }
-
-    /**
-     *
-     * @param threshold
-     */
-    public void setThresholdDark(int threshold) {
-        commands.add(new Command(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK, threshold));
-    }
-
-    /**
-     *
-     * @param offset
-     */
-    public void setThresholdOffset(int offset) {
-        commands.add(new Command(CONFIG_LIGHT_LEVEL_THRESHOLD_OFFSET, offset));
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/NewLightsResponse.java
deleted file mode 100644 (file)
index 047c038..0000000
+++ /dev/null
@@ -1,22 +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 Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-class NewLightsResponse {
-    public String lastscan;
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PortalDiscoveryResult.java
deleted file mode 100644 (file)
index 1622061..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.lang.reflect.Type;
-import java.util.List;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-class PortalDiscoveryResult {
-    public static final Type GSON_TYPE = new TypeToken<List<PortalDiscoveryResult>>() {
-    }.getType();
-
-    private String internalipaddress;
-
-    public String getIPAddress() {
-        return internalipaddress;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/PresenceConfigUpdate.java
deleted file mode 100644 (file)
index 51d86bd..0000000
+++ /dev/null
@@ -1,39 +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 static org.openhab.binding.hue.internal.FullSensor.*;
-
-/**
- * Updates the configuration of a presence sensor
- *
- * @author Samuel Leisering - Initial contribution
- * @author Christoph Weitkamp - Initial contribution
- */
-public class PresenceConfigUpdate extends SensorConfigUpdate {
-    /**
-     *
-     * @param onOff
-     */
-    public void setLedIndication(boolean onOff) {
-        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
-    }
-
-    /**
-     *
-     * @param sensitivity
-     */
-    public void setSensitivity(int sensitivity) {
-        commands.add(new Command(CONFIG_PRESENCE_SENSITIVITY, sensitivity));
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Scene.java
deleted file mode 100644 (file)
index c252bc1..0000000
+++ /dev/null
@@ -1,161 +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 java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.types.StateOption;
-
-import com.google.gson.annotations.SerializedName;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Basic scene information.
- *
- * @author Hengrui Jiang - Initial contribution
- */
-@NonNullByDefault
-public class Scene {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, Scene>>() {
-    }.getType();
-
-    private @NonNullByDefault({}) String id;
-    private @NonNullByDefault({}) String name;
-    @SerializedName("lights")
-    private @NonNullByDefault({}) List<String> lightIds;
-    @SerializedName("group")
-    private @Nullable String groupId;
-    private boolean recycle;
-
-    /**
-     * Default constructor for GSon.
-     */
-    public Scene() {
-        super();
-    }
-
-    /**
-     * Test constructor
-     */
-    Scene(String id, String name, @Nullable String groupId, List<String> lightIds, boolean recycle) {
-        this.id = id;
-        this.name = name;
-        this.groupId = groupId;
-        this.lightIds = lightIds;
-        this.recycle = recycle;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    void setId(String id) {
-        this.id = id;
-    }
-
-    /**
-     * Returns the human readable name of the scene. If the name is omitted upon creation, this
-     * defaults to the ID.
-     *
-     * @return human readable name of the scene
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns the list of lights that the scene applies to. For group scenes, this list should be identical to the list
-     * of all lights that are in the group.
-     *
-     * @return list of lights that the scene applies to
-     */
-    public List<String> getLightIds() {
-        return lightIds;
-    }
-
-    /**
-     * Returns the group that the scene belongs to. This field is optional for scenes that applies to a specific list of
-     * lights instead of a group.
-     *
-     * @return the group that the scene belongs to
-     */
-    public @Nullable String getGroupId() {
-        return groupId;
-    }
-
-    /**
-     * Indicates if the scene can be recycled by the bridge. A recyclable scene is not able to be activated.
-     *
-     * @return whether the scene can be recycled
-     */
-    public boolean isRecycle() {
-        return recycle;
-    }
-
-    /**
-     * Creates a {@link StateOption} to display this scene, including the group that it belongs to.
-     * <p>
-     * The display name is built with the following pattern:
-     * <ol>
-     * <li>Human readable name of the scene if set. Otherwise, the ID is displayed</li>
-     * <li>Group for which the scene is defined</li>
-     * </ol>
-     */
-    public StateOption toStateOption(Map<String, String> groupNames) {
-        StringBuilder stateOptionLabel = new StringBuilder(name);
-        if (groupId != null && groupNames.containsKey(groupId)) {
-            stateOptionLabel.append(" (").append(groupNames.get(groupId)).append(")");
-        }
-
-        return new StateOption(id, stateOptionLabel.toString());
-    }
-
-    /**
-     * Creates a {@link StateOption} to display this scene.
-     */
-    public StateOption toStateOption() {
-        return new StateOption(id, name);
-    }
-
-    /**
-     * Returns whether the scene is applicable to the given group.
-     * <p>
-     * According to the hue API, a scene is applicable to a group if either
-     * <ol>
-     * <li>The scene is defined for the group</li>
-     * <li>All lights of the scene also belong to the group</li>
-     * </ol>
-     */
-    public boolean isApplicableTo(FullGroup group) {
-        if (getGroupId() == null) {
-            return getLightIds().stream().allMatch(id -> group.getLightIds().contains(id));
-        } else {
-            String groupId = getGroupId();
-            return groupId != null ? group.getId().contentEquals(groupId) : false;
-        }
-    }
-
-    public String extractKeyForComparator() {
-        return (groupId != null ? groupId : "") + "#" + name;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("{Scene name: %s; id: %s; lightIds: %s; groupId: %s; recycle: %s}", name, id, lightIds,
-                groupId, recycle);
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Schedule.java
deleted file mode 100644 (file)
index 2299085..0000000
+++ /dev/null
@@ -1,44 +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.Map;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Basic schedule information.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class Schedule {
-    public static final Type GSON_TYPE = new TypeToken<Map<String, Schedule>>() {
-    }.getType();
-
-    private String id;
-    private String name;
-
-    void setId(String id) {
-        this.id = id;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleCommand.java
deleted file mode 100644 (file)
index 7809968..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import com.google.gson.JsonElement;
-
-/**
- * Information about a scheduled command.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class ScheduleCommand {
-    private String address;
-    private String method;
-    private JsonElement body;
-
-    ScheduleCommand(String address, String method, JsonElement body) {
-        this.address = address;
-        this.method = method;
-        this.body = body;
-    }
-
-    /**
-     * Returns the relative request url.
-     *
-     * @return request url
-     */
-    public String getAddress() {
-        return address;
-    }
-
-    /**
-     * Returns the request method.
-     * Can be GET, PUT, POST or DELETE.
-     *
-     * @return request method
-     */
-    public String getMethod() {
-        return method;
-    }
-
-    /**
-     * Returns the request body.
-     *
-     * @return request body
-     */
-    public String getBody() {
-        return body.toString();
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/ScheduleUpdate.java
deleted file mode 100644 (file)
index f0de942..0000000
+++ /dev/null
@@ -1,66 +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;
-
-/**
- * Collection of updates to a schedule.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - refactor configuration updates
- */
-public class ScheduleUpdate extends ConfigUpdate {
-
-    /**
-     * Set the name of the schedule.
-     *
-     * @param name new name
-     * @return this object for chaining calls
-     */
-    public ScheduleUpdate setName(String name) {
-        if (Util.stringSize(name) > 32) {
-            throw new IllegalArgumentException("Schedule name can be at most 32 characters long");
-        }
-
-        commands.add(new Command("name", name));
-        return this;
-    }
-
-    /**
-     * Set the description of the schedule.
-     *
-     * @param description new description
-     * @return this object for chaining calls
-     */
-    public ScheduleUpdate setDescription(String description) {
-        if (Util.stringSize(description) > 64) {
-            throw new IllegalArgumentException("Schedule description can be at most 64 characters long");
-        }
-
-        commands.add(new Command("description", description));
-        return this;
-    }
-
-    /**
-     * Set the time of the schedule.
-     *
-     * @param time new time
-     * @return this object for chaining calls
-     */
-    public ScheduleUpdate setTime(Date time) {
-        commands.add(new Command("time", time));
-        return this;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SearchForLightsRequest.java
deleted file mode 100644 (file)
index 04c2a73..0000000
+++ /dev/null
@@ -1,35 +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.List;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Andre Fuechsel - search for lights with given serial number added
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@SuppressWarnings("unused")
-class SearchForLightsRequest {
-    private List<String> deviceid;
-
-    public SearchForLightsRequest(List<String> deviceid) {
-        if (deviceid != null && (deviceid.isEmpty() || deviceid.size() > 16)) {
-            throw new IllegalArgumentException("Group cannot be empty and cannot have more than 16 lights");
-        }
-        if (deviceid != null) {
-            this.deviceid = deviceid;
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SensorConfigUpdate.java
deleted file mode 100644 (file)
index bb7153a..0000000
+++ /dev/null
@@ -1,30 +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 static org.openhab.binding.hue.internal.FullSensor.CONFIG_ON;
-
-/**
- * Collection of updates to the sensor configuration.
- *
- * @author Christoph Weitkamp - Initial contribution
- */
-public class SensorConfigUpdate extends ConfigUpdate {
-    /**
-     *
-     * @param onOff
-     */
-    public void setOn(boolean onOff) {
-        commands.add(new Command(CONFIG_ON, onOff));
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SetAttributesRequest.java
deleted file mode 100644 (file)
index bebe904..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import java.util.List;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@SuppressWarnings("unused")
-class SetAttributesRequest {
-    private String name;
-    private List<String> lights;
-
-    public SetAttributesRequest(String name) {
-        this(name, null);
-    }
-
-    public SetAttributesRequest(List<HueObject> lights) {
-        this(null, lights);
-    }
-
-    public SetAttributesRequest(String name, List<HueObject> lights) {
-        if (name != null && Util.stringSize(name) > 32) {
-            throw new IllegalArgumentException("Name can be at most 32 characters long");
-        } else if (lights != null && (lights.isEmpty() || lights.size() > 16)) {
-            throw new IllegalArgumentException("Group cannot be empty and cannot have more than 16 lights");
-        }
-
-        this.name = name;
-        if (lights != null) {
-            this.lights = Util.lightsToIds(lights);
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SoftwareUpdate.java
deleted file mode 100644 (file)
index a72ce1c..0000000
+++ /dev/null
@@ -1,65 +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;
-
-/**
- * Details of a bridge firmware update.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class SoftwareUpdate {
-    private int updatestate;
-    private String url;
-    private String text;
-    private boolean notify;
-
-    /**
-     * Returns the state of the update.
-     *
-     * <p>
-     * Actual meaning currently undocumented
-     *
-     * @return state of update
-     */
-    public int getUpdateState() {
-        return updatestate;
-    }
-
-    /**
-     * Returns the url of the changelog.
-     *
-     * @return changelog url
-     */
-    public String getUrl() {
-        return url;
-    }
-
-    /**
-     * Returns a description of the update.
-     *
-     * @return update description
-     */
-    public String getText() {
-        return text;
-    }
-
-    /**
-     * Returns if there will be a notification about this update.
-     *
-     * @return true for notification, false otherwise
-     */
-    public boolean isNotified() {
-        return notify;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/State.java
deleted file mode 100644 (file)
index e639d2f..0000000
+++ /dev/null
@@ -1,292 +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.Arrays;
-
-/**
- * Current state of light.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- * @author Laurent Garnier - add few methods to update the object
- */
-public class State {
-    private boolean on;
-    int bri;
-    int hue;
-    int sat;
-    private float[] xy;
-    int ct;
-    private String alert;
-    private String effect;
-    String colormode;
-    private boolean reachable;
-
-    public State() {
-    }
-
-    /**
-     * Color modes of a light.
-     */
-    public enum ColorMode {
-        /**
-         * CIE color space coordinates
-         */
-        XY,
-
-        /**
-         * Hue and saturation
-         */
-        HS,
-
-        /**
-         * Color temperature in mired
-         */
-        CT
-    }
-
-    /**
-     * Alert modes of a light.
-     */
-    public enum AlertMode {
-        /**
-         * Light is not performing alert effect
-         */
-        NONE,
-
-        /**
-         * Light is performing one breathe cycle
-         */
-        SELECT,
-
-        /**
-         * Light is performing breathe cycles for 30 seconds (unless cancelled)
-         */
-        LSELECT
-    }
-
-    /**
-     * Effects possible for a light.
-     */
-    public enum Effect {
-        /**
-         * No effect
-         */
-        NONE,
-
-        /**
-         * Cycle through all hues with current saturation and brightness
-         */
-        COLORLOOP
-    }
-
-    /**
-     * Returns the on state.
-     *
-     * @return true if the light is on, false if it isn't
-     */
-    public boolean isOn() {
-        return on;
-    }
-
-    public void setOn(boolean on) {
-        this.on = on;
-    }
-
-    /**
-     * Returns the brightness.
-     *
-     * @return brightness
-     */
-    public int getBrightness() {
-        return bri;
-    }
-
-    public void setBri(int bri) {
-        this.bri = bri;
-    }
-
-    /**
-     * Returns the hue.
-     *
-     * @return hue
-     */
-    public int getHue() {
-        return hue;
-    }
-
-    public void setHue(int hue) {
-        this.hue = hue;
-    }
-
-    /**
-     * Returns the saturation.
-     *
-     * @return saturation
-     */
-    public int getSaturation() {
-        return sat;
-    }
-
-    public void setSaturation(int sat) {
-        this.sat = sat;
-    }
-
-    /**
-     * Returns the coordinates in CIE color space.
-     *
-     * @return cie color spaces coordinates
-     */
-    public float[] getXY() {
-        return xy;
-    }
-
-    public void setXY(float[] xy) {
-        this.xy = xy;
-    }
-
-    /**
-     * Returns the color temperature.
-     *
-     * @return color temperature
-     */
-    public int getColorTemperature() {
-        return ct;
-    }
-
-    public void setColorTemperature(int ct) {
-        this.ct = ct;
-    }
-
-    /**
-     * Returns the last alert mode set.
-     * Future firmware updates may change this to actually report the current alert mode.
-     *
-     * @return last alert mode
-     */
-    public AlertMode getAlertMode() {
-        if (alert == null) {
-            return null;
-        }
-        return AlertMode.valueOf(alert.toUpperCase());
-    }
-
-    /**
-     * Returns the current color mode.
-     *
-     * @return current color mode
-     */
-    public ColorMode getColorMode() {
-        if (colormode == null) {
-            return null;
-        }
-        return ColorMode.valueOf(colormode.toUpperCase());
-    }
-
-    public void setColormode(ColorMode colormode) {
-        this.colormode = colormode.name();
-    }
-
-    /**
-     * Returns the current active effect.
-     *
-     * @return current active effect
-     */
-    public Effect getEffect() {
-        if (effect == null) {
-            return null;
-        }
-        return Effect.valueOf(effect.toUpperCase());
-    }
-
-    /**
-     * Returns reachability.
-     *
-     * @return true if reachable, false if it isn't
-     */
-    public boolean isReachable() {
-        return reachable;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((alert == null) ? 0 : alert.hashCode());
-        result = prime * result + bri;
-        result = prime * result + ((colormode == null) ? 0 : colormode.hashCode());
-        result = prime * result + ct;
-        result = prime * result + ((effect == null) ? 0 : effect.hashCode());
-        result = prime * result + hue;
-        result = prime * result + (on ? 1231 : 1237);
-        result = prime * result + (reachable ? 1231 : 1237);
-        result = prime * result + sat;
-        result = prime * result + Arrays.hashCode(xy);
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        State other = (State) obj;
-        if (alert == null) {
-            if (other.alert != null) {
-                return false;
-            }
-        } else if (!alert.equals(other.alert)) {
-            return false;
-        }
-        if (bri != other.bri) {
-            return false;
-        }
-        if (colormode == null) {
-            if (other.colormode != null) {
-                return false;
-            }
-        } else if (!colormode.equals(other.colormode)) {
-            return false;
-        }
-        if (ct != other.ct) {
-            return false;
-        }
-        if (effect == null) {
-            if (other.effect != null) {
-                return false;
-            }
-        } else if (!effect.equals(other.effect)) {
-            return false;
-        }
-        if (hue != other.hue) {
-            return false;
-        }
-        if (on != other.on) {
-            return false;
-        }
-        if (reachable != other.reachable) {
-            return false;
-        }
-        if (sat != other.sat) {
-            return false;
-        }
-        return Arrays.equals(xy, other.xy);
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java
deleted file mode 100644 (file)
index 50331ba..0000000
+++ /dev/null
@@ -1,234 +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.binding.hue.internal.State.AlertMode;
-import org.openhab.binding.hue.internal.State.Effect;
-import org.openhab.binding.hue.internal.dto.ColorTemperature;
-
-/**
- * Collection of updates to the state of a light.
- *
- * @author Q42 - Initial contribution
- * @author Thomas Höfer - added unique id and changed range check for brightness and saturation
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
- * @author Samuel Leisering - refactor configuration updates
- */
-public class StateUpdate extends ConfigUpdate {
-
-    private Integer colorTemperature;
-    private Integer brightness;
-
-    /**
-     * Turn light on.
-     *
-     * @return this object for chaining calls
-     */
-    public StateUpdate turnOn() {
-        return setOn(true);
-    }
-
-    /**
-     * Turn light off.
-     *
-     * @return this object for chaining calls
-     */
-    public StateUpdate turnOff() {
-        return setOn(false);
-    }
-
-    /**
-     * Turn light on or off.
-     *
-     * @param on on if true, off otherwise
-     * @return this object for chaining calls
-     */
-    public StateUpdate setOn(boolean on) {
-        commands.add(new Command("on", on));
-        return this;
-    }
-
-    /**
-     * Set brightness of light.
-     * Brightness 0 is not the same as off.
-     *
-     * @param brightness brightness [1..254]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setBrightness(int brightness) {
-        if (brightness < 1 || brightness > 254) {
-            throw new IllegalArgumentException("Brightness out of range");
-        }
-
-        commands.add(new Command("bri", brightness));
-        this.brightness = brightness;
-        return this;
-    }
-
-    public Integer getBrightness() {
-        return this.brightness;
-    }
-
-    /**
-     * Switch to HS color mode and set hue.
-     *
-     * @param hue hue [0..65535]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setHue(int hue) {
-        if (hue < 0 || hue > 65535) {
-            throw new IllegalArgumentException("Hue out of range");
-        }
-
-        commands.add(new Command("hue", hue));
-        return this;
-    }
-
-    /**
-     * Switch to HS color mode and set saturation.
-     *
-     * @param saturation saturation [0..254]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setSat(int saturation) {
-        if (saturation < 0 || saturation > 254) {
-            throw new IllegalArgumentException("Saturation out of range");
-        }
-
-        commands.add(new Command("sat", saturation));
-        return this;
-    }
-
-    /**
-     * Switch to XY color mode and set CIE color space coordinates.
-     *
-     * @param x x coordinate [0..1]
-     * @param y y coordinate [0..1]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setXY(float x, float y) {
-        return setXY(new float[] { x, y });
-    }
-
-    /**
-     * Switch to XY color mode and set CIE color space coordinates.
-     *
-     * @param xy x and y coordinates [0..1, 0..1]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setXY(float[] xy) {
-        if (xy.length != 2) {
-            throw new IllegalArgumentException("Invalid coordinate array given");
-        } else if (xy[0] < 0.0f || xy[0] > 1.0f || xy[1] < 0.0f || xy[1] > 1.0f) {
-            throw new IllegalArgumentException("X and/or Y coordinate(s) out of bounds");
-        }
-
-        commands.add(new Command("xy", xy));
-        return this;
-    }
-
-    /**
-     * Switch to CT color mode and set color temperature in mired.
-     *
-     * @param colorTemperature color temperature
-     * @return this object for chaining calls
-     */
-    public StateUpdate setColorTemperature(int colorTemperature, ColorTemperature capabilities) {
-        if (colorTemperature < capabilities.min || colorTemperature > capabilities.max) {
-            throw new IllegalArgumentException(String.format("Color temperature %d is out of range [%d..%d]",
-                    colorTemperature, capabilities.min, capabilities.max));
-        }
-
-        commands.add(new Command("ct", colorTemperature));
-        this.colorTemperature = colorTemperature;
-        return this;
-    }
-
-    public Integer getColorTemperature() {
-        return this.colorTemperature;
-    }
-
-    /**
-     * Set the alert mode.
-     *
-     * @see AlertMode
-     * @param mode alert mode
-     * @return this object for chaining calls
-     */
-    public StateUpdate setAlert(AlertMode mode) {
-        commands.add(new Command("alert", mode.toString().toLowerCase()));
-        return this;
-    }
-
-    /**
-     * Set the current effect.
-     *
-     * @see Effect
-     * @param effect effect
-     * @return this object for chaining calls
-     */
-    public StateUpdate setEffect(Effect effect) {
-        commands.add(new Command("effect", effect.toString().toLowerCase()));
-        return this;
-    }
-
-    /**
-     * Set the transition time from the current state to the new state.
-     * Time is accurate to 100 milliseconds.
-     *
-     * @param timeMillis time in milliseconds [0..6553600]
-     * @return this object for chaining calls
-     */
-    public StateUpdate setTransitionTime(long timeMillis) {
-        if (timeMillis < 0 || timeMillis > 6553600) {
-            throw new IllegalArgumentException("Transition time out of range");
-        }
-
-        commands.add(new Command("transitiontime", timeMillis / 100));
-        return this;
-    }
-
-    /**
-     * Turn sensor flag on or off.
-     *
-     * @param flag on if true, off otherwise
-     * @return this object for chaining calls
-     */
-
-    public StateUpdate setFlag(boolean flag) {
-        commands.add(new Command("flag", flag));
-        return this;
-    }
-
-    /**
-     * Set status of sensor.
-     *
-     * @param status status
-     * @return this object for chaining calls
-     */
-    public StateUpdate setStatus(int status) {
-        commands.add(new Command("status", status));
-        return this;
-    }
-
-    /**
-     * Recall the given scene.
-     *
-     * @param sceneId Identifier of the scene
-     * @return this object for chaining calls
-     */
-    public StateUpdate setScene(String sceneId) {
-        commands.add(new Command("scene", sceneId));
-        return this;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/SuccessResponse.java
deleted file mode 100644 (file)
index 1ba1e96..0000000
+++ /dev/null
@@ -1,31 +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 java.util.Map;
-
-import com.google.gson.reflect.TypeToken;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-class SuccessResponse {
-    public static final Type GSON_TYPE = new TypeToken<List<SuccessResponse>>() {
-    }.getType();
-
-    public Map<String, Object> success;
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/TemperatureConfigUpdate.java
deleted file mode 100644 (file)
index becda09..0000000
+++ /dev/null
@@ -1,30 +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 static org.openhab.binding.hue.internal.FullSensor.CONFIG_LED_INDICATION;
-
-/**
- * Updates the configuration of a temperature sensor
- *
- * @author Christoph Weitkamp - Initial contribution
- */
-public class TemperatureConfigUpdate extends SensorConfigUpdate {
-    /**
-     *
-     * @param onOff
-     */
-    public void setLedIndication(boolean onOff) {
-        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/User.java
deleted file mode 100644 (file)
index 936d06c..0000000
+++ /dev/null
@@ -1,60 +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;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * A whitelisted user.
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-public class User {
-    @SerializedName("last use date")
-    private Date lastUseDate;
-
-    @SerializedName("create date")
-    private Date createDate;
-
-    private String name;
-
-    /**
-     * Returns the last time a command was issued as this user.
-     *
-     * @return time of last command by this user
-     */
-    public Date getLastUseDate() {
-        return lastUseDate;
-    }
-
-    /**
-     * Returns the date this user was created.
-     *
-     * @return creation date of user
-     */
-    public Date getCreationDate() {
-        return createDate;
-    }
-
-    /**
-     * Returns the username of this user.
-     *
-     * @return username
-     */
-    public String getUsername() {
-        return name;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/Util.java
deleted file mode 100644 (file)
index 7b4dc1b..0000000
+++ /dev/null
@@ -1,66 +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.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- *
- * @author Q42 - Initial contribution
- * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
- */
-@NonNullByDefault
-class Util {
-    private Util() {
-    }
-
-    // This is used to check what byte size strings have, because the bridge doesn't natively support UTF-8
-    public static int stringSize(String str) {
-        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) {
-        Matcher m = Pattern.compile(needle).matcher(haystack);
-        m.find();
-        return m.group(1);
-    }
-}
index 91fc89aadc5a44ae90c763e5e1a43186fb320906..fa0295facd2911ba23dc04d9fae93adcbd18fbaa 100644 (file)
@@ -26,7 +26,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The {@link LightActions} defines {@link ThingActions} for the hue lights.
+ * The {@link LightActions} defines {@link ThingActions} for the Hue lights.
  *
  * @author Jochen Leopold - Initial contribution
  */
index e7dd794fad418453613964f810388ef833f96af0..77ab7c25dc734275605bd548fa5f02a215e5d1fb 100644 (file)
@@ -26,59 +26,16 @@ public class HueBridgeConfig {
     public static final String HTTP = "http";
     public static final String HTTPS = "https";
 
-    private @Nullable String ipAddress;
-    private @Nullable Integer port;
-    private String protocol = HTTP;
-    private @Nullable String userName;
-    private int pollingInterval = 10;
-    private int sensorPollingInterval = 500;
-
-    public @Nullable String getIpAddress() {
-        return ipAddress;
-    }
-
-    public void setIpAddress(String ipAddress) {
-        this.ipAddress = ipAddress;
-    }
+    public @Nullable String ipAddress;
+    public @Nullable Integer port;
+    public String protocol = HTTPS;
+    public boolean useSelfSignedCertificate = true;
+    public @Nullable String userName;
+    public int pollingInterval = 10;
+    public int sensorPollingInterval = 500;
 
     public int getPort() {
-        Integer thePort = this.port;
+        Integer thePort = port;
         return (thePort != null) ? thePort.intValue() : HTTPS.equals(protocol) ? 443 : 80;
     }
-
-    public void setPort(int port) {
-        this.port = port;
-    }
-
-    public String getProtocol() {
-        return protocol;
-    }
-
-    public void setProtocol(String protocol) {
-        this.protocol = protocol;
-    }
-
-    public @Nullable String getUserName() {
-        return userName;
-    }
-
-    public void setUserName(String userName) {
-        this.userName = userName;
-    }
-
-    public int getPollingInterval() {
-        return pollingInterval;
-    }
-
-    public void setPollingInterval(int pollingInterval) {
-        this.pollingInterval = pollingInterval;
-    }
-
-    public int getSensorPollingInterval() {
-        return sensorPollingInterval;
-    }
-
-    public void setSensorPollingInterval(int sensorPollingInterval) {
-        this.sensorPollingInterval = sensorPollingInterval;
-    }
 }
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueBridge.java
new file mode 100644 (file)
index 0000000..532acb1
--- /dev/null
@@ -0,0 +1,1124 @@
+/**
+ * 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 static org.openhab.binding.hue.internal.HueBindingConstants.*;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.hue.internal.dto.ApiVersion;
+import org.openhab.binding.hue.internal.dto.ApiVersionUtils;
+import org.openhab.binding.hue.internal.dto.Config;
+import org.openhab.binding.hue.internal.dto.ConfigUpdate;
+import org.openhab.binding.hue.internal.dto.CreateUserRequest;
+import org.openhab.binding.hue.internal.dto.ErrorResponse;
+import org.openhab.binding.hue.internal.dto.FullConfig;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.FullHueObject;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.Group;
+import org.openhab.binding.hue.internal.dto.HueObject;
+import org.openhab.binding.hue.internal.dto.NewLightsResponse;
+import org.openhab.binding.hue.internal.dto.Scene;
+import org.openhab.binding.hue.internal.dto.Schedule;
+import org.openhab.binding.hue.internal.dto.ScheduleUpdate;
+import org.openhab.binding.hue.internal.dto.SearchForLightsRequest;
+import org.openhab.binding.hue.internal.dto.SetAttributesRequest;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
+import org.openhab.binding.hue.internal.dto.SuccessResponse;
+import org.openhab.binding.hue.internal.dto.Util;
+import org.openhab.binding.hue.internal.exceptions.ApiException;
+import org.openhab.binding.hue.internal.exceptions.DeviceOffException;
+import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException;
+import org.openhab.binding.hue.internal.exceptions.GroupTableFullException;
+import org.openhab.binding.hue.internal.exceptions.InvalidCommandException;
+import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
+import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
+import org.openhab.core.i18n.CommunicationException;
+import org.openhab.core.i18n.ConfigurationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+/**
+ * Representation of a connection with a Hue Bridge.
+ *
+ * @author Q42 - Initial contribution
+ * @author Andre Fuechsel - search for lights with given serial number added
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - added cached config and API-Version
+ * @author Laurent Garnier - change the return type of getGroups
+ */
+@NonNullByDefault
+public class HueBridge {
+
+    private final Logger logger = LoggerFactory.getLogger(HueBridge.class);
+
+    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+
+    private final HttpClient httpClient;
+    private final String ip;
+    private final String baseUrl;
+    private @Nullable String username;
+    private long timeout = TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS);
+
+    private final Gson gson = new GsonBuilder().setDateFormat(DATE_FORMAT).create();
+
+    private final LinkedList<AsyncPutParameters> commandsQueue = new LinkedList<>();
+    private @Nullable Future<?> job;
+    private final ScheduledExecutorService scheduler;
+
+    private @Nullable Config cachedConfig;
+
+    /**
+     * Connect with a bridge as a new user.
+     *
+     * @param httpClient instance of the Jetty shared client
+     * @param ip ip address of bridge
+     * @param port port of bridge
+     * @param protocol protocol to connect to the bridge
+     * @param scheduler the ExecutorService to schedule commands
+     */
+    public HueBridge(HttpClient httpClient, String ip, int port, String protocol, ScheduledExecutorService scheduler) {
+        this.httpClient = httpClient;
+        this.ip = ip;
+        String baseUrl;
+        try {
+            URI uri = new URI(protocol, null, ip, port, "/api", null, null);
+            baseUrl = uri.toString();
+        } catch (URISyntaxException e) {
+            logger.error("exception during constructing URI protocol={}, host={}, port={}", protocol, ip, port, e);
+            baseUrl = protocol + "://" + ip + ":" + port + "/api";
+        }
+        this.baseUrl = baseUrl;
+        this.scheduler = scheduler;
+    }
+
+    /**
+     * Connect with a bridge as an existing user.
+     *
+     * The username is verified by requesting the list of lights.
+     * Use the ip only constructor and authenticate() function if
+     * you don't want to connect right now.
+     *
+     * @param httpClient instance of the Jetty shared client
+     * @param ip ip address of bridge
+     * @param port port of bridge
+     * @param protocol protocol to connect to the bridge
+     * @param username username to authenticate with
+     * @param scheduler the ExecutorService to schedule commands
+     */
+    public HueBridge(HttpClient httpClient, String ip, int port, String protocol, String username,
+            ScheduledExecutorService scheduler)
+            throws IOException, ApiException, ConfigurationException, UnauthorizedException {
+        this(httpClient, ip, port, protocol, scheduler);
+        authenticate(username);
+    }
+
+    /**
+     * Set the connect and read timeout for HTTP requests.
+     *
+     * @param timeout timeout in milliseconds or 0 for indefinitely
+     */
+    public void setTimeout(long timeout) {
+        this.timeout = timeout;
+    }
+
+    /**
+     * Returns the IP address of the bridge.
+     *
+     * @return ip address of bridge
+     */
+    public String getIPAddress() {
+        return ip;
+    }
+
+    public ApiVersion getVersion() throws IOException, ApiException {
+        Config c = getCachedConfig();
+        return ApiVersion.of(c.getApiVersion());
+    }
+
+    /**
+     * Returns a cached version of the basic {@link Config} mostly immutable configuration.
+     * This can be used to reduce load on the bridge.
+     *
+     * @return The {@link Config} of the Hue Bridge, loaded and cached lazily on the first call
+     * @throws IOException
+     * @throws ApiException
+     */
+    private Config getCachedConfig() throws IOException, ApiException {
+        if (cachedConfig == null) {
+            cachedConfig = getConfig();
+        }
+
+        return Objects.requireNonNull(cachedConfig);
+    }
+
+    /**
+     * Returns the username currently authenticated with or null if there isn't one.
+     *
+     * @return username or null
+     */
+    public @Nullable String getUsername() {
+        return username;
+    }
+
+    /**
+     * Returns if authentication was successful on the bridge.
+     *
+     * @return true if authenticated on the bridge, false otherwise
+     */
+    public boolean isAuthenticated() {
+        return getUsername() != null;
+    }
+
+    /**
+     * Returns a list of lights known to the bridge.
+     *
+     * @return list of known lights as {@link FullLight}s
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public List<FullLight> getFullLights() throws IOException, ApiException {
+        if (ApiVersionUtils.supportsFullLights(getVersion())) {
+            Type gsonType = FullLight.GSON_TYPE;
+            return getTypedLights(gsonType);
+        } else {
+            return getFullConfig().getLights();
+        }
+    }
+
+    /**
+     * Returns a list of lights known to the bridge.
+     *
+     * @return list of known lights
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public List<HueObject> getLights() throws IOException, ApiException {
+        Type gsonType = HueObject.GSON_TYPE;
+        return getTypedLights(gsonType);
+    }
+
+    private <T extends HueObject> List<T> getTypedLights(Type gsonType)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("lights"));
+
+        handleErrors(result);
+
+        Map<String, T> lightMap = safeFromJson(result.body, gsonType);
+        List<T> lights = new ArrayList<>();
+        lightMap.forEach((id, light) -> {
+            light.setId(id);
+            lights.add(light);
+        });
+        return lights;
+    }
+
+    /**
+     * Returns a list of sensors known to the bridge
+     *
+     * @return list of sensors
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public List<FullSensor> getSensors()
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("sensors"));
+
+        handleErrors(result);
+
+        Map<String, FullSensor> sensorMap = safeFromJson(result.body, FullSensor.GSON_TYPE);
+        List<FullSensor> sensors = new ArrayList<>();
+        sensorMap.forEach((id, sensor) -> {
+            sensor.setId(id);
+            sensors.add(sensor);
+        });
+        return sensors;
+    }
+
+    /**
+     * Returns the last time a search for new lights was started.
+     * If a search is currently running, the current time will be
+     * returned or null if a search has never been started.
+     *
+     * @return last search time
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public @Nullable Date getLastSearch()
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("lights/new"));
+
+        handleErrors(result);
+
+        String lastScan = safeFromJson(result.body, NewLightsResponse.class).lastscan;
+
+        switch (lastScan) {
+            case "none":
+                return null;
+            case "active":
+                return new Date();
+            default:
+                try {
+                    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(lastScan);
+                } catch (ParseException e) {
+                    return null;
+                }
+        }
+    }
+
+    /**
+     * Start searching for new lights for 1 minute.
+     * A maximum amount of 15 new lights will be added.
+     *
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public void startSearch() throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = post(getRelativeURL("lights"), "");
+
+        handleErrors(result);
+    }
+
+    /**
+     * Start searching for new lights with given serial numbers for 1 minute.
+     * A maximum amount of 15 new lights will be added.
+     *
+     * @param serialNumbers list of serial numbers
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public void startSearch(List<String> serialNumbers)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = post(getRelativeURL("lights"), gson.toJson(new SearchForLightsRequest(serialNumbers)));
+
+        handleErrors(result);
+    }
+
+    /**
+     * Returns detailed information for the given light.
+     *
+     * @param light light
+     * @return detailed light information
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if a light with the given id doesn't exist
+     */
+    public FullHueObject getLight(HueObject light)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("lights/" + enc(light.getId())));
+
+        handleErrors(result);
+
+        FullHueObject fullLight = safeFromJson(result.body, FullLight.class);
+        fullLight.setId(light.getId());
+        return fullLight;
+    }
+
+    /**
+     * Changes the name of the light and returns the new name.
+     * A number will be appended to duplicate names, which may result in a new name exceeding 32 characters.
+     *
+     * @param light light
+     * @param name new name [0..32]
+     * @return new name
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified light no longer exists
+     */
+    public String setLightName(HueObject light, String name)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = put(getRelativeURL("lights/" + enc(light.getId())),
+                gson.toJson(new SetAttributesRequest(name)));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        String lightName = (String) response.success.get("/lights/" + enc(light.getId()) + "/name");
+        if (lightName == null) {
+            throw new ApiException("Response didn't contain light name.");
+        }
+        return lightName;
+    }
+
+    /**
+     * Changes the state of a light.
+     *
+     * @param light light
+     * @param update changes to the state
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified light no longer exists
+     * @throws DeviceOffException thrown if the specified light is turned off
+     * @throws IOException if the bridge cannot be reached
+     */
+    public CompletableFuture<HueResult> setLightState(FullLight light, StateUpdate update) {
+        requireAuthentication();
+
+        return putAsync(getRelativeURL("lights/" + enc(light.getId()) + "/state"), update.toJson(),
+                update.getMessageDelay());
+    }
+
+    /**
+     * Changes the state of a clip sensor.
+     *
+     * @param sensor sensor
+     * @param update changes to the state
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified sensor no longer exists
+     * @throws DeviceOffException thrown if the specified sensor is turned off
+     * @throws IOException if the bridge cannot be reached
+     */
+    public CompletableFuture<HueResult> setSensorState(FullSensor sensor, StateUpdate update) {
+        requireAuthentication();
+
+        return putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/state"), update.toJson(),
+                update.getMessageDelay());
+    }
+
+    /**
+     * Changes the config of a sensor.
+     *
+     * @param sensor sensor
+     * @param update changes to the config
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified sensor no longer exists
+     * @throws IOException if the bridge cannot be reached
+     */
+    public CompletableFuture<HueResult> updateSensorConfig(FullSensor sensor, ConfigUpdate update) {
+        requireAuthentication();
+
+        return putAsync(getRelativeURL("sensors/" + enc(sensor.getId()) + "/config"), update.toJson(),
+                update.getMessageDelay());
+    }
+
+    /**
+     * Returns a group object representing all lights.
+     *
+     * @return all lights pseudo group
+     */
+    public Group getAllGroup() {
+        return new Group();
+    }
+
+    /**
+     * Returns the list of groups, including the unmodifiable all lights group.
+     *
+     * @return list of groups
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public List<FullGroup> getGroups()
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("groups"));
+
+        handleErrors(result);
+
+        Map<String, FullGroup> groupMap = safeFromJson(result.body, FullGroup.GSON_TYPE);
+        List<FullGroup> groups = new ArrayList<>();
+        if (groupMap.get("0") == null) {
+            // Group 0 is not returned, we create it as in fact it exists
+            try {
+                groups.add(getGroup(getAllGroup()));
+            } catch (FileNotFoundException e) {
+                // We need a special exception handling here to further support deCONZ REST API. On deCONZ group "0" may
+                // not exist and the APIs will return a different HTTP status code if requesting a non existing group
+                // (Hue: 200, deCONZ: 404).
+                // see https://github.com/openhab/openhab-addons/issues/9175
+                logger.debug("Cannot find AllGroup with id \"0\" on Hue Bridge. Skipping it.");
+            }
+        }
+        groupMap.forEach((id, group) -> {
+            group.setId(id);
+            groups.add(group);
+        });
+        return groups;
+    }
+
+    /**
+     * Creates a new group and returns it.
+     * Due to API limitations, the name of the returned object
+     * will simply be "Group". The bridge will append a number to this
+     * name if it's a duplicate. To get the final name, call getGroup
+     * with the returned object.
+     *
+     * @param lights lights in group
+     * @return object representing new group
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws GroupTableFullException thrown if the group limit has been reached
+     */
+    public Group createGroup(List<HueObject> lights)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = post(getRelativeURL("groups"), gson.toJson(new SetAttributesRequest(lights)));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        Group group = new Group();
+        group.setName("Group");
+        group.setId(Util.quickMatch("^/groups/([0-9]+)$", (String) response.success.values().toArray()[0]));
+        return group;
+    }
+
+    /**
+     * Creates a new group and returns it.
+     * Due to API limitations, the name of the returned object
+     * will simply be the same as the name parameter. The bridge will
+     * append a number to the name if it's a duplicate. To get the final
+     * name, call getGroup with the returned object.
+     *
+     * @param name new group name
+     * @param lights lights in group
+     * @return object representing new group
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws GroupTableFullException thrown if the group limit has been reached
+     */
+    public Group createGroup(String name, List<HueObject> lights)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = post(getRelativeURL("groups"), gson.toJson(new SetAttributesRequest(name, lights)));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        Group group = new Group();
+        group.setName(name);
+        group.setId(Util.quickMatch("^/groups/([0-9]+)$", (String) response.success.values().toArray()[0]));
+        return group;
+    }
+
+    /**
+     * Returns detailed information for the given group.
+     *
+     * @param group group
+     * @return detailed group information
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if a group with the given id doesn't exist
+     */
+    public FullGroup getGroup(Group group)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("groups/" + enc(group.getId())));
+
+        handleErrors(result);
+
+        FullGroup fullGroup = safeFromJson(result.body, FullGroup.class);
+        fullGroup.setId(group.getId());
+        return fullGroup;
+    }
+
+    /**
+     * Changes the name of the group and returns the new name.
+     * A number will be appended to duplicate names, which may result in a new name exceeding 32 characters.
+     *
+     * @param group group
+     * @param name new name [0..32]
+     * @return new name
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified group no longer exists
+     */
+    public String setGroupName(Group group, String name)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        if (!group.isModifiable()) {
+            throw new IllegalArgumentException("Group cannot be modified");
+        }
+
+        HueResult result = put(getRelativeURL("groups/" + enc(group.getId())),
+                gson.toJson(new SetAttributesRequest(name)));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name");
+        if (groupName == null) {
+            throw new ApiException("Response didn't contain group name.");
+        }
+        return groupName;
+    }
+
+    /**
+     * Changes the lights in the group.
+     *
+     * @param group group
+     * @param lights new lights [1..16]
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified group no longer exists
+     */
+    public void setGroupLights(Group group, List<HueObject> lights)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        if (!group.isModifiable()) {
+            throw new IllegalArgumentException("Group cannot be modified");
+        }
+
+        HueResult result = put(getRelativeURL("groups/" + enc(group.getId())),
+                gson.toJson(new SetAttributesRequest(lights)));
+
+        handleErrors(result);
+    }
+
+    /**
+     * Changes the name and the lights of a group and returns the new name.
+     *
+     * @param group group
+     * @param name new name [0..32]
+     * @param lights [1..16]
+     * @return new name
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified group no longer exists
+     */
+    public String setGroupAttributes(Group group, String name, List<HueObject> lights)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        if (!group.isModifiable()) {
+            throw new IllegalArgumentException("Group cannot be modified");
+        }
+
+        HueResult result = put(getRelativeURL("groups/" + enc(group.getId())),
+                gson.toJson(new SetAttributesRequest(name, lights)));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        String groupName = (String) response.success.get("/groups/" + enc(group.getId()) + "/name");
+        if (groupName == null) {
+            throw new ApiException("Response didn't contain group name.");
+        }
+        return groupName;
+    }
+
+    /**
+     * Changes the state of a group.
+     *
+     * @param group group
+     * @param update changes to the state
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified group no longer exists
+     */
+    public CompletableFuture<HueResult> setGroupState(Group group, StateUpdate update) {
+        requireAuthentication();
+
+        return putAsync(getRelativeURL("groups/" + enc(group.getId()) + "/action"), update.toJson(),
+                update.getMessageDelay());
+    }
+
+    /**
+     * Delete a group.
+     *
+     * @param group group
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified group no longer exists
+     */
+    public void deleteGroup(Group group)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        if (!group.isModifiable()) {
+            throw new IllegalArgumentException("Group cannot be modified");
+        }
+
+        HueResult result = delete(getRelativeURL("groups/" + enc(group.getId())));
+
+        handleErrors(result);
+    }
+
+    /**
+     * Returns a list of schedules on the bridge.
+     *
+     * @return schedules
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public List<Schedule> getSchedules()
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("schedules"));
+
+        handleErrors(result);
+
+        Map<String, Schedule> scheduleMap = safeFromJson(result.body, Schedule.GSON_TYPE);
+        List<Schedule> schedules = new ArrayList<>();
+        scheduleMap.forEach((id, schedule) -> {
+            schedule.setId(id);
+            schedules.add(schedule);
+        });
+        return schedules;
+    }
+
+    /**
+     * Changes a schedule.
+     *
+     * @param schedule schedule
+     * @param update changes
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the specified schedule no longer exists
+     */
+    public void setSchedule(Schedule schedule, ScheduleUpdate update)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = put(getRelativeURL("schedules/" + enc(schedule.getId())), update.toJson());
+
+        handleErrors(result);
+    }
+
+    /**
+     * Delete a schedule.
+     *
+     * @param schedule schedule
+     * @throws UnauthorizedException thrown if the user no longer exists
+     * @throws EntityNotAvailableException thrown if the schedule no longer exists
+     */
+    public void deleteSchedule(Schedule schedule)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = delete(getRelativeURL("schedules/" + enc(schedule.getId())));
+
+        handleErrors(result);
+    }
+
+    /**
+     * Returns the list of scenes that are not recyclable.
+     *
+     * @return all scenes that can be activated
+     */
+    public List<Scene> getScenes() throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("scenes"));
+
+        handleErrors(result);
+
+        Map<String, Scene> sceneMap = safeFromJson(result.body, Scene.GSON_TYPE);
+        return sceneMap.entrySet().stream()//
+                .map(e -> {
+                    e.getValue().setId(e.getKey());
+                    return e.getValue();
+                })//
+                .filter(scene -> !scene.isRecycle())//
+                .sorted(Comparator.comparing(Scene::extractKeyForComparator))//
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Activate scene to all lights that belong to the scene.
+     *
+     * @param id the scene to be activated
+     * @throws IOException if the bridge cannot be reached
+     */
+    public CompletableFuture<HueResult> recallScene(String id) {
+        Group allLightsGroup = new Group();
+        return setGroupState(allLightsGroup, new StateUpdate().setScene(id));
+    }
+
+    /**
+     * Authenticate on the bridge as the specified user.
+     * This function verifies that the specified username is valid and will use
+     * it for subsequent requests if it is, otherwise an UnauthorizedException
+     * is thrown and the internal username is not changed.
+     *
+     * @param username username to authenticate
+     * @throws ConfigurationException thrown on ssl failure
+     * @throws UnauthorizedException thrown if authentication failed
+     */
+    public void authenticate(String username)
+            throws IOException, ApiException, ConfigurationException, UnauthorizedException {
+        try {
+            this.username = username;
+            getLights();
+        } catch (ConfigurationException e) {
+            throw e;
+        } catch (Exception e) {
+            this.username = null;
+            throw new UnauthorizedException(e.toString());
+        }
+    }
+
+    /**
+     * Link with bridge using the specified username and device type.
+     *
+     * @param username username for new user [10..40]
+     * @param devicetype identifier of application [0..40]
+     * @throws LinkButtonException thrown if the bridge button has not been pressed
+     */
+    public void link(String username, String devicetype)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        this.username = link(new CreateUserRequest(username, devicetype));
+    }
+
+    /**
+     * Link with bridge using the specified device type. A random valid username will be generated by the bridge and
+     * returned.
+     *
+     * @return new random username generated by bridge
+     * @param devicetype identifier of application [0..40]
+     * @throws LinkButtonException thrown if the bridge button has not been pressed
+     */
+    public String link(String devicetype)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        return (this.username = link(new CreateUserRequest(devicetype)));
+    }
+
+    private String link(CreateUserRequest request)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        if (this.username != null) {
+            throw new IllegalStateException("already linked");
+        }
+
+        HueResult result = post(getRelativeURL(""), gson.toJson(request));
+
+        handleErrors(result);
+
+        List<SuccessResponse> entries = safeFromJson(result.body, SuccessResponse.GSON_TYPE);
+        SuccessResponse response = entries.get(0);
+
+        String username = (String) response.success.get("username");
+        if (username == null) {
+            throw new ApiException("Response didn't contain username");
+        }
+        return username;
+    }
+
+    /**
+     * Returns bridge configuration.
+     *
+     * @see Config
+     * @return bridge configuration
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public Config getConfig() throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL("config"));
+
+        handleErrors(result);
+
+        return safeFromJson(result.body, Config.class);
+    }
+
+    /**
+     * Change the configuration of the bridge.
+     *
+     * @param update changes to the configuration
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public void setConfig(ConfigUpdate update)
+            throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = put(getRelativeURL("config"), update.toJson());
+
+        handleErrors(result);
+    }
+
+    /**
+     * Unlink the current user from the bridge.
+     *
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public void unlink() throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = delete(getRelativeURL("config/whitelist/" + enc(username)));
+
+        handleErrors(result);
+    }
+
+    /**
+     * Returns the entire bridge configuration.
+     * This request is rather resource intensive for the bridge,
+     * don't use it more often than necessary. Prefer using requests for
+     * specific information your app needs.
+     *
+     * @return full bridge configuration
+     * @throws UnauthorizedException thrown if the user no longer exists
+     */
+    public FullConfig getFullConfig() throws IOException, ApiException, ConfigurationException, CommunicationException {
+        requireAuthentication();
+
+        HueResult result = get(getRelativeURL(""));
+
+        handleErrors(result);
+
+        FullConfig fullConfig = gson.fromJson(result.body, FullConfig.class);
+        return Objects.requireNonNull(fullConfig);
+    }
+
+    // Used as assert in requests that require authentication
+    private void requireAuthentication() {
+        if (this.username == null) {
+            throw new IllegalStateException("linking is required before interacting with the bridge");
+        }
+    }
+
+    // Methods that convert gson exceptions into ApiExceptions
+    private <T> T safeFromJson(String json, Type typeOfT) throws ApiException {
+        try {
+            return gson.fromJson(json, typeOfT);
+        } catch (JsonParseException e) {
+            throw new ApiException("API returned unexpected result: " + e.getMessage());
+        }
+    }
+
+    private <T> T safeFromJson(String json, Class<T> classOfT) throws ApiException {
+        try {
+            return gson.fromJson(json, classOfT);
+        } catch (JsonParseException e) {
+            throw new ApiException("API returned unexpected result: " + e.getMessage());
+        }
+    }
+
+    // Used as assert in all requests to elegantly catch common errors
+    public void handleErrors(HueResult result) throws IOException, ApiException {
+        if (result.responseCode != HttpStatus.OK_200) {
+            throw new IOException();
+        } else {
+            try {
+                List<ErrorResponse> errors = gson.fromJson(result.body, ErrorResponse.GSON_TYPE);
+                if (errors == null) {
+                    return;
+                }
+
+                for (ErrorResponse error : errors) {
+                    if (error.getType() == null) {
+                        continue;
+                    }
+
+                    switch (error.getType()) {
+                        case 1:
+                            username = null;
+                            throw new UnauthorizedException(error.getDescription());
+                        case 3:
+                            throw new EntityNotAvailableException(error.getDescription());
+                        case 7:
+                            throw new InvalidCommandException(error.getDescription());
+                        case 101:
+                            throw new LinkButtonException(error.getDescription());
+                        case 201:
+                            throw new DeviceOffException(error.getDescription());
+                        case 301:
+                            throw new GroupTableFullException(error.getDescription());
+                        default:
+                            throw new ApiException(error.getDescription());
+                    }
+                }
+            } catch (JsonParseException e) {
+                // Not an error
+            }
+        }
+    }
+
+    // UTF-8 URL encode
+    private String enc(@Nullable String str) {
+        return str == null ? "" : URLEncoder.encode(str, StandardCharsets.UTF_8);
+    }
+
+    private String getRelativeURL(String path) {
+        String relativeUrl = baseUrl;
+        if (username != null) {
+            relativeUrl += "/" + enc(username);
+        }
+        return path.isEmpty() ? relativeUrl : relativeUrl + "/" + path;
+    }
+
+    public HueResult get(String address) throws ConfigurationException, CommunicationException {
+        return doNetwork(address, HttpMethod.GET);
+    }
+
+    public HueResult post(String address, String body) throws ConfigurationException, CommunicationException {
+        return doNetwork(address, HttpMethod.POST, body);
+    }
+
+    public HueResult put(String address, String body) throws ConfigurationException, CommunicationException {
+        return doNetwork(address, HttpMethod.PUT, body);
+    }
+
+    public HueResult delete(String address) throws ConfigurationException, CommunicationException {
+        return doNetwork(address, HttpMethod.DELETE);
+    }
+
+    private HueResult doNetwork(String address, HttpMethod requestMethod)
+            throws ConfigurationException, CommunicationException {
+        return doNetwork(address, requestMethod, null);
+    }
+
+    private HueResult doNetwork(String address, HttpMethod requestMethod, @Nullable String body)
+            throws ConfigurationException, CommunicationException {
+        logger.trace("Hue request: {} - URL = '{}'", requestMethod, address);
+        try {
+            final Request request = httpClient.newRequest(address).method(requestMethod).timeout(timeout,
+                    TimeUnit.MILLISECONDS);
+
+            if (body != null) {
+                logger.trace("Hue request body: '{}'", body);
+                request.content(new StringContentProvider(body), "application/json");
+            }
+
+            final ContentResponse contentResponse = request.send();
+
+            final int httpStatus = contentResponse.getStatus();
+            final String content = contentResponse.getContentAsString();
+            logger.trace("Hue response: status = {}, content = '{}'", httpStatus, content);
+            return new HueResult(content, httpStatus);
+        } catch (ExecutionException e) {
+            String message = e.getMessage();
+            if (e.getCause() instanceof SSLHandshakeException) {
+                logger.debug("SSLHandshakeException occurred during execution: {}", message, e);
+                throw new ConfigurationException(TEXT_OFFLINE_CONFIGURATION_ERROR_INVALID_SSL_CERIFICATE, e.getCause());
+            } else {
+                logger.debug("ExecutionException occurred during execution: {}", message, e);
+                throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message,
+                        e.getCause());
+            }
+        } catch (TimeoutException e) {
+            String message = e.getMessage();
+            logger.debug("TimeoutException occurred during execution: {}", message, e);
+            throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            String message = e.getMessage();
+            logger.debug("InterruptedException occurred during execution: {}", message, e);
+            throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message);
+        }
+    }
+
+    private CompletableFuture<HueResult> putAsync(String address, String body, long delay) {
+        AsyncPutParameters asyncPutParameters = new AsyncPutParameters(address, body, delay);
+        synchronized (commandsQueue) {
+            if (commandsQueue.isEmpty()) {
+                commandsQueue.offer(asyncPutParameters);
+                Future<?> localJob = job;
+                if (localJob == null || localJob.isDone()) {
+                    job = scheduler.submit(this::executeCommands);
+                }
+            } else {
+                commandsQueue.offer(asyncPutParameters);
+            }
+        }
+        return asyncPutParameters.future;
+    }
+
+    private void executeCommands() {
+        while (true) {
+            try {
+                long delayTime = 0;
+                synchronized (commandsQueue) {
+                    AsyncPutParameters payloadCallbackPair = commandsQueue.poll();
+                    if (payloadCallbackPair != null) {
+                        logger.debug("Async sending put to address: {} delay: {} body: {}", payloadCallbackPair.address,
+                                payloadCallbackPair.delay, payloadCallbackPair.body);
+                        try {
+                            HueResult result = doNetwork(payloadCallbackPair.address, HttpMethod.PUT,
+                                    payloadCallbackPair.body);
+                            payloadCallbackPair.future.complete(result);
+                        } catch (ConfigurationException | CommunicationException e) {
+                            payloadCallbackPair.future.completeExceptionally(e);
+                        }
+                        delayTime = payloadCallbackPair.delay;
+                    } else {
+                        return;
+                    }
+                }
+                Thread.sleep(delayTime);
+            } catch (InterruptedException e) {
+                logger.debug("commandExecutorThread was interrupted", e);
+            }
+        }
+    }
+
+    public static class HueResult {
+        public final String body;
+        public final int responseCode;
+
+        public HueResult(String body, int responseCode) {
+            this.body = body;
+            this.responseCode = responseCode;
+        }
+    }
+
+    public final class AsyncPutParameters {
+        public final String address;
+        public final String body;
+        public final CompletableFuture<HueResult> future;
+        public final long delay;
+
+        public AsyncPutParameters(String address, String body, long delay) {
+            this.address = address;
+            this.body = body;
+            this.future = new CompletableFuture<>();
+            this.delay = delay;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/connection/HueTlsTrustManagerProvider.java
new file mode 100644 (file)
index 0000000..5fa2820
--- /dev/null
@@ -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;
+    }
+}
index 8e5fe3c3f130a87224bba50b18e8713dedcd1eca..a1ee6c75994450e2c668cad70c70de683d0b51e0 100644 (file)
@@ -46,7 +46,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
 
     @Activate
     public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
-        super("hue", "Interact with the hue binding.");
+        super("hue", "Interact with the Hue binding.");
         this.thingRegistry = thingRegistry;
     }
 
@@ -78,7 +78,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
                 console.println("No handler initialized for the thingUID '" + args[0] + "'");
                 printUsage(console);
             } else if (bridgeHandler == null && groupHandler == null) {
-                console.println("'" + args[0] + "' is neither a Hue bridgeUID nor a Hue groupThingUID");
+                console.println("'" + args[0] + "' is neither a Hue BridgeUID nor a Hue groupThingUID");
                 printUsage(console);
             } else {
                 switch (args[1]) {
@@ -87,7 +87,7 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension {
                             String userName = bridgeHandler.getUserName();
                             console.println("Your user name is " + (userName != null ? userName : "undefined"));
                         } else {
-                            console.println("'" + args[0] + "' is not a Hue bridgeUID");
+                            console.println("'" + args[0] + "' is not a Hue BridgeUID");
                             printUsage(console);
                         }
                         break;
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java
deleted file mode 100644 (file)
index fb40cda..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal.discovery;
-
-import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.jupnp.model.meta.DeviceDetails;
-import org.jupnp.model.meta.ModelDetails;
-import org.jupnp.model.meta.RemoteDevice;
-import org.openhab.binding.hue.internal.HueBindingConstants;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
-import org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link HueBridgeDiscoveryParticipant} is responsible for discovering new and
- * removed hue bridges. It uses the central {@link UpnpDiscoveryService}.
- *
- * @author Kai Kreuzer - Initial contribution
- * @author Thomas Höfer - Added representation
- */
-@NonNullByDefault
-@Component(service = UpnpDiscoveryParticipant.class)
-public class HueBridgeDiscoveryParticipant implements UpnpDiscoveryParticipant {
-
-    private final Logger logger = LoggerFactory.getLogger(HueBridgeDiscoveryParticipant.class);
-
-    // Hue bridges have maxAge 100 seconds, so set the default grace period to half of that
-    private long removalGracePeriodSeconds = 50;
-
-    private final ConfigurationAdmin configAdmin;
-
-    @Activate
-    public HueBridgeDiscoveryParticipant(final @Reference ConfigurationAdmin configAdmin) {
-        this.configAdmin = configAdmin;
-    }
-
-    @Override
-    public Set<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;
-    }
-}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeMDNSDiscoveryParticipant.java
new file mode 100644 (file)
index 0000000..2014c0e
--- /dev/null
@@ -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;
+    }
+}
index f01d6c857b858a4af4e63f89cf134fe2714a3671..ce3ce165268f2e32d7200c434885f6076e29f9e2 100644 (file)
 package org.openhab.binding.hue.internal.discovery;
 
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.config.discovery.DiscoveryService;
 import org.openhab.core.io.net.http.HttpUtil;
-import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingUID;
 import org.osgi.service.component.annotations.Component;
 import org.slf4j.Logger;
@@ -42,37 +38,29 @@ import com.google.gson.JsonParseException;
 import com.google.gson.reflect.TypeToken;
 
 /**
- * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new hue bridges. It uses the 'NUPnP service
+ * The {@link HueBridgeNupnpDiscovery} is responsible for discovering new Hue Bridges. It uses the 'NUPnP service
  * provided by Philips'.
  *
  * @author Awelkiyar Wehabrebi - Initial contribution
  * @author Christoph Knauf - Refactorings
  * @author Andre Fuechsel - make {@link #startScan()} asynchronous
  */
-@NonNullByDefault
 @Component(service = DiscoveryService.class, configurationPid = "discovery.hue")
+@NonNullByDefault
 public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
 
-    private static final String MODEL_NAME_PHILIPS_HUE = "<modelName>Philips hue";
-
+    private static final String MODEL_NAME_PHILIPS_HUE = "\"name\":\"Philips Hue\"";
     protected static final String BRIDGE_INDICATOR = "fffe";
-
     private static final String DISCOVERY_URL = "https://discovery.meethue.com/";
-
-    protected static final String LABEL_PATTERN = "Philips hue (IP)";
-
-    private static final String DESC_URL_PATTERN = "http://HOST/description.xml";
-
+    protected static final String LABEL_PATTERN = "Philips Hue (%s)";
+    private static final String CONFIG_URL_PATTERN = "http://%s/api/0/config";
     private static final int REQUEST_TIMEOUT = 5000;
-
     private static final int DISCOVERY_TIMEOUT = 10;
 
-    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
-
     private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
 
     public HueBridgeNupnpDiscovery() {
-        super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
+        super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
     }
 
     @Override
@@ -87,37 +75,25 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
         for (BridgeJsonParameters bridge : getBridgeList()) {
             if (isReachableAndValidHueBridge(bridge)) {
                 String host = bridge.getInternalIpAddress();
-                String serialNumber = bridge.getId().substring(0, 6) + bridge.getId().substring(10);
-                serialNumber = serialNumber.toLowerCase();
+                String serialNumber = bridge.getId().toLowerCase();
                 ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
-                DiscoveryResult result = DiscoveryResultBuilder.create(uid)
-                        .withProperties(buildProperties(host, serialNumber))
-                        .withLabel(LABEL_PATTERN.replace("IP", host)).withRepresentationProperty(PROPERTY_SERIAL_NUMBER)
+                DiscoveryResult result = DiscoveryResultBuilder.create(uid) //
+                        .withProperties(Map.of( //
+                                HOST, host, //
+                                Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) //
+                        .withLabel(String.format(LABEL_PATTERN, host)) //
+                        .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
                         .build();
                 thingDiscovered(result);
             }
         }
     }
 
-    /**
-     * Builds the bridge properties.
-     *
-     * @param host the ip of the bridge
-     * @param serialNumber the id of the bridge
-     * @return the bridge properties
-     */
-    private Map<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.
      *
      * @param bridge the {@link BridgeJsonParameters}s
-     * @return true if Bridge is a reachable Hue Bridge with a id containing
+     * @return true if Hue Bridge is a reachable Hue Bridge with a id containing
      *         BRIDGE_INDICATOR longer then 10
      */
     private boolean isReachableAndValidHueBridge(BridgeJsonParameters bridge) {
@@ -136,14 +112,14 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
             logger.debug("Bridge not discovered: id {} is shorter then 10.", id);
             return false;
         }
-        if (!id.substring(6, 10).equals(BRIDGE_INDICATOR)) {
+        if (!BRIDGE_INDICATOR.equals(id.substring(6, 10))) {
             logger.debug(
                     "Bridge not discovered: id {} does not contain bridge indicator {} or its at the wrong position.",
                     id, BRIDGE_INDICATOR);
             return false;
         }
         try {
-            description = doGetRequest(DESC_URL_PATTERN.replace("HOST", host));
+            description = doGetRequest(String.format(CONFIG_URL_PATTERN, host));
         } catch (IOException e) {
             logger.debug("Bridge not discovered: Failure accessing description file for ip: {}", host);
             return false;
@@ -170,10 +146,10 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
             return Objects.requireNonNull(bridgeParameters);
         } catch (IOException e) {
             logger.debug("Philips Hue NUPnP service not reachable. Can't discover bridges");
-        } catch (JsonParseException je) {
+        } catch (JsonParseException e) {
             logger.debug("Invalid json respone from Hue NUPnP service. Can't discover bridges");
         }
-        return new ArrayList<>();
+        return List.of();
     }
 
     /**
index 19f9543d578a43f5c055f563f954e237352a9c00..0e7009e7ed7ee835da74f511c3f48af5483fc4fa 100644 (file)
@@ -15,7 +15,6 @@ package org.openhab.binding.hue.internal.discovery;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
 
 import java.util.AbstractMap.SimpleEntry;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -26,10 +25,10 @@ import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.FullHueObject;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.FullSensor;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.FullHueObject;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.FullSensor;
 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
 import org.openhab.binding.hue.internal.handler.HueGroupHandler;
 import org.openhab.binding.hue.internal.handler.HueLightHandler;
@@ -43,7 +42,6 @@ import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.config.discovery.DiscoveryService;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
@@ -53,8 +51,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected
- * to a paired hue bridge. The default search time for hue is 60 seconds.
+ * The {@link HueBridgeServiceTracker} tracks for Hue lights, sensors and groups which are connected
+ * to a paired Hue Bridge. The default search time for Hue is 60 seconds.
  *
  * @author Kai Kreuzer - Initial contribution
  * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
@@ -67,16 +65,15 @@ import org.slf4j.LoggerFactory;
  * @author Laurent Garnier - Added support for groups
  */
 @NonNullByDefault
-public class HueDeviceDiscoveryService extends AbstractDiscoveryService
-        implements DiscoveryService, ThingHandlerService {
+public class HueDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
 
-    public static final Set<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(),
                     TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
                     GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
                     TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
                     ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
-            .flatMap(i -> i).collect(Collectors.toSet()));
+            .flatMap(i -> i).collect(Collectors.toUnmodifiableSet());
 
     // @formatter:off
     private static final Map<String, String> TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries(
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersion.java
new file mode 100644 (file)
index 0000000..831fe8f
--- /dev/null
@@ -0,0 +1,131 @@
+/**
+ * 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.dto;
+
+import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents the version of the API of the form 1.0 or 1.2.1
+ *
+ * @author Samuel Leisering - Initial contribution
+ */
+public class ApiVersion {
+    private final int major;
+    private final int minor;
+    private final int micro;
+
+    private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)(\\.([0-9]+))?$");
+
+    public ApiVersion(int major, int minor, int micro) {
+        this.major = major;
+        this.minor = minor;
+        this.micro = micro;
+    }
+
+    public static ApiVersion of(String version) {
+        Matcher matcher = VERSION_PATTERN.matcher(version);
+        if (matcher.matches()) {
+            int major = Integer.parseInt(matcher.group(1));
+            int minor = Integer.parseInt(matcher.group(2));
+            String microString = matcher.group(4);
+            int micro = Integer.parseInt(microString == null ? "0" : microString);
+
+            ApiVersion apiVersion = new ApiVersion(major, minor, micro);
+            return apiVersion;
+        }
+
+        throw new IllegalArgumentException("Version \"" + version + "\" is not valid");
+    }
+
+    /**
+     * returns the major version part of the version
+     *
+     * @return the major part of the version
+     */
+    public int getMajor() {
+        return major;
+    }
+
+    /**
+     * returns the minor version part of the version
+     *
+     * @return the minor part of the version
+     */
+    public int getMinor() {
+        return minor;
+    }
+
+    /**
+     * returns the micro version part of the version
+     *
+     * @return the micro part of the version
+     */
+    public int getMicro() {
+        return micro;
+    }
+
+    /**
+     * compare API versions according to {@link Comparator#compare(Object, Object)}
+     *
+     * @param other
+     * @return
+     */
+    public int compare(ApiVersion other) {
+        int c = Integer.compare(major, other.major);
+        if (c == 0) {
+            c = Integer.compare(minor, other.minor);
+            if (c == 0) {
+                c = Integer.compare(micro, other.micro);
+            }
+        }
+        return c;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + major;
+        result = prime * result + micro;
+        result = prime * result + minor;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ApiVersion other = (ApiVersion) obj;
+        if (major != other.major) {
+            return false;
+        }
+        if (micro != other.micro) {
+            return false;
+        }
+        return minor == other.minor;
+    }
+
+    @Override
+    public String toString() {
+        return major + "." + minor + "." + micro;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ApiVersionUtils.java
new file mode 100644 (file)
index 0000000..70fd84b
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * 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.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Samuel Leisering - Initial contribution
+ */
+@NonNullByDefault
+public final class ApiVersionUtils {
+
+    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
+     * {@link HueObject}s.
+     *
+     * @return
+     */
+    public static boolean supportsFullLights(ApiVersion version) {
+        return FULL_LIGHTS.compare(version) <= 0;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/BridgeConfigUpdate.java
new file mode 100644 (file)
index 0000000..d300844
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * 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.dto;
+
+/**
+ * Collection of updates to the bridge configuration.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - added Sensor support
+ */
+public class BridgeConfigUpdate extends ConfigUpdate {
+    /**
+     * Set the port of the proxy or null if there is no proxy.
+     *
+     * @param port port for proxy
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setProxyPort(Integer port) {
+        if (port != null && port < 0) {
+            throw new IllegalArgumentException("Invalid value for port");
+        }
+
+        commands.add(new Command("proxyport", port == null ? 0 : port));
+        return this;
+    }
+
+    /**
+     * Set the name of the bridge, which also functions as the UPnP name.
+     *
+     * @param name new name [4..16]
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setName(String name) {
+        if (Util.stringSize(name) < 4 || Util.stringSize(name) > 16) {
+            throw new IllegalArgumentException("Bridge name must be between 4 and 16 characters long");
+        }
+
+        commands.add(new Command("name", name));
+        return this;
+    }
+
+    /**
+     * Set the address of the proxy or null if there is no proxy.
+     *
+     * @param ip ip of proxy
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setProxyAddress(String ip) {
+        if (ip != null && Util.stringSize(ip) > 40) {
+            throw new IllegalArgumentException("Bridge proxy address can be at most 40 characters long");
+        }
+
+        commands.add(new Command("proxyaddress", ip == null ? "none" : ip));
+        return this;
+    }
+
+    /**
+     * Set whether the link button has been pressed within the last 30 seconds or not.
+     *
+     * @param pressed true for pressed, false for not pressed
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setLinkButton(boolean pressed) {
+        commands.add(new Command("linkbutton", pressed));
+        return this;
+    }
+
+    /**
+     * Set the IP address of the bridge.
+     *
+     * @param ip ip address of bridge
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setIPAddress(String ip) {
+        commands.add(new Command("ipaddress", ip));
+        return this;
+    }
+
+    /**
+     * Set the network mask of the bridge.
+     *
+     * @param netmask network mask
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setNetworkMask(String netmask) {
+        commands.add(new Command("netmask", netmask));
+        return this;
+    }
+
+    /**
+     * Set the gateway address of the bridge.
+     *
+     * @param ip gateway address
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setGateway(String ip) {
+        commands.add(new Command("gateway", ip));
+        return this;
+    }
+
+    /**
+     * Set whether the bridge uses DHCP to get an ip address or not.
+     *
+     * @param enabled dhcp enabled
+     * @return this object for chaining calls
+     */
+    public BridgeConfigUpdate setDHCP(boolean enabled) {
+        commands.add(new Command("dhcp", enabled));
+        return this;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Command.java
new file mode 100644 (file)
index 0000000..0014046
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * 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.dto;
+
+import com.google.gson.Gson;
+
+/**
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Samuel Leisering - changed Command visibility to public
+ */
+public class Command {
+    public String key;
+    public Object value;
+
+    public Command(String key, Object value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    String toJson() {
+        return "\"" + key + "\":" + new Gson().toJson(value);
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Config.java
new file mode 100644 (file)
index 0000000..8c68bf0
--- /dev/null
@@ -0,0 +1,199 @@
+/**
+ * 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.dto;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detailed bridge info available if authenticated.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - added API-Version
+ */
+public class Config {
+    private String name;
+    private String swversion;
+    private String apiversion;
+    private String bridgeid;
+    private String mac;
+    private String modelid;
+    private boolean dhcp;
+    private String ipaddress;
+    private String netmask;
+    private String gateway;
+    private String proxyaddress;
+    private int proxyport;
+    private Date UTC;
+    private boolean linkbutton;
+    private Map<String, User> whitelist;
+    private SoftwareUpdate swupdate;
+
+    Config() {
+    }
+
+    /**
+     * Returns the name.
+     *
+     * @return name of the bridge
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the version of the software.
+     *
+     * @return version of software on the bridge
+     */
+    public String getSoftwareVersion() {
+        return swversion;
+    }
+
+    /**
+     * Returns the bridge id
+     *
+     * @return bridge id
+     */
+    public String getBridgeId() {
+        return bridgeid;
+    }
+
+    /**
+     * Returns the MAC address.
+     *
+     * @return mac address of bridge
+     */
+    public String getMACAddress() {
+        return mac;
+    }
+
+    /**
+     * Returns the model id
+     *
+     * @return model id
+     */
+    public String getModelId() {
+        return modelid;
+    }
+
+    /**
+     * Returns if the current IP address was obtained with DHCP.
+     *
+     * @return true if the current IP address was obtained with DHCP, false otherwise.
+     */
+    public boolean isDHCPEnabled() {
+        return dhcp;
+    }
+
+    /**
+     * Returns the IP address.
+     *
+     * @return ip address of bridge
+     */
+    public String getIPAddress() {
+        return ipaddress;
+    }
+
+    /**
+     * Returns the network mask.
+     *
+     * @return network mask
+     */
+    public String getNetworkMask() {
+        return netmask;
+    }
+
+    /**
+     * Returns the IP address of the gateway.
+     *
+     * @return ip address of gateway
+     */
+    public String getGateway() {
+        return gateway;
+    }
+
+    /**
+     * Returns the IP address of the proxy or null if there is none.
+     *
+     * @return ip address of proxy or null
+     */
+    public String getProxyAddress() {
+        return "none".equals(proxyaddress) ? null : proxyaddress;
+    }
+
+    /**
+     * Returns the port of the proxy or null if there is none.
+     *
+     * @return port of proxy or null
+     */
+    public Integer getProxyPort() {
+        return "none".equals(proxyaddress) ? null : proxyport;
+    }
+
+    /**
+     * Returns the time on the bridge.
+     *
+     * @return time on the bridge
+     */
+    public Date getUTCTime() {
+        return UTC;
+    }
+
+    /**
+     * Returns if the link button has been pressed within the last 30 seconds.
+     *
+     * @return true if the link button has been pressed within the last 30 seconds, false otherwise
+     */
+    public boolean isLinkButtonPressed() {
+        return linkbutton;
+    }
+
+    /**
+     * Returns the list of whitelisted users.
+     *
+     * @return list of whitelisted users
+     */
+    public List<User> getWhitelist() {
+        ArrayList<User> usersList = new ArrayList<>();
+
+        usersList.addAll(whitelist.values());
+
+        return usersList;
+    }
+
+    /**
+     * Returns information about a bridge firmware update.
+     *
+     * @return bridge firmware update info
+     */
+    public SoftwareUpdate getSoftwareUpdate() {
+        return swupdate;
+    }
+
+    /**
+     * Returns the current API-Version of the Bridge. This always returns <code>1.0</code>
+     * for bridges with version less than <code>1.2.1</code>, which introduces this call.
+     *
+     * @return
+     */
+    public String getApiVersion() {
+        if (apiversion == null) {
+            return "1.0";
+        }
+        return apiversion;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ConfigUpdate.java
new file mode 100644 (file)
index 0000000..e2453e5
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * 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.dto;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.ArrayList;
+
+/**
+ * Collection of updates
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - Added support for sensor API
+ * @author Christoph Weitkamp - Added support for sensor API
+ */
+public class ConfigUpdate {
+
+    public final ArrayList<Command> commands = new ArrayList<>();
+
+    public ConfigUpdate() {
+        super();
+    }
+
+    public boolean isEmpty() {
+        return commands.isEmpty();
+    }
+
+    public String toJson() {
+        return commands.stream().map(c -> c.toJson()).collect(joining(",", "{", "}"));
+    }
+
+    /**
+     * Returns the message delay recommended by Philips
+     * Regarding to this article: https://developers.meethue.com/documentation/hue-system-performance
+     */
+    public long getMessageDelay() {
+        return commands.size() * 40L;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/CreateUserRequest.java
new file mode 100644 (file)
index 0000000..a763d17
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * 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.dto;
+
+/**
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+@SuppressWarnings("unused")
+public class CreateUserRequest {
+    private String username;
+    private String devicetype;
+
+    public CreateUserRequest(String username, String devicetype) {
+        if (Util.stringSize(devicetype) > 40) {
+            throw new IllegalArgumentException("Device type can be at most 40 characters long");
+        }
+
+        if (Util.stringSize(username) < 10 || Util.stringSize(username) > 40) {
+            throw new IllegalArgumentException("Username must be between 10 and 40 characters long");
+        }
+
+        this.username = username;
+        this.devicetype = devicetype;
+    }
+
+    public CreateUserRequest(String devicetype) {
+        if (Util.stringSize(devicetype) > 40) {
+            throw new IllegalArgumentException("Device type can be at most 40 characters long");
+        }
+
+        this.devicetype = devicetype;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ErrorResponse.java
new file mode 100644 (file)
index 0000000..82f4d36
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * 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.dto;
+
+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
+ */
+public class ErrorResponse {
+    public static final Type GSON_TYPE = new TypeToken<List<ErrorResponse>>() {
+    }.getType();
+
+    public class Error {
+        private Integer type;
+        private String address;
+        private String description;
+    }
+
+    private Error error;
+
+    public Integer getType() {
+        if (error == null) {
+            return null;
+        }
+        return error.type;
+    }
+
+    public String getAddress() {
+        if (error == null) {
+            return null;
+        }
+        return error.address;
+    }
+
+    public String getDescription() {
+        if (error == null) {
+            return null;
+        }
+        return error.description;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullConfig.java
new file mode 100644 (file)
index 0000000..0d4ee7c
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * 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.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Container for all data on a bridge.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class FullConfig {
+    private Map<String, FullLight> lights;
+    private Map<String, FullGroup> groups;
+    private Config config;
+
+    /**
+     * Returns detailed information about all lights known to the bridge.
+     *
+     * @return detailed lights list
+     */
+    public List<FullLight> getLights() {
+        ArrayList<FullLight> lightsList = new ArrayList<>();
+
+        for (String id : lights.keySet()) {
+            FullLight light = lights.get(id);
+            light.setId(id);
+            lightsList.add(light);
+        }
+
+        return lightsList;
+    }
+
+    /**
+     * Returns detailed information about all groups on the bridge.
+     *
+     * @return detailed groups list
+     */
+    public List<FullGroup> getGroups() {
+        ArrayList<FullGroup> groupsList = new ArrayList<>();
+
+        for (String id : groups.keySet()) {
+            FullGroup group = groups.get(id);
+            group.setId(id);
+            groupsList.add(group);
+        }
+
+        return groupsList;
+    }
+
+    /**
+     * Returns bridge configuration.
+     * Use HueBridge.getConfig() if you only need this.
+     *
+     * @return bridge configuration
+     */
+    public Config getConfig() {
+        return config;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullGroup.java
new file mode 100644 (file)
index 0000000..1d98cc1
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Detailed group information.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Laurent Garnier - field state added
+ */
+public class FullGroup extends Group {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, FullGroup>>() {
+    }.getType();
+
+    private State action;
+    private List<String> lights;
+    private State groupState; // Will not be set by hue API
+
+    FullGroup() {
+        super();
+    }
+
+    /**
+     * Test constructor
+     */
+    public FullGroup(String id, String name, String type, State action, List<String> lights, State state) {
+        super(id, name, type);
+        this.action = action;
+        this.lights = lights;
+        this.groupState = state;
+    }
+
+    /**
+     * Returns the last sent state update to the group.
+     * This does not have to reflect the current state of the group.
+     *
+     * @return last state update
+     */
+    public State getAction() {
+        return action;
+    }
+
+    /**
+     * Returns a list of the lights in the group.
+     *
+     * @return lights in the group
+     */
+    public List<String> getLightIds() {
+        return lights;
+    }
+
+    /**
+     * Returns the current state of the group.
+     *
+     * @return current state
+     */
+    public State getState() {
+        return groupState;
+    }
+
+    public void setState(State state) {
+        this.groupState = state;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullHueObject.java
new file mode 100644 (file)
index 0000000..224f037
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * 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.dto;
+
+import static org.openhab.binding.hue.internal.HueBindingConstants.NORMALIZE_ID_REGEX;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Detailed information about an object on the Hue Bridge
+ *
+ * @author Samuel Leisering - Initial contribution
+ * @author Christoph Weitkamp - Initial contribution
+ */
+@NonNullByDefault
+public class FullHueObject extends HueObject {
+
+    private @NonNullByDefault({}) String type;
+    private @Nullable String modelid;
+    @SerializedName("manufacturername")
+    private @NonNullByDefault({}) String manufacturerName;
+    @SerializedName("productname")
+    private @NonNullByDefault({}) String productName;
+    private @Nullable String swversion;
+    private @Nullable String uniqueid;
+
+    public FullHueObject() {
+        super();
+    }
+
+    /**
+     * Returns the type of the object.
+     *
+     * @return type
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Set the type of the object.
+     */
+    public void setType(final String type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns the model ID of the object.
+     *
+     * @return model id
+     */
+    public @Nullable String getModelID() {
+        return modelid;
+    }
+
+    public @Nullable String getNormalizedModelID() {
+        return modelid != null ? modelid.replaceAll(NORMALIZE_ID_REGEX, "_") : modelid;
+    }
+
+    /**
+     * Set the model ID of the object.
+     */
+    public void setModelID(final String modelId) {
+        this.modelid = modelId;
+    }
+
+    public String getManufacturerName() {
+        return manufacturerName;
+    }
+
+    public void setManufacturerName(String manufacturerName) {
+        this.manufacturerName = manufacturerName;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(String productName) {
+        this.productName = productName;
+    }
+
+    /**
+     * Returns the software version of the object.
+     *
+     * @return software version
+     */
+    public @Nullable String getSoftwareVersion() {
+        return swversion;
+    }
+
+    /**
+     * Returns the unique id of the object. The unique is the MAC address of the device with a unique endpoint id in the
+     * form: AA:BB:CC:DD:EE:FF:00:11-XX
+     *
+     * @return the unique id, can be null for some virtual types like the daylight sensor
+     */
+    public @Nullable String getUniqueID() {
+        return uniqueid;
+    }
+
+    /**
+     * Sets the unique id of the object.
+     */
+    public void setUniqueID(final String uniqueid) {
+        this.uniqueid = uniqueid;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullLight.java
new file mode 100644 (file)
index 0000000..13b1528
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.time.Duration;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Detailed light information.
+ *
+ * @author Q42 - Initial contribution
+ * @author Thomas Höfer - added unique id and changed range check for brightness and saturation
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Samuel Leisering - added GSon Type to FullLight, refactored content to {@link FullHueObject}
+ */
+@NonNullByDefault
+public class FullLight extends FullHueObject {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, FullLight>>() {
+    }.getType();
+
+    public @Nullable Capabilities capabilities;
+    private @NonNullByDefault({}) State state;
+    private final long fadetime = 400; // milliseconds
+
+    /**
+     * Returns the current state of the light.
+     *
+     * @return current state
+     */
+    public State getState() {
+        return state;
+    }
+
+    public Duration getFadeTime() {
+        return Duration.ofMillis(fadetime);
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/FullSensor.java
new file mode 100644 (file)
index 0000000..867c47c
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Detailed sensor information
+ *
+ * @author Samuel Leisering - Initial contribution
+ * @author Christoph Weitkamp - Initial contribution
+ */
+@NonNullByDefault
+public class FullSensor extends FullHueObject {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, FullSensor>>() {
+    }.getType();
+
+    public static final String STATE_LAST_UPDATED = "lastupdated";
+    public static final String STATE_BUTTON_EVENT = "buttonevent";
+    public static final String STATE_PRESENCE = "presence";
+    public static final String STATE_TEMPERATURE = "temperature";
+    public static final String STATE_LIGHT_LEVEL = "lightlevel";
+    public static final String STATE_DARK = "dark";
+    public static final String STATE_DAYLIGHT = "daylight";
+    public static final String STATE_STATUS = "status";
+    public static final String STATE_FLAG = "flag";
+
+    public static final String CONFIG_REACHABLE = "reachable";
+    public static final String CONFIG_BATTERY = "battery";
+    public static final String CONFIG_ON = "on";
+    public static final String CONFIG_LED_INDICATION = "ledindication";
+
+    public static final String CONFIG_PRESENCE_SENSITIVITY = "sensitivity";
+    public static final String CONFIG_PRESENCE_SENSITIVITY_MAX = "sensitivitymax";
+
+    public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_DARK = "tholddark";
+    public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_OFFSET = "tholdoffset";
+
+    private @NonNullByDefault({}) Map<String, Object> state;
+    private @NonNullByDefault({}) Map<String, Object> config;
+
+    public Map<String, Object> getState() {
+        return state;
+    }
+
+    public Map<String, Object> getConfig() {
+        return config;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Group.java
new file mode 100644 (file)
index 0000000..5c49d77
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * 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.dto;
+
+/**
+ * Basic group information.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Laurent Garnier - field type added
+ */
+public class Group {
+    private String id;
+    private String name;
+    private String type;
+
+    public Group() {
+        this.id = "0";
+        this.name = "Lightset 0";
+        this.type = "LightGroup";
+    }
+
+    /**
+     * Test constructor
+     */
+    Group(String id, String name, String type) {
+        this.id = id;
+        this.name = name;
+        this.type = type;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    void setType(String type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns if the group can be modified.
+     * Currently only returns false for the all lights pseudo group.
+     *
+     * @return modifiability of group
+     */
+    public boolean isModifiable() {
+        return !"0".equals(id);
+    }
+
+    /**
+     * Returns the id of the group.
+     *
+     * @return id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Returns the name of the group.
+     *
+     * @return name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the tyoe of the group.
+     *
+     * @return type
+     */
+    public String getType() {
+        return type;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/HueObject.java
new file mode 100644 (file)
index 0000000..82b8a5e
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Basic hue object information.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Samuel Leisering - introduced Sensor support, renamed supertype to HueObject
+ */
+public class HueObject {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, HueObject>>() {
+    }.getType();
+
+    private String id;
+    private String name;
+
+    HueObject() {
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the id of the light.
+     *
+     * @return id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Returns the name of the light.
+     *
+     * @return name
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/LightLevelConfigUpdate.java
new file mode 100644 (file)
index 0000000..5586d08
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * 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.dto;
+
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
+
+/**
+ * Updates the configuration of a light level sensor
+ *
+ * @author Samuel Leisering - Initial contribution
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class LightLevelConfigUpdate extends SensorConfigUpdate {
+    /**
+     *
+     * @param onOff
+     */
+    public void setLedIndication(boolean onOff) {
+        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
+    }
+
+    /**
+     *
+     * @param threshold
+     */
+    public void setThresholdDark(int threshold) {
+        commands.add(new Command(CONFIG_LIGHT_LEVEL_THRESHOLD_DARK, threshold));
+    }
+
+    /**
+     *
+     * @param offset
+     */
+    public void setThresholdOffset(int offset) {
+        commands.add(new Command(CONFIG_LIGHT_LEVEL_THRESHOLD_OFFSET, offset));
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/NewLightsResponse.java
new file mode 100644 (file)
index 0000000..37606af
--- /dev/null
@@ -0,0 +1,21 @@
+/**
+ * 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.dto;
+
+/**
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class NewLightsResponse {
+    public String lastscan;
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/PresenceConfigUpdate.java
new file mode 100644 (file)
index 0000000..c6cdf65
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * 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.dto;
+
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
+
+/**
+ * Updates the configuration of a presence sensor
+ *
+ * @author Samuel Leisering - Initial contribution
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class PresenceConfigUpdate extends SensorConfigUpdate {
+    /**
+     *
+     * @param onOff
+     */
+    public void setLedIndication(boolean onOff) {
+        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
+    }
+
+    /**
+     *
+     * @param sensitivity
+     */
+    public void setSensitivity(int sensitivity) {
+        commands.add(new Command(CONFIG_PRESENCE_SENSITIVITY, sensitivity));
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Scene.java
new file mode 100644 (file)
index 0000000..86d74b1
--- /dev/null
@@ -0,0 +1,161 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.types.StateOption;
+
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Basic scene information.
+ *
+ * @author Hengrui Jiang - Initial contribution
+ */
+@NonNullByDefault
+public class Scene {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, Scene>>() {
+    }.getType();
+
+    private @NonNullByDefault({}) String id;
+    private @NonNullByDefault({}) String name;
+    @SerializedName("lights")
+    private @NonNullByDefault({}) List<String> lightIds;
+    @SerializedName("group")
+    private @Nullable String groupId;
+    private boolean recycle;
+
+    /**
+     * Default constructor for GSon.
+     */
+    public Scene() {
+        super();
+    }
+
+    /**
+     * Test constructor
+     */
+    public Scene(String id, String name, @Nullable String groupId, List<String> lightIds, boolean recycle) {
+        this.id = id;
+        this.name = name;
+        this.groupId = groupId;
+        this.lightIds = lightIds;
+        this.recycle = recycle;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the human readable name of the scene. If the name is omitted upon creation, this
+     * defaults to the ID.
+     *
+     * @return human readable name of the scene
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the list of lights that the scene applies to. For group scenes, this list should be identical to the list
+     * of all lights that are in the group.
+     *
+     * @return list of lights that the scene applies to
+     */
+    public List<String> getLightIds() {
+        return lightIds;
+    }
+
+    /**
+     * Returns the group that the scene belongs to. This field is optional for scenes that applies to a specific list of
+     * lights instead of a group.
+     *
+     * @return the group that the scene belongs to
+     */
+    public @Nullable String getGroupId() {
+        return groupId;
+    }
+
+    /**
+     * Indicates if the scene can be recycled by the bridge. A recyclable scene is not able to be activated.
+     *
+     * @return whether the scene can be recycled
+     */
+    public boolean isRecycle() {
+        return recycle;
+    }
+
+    /**
+     * Creates a {@link StateOption} to display this scene, including the group that it belongs to.
+     * <p>
+     * The display name is built with the following pattern:
+     * <ol>
+     * <li>Human readable name of the scene if set. Otherwise, the ID is displayed</li>
+     * <li>Group for which the scene is defined</li>
+     * </ol>
+     */
+    public StateOption toStateOption(Map<String, String> groupNames) {
+        StringBuilder stateOptionLabel = new StringBuilder(name);
+        if (groupId != null && groupNames.containsKey(groupId)) {
+            stateOptionLabel.append(" (").append(groupNames.get(groupId)).append(")");
+        }
+
+        return new StateOption(id, stateOptionLabel.toString());
+    }
+
+    /**
+     * Creates a {@link StateOption} to display this scene.
+     */
+    public StateOption toStateOption() {
+        return new StateOption(id, name);
+    }
+
+    /**
+     * Returns whether the scene is applicable to the given group.
+     * <p>
+     * According to the hue API, a scene is applicable to a group if either
+     * <ol>
+     * <li>The scene is defined for the group</li>
+     * <li>All lights of the scene also belong to the group</li>
+     * </ol>
+     */
+    public boolean isApplicableTo(FullGroup group) {
+        if (getGroupId() == null) {
+            return getLightIds().stream().allMatch(id -> group.getLightIds().contains(id));
+        } else {
+            String groupId = getGroupId();
+            return groupId != null ? group.getId().contentEquals(groupId) : false;
+        }
+    }
+
+    public String extractKeyForComparator() {
+        return (groupId != null ? groupId : "") + "#" + name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{Scene name: %s; id: %s; lightIds: %s; groupId: %s; recycle: %s}", name, id, lightIds,
+                groupId, recycle);
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Schedule.java
new file mode 100644 (file)
index 0000000..9f467ec
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Basic schedule information.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class Schedule {
+    public static final Type GSON_TYPE = new TypeToken<Map<String, Schedule>>() {
+    }.getType();
+
+    private String id;
+    private String name;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/ScheduleUpdate.java
new file mode 100644 (file)
index 0000000..1c890ab
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * 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.dto;
+
+import java.util.Date;
+
+/**
+ * Collection of updates to a schedule.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - refactor configuration updates
+ */
+public class ScheduleUpdate extends ConfigUpdate {
+
+    /**
+     * Set the name of the schedule.
+     *
+     * @param name new name
+     * @return this object for chaining calls
+     */
+    public ScheduleUpdate setName(String name) {
+        if (Util.stringSize(name) > 32) {
+            throw new IllegalArgumentException("Schedule name can be at most 32 characters long");
+        }
+
+        commands.add(new Command("name", name));
+        return this;
+    }
+
+    /**
+     * Set the description of the schedule.
+     *
+     * @param description new description
+     * @return this object for chaining calls
+     */
+    public ScheduleUpdate setDescription(String description) {
+        if (Util.stringSize(description) > 64) {
+            throw new IllegalArgumentException("Schedule description can be at most 64 characters long");
+        }
+
+        commands.add(new Command("description", description));
+        return this;
+    }
+
+    /**
+     * Set the time of the schedule.
+     *
+     * @param time new time
+     * @return this object for chaining calls
+     */
+    public ScheduleUpdate setTime(Date time) {
+        commands.add(new Command("time", time));
+        return this;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SearchForLightsRequest.java
new file mode 100644 (file)
index 0000000..0a7a496
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * 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.dto;
+
+import java.util.List;
+
+/**
+ *
+ * @author Q42 - Initial contribution
+ * @author Andre Fuechsel - search for lights with given serial number added
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+@SuppressWarnings("unused")
+public class SearchForLightsRequest {
+    private List<String> deviceid;
+
+    public SearchForLightsRequest(List<String> deviceid) {
+        if (deviceid != null && (deviceid.isEmpty() || deviceid.size() > 16)) {
+            throw new IllegalArgumentException("Group cannot be empty and cannot have more than 16 lights");
+        }
+        if (deviceid != null) {
+            this.deviceid = deviceid;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SensorConfigUpdate.java
new file mode 100644 (file)
index 0000000..294a8d3
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.dto;
+
+import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_ON;
+
+/**
+ * Collection of updates to the sensor configuration.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class SensorConfigUpdate extends ConfigUpdate {
+    /**
+     *
+     * @param onOff
+     */
+    public void setOn(boolean onOff) {
+        commands.add(new Command(CONFIG_ON, onOff));
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SetAttributesRequest.java
new file mode 100644 (file)
index 0000000..91a7bf5
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * 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.dto;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+@SuppressWarnings("unused")
+@NonNullByDefault
+public class SetAttributesRequest {
+    private final @Nullable String name;
+    private final @Nullable List<String> lights;
+
+    public SetAttributesRequest(String name) {
+        this(name, null);
+    }
+
+    public SetAttributesRequest(List<HueObject> lights) {
+        this(null, lights);
+    }
+
+    public SetAttributesRequest(@Nullable String name, @Nullable List<HueObject> lights) {
+        if (name != null && Util.stringSize(name) > 32) {
+            throw new IllegalArgumentException("Name can be at most 32 characters long");
+        } else if (lights != null && (lights.isEmpty() || lights.size() > 16)) {
+            throw new IllegalArgumentException("Group cannot be empty and cannot have more than 16 lights");
+        }
+
+        this.name = name;
+        this.lights = lights == null ? null : lights.stream().map(l -> l.getId()).collect(Collectors.toList());
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SoftwareUpdate.java
new file mode 100644 (file)
index 0000000..2a216e5
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * 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.dto;
+
+/**
+ * Details of a bridge firmware update.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class SoftwareUpdate {
+    private int updatestate;
+    private String url;
+    private String text;
+    private boolean notify;
+
+    /**
+     * Returns the state of the update.
+     *
+     * <p>
+     * Actual meaning currently undocumented
+     *
+     * @return state of update
+     */
+    public int getUpdateState() {
+        return updatestate;
+    }
+
+    /**
+     * Returns the url of the changelog.
+     *
+     * @return changelog url
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Returns a description of the update.
+     *
+     * @return update description
+     */
+    public String getText() {
+        return text;
+    }
+
+    /**
+     * Returns if there will be a notification about this update.
+     *
+     * @return true for notification, false otherwise
+     */
+    public boolean isNotified() {
+        return notify;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/State.java
new file mode 100644 (file)
index 0000000..38e4f6c
--- /dev/null
@@ -0,0 +1,292 @@
+/**
+ * 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.dto;
+
+import java.util.Arrays;
+
+/**
+ * Current state of light.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ * @author Laurent Garnier - add few methods to update the object
+ */
+public class State {
+    private boolean on;
+    public int bri;
+    public int hue;
+    public int sat;
+    private float[] xy;
+    public int ct;
+    private String alert;
+    private String effect;
+    public String colormode;
+    private boolean reachable;
+
+    public State() {
+    }
+
+    /**
+     * Color modes of a light.
+     */
+    public enum ColorMode {
+        /**
+         * CIE color space coordinates
+         */
+        XY,
+
+        /**
+         * Hue and saturation
+         */
+        HS,
+
+        /**
+         * Color temperature in mired
+         */
+        CT
+    }
+
+    /**
+     * Alert modes of a light.
+     */
+    public enum AlertMode {
+        /**
+         * Light is not performing alert effect
+         */
+        NONE,
+
+        /**
+         * Light is performing one breathe cycle
+         */
+        SELECT,
+
+        /**
+         * Light is performing breathe cycles for 30 seconds (unless cancelled)
+         */
+        LSELECT
+    }
+
+    /**
+     * Effects possible for a light.
+     */
+    public enum Effect {
+        /**
+         * No effect
+         */
+        NONE,
+
+        /**
+         * Cycle through all hues with current saturation and brightness
+         */
+        COLORLOOP
+    }
+
+    /**
+     * Returns the on state.
+     *
+     * @return true if the light is on, false if it isn't
+     */
+    public boolean isOn() {
+        return on;
+    }
+
+    public void setOn(boolean on) {
+        this.on = on;
+    }
+
+    /**
+     * Returns the brightness.
+     *
+     * @return brightness
+     */
+    public int getBrightness() {
+        return bri;
+    }
+
+    public void setBri(int bri) {
+        this.bri = bri;
+    }
+
+    /**
+     * Returns the hue.
+     *
+     * @return hue
+     */
+    public int getHue() {
+        return hue;
+    }
+
+    public void setHue(int hue) {
+        this.hue = hue;
+    }
+
+    /**
+     * Returns the saturation.
+     *
+     * @return saturation
+     */
+    public int getSaturation() {
+        return sat;
+    }
+
+    public void setSaturation(int sat) {
+        this.sat = sat;
+    }
+
+    /**
+     * Returns the coordinates in CIE color space.
+     *
+     * @return cie color spaces coordinates
+     */
+    public float[] getXY() {
+        return xy;
+    }
+
+    public void setXY(float[] xy) {
+        this.xy = xy;
+    }
+
+    /**
+     * Returns the color temperature.
+     *
+     * @return color temperature
+     */
+    public int getColorTemperature() {
+        return ct;
+    }
+
+    public void setColorTemperature(int ct) {
+        this.ct = ct;
+    }
+
+    /**
+     * Returns the last alert mode set.
+     * Future firmware updates may change this to actually report the current alert mode.
+     *
+     * @return last alert mode
+     */
+    public AlertMode getAlertMode() {
+        if (alert == null) {
+            return null;
+        }
+        return AlertMode.valueOf(alert.toUpperCase());
+    }
+
+    /**
+     * Returns the current color mode.
+     *
+     * @return current color mode
+     */
+    public ColorMode getColorMode() {
+        if (colormode == null) {
+            return null;
+        }
+        return ColorMode.valueOf(colormode.toUpperCase());
+    }
+
+    public void setColormode(ColorMode colormode) {
+        this.colormode = colormode.name();
+    }
+
+    /**
+     * Returns the current active effect.
+     *
+     * @return current active effect
+     */
+    public Effect getEffect() {
+        if (effect == null) {
+            return null;
+        }
+        return Effect.valueOf(effect.toUpperCase());
+    }
+
+    /**
+     * Returns reachability.
+     *
+     * @return true if reachable, false if it isn't
+     */
+    public boolean isReachable() {
+        return reachable;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((alert == null) ? 0 : alert.hashCode());
+        result = prime * result + bri;
+        result = prime * result + ((colormode == null) ? 0 : colormode.hashCode());
+        result = prime * result + ct;
+        result = prime * result + ((effect == null) ? 0 : effect.hashCode());
+        result = prime * result + hue;
+        result = prime * result + (on ? 1231 : 1237);
+        result = prime * result + (reachable ? 1231 : 1237);
+        result = prime * result + sat;
+        result = prime * result + Arrays.hashCode(xy);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        State other = (State) obj;
+        if (alert == null) {
+            if (other.alert != null) {
+                return false;
+            }
+        } else if (!alert.equals(other.alert)) {
+            return false;
+        }
+        if (bri != other.bri) {
+            return false;
+        }
+        if (colormode == null) {
+            if (other.colormode != null) {
+                return false;
+            }
+        } else if (!colormode.equals(other.colormode)) {
+            return false;
+        }
+        if (ct != other.ct) {
+            return false;
+        }
+        if (effect == null) {
+            if (other.effect != null) {
+                return false;
+            }
+        } else if (!effect.equals(other.effect)) {
+            return false;
+        }
+        if (hue != other.hue) {
+            return false;
+        }
+        if (on != other.on) {
+            return false;
+        }
+        if (reachable != other.reachable) {
+            return false;
+        }
+        if (sat != other.sat) {
+            return false;
+        }
+        return Arrays.equals(xy, other.xy);
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/StateUpdate.java
new file mode 100644 (file)
index 0000000..d991c88
--- /dev/null
@@ -0,0 +1,233 @@
+/**
+ * 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.dto;
+
+import org.openhab.binding.hue.internal.dto.State.AlertMode;
+import org.openhab.binding.hue.internal.dto.State.Effect;
+
+/**
+ * Collection of updates to the state of a light.
+ *
+ * @author Q42 - Initial contribution
+ * @author Thomas Höfer - added unique id and changed range check for brightness and saturation
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding, minor code cleanup
+ * @author Samuel Leisering - refactor configuration updates
+ */
+public class StateUpdate extends ConfigUpdate {
+
+    private Integer colorTemperature;
+    private Integer brightness;
+
+    /**
+     * Turn light on.
+     *
+     * @return this object for chaining calls
+     */
+    public StateUpdate turnOn() {
+        return setOn(true);
+    }
+
+    /**
+     * Turn light off.
+     *
+     * @return this object for chaining calls
+     */
+    public StateUpdate turnOff() {
+        return setOn(false);
+    }
+
+    /**
+     * Turn light on or off.
+     *
+     * @param on on if true, off otherwise
+     * @return this object for chaining calls
+     */
+    public StateUpdate setOn(boolean on) {
+        commands.add(new Command("on", on));
+        return this;
+    }
+
+    /**
+     * Set brightness of light.
+     * Brightness 0 is not the same as off.
+     *
+     * @param brightness brightness [1..254]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setBrightness(int brightness) {
+        if (brightness < 1 || brightness > 254) {
+            throw new IllegalArgumentException("Brightness out of range");
+        }
+
+        commands.add(new Command("bri", brightness));
+        this.brightness = brightness;
+        return this;
+    }
+
+    public Integer getBrightness() {
+        return this.brightness;
+    }
+
+    /**
+     * Switch to HS color mode and set hue.
+     *
+     * @param hue hue [0..65535]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setHue(int hue) {
+        if (hue < 0 || hue > 65535) {
+            throw new IllegalArgumentException("Hue out of range");
+        }
+
+        commands.add(new Command("hue", hue));
+        return this;
+    }
+
+    /**
+     * Switch to HS color mode and set saturation.
+     *
+     * @param saturation saturation [0..254]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setSat(int saturation) {
+        if (saturation < 0 || saturation > 254) {
+            throw new IllegalArgumentException("Saturation out of range");
+        }
+
+        commands.add(new Command("sat", saturation));
+        return this;
+    }
+
+    /**
+     * Switch to XY color mode and set CIE color space coordinates.
+     *
+     * @param x x coordinate [0..1]
+     * @param y y coordinate [0..1]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setXY(float x, float y) {
+        return setXY(new float[] { x, y });
+    }
+
+    /**
+     * Switch to XY color mode and set CIE color space coordinates.
+     *
+     * @param xy x and y coordinates [0..1, 0..1]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setXY(float[] xy) {
+        if (xy.length != 2) {
+            throw new IllegalArgumentException("Invalid coordinate array given");
+        } else if (xy[0] < 0.0f || xy[0] > 1.0f || xy[1] < 0.0f || xy[1] > 1.0f) {
+            throw new IllegalArgumentException("X and/or Y coordinate(s) out of bounds");
+        }
+
+        commands.add(new Command("xy", xy));
+        return this;
+    }
+
+    /**
+     * Switch to CT color mode and set color temperature in mired.
+     *
+     * @param colorTemperature color temperature
+     * @return this object for chaining calls
+     */
+    public StateUpdate setColorTemperature(int colorTemperature, ColorTemperature capabilities) {
+        if (colorTemperature < capabilities.min || colorTemperature > capabilities.max) {
+            throw new IllegalArgumentException(String.format("Color temperature %d is out of range [%d..%d]",
+                    colorTemperature, capabilities.min, capabilities.max));
+        }
+
+        commands.add(new Command("ct", colorTemperature));
+        this.colorTemperature = colorTemperature;
+        return this;
+    }
+
+    public Integer getColorTemperature() {
+        return this.colorTemperature;
+    }
+
+    /**
+     * Set the alert mode.
+     *
+     * @see AlertMode
+     * @param mode alert mode
+     * @return this object for chaining calls
+     */
+    public StateUpdate setAlert(AlertMode mode) {
+        commands.add(new Command("alert", mode.toString().toLowerCase()));
+        return this;
+    }
+
+    /**
+     * Set the current effect.
+     *
+     * @see Effect
+     * @param effect effect
+     * @return this object for chaining calls
+     */
+    public StateUpdate setEffect(Effect effect) {
+        commands.add(new Command("effect", effect.toString().toLowerCase()));
+        return this;
+    }
+
+    /**
+     * Set the transition time from the current state to the new state.
+     * Time is accurate to 100 milliseconds.
+     *
+     * @param timeMillis time in milliseconds [0..6553600]
+     * @return this object for chaining calls
+     */
+    public StateUpdate setTransitionTime(long timeMillis) {
+        if (timeMillis < 0 || timeMillis > 6553600) {
+            throw new IllegalArgumentException("Transition time out of range");
+        }
+
+        commands.add(new Command("transitiontime", timeMillis / 100));
+        return this;
+    }
+
+    /**
+     * Turn sensor flag on or off.
+     *
+     * @param flag on if true, off otherwise
+     * @return this object for chaining calls
+     */
+
+    public StateUpdate setFlag(boolean flag) {
+        commands.add(new Command("flag", flag));
+        return this;
+    }
+
+    /**
+     * Set status of sensor.
+     *
+     * @param status status
+     * @return this object for chaining calls
+     */
+    public StateUpdate setStatus(int status) {
+        commands.add(new Command("status", status));
+        return this;
+    }
+
+    /**
+     * Recall the given scene.
+     *
+     * @param sceneId Identifier of the scene
+     * @return this object for chaining calls
+     */
+    public StateUpdate setScene(String sceneId) {
+        commands.add(new Command("scene", sceneId));
+        return this;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/SuccessResponse.java
new file mode 100644 (file)
index 0000000..e0cc18a
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.dto;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class SuccessResponse {
+    public static final Type GSON_TYPE = new TypeToken<List<SuccessResponse>>() {
+    }.getType();
+
+    public Map<String, Object> success;
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/TemperatureConfigUpdate.java
new file mode 100644 (file)
index 0000000..017dee9
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.dto;
+
+import static org.openhab.binding.hue.internal.dto.FullSensor.CONFIG_LED_INDICATION;
+
+/**
+ * Updates the configuration of a temperature sensor
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+public class TemperatureConfigUpdate extends SensorConfigUpdate {
+    /**
+     *
+     * @param onOff
+     */
+    public void setLedIndication(boolean onOff) {
+        commands.add(new Command(CONFIG_LED_INDICATION, onOff));
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/User.java
new file mode 100644 (file)
index 0000000..30c375f
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * 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.dto;
+
+import java.util.Date;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * A whitelisted user.
+ *
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+public class User {
+    @SerializedName("last use date")
+    private Date lastUseDate;
+
+    @SerializedName("create date")
+    private Date createDate;
+
+    private String name;
+
+    /**
+     * Returns the last time a command was issued as this user.
+     *
+     * @return time of last command by this user
+     */
+    public Date getLastUseDate() {
+        return lastUseDate;
+    }
+
+    /**
+     * Returns the date this user was created.
+     *
+     * @return creation date of user
+     */
+    public Date getCreationDate() {
+        return createDate;
+    }
+
+    /**
+     * Returns the username of this user.
+     *
+     * @return username
+     */
+    public String getUsername() {
+        return name;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/Util.java
new file mode 100644 (file)
index 0000000..1eced14
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * 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.dto;
+
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * @author Q42 - Initial contribution
+ * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
+ */
+@NonNullByDefault
+public final class Util {
+
+    private Util() {
+    }
+
+    // This is used to check what byte size strings have, because the bridge doesn't natively support UTF-8
+    public static int stringSize(String str) {
+        return str.getBytes(StandardCharsets.UTF_8).length;
+    }
+
+    public static @Nullable String quickMatch(String needle, String haystack) {
+        Matcher m = Pattern.compile(needle).matcher(haystack);
+        m.find();
+        return m.group(1);
+    }
+}
index b678b7beb2ee93038824a1605d3acd1ed65cdbdb..10fb3d95419fc22e7c9e16a34e25c0061f649e45 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when the API returns an unknown error.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class ApiException extends Exception {
     public ApiException() {
     }
index 42eb58996836632850e05fd0d1600574e5306fc4..1a377c47a63555ac36520ece09067049f885f129 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when trying to change the state of a light that is off.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class DeviceOffException extends ApiException {
     public DeviceOffException() {
     }
index 339e09df1d6396822e2f0441f5f564bfaea933fb..628e61bd3e20cc3236ed41af4323902accd8cda1 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when operating on a group, light or user that doesn't exist.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class EntityNotAvailableException extends ApiException {
     public EntityNotAvailableException() {
     }
index 698d1cda640f65ca6a83f5d075f2a477af4bc808..1d761d3ceed5d2bd86fec155ace2c0eacbdae095 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when adding more than 16 groups (excluding all lights group) to a bridge.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class GroupTableFullException extends ApiException {
     public GroupTableFullException() {
     }
index 1f61e138f822085e2f7888910880e8216f12a3b2..259e26fe8c2ab5167b0c1ac6ffe9b1ebe754e0d8 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when scheduling an invalid command.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class InvalidCommandException extends ApiException {
     public InvalidCommandException() {
     }
index 78ff7d516eb0d2e5b2da704448b0baf2172f0e06..7ff36c1d9dadb73ecef40ab55d3b645f413a4705 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown if the link button hasn't been pressed in the last 30 seconds.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class LinkButtonException extends ApiException {
     public LinkButtonException() {
     }
index a105d6552508cbf52061a0ee19035d3d7427982b..ab025f68321dcbb7f74f5fc66eff23fc6e10724c 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.hue.internal.exceptions;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * Thrown when the specified user is no longer whitelisted on the bridge.
  *
@@ -19,6 +21,7 @@ package org.openhab.binding.hue.internal.exceptions;
  * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
  */
 @SuppressWarnings("serial")
+@NonNullByDefault
 public class UnauthorizedException extends ApiException {
     public UnauthorizedException() {
     }
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/factory/HueThingHandlerFactory.java
new file mode 100644 (file)
index 0000000..d2783c0
--- /dev/null
@@ -0,0 +1,180 @@
+/**
+ * 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.factory;
+
+import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
+import org.openhab.binding.hue.internal.handler.HueGroupHandler;
+import org.openhab.binding.hue.internal.handler.HueLightHandler;
+import org.openhab.binding.hue.internal.handler.HueStateDescriptionProvider;
+import org.openhab.binding.hue.internal.handler.sensors.ClipHandler;
+import org.openhab.binding.hue.internal.handler.sensors.DimmerSwitchHandler;
+import org.openhab.binding.hue.internal.handler.sensors.GeofencePresenceHandler;
+import org.openhab.binding.hue.internal.handler.sensors.LightLevelHandler;
+import org.openhab.binding.hue.internal.handler.sensors.PresenceHandler;
+import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
+import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * {@link HueThingHandlerFactory} is a factory for {@link HueBridgeHandler}s.
+ *
+ * @author Dennis Nobel - Initial contribution of hue binding
+ * @author Kai Kreuzer - added supportsThingType method
+ * @author Andre Fuechsel - implemented to use one discovery service per bridge
+ * @author Samuel Leisering - Added support for sensor API
+ * @author Christoph Weitkamp - Added support for sensor API
+ * @author Laurent Garnier - Added support for groups
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
+public class HueThingHandlerFactory extends BaseThingHandlerFactory {
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
+            .of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(),
+                    DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(),
+                    PresenceHandler.SUPPORTED_THING_TYPES.stream(),
+                    GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
+                    TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
+                    ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
+            .flatMap(i -> i).collect(Collectors.toUnmodifiableSet());
+
+    private final HttpClient httpClient;
+    private final HueStateDescriptionProvider stateDescriptionProvider;
+    private final TranslationProvider i18nProvider;
+    private final LocaleProvider localeProvider;
+
+    @Activate
+    public HueThingHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
+            final @Reference HueStateDescriptionProvider stateDescriptionProvider,
+            final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) {
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+        this.stateDescriptionProvider = stateDescriptionProvider;
+        this.i18nProvider = i18nProvider;
+        this.localeProvider = localeProvider;
+    }
+
+    @Override
+    public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
+            @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
+        if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
+            return super.createThing(thingTypeUID, configuration, thingUID, null);
+        } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
+            ThingUID hueLightUID = getLightUID(thingTypeUID, thingUID, configuration, bridgeUID);
+            return super.createThing(thingTypeUID, configuration, hueLightUID, bridgeUID);
+        } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || PresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || TemperatureHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || LightLevelHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
+                || ClipHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
+            ThingUID hueSensorUID = getSensorUID(thingTypeUID, thingUID, configuration, bridgeUID);
+            return super.createThing(thingTypeUID, configuration, hueSensorUID, bridgeUID);
+        } else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
+            ThingUID hueGroupUID = getGroupUID(thingTypeUID, thingUID, configuration, bridgeUID);
+            return super.createThing(thingTypeUID, configuration, hueGroupUID, bridgeUID);
+        }
+
+        throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the Hue binding.");
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES.contains(thingTypeUID);
+    }
+
+    private ThingUID getLightUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
+            @Nullable ThingUID bridgeUID) {
+        if (thingUID != null) {
+            return thingUID;
+        } else {
+            return getThingUID(thingTypeUID, configuration.get(LIGHT_ID).toString(), bridgeUID);
+        }
+    }
+
+    private ThingUID getSensorUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
+            @Nullable ThingUID bridgeUID) {
+        if (thingUID != null) {
+            return thingUID;
+        } else {
+            return getThingUID(thingTypeUID, configuration.get(SENSOR_ID).toString(), bridgeUID);
+        }
+    }
+
+    private ThingUID getGroupUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
+            @Nullable ThingUID bridgeUID) {
+        if (thingUID != null) {
+            return thingUID;
+        } else {
+            return getThingUID(thingTypeUID, configuration.get(GROUP_ID).toString(), bridgeUID);
+        }
+    }
+
+    private ThingUID getThingUID(ThingTypeUID thingTypeUID, String id, @Nullable ThingUID bridgeUID) {
+        if (bridgeUID != null) {
+            return new ThingUID(thingTypeUID, id, bridgeUID.getId());
+        } else {
+            return new ThingUID(thingTypeUID, id);
+        }
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new HueBridgeHandler((Bridge) thing, httpClient, stateDescriptionProvider, i18nProvider,
+                    localeProvider);
+        } else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new HueLightHandler(thing, stateDescriptionProvider);
+        } else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new DimmerSwitchHandler(thing);
+        } else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new TapSwitchHandler(thing);
+        } else if (PresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new PresenceHandler(thing);
+        } else if (GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new GeofencePresenceHandler(thing);
+        } else if (TemperatureHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new TemperatureHandler(thing);
+        } else if (LightLevelHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new LightLevelHandler(thing);
+        } else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new ClipHandler(thing);
+        } else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+            return new HueGroupHandler(thing, stateDescriptionProvider);
+        } else {
+            return null;
+        }
+    }
+}
index cfbf26a6262a73942f356f0a30126aaa583454eb..2494c7fb388db403b9507ed501704ed2b111ea88 100644 (file)
@@ -15,8 +15,8 @@ package org.openhab.binding.hue.internal.handler;
 import java.util.List;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.Scene;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.Scene;
 
 /**
  * The {@link GroupStatusListener} is notified when a group status has changed or a group has been removed or added.
index 3257fae32f82391c0bd4dfe0a83103a9e4560ee9..ade906cdb36e80a8f86d09e867cfcefc1b3abc43 100644 (file)
@@ -18,7 +18,6 @@ import static org.openhab.core.thing.Thing.*;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -33,20 +32,21 @@ import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.ApiVersionUtils;
-import org.openhab.binding.hue.internal.Config;
-import org.openhab.binding.hue.internal.ConfigUpdate;
-import org.openhab.binding.hue.internal.FullConfig;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.HueBridge;
-import org.openhab.binding.hue.internal.HueConfigStatusMessage;
-import org.openhab.binding.hue.internal.Scene;
-import org.openhab.binding.hue.internal.State;
-import org.openhab.binding.hue.internal.StateUpdate;
+import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.hue.internal.config.HueBridgeConfig;
+import org.openhab.binding.hue.internal.connection.HueBridge;
+import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider;
 import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
+import org.openhab.binding.hue.internal.dto.ApiVersionUtils;
+import org.openhab.binding.hue.internal.dto.Config;
+import org.openhab.binding.hue.internal.dto.ConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullConfig;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.Scene;
+import org.openhab.binding.hue.internal.dto.State;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.binding.hue.internal.exceptions.ApiException;
 import org.openhab.binding.hue.internal.exceptions.DeviceOffException;
 import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException;
@@ -54,8 +54,11 @@ import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
 import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.config.core.status.ConfigStatusMessage;
+import org.openhab.core.i18n.CommunicationException;
+import org.openhab.core.i18n.ConfigurationException;
 import org.openhab.core.i18n.LocaleProvider;
 import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.StringType;
@@ -68,11 +71,13 @@ import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
 import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.StateOption;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * {@link HueBridgeHandler} is the handler for a hue bridge and connects it to
+ * {@link HueBridgeHandler} is the handler for a Hue Bridge and connects it to
  * the framework. All {@link HueLightHandler}s use the {@link HueBridgeHandler} to execute the actual commands.
  *
  * @author Dennis Nobel - Initial contribution
@@ -93,12 +98,13 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
 
     private static final long BYPASS_MIN_DURATION_BEFORE_CMD = 1500L;
-
-    private static final String DEVICE_TYPE = "EclipseSmartHome";
-
     private static final long SCENE_POLLING_INTERVAL = TimeUnit.SECONDS.convert(10, TimeUnit.MINUTES);
 
+    private static final String DEVICE_TYPE = "openHAB";
+
     private final Logger logger = LoggerFactory.getLogger(HueBridgeHandler.class);
+    private @Nullable ServiceRegistration<?> serviceRegistration;
+    private final HttpClient httpClient;
     private final HueStateDescriptionProvider stateDescriptionOptionProvider;
     private final TranslationProvider i18nProvider;
     private final LocaleProvider localeProvider;
@@ -120,8 +126,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
             try {
                 pollingLock.lock();
                 if (!lastBridgeConnectionState) {
-                    // if user is not set in configuration try to create a new user on Hue bridge
-                    if (hueBridgeConfig.getUserName() == null) {
+                    // if user is not set in configuration try to create a new user on Hue Bridge
+                    if (hueBridgeConfig.userName == null) {
                         hueBridge.getFullConfig();
                     }
                     lastBridgeConnectionState = tryResumeBridgeConnection();
@@ -132,6 +138,8 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
                         updateStatus(ThingStatus.ONLINE);
                     }
                 }
+            } catch (ConfigurationException e) {
+                handleConfigurationFailure(e);
             } catch (UnauthorizedException | IllegalStateException e) {
                 if (isReachable(hueBridge.getIPAddress())) {
                     lastBridgeConnectionState = false;
@@ -142,7 +150,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
                     lastBridgeConnectionState = false;
                     onConnectionLost();
                 }
-            } catch (ApiException | IOException e) {
+            } catch (ApiException | CommunicationException | IOException e) {
                 if (hueBridge != null && lastBridgeConnectionState) {
                     logger.debug("Connection to Hue Bridge {} lost: {}", hueBridge.getIPAddress(), e.getMessage(), e);
                     lastBridgeConnectionState = false;
@@ -165,7 +173,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
 
                 // If there is no connection, this line will fail
                 hueBridge.authenticate("invalid");
-            } catch (IOException e) {
+            } catch (ConfigurationException | IOException e) {
                 return false;
             } catch (ApiException e) {
                 String message = e.getMessage();
@@ -407,9 +415,11 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
 
     private List<String> consoleScenesList = new ArrayList<>();
 
-    public HueBridgeHandler(Bridge bridge, HueStateDescriptionProvider stateDescriptionOptionProvider,
-            TranslationProvider i18nProvider, LocaleProvider localeProvider) {
+    public HueBridgeHandler(Bridge bridge, HttpClient httpClient,
+            HueStateDescriptionProvider stateDescriptionOptionProvider, TranslationProvider i18nProvider,
+            LocaleProvider localeProvider) {
         super(bridge);
+        this.httpClient = httpClient;
         this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
         this.i18nProvider = i18nProvider;
         this.localeProvider = localeProvider;
@@ -417,7 +427,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
 
     @Override
     public Collection<Class<? extends ThingHandlerService>> getServices() {
-        return Collections.singleton(HueDeviceDiscoveryService.class);
+        return Set.of(HueDeviceDiscoveryService.class);
     }
 
     @Override
@@ -596,10 +606,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
         ScheduledFuture<?> job = lightPollingJob;
         if (job == null || job.isCancelled()) {
             long lightPollingInterval;
-            int configPollingInterval = hueBridgeConfig.getPollingInterval();
+            int configPollingInterval = hueBridgeConfig.pollingInterval;
             if (configPollingInterval < 1) {
                 lightPollingInterval = TimeUnit.SECONDS.toSeconds(10);
-                logger.info("Wrong configuration value for polling interval. Using default value: {}s",
+                logger.warn("Wrong configuration value for polling interval. Using default value: {}s",
                         lightPollingInterval);
             } else {
                 lightPollingInterval = configPollingInterval;
@@ -621,12 +631,12 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
     private void startSensorPolling() {
         ScheduledFuture<?> job = sensorPollingJob;
         if (job == null || job.isCancelled()) {
-            int configSensorPollingInterval = hueBridgeConfig.getSensorPollingInterval();
+            int configSensorPollingInterval = hueBridgeConfig.sensorPollingInterval;
             if (configSensorPollingInterval > 0) {
                 long sensorPollingInterval;
                 if (configSensorPollingInterval < 50) {
                     sensorPollingInterval = TimeUnit.MILLISECONDS.toMillis(500);
-                    logger.info("Wrong configuration value for sensor polling interval. Using default value: {}ms",
+                    logger.warn("Wrong configuration value for sensor polling interval. Using default value: {}ms",
                             sensorPollingInterval);
                 } else {
                     sensorPollingInterval = configSensorPollingInterval;
@@ -665,7 +675,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
 
     @Override
     public void dispose() {
-        logger.debug("Handler disposed.");
+        logger.debug("Disposing Hue Bridge handler ...");
         Future<?> job = initJob;
         if (job != null) {
             job.cancel(true);
@@ -676,46 +686,50 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
         if (hueBridge != null) {
             hueBridge = null;
         }
+        ServiceRegistration<?> localServiceRegistration = serviceRegistration;
+        if (localServiceRegistration != null) {
+            // remove trustmanager service
+            localServiceRegistration.unregister();
+            serviceRegistration = null;
+        }
     }
 
     @Override
     public void initialize() {
-        logger.debug("Initializing hue bridge handler.");
+        logger.debug("Initializing Hue Bridge handler ...");
         hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
 
-        String ip = hueBridgeConfig.getIpAddress();
+        String ip = hueBridgeConfig.ipAddress;
         if (ip == null || ip.isEmpty()) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-no-ip-address");
         } else {
             if (hueBridge == null) {
-                hueBridge = new HueBridge(ip, hueBridgeConfig.getPort(), hueBridgeConfig.getProtocol(), scheduler);
-                hueBridge.setTimeout(5000);
+                if (HueBridgeConfig.HTTPS.equals(hueBridgeConfig.protocol)) {
+                    // register trustmanager service
+                    HueTlsTrustManagerProvider tlsTrustManagerProvider = new HueTlsTrustManagerProvider(
+                            ip + ":" + hueBridgeConfig.getPort(), hueBridgeConfig.useSelfSignedCertificate);
+                    serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext()
+                            .registerService(TlsTrustManagerProvider.class.getName(), tlsTrustManagerProvider, null);
+                }
 
-                updateStatus(ThingStatus.UNKNOWN);
+                hueBridge = new HueBridge(httpClient, ip, hueBridgeConfig.getPort(), hueBridgeConfig.protocol,
+                        scheduler);
 
-                // 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 {
-                    }
-                });
+                updateStatus(ThingStatus.UNKNOWN);
             }
             onUpdate();
         }
     }
 
     public @Nullable String getUserName() {
-        return hueBridgeConfig == null ? null : hueBridgeConfig.getUserName();
+        return hueBridgeConfig == null ? null : hueBridgeConfig.userName;
     }
 
     private synchronized void onUpdate() {
-        if (hueBridge != null) {
-            startLightPolling();
-            startSensorPolling();
-            startScenePolling();
-        }
+        startLightPolling();
+        startSensorPolling();
+        startScenePolling();
     }
 
     /**
@@ -761,9 +775,9 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
      */
     private boolean tryResumeBridgeConnection() throws IOException, ApiException {
         logger.debug("Connection to Hue Bridge {} established.", hueBridge.getIPAddress());
-        if (hueBridgeConfig.getUserName() == null) {
+        if (hueBridgeConfig.userName == null) {
             logger.warn(
-                    "User name for Hue bridge authentication not available in configuration. Setting ThingStatus to OFFLINE.");
+                    "User name for Hue Bridge authentication not available in configuration. Setting ThingStatus to OFFLINE.");
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-no-username");
             return false;
@@ -780,21 +794,24 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
      * If there is a user name available, it attempts to re-authenticate. Otherwise new authentication credentials will
      * be requested from the bridge.
      *
-     * @param bridge the hue bridge the connection is not authorized
+     * @param bridge the Hue Bridge the connection is not authorized
      * @return returns {@code true} if re-authentication was successful, {@code false} otherwise
      */
     public boolean onNotAuthenticated() {
         if (hueBridge == null) {
             return false;
         }
-        String userName = hueBridgeConfig.getUserName();
+        String userName = hueBridgeConfig.userName;
         if (userName == null) {
             createUser();
         } else {
             try {
                 hueBridge.authenticate(userName);
                 return true;
+            } catch (ConfigurationException e) {
+                handleConfigurationFailure(e);
             } catch (Exception e) {
+                logger.trace("", e);
                 handleAuthenticationFailure(e, userName);
             }
         }
@@ -813,10 +830,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
     }
 
     private String createUserOnPhysicalBridge() throws IOException, ApiException {
-        logger.info("Creating new user on Hue bridge {} - please press the pairing button on the bridge.",
-                hueBridgeConfig.getIpAddress());
+        logger.info("Creating new user on Hue Bridge {} - please press the pairing button on the bridge.",
+                hueBridgeConfig.ipAddress);
         String userName = hueBridge.link(DEVICE_TYPE);
-        logger.info("User has been successfully added to Hue bridge.");
+        logger.info("User has been successfully added to Hue Bridge.");
         return userName;
     }
 
@@ -829,26 +846,32 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
             hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
         } catch (IllegalStateException e) {
             logger.trace("Configuration update failed.", e);
-            logger.warn("Unable to update configuration of Hue bridge.");
+            logger.warn("Unable to update configuration of Hue Bridge.");
             logger.warn("Please configure the user name manually.");
         }
     }
 
+    private void handleConfigurationFailure(ConfigurationException ex) {
+        logger.warn(
+                "Invalid certificate for secured connection. You might want to enable the \"Use Self-Signed Certificate\" configuration.");
+        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getRawMessage());
+    }
+
     private void handleAuthenticationFailure(Exception ex, String userName) {
-        logger.warn("User is not authenticated on Hue bridge {}", hueBridgeConfig.getIpAddress());
+        logger.warn("User is not authenticated on Hue Bridge {}", hueBridgeConfig.ipAddress);
         logger.warn("Please configure a valid user or remove user from configuration to generate a new one.");
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                 "@text/offline.conf-error-invalid-username");
     }
 
     private void handleLinkButtonNotPressed(LinkButtonException ex) {
-        logger.debug("Failed creating new user on Hue bridge: {}", ex.getMessage());
+        logger.debug("Failed creating new user on Hue Bridge: {}", ex.getMessage());
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                 "@text/offline.conf-error-press-pairing-button");
     }
 
     private void handleExceptionWhileCreatingUser(Exception ex) {
-        logger.warn("Failed creating new user on Hue bridge", ex);
+        logger.warn("Failed creating new user on Hue Bridge", ex);
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                 "@text/offline.conf-error-creation-username");
     }
@@ -1041,10 +1064,10 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
         // Check whether an IP address is provided
         hueBridgeConfig = getConfigAs(HueBridgeConfig.class);
 
-        String ip = hueBridgeConfig.getIpAddress();
+        String ip = hueBridgeConfig.ipAddress;
         if (ip == null || ip.isEmpty()) {
-            return List.of(ConfigStatusMessage.Builder.error(HOST)
-                    .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build());
+            return List.of(ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
+                    .withArguments(HOST).build());
         } else {
             return List.of();
         }
index 6d6dad65ede3cd701f29b7b90d60e37c18f40d67..898730ce2aa7862aaef4c9c499712420c9628ad6 100644 (file)
@@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.ConfigUpdate;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
+import org.openhab.binding.hue.internal.dto.ConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 
 /**
  * Access to the Hue system for light handlers.
index f8860bde8789cd47ea99573e327a9940d1e638bb..8d917fe3cb47e56c67ce4e78705aa670f523690e 100644 (file)
@@ -25,11 +25,11 @@ import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.Scene;
-import org.openhab.binding.hue.internal.State;
-import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.Scene;
+import org.openhab.binding.hue.internal.dto.State;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
@@ -87,7 +87,7 @@ public class HueGroupHandler extends BaseThingHandler implements HueLightActions
 
     @Override
     public void initialize() {
-        logger.debug("Initializing hue group handler.");
+        logger.debug("Initializing Hue group handler.");
         Bridge bridge = getBridge();
         initializeThing((bridge == null) ? null : bridge.getStatus());
     }
@@ -173,13 +173,13 @@ public class HueGroupHandler extends BaseThingHandler implements HueLightActions
     public void handleCommand(String channel, Command command, long fadeTime) {
         HueClient bridgeHandler = getHueClient();
         if (bridgeHandler == null) {
-            logger.debug("hue bridge handler not found. Cannot handle command without bridge.");
+            logger.debug("Hue Bridge handler not found. Cannot handle command without bridge.");
             return;
         }
 
         FullGroup group = bridgeHandler.getGroupById(groupId);
         if (group == null) {
-            logger.debug("hue group not known on bridge. Cannot handle command.");
+            logger.debug("Hue group not known on bridge. Cannot handle command.");
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-wrong-group-id");
             return;
index cf890b5bb1fbeefbc4c18dd7d2e43541c1138c13..6b145ff72d263671a0cb2f9e7cd4f68e090b4bde 100644 (file)
@@ -24,11 +24,11 @@ import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.State;
-import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.binding.hue.internal.dto.Capabilities;
 import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.State;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
@@ -51,7 +51,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * {@link HueLightHandler} is the handler for a hue light. It uses the {@link HueClient} to execute the actual
+ * {@link HueLightHandler} is the handler for a Hue light. It uses the {@link HueClient} to execute the actual
  * command.
  *
  * @author Dennis Nobel - Initial contribution
@@ -116,7 +116,7 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions
 
     @Override
     public void initialize() {
-        logger.debug("Initializing hue light handler.");
+        logger.debug("Initializing Hue light handler.");
         Bridge bridge = getBridge();
         initializeThing((bridge == null) ? null : bridge.getStatus());
     }
@@ -234,13 +234,13 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions
     public void handleCommand(String channel, Command command, long fadeTime) {
         HueClient bridgeHandler = getHueClient();
         if (bridgeHandler == null) {
-            logger.warn("hue bridge handler not found. Cannot handle command without bridge.");
+            logger.warn("Hue Bridge handler not found. Cannot handle command without bridge.");
             return;
         }
 
         final FullLight light = lastFullLight == null ? bridgeHandler.getLightById(lightId) : lastFullLight;
         if (light == null) {
-            logger.debug("hue light not known on bridge. Cannot handle command.");
+            logger.debug("Hue light not known on bridge. Cannot handle command.");
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-wrong-light-id");
             return;
index 9669c1b1e8b8dd5efb58591b5b79ece98c28f0fe..8189bea1aa314fa14b8451c29ee66657cad09d12 100644 (file)
@@ -12,8 +12,8 @@
  */
 package org.openhab.binding.hue.internal.handler;
 
-import static org.openhab.binding.hue.internal.FullSensor.*;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
 import static org.openhab.core.thing.Thing.*;
 
 import java.time.LocalDateTime;
@@ -27,9 +27,9 @@ import java.util.Objects;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
-import org.openhab.binding.hue.internal.StateUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
@@ -72,7 +72,7 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
 
     @Override
     public void initialize() {
-        logger.debug("Initializing hue sensor handler.");
+        logger.debug("Initializing Hue sensor handler.");
         Bridge bridge = getBridge();
         initializeThing((bridge == null) ? null : bridge.getStatus());
     }
@@ -167,13 +167,13 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
     protected void handleCommand(String channel, Command command) {
         HueClient bridgeHandler = getHueClient();
         if (bridgeHandler == null) {
-            logger.warn("hue bridge handler not found. Cannot handle command without bridge.");
+            logger.warn("Hue Bridge handler not found. Cannot handle command without bridge.");
             return;
         }
 
         final FullSensor sensor = lastFullSensor;
         if (sensor == null) {
-            logger.debug("hue sensor not known on bridge. Cannot handle command.");
+            logger.debug("Hue sensor not known on bridge. Cannot handle command.");
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-wrong-sensor-id");
             return;
@@ -206,13 +206,13 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
         if (!configUpdate.isEmpty()) {
             HueClient hueBridge = getHueClient();
             if (hueBridge == null) {
-                logger.warn("hue bridge handler not found. Cannot handle configuration update without bridge.");
+                logger.warn("Hue Bridge handler not found. Cannot handle configuration update without bridge.");
                 return;
             }
 
             final FullSensor sensor = lastFullSensor;
             if (sensor == null) {
-                logger.debug("hue sensor not known on bridge. Cannot handle configuration update.");
+                logger.debug("Hue sensor not known on bridge. Cannot handle configuration update.");
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                         "@text/offline.conf-error-wrong-sensor-id");
                 return;
index 559c9adc8fd8587abf23678edca3c6feeaa72ae8..1212573e608feedcc85df167acb52d5d5fb3c28d 100644 (file)
@@ -14,12 +14,12 @@ package org.openhab.binding.hue.internal.handler;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.State;
-import org.openhab.binding.hue.internal.State.AlertMode;
-import org.openhab.binding.hue.internal.State.ColorMode;
-import org.openhab.binding.hue.internal.State.Effect;
-import org.openhab.binding.hue.internal.StateUpdate;
 import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.binding.hue.internal.dto.State;
+import org.openhab.binding.hue.internal.dto.State.AlertMode;
+import org.openhab.binding.hue.internal.dto.State.ColorMode;
+import org.openhab.binding.hue.internal.dto.State.Effect;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
index 2f0d8262634f7d17394a239a3761a4c62890f56b..c7a3effce4e05a1c2b0f54ea71885d72132da03e 100644 (file)
@@ -13,7 +13,7 @@
 package org.openhab.binding.hue.internal.handler;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullLight;
+import org.openhab.binding.hue.internal.dto.FullLight;
 
 /**
  * The {@link LightStatusListener} is notified when a light status has changed or a light has been removed or added.
index e763d116354f4aab524fed5ec02eaa7f667a82e5..42da8c0c916d91cecc05e3645283e70922eb5733 100644 (file)
@@ -13,7 +13,7 @@
 package org.openhab.binding.hue.internal.handler;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
+import org.openhab.binding.hue.internal.dto.FullSensor;
 
 /**
  * The {@link SensorStatusListener} is notified when a sensor status has changed or a sensor has been removed or added.
index 0dd736c71a6619790597dd07cc89eda869aac989..866dd1e3b0de53f2db76fa13207c3348af8f99f5 100644 (file)
@@ -20,8 +20,8 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.thing.Thing;
index baf0f8bed407704207802f6097e618a463648009..0b676faa7dc1a2567d4579be5438e5f68bbddf92 100644 (file)
@@ -21,13 +21,12 @@ import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.DecimalType;
@@ -43,7 +42,7 @@ import org.openhab.core.thing.ThingTypeUID;
 @NonNullByDefault
 public class DimmerSwitchHandler extends HueSensorHandler {
 
-    public static final Set<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) {
         super(thing);
index fa731209765cc65aff008bb82241a97b6108ec54..0b20d019f611a719a81b819d82e6887d5e895295 100644 (file)
  */
 package org.openhab.binding.hue.internal.handler.sensors;
 
-import static org.openhab.binding.hue.internal.FullSensor.STATE_PRESENCE;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.binding.hue.internal.dto.FullSensor.STATE_PRESENCE;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.OnOffType;
@@ -35,7 +34,8 @@ import org.openhab.core.thing.ThingTypeUID;
  */
 @NonNullByDefault
 public class GeofencePresenceHandler extends HueSensorHandler {
-    public static final Set<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) {
         super(thing);
index afadf2de323061570bcf0a5384b037ad850f213c..8ccfef70f3cf45a34c716576c65466fb933afff1 100644 (file)
  */
 package org.openhab.binding.hue.internal.handler.sensors;
 
-import static org.openhab.binding.hue.internal.FullSensor.*;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
 
 import java.math.BigDecimal;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.LightLevelConfigUpdate;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.LightLevelConfigUpdate;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.DecimalType;
@@ -41,7 +40,8 @@ import org.openhab.core.thing.ThingTypeUID;
  */
 @NonNullByDefault
 public class LightLevelHandler extends HueSensorHandler {
-    public static final Set<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) {
         super(thing);
index 338e9deb669fb4e6f257b42eb2f857c2f371df00..7e0d1b16b01b597679adaad6fa55bfd3eb1d0e96 100644 (file)
  */
 package org.openhab.binding.hue.internal.handler.sensors;
 
-import static org.openhab.binding.hue.internal.FullSensor.*;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.PresenceConfigUpdate;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.PresenceConfigUpdate;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueClient;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
@@ -43,7 +42,8 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class PresenceHandler extends HueSensorHandler {
-    public static final Set<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);
 
@@ -55,13 +55,13 @@ public class PresenceHandler extends HueSensorHandler {
     public void handleCommand(String channel, Command command) {
         HueClient hueBridge = getHueClient();
         if (hueBridge == null) {
-            logger.warn("hue bridge handler not found. Cannot handle command without bridge.");
+            logger.warn("Hue Bridge handler not found. Cannot handle command without bridge.");
             return;
         }
 
         final FullSensor sensor = lastFullSensor;
         if (sensor == null) {
-            logger.debug("hue sensor not known on bridge. Cannot handle command.");
+            logger.debug("Hue sensor not known on bridge. Cannot handle command.");
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "@text/offline.conf-error-wrong-sensor-id");
             return;
index 579e49a2443be66c2f937dac6d7209d8f40c7ec4..369b4dd0f233145e41ca580c2feefde57dabe7e9 100644 (file)
@@ -21,13 +21,12 @@ import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.DecimalType;
@@ -42,7 +41,7 @@ import org.openhab.core.thing.ThingTypeUID;
 @NonNullByDefault
 public class TapSwitchHandler extends HueSensorHandler {
 
-    public static final Set<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) {
         super(thing);
index 0c21cfe2e0749e2d0d65e7ae4a06dd298e56c339..c84e6298a74a275ab18b0d77580999ecbb604a67 100644 (file)
  */
 package org.openhab.binding.hue.internal.handler.sensors;
 
-import static org.openhab.binding.hue.internal.FullSensor.*;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.binding.hue.internal.dto.FullSensor.*;
 
 import java.math.BigDecimal;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.SensorConfigUpdate;
-import org.openhab.binding.hue.internal.TemperatureConfigUpdate;
+import org.openhab.binding.hue.internal.dto.FullSensor;
+import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
+import org.openhab.binding.hue.internal.dto.TemperatureConfigUpdate;
 import org.openhab.binding.hue.internal.handler.HueSensorHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.QuantityType;
@@ -39,7 +38,8 @@ import org.openhab.core.thing.ThingTypeUID;
  */
 @NonNullByDefault
 public class TemperatureHandler extends HueSensorHandler {
-    public static final Set<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) {
         super(thing);
index 87ae19a6aa0b83eea25a3a2bccc2d9c517c593da..722437d1edbe5966440202d089bb523a2f1c18b0 100644 (file)
@@ -6,13 +6,4 @@
        <name>Hue Binding</name>
        <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>
index bddda1e9b05e2cabd8c7f0a4f2cb79e913f5d662..ad9069f649a86ead6c72842fa74c4b37f5c88b8c 100644 (file)
        <config-description uri="thing-type:hue:group">
                <parameter name="groupId" type="text" required="true">
                        <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 name="fadetime" type="integer" min="0" step="100" unit="ms">
                        <label>@text/config.fadetime.label</label>
index 49e40b30088539a6c52b1f099e82a8386760bb75..2b2af1df10a669bb4f47755ac27bd3853e221326 100644 (file)
@@ -3,11 +3,6 @@
 binding.hue.name = Hue Binding
 binding.hue.description = The Hue Binding integrates the Philips Hue system. It allows to control Hue bulbs.
 
-# binding config
-
-binding.config.hue.removalGracePeriod.label = Removal Grace Period
-binding.config.hue.removalGracePeriod.description = Extra grace period (seconds) that UPnP discovery shall wait before removing a lost Bridge from the Inbox. Default is 50 seconds.
-
 # thing types
 
 thing-type.hue.0000.label = On/Off Light
@@ -39,7 +34,7 @@ thing-type.hue.0840.description = A generic sensor object for IP sensor use.
 thing-type.hue.0850.label = CLIP Generic Flag Sensor
 thing-type.hue.0850.description = A generic sensor object for IP sensor use.
 thing-type.hue.bridge.label = Hue Bridge
-thing-type.hue.bridge.description = The Hue bridge represents the Philips Hue bridge.
+thing-type.hue.bridge.description = The Hue Bridge represents the Philips Hue Bridge.
 thing-type.hue.geofencesensor.label = Geofence Sensor
 thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection.
 thing-type.hue.group.label = Hue Group
@@ -48,17 +43,23 @@ thing-type.hue.group.description = A group of lights or a room that could be swi
 # thing types config
 
 thing-type.config.hue.bridge.ipAddress.label = Network Address
-thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue bridge.
+thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue Bridge.
 thing-type.config.hue.bridge.pollingInterval.label = Polling Interval
-thing-type.config.hue.bridge.pollingInterval.description = Seconds between fetching values from the Hue bridge. Default is 10.
+thing-type.config.hue.bridge.pollingInterval.description = Seconds between fetching values from the Hue Bridge. Default is 10.
 thing-type.config.hue.bridge.port.label = Port
-thing-type.config.hue.bridge.port.description = Port of the Hue bridge.
+thing-type.config.hue.bridge.port.description = Port of the Hue Bridge.
+thing-type.config.hue.bridge.protocol.label = Protocol
+thing-type.config.hue.bridge.protocol.description = Protocol to connect to the Hue Bridge (http or https).
+thing-type.config.hue.bridge.protocol.option.http = HTTP
+thing-type.config.hue.bridge.protocol.option.https = HTTPS
 thing-type.config.hue.bridge.sensorPollingInterval.label = Sensor Polling Interval
-thing-type.config.hue.bridge.sensorPollingInterval.description = Milliseconds between fetching sensor-values from the Hue bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the Hue bridge. Use 0 to disable the polling for sensors. Default is 500.
+thing-type.config.hue.bridge.sensorPollingInterval.description = Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the Hue Bridge. Use 0 to disable the polling for sensors. Default is 500.
+thing-type.config.hue.bridge.useSelfSignedCertificate.label = Use Self-Signed Certificate
+thing-type.config.hue.bridge.useSelfSignedCertificate.description = Use self-signed certificate for HTTPS connection to Hue Bridge.
 thing-type.config.hue.bridge.userName.label = Username
-thing-type.config.hue.bridge.userName.description = Name of a registered Hue bridge user, that allows to access the API.
+thing-type.config.hue.bridge.userName.description = Name of a registered Hue Bridge user, that allows to access the API.
 thing-type.config.hue.group.groupId.label = Group ID
-thing-type.config.hue.group.groupId.description = The group identifier identifies one certain hue group or room.
+thing-type.config.hue.group.groupId.description = The group identifier identifies one certain Hue group or room.
 thing-type.config.hue.lightlevelsensor.tholddark.label = Threshold Dark
 thing-type.config.hue.lightlevelsensor.tholddark.description = Threshold the user configured to be used in rules to determine insufficient light level (ie below threshold). Default value 16000.
 thing-type.config.hue.lightlevelsensor.tholdoffset.label = Threshold Offset
@@ -132,37 +133,39 @@ config.fadetime.description = Fade time in milliseconds for changing values.
 config.ledindication.label = LED Indication
 config.ledindication.description = Turns device LED during normal operation on or off. Devices might still indicate exceptional operation (Reset, SW Update, Battery Low).
 config.lightId.label = Light ID
-config.lightId.description = The light identifier that is used within the hue bridge.
+config.lightId.description = The light identifier that is used within the Hue Bridge.
 config.plugId.label = Plug ID
-config.plugId.description = The plug identifier that is used within the hue bridge.
+config.plugId.description = The plug identifier that is used within the Hue Bridge.
 config.sensorId.label = Sensor ID
-config.sensorId.description = The sensor identifier that is used within the hue bridge.
+config.sensorId.description = The sensor identifier that is used within the Hue Bridge.
 config.on.label = Sensor Status
 config.on.description = Enables or disables the sensor.
 
 # config status messages
 
-config-status.error.missing-ip-address-configuration = No IP address for the Hue bridge has been provided.
+config-status.error.missing-ip-address-configuration = No IP address for the Hue Bridge has been provided.
 
 # thing status descriptions
 
-offline.conf-error-no-ip-address = Cannot connect to Hue bridge. IP address not available in configuration.
-offline.conf-error-no-username = Cannot connect to Hue bridge. User name for authentication not available in configuration.
+offline.communication-error = An unexpected exception occurred during execution.
+offline.conf-error-invalid-ssl-certificate = Invalid certificate for secured connection. You might want to enable the "Use Self-Signed Certificate" configuration.
+offline.conf-error-no-ip-address = Cannot connect to Hue Bridge. IP address not available in configuration.
+offline.conf-error-no-username = Cannot connect to Hue Bridge. User name for authentication not available in configuration.
 offline.conf-error-invalid-username = Authentication failed. Remove user name from configuration to generate a new one.
-offline.conf-error-press-pairing-button = Not authenticated. Press pairing button on the Hue bridge or set a valid user name in configuration.
-offline.conf-error-creation-username = Failed to create new user on Hue bridge.
-offline.bridge-connection-lost = Hue bridge connection lost.
+offline.conf-error-press-pairing-button = Not authenticated. Press pairing button on the Hue Bridge or set a valid user name in configuration.
+offline.conf-error-creation-username = Failed to create new user on Hue Bridge.
+offline.bridge-connection-lost = Hue Bridge connection lost.
 offline.conf-error-no-light-id = Light ID not available in configuration.
 offline.conf-error-no-sensor-id = Sensor ID not available in configuration.
 offline.conf-error-no-group-id = Group ID not available in configuration.
-offline.conf-error-wrong-light-id = No light with given ID available on Hue bridge.
-offline.conf-error-wrong-sensor-id = No sensor with given ID available on Hue bridge.
-offline.conf-error-wrong-group-id = No group with given ID available on Hue bridge.
-offline.light-not-reachable = Hue bridge reports light as not reachable.
-offline.sensor-not-reachable = Hue bridge reports sensor as not reachable.
-offline.light-removed = Hue bridge reports light as removed.
-offline.sensor-removed = Hue bridge reports sensor as removed.
-offline.group-removed = Hue bridge reports group as removed.
+offline.conf-error-wrong-light-id = No light with given ID available on Hue Bridge.
+offline.conf-error-wrong-sensor-id = No sensor with given ID available on Hue Bridge.
+offline.conf-error-wrong-group-id = No group with given ID available on Hue Bridge.
+offline.light-not-reachable = Hue Bridge reports light as not reachable.
+offline.sensor-not-reachable = Hue Bridge reports sensor as not reachable.
+offline.light-removed = Hue Bridge reports light as removed.
+offline.sensor-removed = Hue Bridge reports sensor as removed.
+offline.group-removed = Hue Bridge reports group as removed.
 
 # lightactions
 
index a062d93fad48b8454b67bc0e29516c701c75888e..e44c6f3569628404553b647df3d4c8271a1ba8af 100644 (file)
@@ -6,7 +6,7 @@
        <!-- Hue Bridge -->
        <bridge-type id="bridge">
                <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>
                        <channel id="scene" typeId="scene"/>
                        <parameter name="ipAddress" type="text" required="true">
                                <context>network-address</context>
                                <label>Network Address</label>
-                               <description>Network address of the Hue bridge.</description>
+                               <description>Network address of the Hue Bridge.</description>
                        </parameter>
                        <parameter name="port" type="integer" required="false" min="1" max="65535">
                                <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 name="userName" type="text">
                                <context>password</context>
                                <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 name="pollingInterval" type="integer" min="1" step="1" unit="s">
                                <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>
                        </parameter>
                        <parameter name="sensorPollingInterval" type="integer" min="0" step="1" unit="ms">
                                <label>Sensor Polling Interval</label>
-                               <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
+                               <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.</description>
                                <default>500</default>
                        </parameter>
diff --git a/bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem b/bundles/org.openhab.binding.hue/src/main/resources/huebridge_cacert.pem
new file mode 100644 (file)
index 0000000..1cef2d7
--- /dev/null
@@ -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-----
index 5b6e8cc501e9fbae7639437af47570a92850c6b4..4ce38deb66680e043ff5ce8c8e6ee518f9730c23 100644 (file)
  */
 package org.openhab.binding.hue.internal;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
+import org.openhab.binding.hue.internal.dto.ApiVersion;
 
 /**
- *
- *
  * @author Samuel Leisering - Initial contribution
  */
+@NonNullByDefault
 public class ApiVersionTest {
 
     @Test
index 6886e57068e17b1bfe8a09cf46227edd414b9e83..2a47fb8cc4a141a7e38b321522bbe2f68fa2928c 100644 (file)
@@ -14,35 +14,47 @@ package org.openhab.binding.hue.internal;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpStatus;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-import org.openhab.binding.hue.internal.HttpClient.Result;
+import org.openhab.binding.hue.internal.config.HueBridgeConfig;
+import org.openhab.binding.hue.internal.connection.HueBridge;
+import org.openhab.binding.hue.internal.dto.Scene;
 import org.openhab.binding.hue.internal.exceptions.ApiException;
+import org.openhab.core.i18n.CommunicationException;
+import org.openhab.core.i18n.ConfigurationException;
 
 /**
  * @author Hengrui Jiang - initial contribution
  */
+@NonNullByDefault
 public class HueBridgeTest {
 
     @Test
     public void testGetScenesExcludeRecycleScenes() throws IOException, ApiException {
-        HttpClient mockHttpClient = Mockito.mock(HttpClient.class);
-
-        HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1),
-                mockHttpClient);
-
-        List<Scene> testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), true), //
-                new Scene("id2", "name2", "group2", Collections.emptyList(), false));
-        when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200));
+        HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
+                Executors.newScheduledThreadPool(1)) {
+            @Override
+            public HueResult get(String address) throws ConfigurationException, CommunicationException {
+                if ("https://ip:443/api/username/lights".equals(address)) {
+                    return new HueResult("{}", HttpStatus.OK_200);
+                } else if ("https://ip:443/api/username/scenes".equals(address)) {
+                    List<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();
         assertThat(scenes.size(), is(1));
@@ -51,15 +63,22 @@ public class HueBridgeTest {
 
     @Test
     public void testGetScenesOrderByGroup() throws IOException, ApiException {
-        HttpClient mockHttpClient = Mockito.mock(HttpClient.class);
-
-        HueBridge hueBridge = new HueBridge("ip", "baseUrl", "username", Executors.newScheduledThreadPool(1),
-                mockHttpClient);
-
-        List<Scene> testScenes = Arrays.asList(new Scene("id1", "name1", "group1", Collections.emptyList(), false), //
-                new Scene("id2", "name2", "group2", Collections.emptyList(), false),
-                new Scene("id3", "name3", "group1", Collections.emptyList(), false));
-        when(mockHttpClient.get("baseUrl/username/scenes")).thenReturn(new Result(createMockResponse(testScenes), 200));
+        HueBridge hueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
+                Executors.newScheduledThreadPool(1)) {
+            @Override
+            public HueResult get(String address) throws ConfigurationException, CommunicationException {
+                if ("https://ip:443/api/username/lights".equals(address)) {
+                    return new HueResult("{}", HttpStatus.OK_200);
+                } else if ("https://ip:443/api/username/scenes".equals(address)) {
+                    List<Scene> testScenes = List.of( //
+                            new Scene("id1", "name1", "group1", List.of(), false), //
+                            new Scene("id2", "name2", "group2", List.of(), false), //
+                            new Scene("id3", "name3", "group1", List.of(), false));
+                    return new HueResult(createMockResponse(testScenes), HttpStatus.OK_200);
+                }
+                return super.get(address);
+            }
+        };
 
         List<Scene> scenes = hueBridge.getScenes();
         assertThat(scenes.size(), is(3));
@@ -92,8 +111,7 @@ public class HueBridgeTest {
                 "        \"version\": 2,\n" + //
                 "        \"group\": \"%s\"\n" + //
                 "    }";
-        String lights = String.join(",",
-                scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.toList()));
+        String lights = scene.getLightIds().stream().map(id -> "\"" + id + "\"").collect(Collectors.joining(","));
         return String.format(template, scene.getId(), scene.getName(), lights, scene.isRecycle(), scene.getGroupId());
     }
 }
index a96baf3e875826c3e4713541ac8b93b3db906d19..424877446b542224fd3bbcb4a3382d8b8703a25d 100644 (file)
@@ -17,9 +17,12 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
-import org.openhab.binding.hue.internal.State.ColorMode;
 import org.openhab.binding.hue.internal.dto.ColorTemperature;
+import org.openhab.binding.hue.internal.dto.State;
+import org.openhab.binding.hue.internal.dto.State.ColorMode;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.binding.hue.internal.handler.LightStateConverter;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
@@ -31,6 +34,7 @@ import org.openhab.core.library.types.PercentType;
  * @author Denis Dudnik - switched to internally integrated source of Jue library
  * @author Markus Rathgeb - migrated to plain Java test
  */
+@NonNullByDefault
 public class LightStateConverterTest {
 
     @Test
index 44725d6a9d410b320169e136e5a1cf509abff0a6..e6fdc1fbbb6131493f7a032931b98ec9ca9e4b87 100644 (file)
@@ -18,11 +18,16 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import java.util.Arrays;
 import java.util.List;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
+import org.openhab.binding.hue.internal.dto.FullGroup;
+import org.openhab.binding.hue.internal.dto.Scene;
+import org.openhab.binding.hue.internal.dto.State;
 
 /**
  * @author HJiang - initial contribution
  */
+@NonNullByDefault
 public class SceneTest {
 
     private static final State PLACEHOLDER_STATE = new State();
index 7e212306be40ed39de132a03c965592d79e6b0ea..10ca508baee38a43db6664a08a96a53530efb459 100644 (file)
@@ -23,10 +23,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
-import org.openhab.binding.hue.internal.FullConfig;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.State.ColorMode;
-import org.openhab.binding.hue.internal.StateUpdate;
+import org.openhab.binding.hue.internal.dto.FullConfig;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.dto.State.ColorMode;
+import org.openhab.binding.hue.internal.dto.StateUpdate;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
index 5b570413fc47f0854112fa6c88c08801fb441602..8915ea1e5fa988f4dc483482be8b0328b3102fe1 100644 (file)
@@ -12,7 +12,8 @@
  */
 package org.openhab.binding.hue.internal.handler;
 
-import org.openhab.binding.hue.internal.State.ColorMode;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.hue.internal.dto.State.ColorMode;
 
 /**
  * Builder for the current state of a hue light.
@@ -22,6 +23,7 @@ import org.openhab.binding.hue.internal.State.ColorMode;
  * @author Markus Rathgeb - migrated to plain Java test
  * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
  */
+@NonNullByDefault
 public class HueLightState {
 
     int brightness = 200;
index 5afe76fb04dbeab306015bec9d1143232ad5e6ca..8ddabc812be49cc0cf11f77231c74ed186b5da06 100644 (file)
@@ -26,8 +26,6 @@ Fragment-Host: org.openhab.binding.hue
        jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\
        org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
        org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\
-       org.objectweb.asm.commons;version='[9.0.0,9.0.1)',\
-       org.objectweb.asm.tree;version='[9.0.0,9.0.1)',\
        jakarta.annotation-api;version='[2.0.0,2.0.1)',\
        jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\
        javax.measure.unit-api;version='[2.1.2,2.1.3)',\
@@ -45,12 +43,21 @@ Fragment-Host: org.openhab.binding.hue
        org.apache.felix.scr;version='[2.1.30,2.1.31)',\
        org.osgi.util.function;version='[1.2.0,1.2.1)',\
        org.osgi.util.promise;version='[1.2.0,1.2.1)',\
+       org.openhab.binding.hue;version='[3.4.0,3.4.1)',\
+       org.openhab.binding.hue.tests;version='[3.4.0,3.4.1)',\
+       org.openhab.core;version='[3.4.0,3.4.1)',\
+       org.openhab.core.binding.xml;version='[3.4.0,3.4.1)',\
+       org.openhab.core.config.core;version='[3.4.0,3.4.1)',\
+       org.openhab.core.config.discovery;version='[3.4.0,3.4.1)',\
+       org.openhab.core.config.xml;version='[3.4.0,3.4.1)',\
+       org.openhab.core.io.console;version='[3.4.0,3.4.1)',\
+       org.openhab.core.io.net;version='[3.4.0,3.4.1)',\
+       org.openhab.core.test;version='[3.4.0,3.4.1)',\
+       org.openhab.core.thing;version='[3.4.0,3.4.1)',\
+       org.openhab.core.thing.xml;version='[3.4.0,3.4.1)',\
        xstream;version='[1.4.19,1.4.20)',\
        com.google.gson;version='[2.8.9,2.8.10)',\
-       org.objectweb.asm;version='[9.2.0,9.2.1)',\
        org.apache.felix.configadmin;version='[1.9.24,1.9.25)',\
-       org.apache.xbean.bundleutils;version='[4.21.0,4.21.1)',\
-       org.apache.xbean.finder;version='[4.21.0,4.21.1)',\
        org.eclipse.jetty.client;version='[9.4.46,9.4.47)',\
        org.eclipse.jetty.http;version='[9.4.46,9.4.47)',\
        org.eclipse.jetty.io;version='[9.4.46,9.4.47)',\
@@ -63,22 +70,14 @@ Fragment-Host: org.openhab.binding.hue
        org.eclipse.jetty.websocket.client;version='[9.4.46,9.4.47)',\
        org.eclipse.jetty.websocket.common;version='[9.4.46,9.4.47)',\
        org.ops4j.pax.logging.pax-logging-api;version='[2.0.16,2.0.17)',\
-       org.ops4j.pax.web.pax-web-api;version='[7.3.25,7.3.26)',\
-       org.jupnp;version='[2.6.1,2.6.2)',\
-       ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
-       ch.qos.logback.core;version='[1.2.11,1.2.12)',\
        org.eclipse.jdt.annotation;version='[2.2.100,2.2.101)',\
+       javax.jmdns;version='[3.5.8,3.5.9)',\
+       net.bytebuddy.byte-buddy;version='[1.12.1,1.12.2)',\
+       net.bytebuddy.byte-buddy-agent;version='[1.12.1,1.12.2)',\
+       org.mockito.mockito-core;version='[4.1.0,4.1.1)',\
+       org.objenesis;version='[3.2.0,3.2.1)',\
+       org.openhab.core.config.discovery.mdns;version='[3.4.0,3.4.1)',\
+       org.openhab.core.io.transport.mdns;version='[3.4.0,3.4.1)',\
        biz.aQute.tester.junit-platform;version='[6.3.0,6.3.1)',\
-       org.openhab.binding.hue;version='[3.4.0,3.4.1)',\
-       org.openhab.binding.hue.tests;version='[3.4.0,3.4.1)',\
-       org.openhab.core;version='[3.4.0,3.4.1)',\
-       org.openhab.core.binding.xml;version='[3.4.0,3.4.1)',\
-       org.openhab.core.config.core;version='[3.4.0,3.4.1)',\
-       org.openhab.core.config.discovery;version='[3.4.0,3.4.1)',\
-       org.openhab.core.config.discovery.upnp;version='[3.4.0,3.4.1)',\
-       org.openhab.core.config.xml;version='[3.4.0,3.4.1)',\
-       org.openhab.core.io.console;version='[3.4.0,3.4.1)',\
-       org.openhab.core.io.net;version='[3.4.0,3.4.1)',\
-       org.openhab.core.test;version='[3.4.0,3.4.1)',\
-       org.openhab.core.thing;version='[3.4.0,3.4.1)',\
-       org.openhab.core.thing.xml;version='[3.4.0,3.4.1)'
+       ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
+       ch.qos.logback.core;version='[1.2.11,1.2.12)'
index 3ff98df726b1d9105f0c7302b547cbd1deb79b43..53306c45d0c07db57685f8b93bf55abc5d414f85 100644 (file)
@@ -15,26 +15,35 @@ package org.openhab.binding.hue.internal;
 import static org.hamcrest.CoreMatchers.*;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.Collection;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpStatus;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.openhab.binding.hue.internal.config.HueBridgeConfig;
+import org.openhab.binding.hue.internal.connection.HueBridge;
 import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
+import org.openhab.binding.hue.internal.dto.FullLight;
+import org.openhab.binding.hue.internal.exceptions.ApiException;
 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.config.discovery.DiscoveryListener;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultFlag;
 import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.i18n.CommunicationException;
 import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingRegistry;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
@@ -54,7 +63,6 @@ import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
  */
 public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
 
-    protected HueThingHandlerFactory hueThingHandlerFactory;
     protected DiscoveryListener discoveryListener;
     protected ThingRegistry thingRegistry;
     protected Bridge hueBridge;
@@ -74,7 +82,8 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent
         Configuration configuration = new Configuration();
         configuration.put(HOST, "1.2.3.4");
         configuration.put(USER_NAME, "testUserName");
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put("useSelfSignedCertificate", false);
 
         hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
                 configuration);
@@ -150,45 +159,45 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent
     }
 
     @Test
-    public void startSearchIsCalled() {
+    public void startSearchIsCalled() throws IOException, ApiException {
         final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
 
-        MockedHttpClient mockedHttpClient = new MockedHttpClient() {
-
+        HueBridge mockedHueBridge = new HueBridge(mock(HttpClient.class), "ip", 443, HueBridgeConfig.HTTPS, "username",
+                Executors.newScheduledThreadPool(1)) {
             @Override
-            public Result put(String address, String body) throws IOException {
-                return new Result("", 200);
-            }
-
-            @Override
-            public Result get(String address) throws IOException {
+            public HueResult get(String address) throws CommunicationException {
                 if (address.endsWith("testUserName")) {
                     String body = "{\"lights\":{}}";
-                    return new Result(body, 200);
+                    return new HueResult(body, HttpStatus.OK_200);
                 } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) {
                     String body = "{}";
-                    return new Result(body, 200);
+                    return new HueResult(body, HttpStatus.OK_200);
                 } else if (address.endsWith("testUserName/config")) {
-                    String body = "{ \"apiversion\": \"1.26.0\"}";
-                    return new Result(body, 200);
+                    String body = "{\"apiversion\": \"1.26.0\"}";
+                    return new HueResult(body, HttpStatus.OK_200);
                 } else {
-                    return new Result("", 404);
+                    return new HueResult("", HttpStatus.NOT_FOUND_404);
                 }
             }
 
             @Override
-            public Result post(String address, String body) throws IOException {
+            public HueResult post(String address, String body) throws CommunicationException {
                 if (address.endsWith("lights")) {
                     String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
                     searchHasBeenTriggered.set(true);
-                    return new Result(bodyReturn, 200);
+                    return new HueResult(bodyReturn, HttpStatus.OK_200);
                 } else {
-                    return new Result("", 404);
+                    return new HueResult("", HttpStatus.NOT_FOUND_404);
                 }
             }
+
+            @Override
+            public HueResult put(String address, String body) throws CommunicationException {
+                return new HueResult("", HttpStatus.OK_200);
+            }
         };
 
-        installHttpClientMock(hueBridgeHandler, mockedHttpClient);
+        installHttpClientMock(hueBridgeHandler, mockedHueBridge);
 
         ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
         waitForAssert(() -> {
@@ -201,19 +210,17 @@ public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent
         });
     }
 
-    private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, MockedHttpClient mockedHttpClient) {
+    private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, HueBridge mockedHueBridge) {
         waitForAssert(() -> {
             try {
                 // mock HttpClient
                 final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
                 hueBridgeField.setAccessible(true);
+                hueBridgeField.set(hueBridgeHandler, mockedHueBridge);
+
                 final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
                 assertThat(hueBridgeValue, is(notNullValue()));
 
-                final Field httpClientField = HueBridge.class.getDeclaredField("http");
-                httpClientField.setAccessible(true);
-                httpClientField.set(hueBridgeValue, mockedHttpClient);
-
                 final Field usernameField = HueBridge.class.getDeclaredField("username");
                 usernameField.setAccessible(true);
                 usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));
diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/MockedHttpClient.java
deleted file mode 100644 (file)
index e1f9f3f..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-/**
- * @author Denis Dudnik - Initial contribution
- */
-public class MockedHttpClient extends HttpClient {
-
-}
diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipantOSGITest.java
deleted file mode 100644 (file)
index d405dfb..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * Copyright (c) 2010-2022 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal.discovery;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.fail;
-import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.jupnp.model.ValidationException;
-import org.jupnp.model.meta.DeviceDetails;
-import org.jupnp.model.meta.ManufacturerDetails;
-import org.jupnp.model.meta.ModelDetails;
-import org.jupnp.model.meta.RemoteDevice;
-import org.jupnp.model.meta.RemoteDeviceIdentity;
-import org.jupnp.model.meta.RemoteService;
-import org.jupnp.model.types.DeviceType;
-import org.jupnp.model.types.UDN;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultFlag;
-import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
-import org.openhab.core.test.java.JavaOSGiTest;
-import org.openhab.core.thing.ThingUID;
-
-/**
- * Tests for {@link org.openhab.binding.hue.internal.discovery.HueBridgeDiscoveryParticipant}.
- *
- * @author Kai Kreuzer - Initial contribution
- * @author Thomas Höfer - Added representation
- * @author Markus Rathgeb - migrated to plain Java test
- */
-public class HueBridgeDiscoveryParticipantOSGITest extends JavaOSGiTest {
-
-    UpnpDiscoveryParticipant discoveryParticipant;
-
-    RemoteDevice hueDevice;
-    RemoteDevice otherDevice;
-
-    @BeforeEach
-    public void setUp() {
-        discoveryParticipant = getService(UpnpDiscoveryParticipant.class, HueBridgeDiscoveryParticipant.class);
-        assertThat(discoveryParticipant, is(notNullValue()));
-
-        try {
-            final RemoteService remoteService = null;
-
-            hueDevice = new RemoteDevice(
-                    new RemoteDeviceIdentity(new UDN("123"), 60, new URL("http://hue"), null, null),
-                    new DeviceType("namespace", "type"),
-                    new DeviceDetails(new URL("http://1.2.3.4/"), "Hue Bridge", new ManufacturerDetails("Philips"),
-                            new ModelDetails("Philips hue bridge"), "serial123", "upc", null),
-                    remoteService);
-
-            otherDevice = new RemoteDevice(
-                    new RemoteDeviceIdentity(new UDN("567"), 60, new URL("http://acme"), null, null),
-                    new DeviceType("namespace", "type"), new DeviceDetails("Some Device",
-                            new ManufacturerDetails("Taiwan"), new ModelDetails("$%&/"), "serial567", "upc"),
-                    remoteService);
-        } catch (final ValidationException | MalformedURLException ex) {
-            fail("Internal test error.");
-        }
-    }
-
-    @AfterEach
-    public void cleanUp() {
-    }
-
-    @Test
-    public void correctSupportedTypes() {
-        assertThat(discoveryParticipant.getSupportedThingTypeUIDs().size(), is(1));
-        assertThat(discoveryParticipant.getSupportedThingTypeUIDs().iterator().next(), is(THING_TYPE_BRIDGE));
-    }
-
-    @Test
-    public void correctThingUID() {
-        assertThat(discoveryParticipant.getThingUID(hueDevice), is(new ThingUID("hue:bridge:serial123")));
-    }
-
-    @Test
-    public void validDiscoveryResult() {
-        final DiscoveryResult result = discoveryParticipant.createResult(hueDevice);
-        assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW));
-        assertThat(result.getThingUID(), is(new ThingUID("hue:bridge:serial123")));
-        assertThat(result.getThingTypeUID(), is(THING_TYPE_BRIDGE));
-        assertThat(result.getBridgeUID(), is(nullValue()));
-        assertThat(result.getProperties().get(HOST), is("1.2.3.4"));
-        assertThat(result.getProperties().get(PROPERTY_SERIAL_NUMBER), is("serial123"));
-        assertThat(result.getRepresentationProperty(), is(PROPERTY_SERIAL_NUMBER));
-    }
-
-    @Test
-    public void noThingUIDForUnknownDevice() {
-        assertThat(discoveryParticipant.getThingUID(otherDevice), is(nullValue()));
-    }
-
-    @Test
-    public void noDiscoveryResultForUnknownDevice() {
-        assertThat(discoveryParticipant.createResult(otherDevice), is(nullValue()));
-    }
-}
index 747b94b2b119f270da5148c8d7b85014c3b43c84..d649a0f426cb2a84da5a3a991f579eb19ee2b330 100644 (file)
@@ -37,7 +37,6 @@ import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
 
 /**
- *
  * @author Christoph Knauf - Initial contribution
  * @author Markus Rathgeb - migrated to plain Java test
  */
@@ -51,64 +50,18 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
     final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
     final String ip1 = "192.168.31.17";
     final String ip2 = "192.168.30.28";
-    final String sn1 = "00178820057f";
-    final String sn2 = "001788141b41";
+    final String sn1 = "001788fffe20057f";
+    final String sn2 = "001788fffe141b41";
     final ThingUID BRIDGE_THING_UID_1 = new ThingUID(BRIDGE_THING_TYPE_UID, sn1);
     final ThingUID BRIDGE_THING_UID_2 = new ThingUID(BRIDGE_THING_TYPE_UID, sn2);
-    final String validBridgeDiscoveryResult = "[{\"id\":\"001788fffe20057f\",\"internalipaddress\":" + ip1
-            + "},{\"id\":\"001788fffe141b41\",\"internalipaddress\":" + ip2 + "}]";
+    final String validBridgeDiscoveryResult = "[{\"id\":\"" + sn1 + "\",\"internalipaddress\":" + ip1 + "},{\"id\":\""
+            + sn2 + "\",\"internalipaddress\":" + ip2 + "}]";
     String discoveryResult;
-    String expBridgeDescription = "" + //
-            "<?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>";
+    String expBridgeDescription = "{\"name\":\"Philips Hue\",\"datastoreversion\":\"113\",\"swversion\":\"1948086000\",\"apiversion\":\"1.48.0\",\"mac\":\"00:11:22:33:44\",\"bridgeid\":\"$SN\",\"factorynew\":false,\"replacesbridgeid\":null,\"modelid\":\"BSB002\",\"starterkitid\":\"\"}";
 
     private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) {
         assertThat(result.getBridgeUID(), nullValue());
-        assertThat(result.getLabel(), is(HueBridgeNupnpDiscovery.LABEL_PATTERN.replace("IP", expIp)));
+        assertThat(result.getLabel(), is(String.format(HueBridgeNupnpDiscovery.LABEL_PATTERN, expIp)));
         assertThat(result.getProperties().get("ipAddress"), is(expIp));
         assertThat(result.getProperties().get("serialNumber"), is(expSn));
     }
@@ -132,9 +85,9 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
             if (url.contains("meethue")) {
                 return discoveryResult;
             } else if (url.contains(ip1)) {
-                return expBridgeDescription.replaceAll("$IP", ip1);
+                return expBridgeDescription.replaceAll("$SN", sn1);
             } else if (url.contains(ip2)) {
-                return expBridgeDescription.replaceAll("$IP", ip2);
+                return expBridgeDescription.replaceAll("$SN", sn2);
             }
             throw new IOException();
         }
index 8fe44ae26222549338ff8d0926ea0431e744bed3..6af8429989562db5a65f715333c5bea355093f28 100644 (file)
@@ -16,19 +16,19 @@ import static org.eclipse.jdt.annotation.Checks.requireNonNull;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.binding.hue.internal.config.HueBridgeConfig.HTTP;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.concurrent.ScheduledExecutorService;
 
+import org.eclipse.jetty.client.HttpClient;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.hue.internal.AbstractHueOSGiTestParent;
-import org.openhab.binding.hue.internal.HueBridge;
-import org.openhab.binding.hue.internal.HueConfigStatusMessage;
+import org.openhab.binding.hue.internal.config.HueBridgeConfig;
+import org.openhab.binding.hue.internal.connection.HueBridge;
 import org.openhab.binding.hue.internal.exceptions.ApiException;
 import org.openhab.binding.hue.internal.exceptions.LinkButtonException;
 import org.openhab.binding.hue.internal.exceptions.UnauthorizedException;
@@ -36,6 +36,7 @@ import org.openhab.core.common.ThreadPoolManager;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.config.core.status.ConfigStatusMessage;
 import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingRegistry;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
@@ -72,18 +73,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void assertThatANewUserIsAddedToConfigIfNotExistingYet() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
         hueBridgeHandler.thingUpdated(bridge);
 
-        injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) {
-            @Override
-            public String link(String deviceType) throws IOException, ApiException {
-                return TEST_USER_NAME;
-            }
-        });
+        injectBridge(hueBridgeHandler,
+                new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
+                    @Override
+                    public String link(String deviceType) throws IOException, ApiException {
+                        return TEST_USER_NAME;
+                    }
+                });
 
         hueBridgeHandler.onNotAuthenticated();
 
@@ -95,17 +97,18 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
         configuration.put(USER_NAME, TEST_USER_NAME);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
         hueBridgeHandler.thingUpdated(bridge);
 
-        injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) {
-            @Override
-            public void authenticate(String userName) throws IOException, ApiException {
-            }
-        });
+        injectBridge(hueBridgeHandler,
+                new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
+                    @Override
+                    public void authenticate(String userName) throws IOException, ApiException {
+                    }
+                });
 
         hueBridgeHandler.onNotAuthenticated();
 
@@ -117,18 +120,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
         configuration.put(USER_NAME, "notAuthenticatedUser");
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
         hueBridgeHandler.thingUpdated(bridge);
 
-        injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) {
-            @Override
-            public void authenticate(String userName) throws IOException, ApiException {
-                throw new UnauthorizedException();
-            }
-        });
+        injectBridge(hueBridgeHandler,
+                new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
+                    @Override
+                    public void authenticate(String userName) throws IOException, ApiException {
+                        throw new UnauthorizedException();
+                    }
+                });
 
         hueBridgeHandler.onNotAuthenticated();
 
@@ -141,18 +145,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void verifyStatusIfLinkButtonIsNotPressed() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
         hueBridgeHandler.thingUpdated(bridge);
 
-        injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) {
-            @Override
-            public String link(String deviceType) throws IOException, ApiException {
-                throw new LinkButtonException();
-            }
-        });
+        injectBridge(hueBridgeHandler,
+                new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
+                    @Override
+                    public String link(String deviceType) throws IOException, ApiException {
+                        throw new LinkButtonException();
+                    }
+                });
 
         hueBridgeHandler.onNotAuthenticated();
 
@@ -165,18 +170,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void verifyStatusIfNewUserCannotBeCreated() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
         hueBridgeHandler.thingUpdated(bridge);
 
-        injectBridge(hueBridgeHandler, new HueBridge(DUMMY_HOST, 80, HTTP, scheduler) {
-            @Override
-            public String link(String deviceType) throws IOException, ApiException {
-                throw new ApiException();
-            }
-        });
+        injectBridge(hueBridgeHandler,
+                new HueBridge(mock(HttpClient.class), DUMMY_HOST, 80, HueBridgeConfig.HTTP, scheduler) {
+                    @Override
+                    public String link(String deviceType) throws IOException, ApiException {
+                        throw new ApiException();
+                    }
+                });
 
         hueBridgeHandler.onNotAuthenticated();
 
@@ -190,7 +196,7 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void verifyOfflineIsSetWithoutBridgeOfflineStatus() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, DUMMY_HOST);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
@@ -206,14 +212,13 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsNull() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, null);
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
-
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
 
-        ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST)
-                .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build();
+        ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
+                .withArguments(HOST).build();
 
         waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
     }
@@ -222,19 +227,19 @@ public class HueBridgeHandlerOSGiTest extends AbstractHueOSGiTestParent {
     public void assertThatAStatusConfigurationMessageForMissingBridgeIPIsProperlyReturnedIPIsAnEmptyString() {
         Configuration configuration = new Configuration();
         configuration.put(HOST, "");
-        configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
-
+        configuration.put(Thing.PROPERTY_SERIAL_NUMBER, "testSerialNumber");
         Bridge bridge = createBridgeThing(configuration);
 
         HueBridgeHandler hueBridgeHandler = getThingHandler(bridge, HueBridgeHandler.class);
 
-        ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST)
-                .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build();
+        ConfigStatusMessage expected = ConfigStatusMessage.Builder.error(HOST).withMessageKeySuffix(IP_ADDRESS_MISSING)
+                .withArguments(HOST).build();
 
         waitForAssert(() -> assertEquals(expected, hueBridgeHandler.getConfigStatus().iterator().next()));
     }
 
     private Bridge createBridgeThing(Configuration configuration) {
+        configuration.put("useSelfSignedCertificate", false);
         Bridge bridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID,
                 new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge"), null, "Bridge", configuration);