From 418eb57ba930816f9c80dc7499fc7abc2c0b6fb5 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 8 Feb 2024 20:35:01 +0100 Subject: [PATCH 001/128] =?UTF-8?q?=E2=98=A0=EF=B8=8F=20Binding=20skeleton?= =?UTF-8?q?=20created=20for=20org.openhab.binding.huesync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.huesync/NOTICE | 13 +++ bundles/org.openhab.binding.huesync/README.md | 94 ++++++++++++++++ bundles/org.openhab.binding.huesync/pom.xml | 17 +++ .../src/main/feature/feature.xml | 9 ++ .../internal/huesyncBindingConstants.java | 34 ++++++ .../internal/huesyncConfiguration.java | 31 ++++++ .../huesync/internal/huesyncHandler.java | 104 ++++++++++++++++++ .../internal/huesyncHandlerFactory.java | 55 +++++++++ .../src/main/resources/OH-INF/addon/addon.xml | 10 ++ .../resources/OH-INF/i18n/huesync.properties | 3 + .../resources/OH-INF/thing/thing-types.xml | 48 ++++++++ bundles/pom.xml | 5 +- 14 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/NOTICE create mode 100644 bundles/org.openhab.binding.huesync/README.md create mode 100644 bundles/org.openhab.binding.huesync/pom.xml create mode 100644 bundles/org.openhab.binding.huesync/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index ae0d5d1a3c958..e9ea09599fb49 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -157,6 +157,7 @@ /bundles/org.openhab.binding.hpprinter/ @cossey /bundles/org.openhab.binding.http/ @J-N-K /bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg +/bundles/org.openhab.binding.huesync/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.hydrawise/ @digitaldan /bundles/org.openhab.binding.hyperion/ @tavalin /bundles/org.openhab.binding.iammeter/ @lewei50 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 3a3e20519aec8..d518bbbfcc63b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -781,6 +781,11 @@ org.openhab.binding.hue ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.huesync + ${project.version} + org.openhab.addons.bundles org.openhab.binding.hydrawise diff --git a/bundles/org.openhab.binding.huesync/NOTICE b/bundles/org.openhab.binding.huesync/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md new file mode 100644 index 0000000000000..1edd412d65108 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/README.md @@ -0,0 +1,94 @@ +# huesync Binding + +_Give some details about what this binding is meant for - a protocol, system, specific device._ + +_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ +_You can place such resources into a `doc` folder next to this README.md._ + +_Put each sentence in a separate line to improve readability of diffs._ + +## Supported Things + +_Please describe the different supported things / devices including their ThingTypeUID within this section._ +_Which different types are supported, which models were tested etc.?_ +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +- `bridge`: Short description of the Bridge, if any +- `sample`: Short description of the Thing with the ThingTypeUID `sample` + +## Discovery + +_Describe the available auto-discovery features here._ +_Mention for what it works and what needs to be kept in mind when using it._ + +## Binding Configuration + +_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ +_In this section, you should link to this file and provide some information about the options._ +_The file could e.g. look like:_ + +``` +# Configuration for the huesync Binding +# +# Default secret key for the pairing of the huesync Thing. +# It has to be between 10-40 (alphanumeric) characters. +# This may be changed by the user for security reasons. +secret=openHABSecret +``` + +_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ + +_If your binding does not offer any generic configurations, you can remove this section completely._ + +## Thing Configuration + +_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ +_This should be mainly about its mandatory and optional configuration parameters._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +### `sample` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|---------------------------------------|---------|----------|----------| +| hostname | text | Hostname or IP address of the device | N/A | yes | no | +| password | text | Password to access the device | N/A | yes | no | +| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | + +## Channels + +_Here you should provide information about available channel types, what their meaning is and how they can be used._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +| Channel | Type | Read/Write | Description | +|---------|--------|------------|-----------------------------| +| control | Switch | RW | This is the control channel | + +## Full Example + +_Provide a full usage example based on textual configuration files._ +_*.things, *.items examples are mandatory as textual configuration is well used by many users._ +_*.sitemap examples are optional._ + +### Thing Configuration + +```java +Example thing configuration goes here. +``` +### Item Configuration + +```java +Example item configuration goes here. +``` + +### Sitemap Configuration + +```perl +Optional Sitemap configuration goes here. +Remove this section, if not needed. +``` + +## Any custom content here! + +_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml new file mode 100644 index 0000000000000..cc1a5e6c26120 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.2.0-SNAPSHOT + + + org.openhab.binding.huesync + + openHAB Add-ons :: Bundles :: huesync Binding + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml new file mode 100644 index 0000000000000..8b3735e1d61dc --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java new file mode 100644 index 0000000000000..c95f14dc6bb6e --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link huesyncBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class huesyncBindingConstants { + + private static final String BINDING_ID = "huesync"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample"); + + // List of all Channel ids + public static final String CHANNEL_1 = "channel1"; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java new file mode 100644 index 0000000000000..314af3703abb7 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link huesyncConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class huesyncConfiguration { + + /** + * Sample configuration parameters. Replace with your own. + */ + public String hostname = ""; + public String password = ""; + public int refreshInterval = 600; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java new file mode 100644 index 0000000000000..90c7d87770a46 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal; + +import static org.openhab.binding.huesync.internal.huesyncBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link huesyncHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class huesyncHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(huesyncHandler.class); + + private @Nullable huesyncConfiguration config; + + public huesyncHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (CHANNEL_1.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + // TODO: handle data refresh + } + + // TODO: handle command + + // Note: if communication with thing fails for some reason, + // indicate that by setting the status with detail information: + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // "Could not control device at IP address x.x.x.x"); + } + } + + @Override + public void initialize() { + config = getConfigAs(huesyncConfiguration.class); + + // TODO: Initialize the handler. + // The framework requires you to return from this method quickly, i.e. any network access must be done in + // the background initialization below. + // Also, before leaving this method a thing status from one of ONLINE, OFFLINE or UNKNOWN must be set. This + // might already be the real thing status in case you can decide it directly. + // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // background. + + // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. + // the framework is then able to reuse the resources from the thing handler initialization. + // we set this upfront to reliably check status updates in unit tests. + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + + // These logging types should be primarily used by bindings + // logger.trace("Example trace message"); + // logger.debug("Example debug message"); + // logger.warn("Example warn message"); + // + // Logging to INFO should be avoided normally. + // See https://www.openhab.org/docs/developer/guidelines.html#f-logging + + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java new file mode 100644 index 0000000000000..aa5b5ec3136d1 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal; + +import static org.openhab.binding.huesync.internal.huesyncBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +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.Component; + +/** + * The {@link huesyncHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) +public class huesyncHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { + return new huesyncHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..e2749f172f0bc --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + + binding + Hue HDMI Sync Box Binding + This is the binding for the Hue HDMI Sync Box. + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties new file mode 100644 index 0000000000000..0c2f44015a92d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -0,0 +1,3 @@ +# FIXME: please add all English translations to this file so the texts can be translated using Crowdin +# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations +# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..45aca2ce239a5 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,48 @@ + + + + + + + + + Sample thing for huesync Binding + + + + + + + + network-address + + Hostname or IP address of the device + + + password + + Password to access the device + + + + Interval the device is polled in sec. + 600 + true + + + + + + + Number:Temperature + + Sample channel for huesync Binding + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 293021091b5fd..22170b613997d 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -192,6 +192,7 @@ org.openhab.binding.hpprinter org.openhab.binding.http org.openhab.binding.hue + org.openhab.binding.huesync org.openhab.binding.hydrawise org.openhab.binding.hyperion org.openhab.binding.iammeter @@ -486,7 +487,7 @@ target/dependency - + @@ -707,4 +708,4 @@ - + \ No newline at end of file From 1fbd2b6a5e4374f337cc2ad37d1d667a3e4abec9 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 9 Feb 2024 08:52:07 +0100 Subject: [PATCH 002/128] =?UTF-8?q?=F0=9F=94=83=20Version=20Sync=20-=20we'?= =?UTF-8?q?ll=20use=204.1.x=20for=20development=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/pom.xml | 6 +++--- .../src/main/feature/feature.xml | 2 +- .../src/main/resources/OH-INF/addon/addon.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml index cc1a5e6c26120..a292435f94341 100644 --- a/bundles/org.openhab.binding.huesync/pom.xml +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -7,11 +7,11 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.2.0-SNAPSHOT + 4.1.2-SNAPSHOT org.openhab.binding.huesync - openHAB Add-ons :: Bundles :: huesync Binding + openHAB Add-ons :: Bundles :: Hue Play HDMI Sync Box Binding - \ No newline at end of file + diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml index 8b3735e1d61dc..63f941aa4c164 100644 --- a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml @@ -6,4 +6,4 @@ openhab-runtime-base mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} - \ No newline at end of file + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml index e2749f172f0bc..d8bd8447a95ca 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml @@ -5,6 +5,6 @@ binding Hue HDMI Sync Box Binding - This is the binding for the Hue HDMI Sync Box. + Binding for the Hue HDMI Sync Box. - \ No newline at end of file + From 053f7eb5f95aae4d461f59620e65de838064ce96 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 9 Feb 2024 09:02:08 +0100 Subject: [PATCH 003/128] =?UTF-8?q?=F0=9F=92=B3=20updated=20credits=20in?= =?UTF-8?q?=20headers=20(initial=20code=20by=20Marco=20Kawon)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/huesyncBindingConstants.java | 3 ++- .../openhab/binding/huesync/internal/huesyncConfiguration.java | 3 ++- .../org/openhab/binding/huesync/internal/huesyncHandler.java | 3 ++- .../binding/huesync/internal/huesyncHandlerFactory.java | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java index c95f14dc6bb6e..d1725312879dc 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java @@ -19,7 +19,8 @@ * The {@link huesyncBindingConstants} class defines common constants, which are * used across the whole binding. * - * @author Patrik Gfeller - Initial contribution + * @author Marco Kawon - Initial contribution + * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault public class huesyncBindingConstants { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java index 314af3703abb7..5d2f990c26660 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java @@ -17,7 +17,8 @@ /** * The {@link huesyncConfiguration} class contains fields mapping thing configuration parameters. * - * @author Patrik Gfeller - Initial contribution + * @author Marco Kawon - Initial contribution + * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault public class huesyncConfiguration { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java index 90c7d87770a46..ed58d22600660 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java @@ -29,7 +29,8 @@ * The {@link huesyncHandler} is responsible for handling commands, which are * sent to one of the channels. * - * @author Patrik Gfeller - Initial contribution + * @author Marco Kawon - Initial contribution + * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault public class huesyncHandler extends BaseThingHandler { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java index aa5b5ec3136d1..a072fc4d38653 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java @@ -29,7 +29,8 @@ * The {@link huesyncHandlerFactory} is responsible for creating things and thing * handlers. * - * @author Patrik Gfeller - Initial contribution + * @author Marco Kawon - Initial contribution + * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) From 656008d12bd7376549c2423dbe216a683762f85a Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 9 Feb 2024 19:44:59 +0100 Subject: [PATCH 004/128] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20class=20&=20filena?= =?UTF-8?q?mes=20updated=20to=20be=20consistent=20with=20other=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- ...Constants.java => HueSyncBindingConstants.java} | 4 ++-- ...onfiguration.java => HueSyncConfiguration.java} | 4 ++-- .../{huesyncHandler.java => HueSyncHandler.java} | 14 +++++++------- ...dlerFactory.java => HueSyncHandlerFactory.java} | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{huesyncBindingConstants.java => HueSyncBindingConstants.java} (90%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{huesyncConfiguration.java => HueSyncConfiguration.java} (89%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{huesyncHandler.java => HueSyncHandler.java} (90%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{huesyncHandlerFactory.java => HueSyncHandlerFactory.java} (87%) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java similarity index 90% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index d1725312879dc..79339cea85ff3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -16,14 +16,14 @@ import org.openhab.core.thing.ThingTypeUID; /** - * The {@link huesyncBindingConstants} class defines common constants, which are + * The {@link HueSyncBindingConstants} class defines common constants, which are * used across the whole binding. * * @author Marco Kawon - Initial contribution * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault -public class huesyncBindingConstants { +public class HueSyncBindingConstants { private static final String BINDING_ID = "huesync"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java similarity index 89% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java index 5d2f990c26660..975e69f9a131e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java @@ -15,13 +15,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link huesyncConfiguration} class contains fields mapping thing configuration parameters. + * The {@link HueSyncConfiguration} class contains fields mapping thing configuration parameters. * * @author Marco Kawon - Initial contribution * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault -public class huesyncConfiguration { +public class HueSyncConfiguration { /** * Sample configuration parameters. Replace with your own. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java similarity index 90% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java index ed58d22600660..aed66db1c7fac 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.huesync.internal; -import static org.openhab.binding.huesync.internal.huesyncBindingConstants.*; +import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -26,20 +26,20 @@ import org.slf4j.LoggerFactory; /** - * The {@link huesyncHandler} is responsible for handling commands, which are + * The {@link HueSyncHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Marco Kawon - Initial contribution * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure */ @NonNullByDefault -public class huesyncHandler extends BaseThingHandler { +public class HueSyncHandler extends BaseThingHandler { - private final Logger logger = LoggerFactory.getLogger(huesyncHandler.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class); - private @Nullable huesyncConfiguration config; + private @Nullable HueSyncConfiguration config; - public huesyncHandler(Thing thing) { + public HueSyncHandler(Thing thing) { super(thing); } @@ -61,7 +61,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void initialize() { - config = getConfigAs(huesyncConfiguration.class); + config = getConfigAs(HueSyncConfiguration.class); // TODO: Initialize the handler. // The framework requires you to return from this method quickly, i.e. any network access must be done in diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java similarity index 87% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java index a072fc4d38653..062d46acac2b4 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/huesyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.huesync.internal; -import static org.openhab.binding.huesync.internal.huesyncBindingConstants.*; +import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.*; import java.util.Set; @@ -26,7 +26,7 @@ import org.osgi.service.component.annotations.Component; /** - * The {@link huesyncHandlerFactory} is responsible for creating things and thing + * The {@link HueSyncHandlerFactory} is responsible for creating things and thing * handlers. * * @author Marco Kawon - Initial contribution @@ -34,7 +34,7 @@ */ @NonNullByDefault @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) -public class huesyncHandlerFactory extends BaseThingHandlerFactory { +public class HueSyncHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); @@ -48,7 +48,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { - return new huesyncHandler(thing); + return new HueSyncHandler(thing); } return null; From d5d9b62227601d6a6bbf4def9243c9b1d289e407 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 9 Feb 2024 22:52:54 +0100 Subject: [PATCH 005/128] =?UTF-8?q?=F0=9F=94=8E=20skeleton=20mDNS=20discov?= =?UTF-8?q?ery=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 31 ++++++- .../internal/HueSyncBindingConstants.java | 4 +- .../internal/HueSyncHandlerFactory.java | 8 +- .../HueSyncDiscoveryParticipant.java | 81 +++++++++++++++++++ 4 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 1edd412d65108..3d7c99d153c63 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -1,4 +1,4 @@ -# huesync Binding +# HueSync Binding _Give some details about what this binding is meant for - a protocol, system, specific device._ @@ -23,6 +23,35 @@ _Mention for what it works and what needs to be kept in mind when using it._ ## Binding Configuration +The beinding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devies in the local network. +The LED on the Sync Box must be white or red. +This indicates that the device is connected to the Network. +If the LED is blinking blue, you need to setup the device using the official [Hue Sync App](https://www.philips-hue.com/en-in/explore-hue/propositions/entertainment/hue-sync). + +If the device is not discovered you can check if it is properly configured and discoverable in the network: + +:::: tabs + +::: tab Linux + +```bash +avahi-browse --resolve _huesync._tcp ++ wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local += wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local + hostname = [XXXXXXXXXXX.local] + address = [192.168.0.12] + port = [443] + txt = ["name=Sync Box" "devicetype=HSB1" "uniqueid=XXXXXXXXXXX" "path=/api"] +``` + +::: + +::: tab Windows + +::: + +:::: + _If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ _In this section, you should link to this file and provide some information about the options._ _The file could e.g. look like:_ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index 79339cea85ff3..f7779c59bc91e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -25,10 +25,10 @@ @NonNullByDefault public class HueSyncBindingConstants { - private static final String BINDING_ID = "huesync"; + public static final String BINDING_ID = "huesync"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample"); + public static final ThingTypeUID THING_TYPE_SYNCBOX = new ThingTypeUID(BINDING_ID, "box"); // List of all Channel ids public static final String CHANNEL_1 = "channel1"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java index 062d46acac2b4..7f0f623683540 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java @@ -12,8 +12,9 @@ */ package org.openhab.binding.huesync.internal; -import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.*; +import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.THING_TYPE_SYNCBOX; +import java.util.Collections; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -36,7 +37,8 @@ @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) public class HueSyncHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + @SuppressWarnings("null") + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_SYNCBOX); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -47,7 +49,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { + if (THING_TYPE_SYNCBOX.equals(thingTypeUID)) { return new HueSyncHandler(thing); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java new file mode 100644 index 0000000000000..7a7c44d7a9bc5 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -0,0 +1,81 @@ +package org.openhab.binding.huesync.internal.discovery; + +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import java.util.Collections; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.HueSyncBindingConstants; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link HueSyncDiscoveryParticipant} is responsible for discovering + * the remote huesync.boxes using mDNS discovery service. + * + * @author Marco Kawon - Initial contribution + * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * + */ +@NonNullByDefault +@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") +public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { + @SuppressWarnings("null") + private Logger logger = LoggerFactory.getLogger(HueSyncDiscoveryParticipant.class); + + /** + * + * Match the hostname + identifier of the discovered huesync-box. + * Input is like "HueSyncBox-C4299605AAB2._huesync._tcp.local." + * + * @see· + * Service·Name·and·Transport·Protocol·Port·Number·Registry + */ + private static final String SERVICE_TYPE = "_huesync._tcp.local."; + + @SuppressWarnings("null") + @Override + public Set getSupportedThingTypeUIDs() { + return Collections.singleton(HueSyncBindingConstants.THING_TYPE_SYNCBOX); + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { + String qualifiedName = serviceInfo.getQualifiedName(); + + logger.debug("HueSync Device found: {}", qualifiedName); + + return null; + } + + @Override + public @Nullable ThingUID getThingUID(ServiceInfo service) { + return null; + } +} From cc6d39a21769889df709a609e47eb7307bbc1495 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 10 Feb 2024 23:45:19 +0100 Subject: [PATCH 006/128] =?UTF-8?q?=F0=9F=94=8E=20mDNS=20device=20discover?= =?UTF-8?q?y=20-=20use=20API=20to=20get=20device=20information=20(wip=20?= =?UTF-8?q?=F0=9F=94=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../HueSyncDiscoveryParticipant.java | 71 +++++++++++++++++-- .../{ => factory}/HueSyncHandlerFactory.java | 3 +- 2 files changed, 69 insertions(+), 5 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{ => factory}/HueSyncHandlerFactory.java (94%) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 7a7c44d7a9bc5..5f66774cad06b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -14,18 +14,26 @@ */ import java.util.Collections; +import java.util.Dictionary; import java.util.Set; import javax.jmdns.ServiceInfo; 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.util.ssl.SslContextFactory; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; 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; @@ -34,7 +42,8 @@ * the remote huesync.boxes using mDNS discovery service. * * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * @author Patrik Gfeller - Integration into official repository, update to 4.x + * infrastructure * */ @NonNullByDefault @@ -46,13 +55,19 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { /** * * Match the hostname + identifier of the discovered huesync-box. - * Input is like "HueSyncBox-C4299605AAB2._huesync._tcp.local." + * Input is like "HueSyncBox-XXXXXXXXXXXX._huesync._tcp.local." * * @see· * Service·Name·and·Transport·Protocol·Port·Number·Registry */ private static final String SERVICE_TYPE = "_huesync._tcp.local."; + private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; + + // TODO: Implement SSL certificate validation + private static final HttpClient httpClient = new HttpClient(new SslContextFactory.Client(true)); + + private boolean autoDiscoveryEnabled = true; @SuppressWarnings("null") @Override @@ -67,10 +82,26 @@ public String getServiceType() { @Override public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { - String qualifiedName = serviceInfo.getQualifiedName(); + if (this.autoDiscoveryEnabled) { + String qualifiedName = serviceInfo.getQualifiedName(); + + logger.debug("HueSync Device found: {}", qualifiedName); - logger.debug("HueSync Device found: {}", qualifiedName); + try { + String[] addressses = serviceInfo.getHostAddresses(); + if (addressses.length == 0) { + logger.warn("Incomplete mDNS device discovery information - {} ignored.", qualifiedName); + return null; + } + String request = String.format("https://%s:%s/%s", serviceInfo.getHostAddresses()[0], + serviceInfo.getPort(), DEVICE_INFO_ENDPOINT); + ContentResponse response = HueSyncDiscoveryParticipant.httpClient.GET(request); + logger.debug("Device information for {}: {}", qualifiedName, response); + } catch (Exception e) { + logger.error("Unable to query device information for {}: {}", qualifiedName, e); + } + } return null; } @@ -78,4 +109,36 @@ public String getServiceType() { public @Nullable ThingUID getThingUID(ServiceInfo service) { return null; } + + @Activate + protected void activate(ComponentContext componentContext) { + try { + httpClient.start(); + + updateService(componentContext); + } catch (Exception e) { + logger.error("Unable to activate mDNS discovery participant: {}, Exception: {}", SERVICE_TYPE, e); + } + } + + @Modified + protected void modified(ComponentContext componentContext) { + updateService(componentContext); + } + + @SuppressWarnings("null") + private void updateService(ComponentContext componentContext) { + Dictionary properties = componentContext.getProperties(); + String autoDiscoveryPropertyValue = (String) properties + .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); + + if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) { + boolean value = Boolean.valueOf(autoDiscoveryPropertyValue); + if (value != this.autoDiscoveryEnabled) { + logger.debug("{} update: {} ➡️ {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, + autoDiscoveryPropertyValue, value); + this.autoDiscoveryEnabled = value; + } + } + } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 7f0f623683540..6866b8d58a9a9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.huesync.internal; +package org.openhab.binding.huesync.internal.factory; import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.THING_TYPE_SYNCBOX; @@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.HueSyncHandler; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; From a16c04c09707d241b5905b7998bdaf844c409d5c Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 13 Feb 2024 23:14:52 +0100 Subject: [PATCH 007/128] =?UTF-8?q?=F0=9F=94=90=20preparation(s)=20to=20su?= =?UTF-8?q?pport=20SSL=20(wip),=20create=20ThingUID=20for=20discovered=20d?= =?UTF-8?q?evice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../internal/HueSyncBindingConstants.java | 5 +- .../HueSyncTrustManagerProvider.java | 53 +++++++++++++++++++ .../HueSyncDiscoveryParticipant.java | 51 +++++++++++------- .../src/main/resources/hsb_cacert.pem | 12 +++++ 4 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index f7779c59bc91e..a8371f795aef7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -20,7 +20,8 @@ * used across the whole binding. * * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * @author Patrik Gfeller - Integration into official repository, update to 4.x + * infrastructure */ @NonNullByDefault public class HueSyncBindingConstants { @@ -28,7 +29,7 @@ public class HueSyncBindingConstants { public static final String BINDING_ID = "huesync"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SYNCBOX = new ThingTypeUID(BINDING_ID, "box"); + public static final ThingTypeUID THING_TYPE = new ThingTypeUID(BINDING_ID, "box"); // List of all Channel ids public static final String CHANNEL_1 = "channel1"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java new file mode 100644 index 0000000000000..a600690c755f4 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -0,0 +1,53 @@ +package org.openhab.binding.huesync.internal.connection; + +import java.io.IOException; +import java.io.InputStream; +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.openhab.core.io.net.http.PEMTrustManager; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; +import org.openhab.core.io.net.http.TrustAllTrustManager; + +/** + * Provides a {@link PEMTrustManager} to allow secure connections to a Hue HDMI + * Sync Box + * + * @author Patrik Gfeller - Initial Contribution + * Based on the hue binding implementation by Christoph Weitkamp + */ +@NonNullByDefault +public class HueSyncTrustManagerProvider implements TlsTrustManagerProvider { + private static final String FILENAME = "hsb_cacert.pem"; + private final String hostname; + + private X509ExtendedTrustManager trustManager; + + public HueSyncTrustManagerProvider(String hostname) throws IOException, CertificateException { + this.hostname = hostname; + + String certificate = readCertificateStringFromResource(); + this.trustManager = (certificate != null) ? new PEMTrustManager(certificate) + : TrustAllTrustManager.getInstance(); + } + + @Override + public String getHostName() { + return this.hostname; + } + + @Override + public X509ExtendedTrustManager getTrustManager() { + return this.trustManager; + } + + private String readCertificateStringFromResource() throws IOException { + URL resource = Thread.currentThread().getContextClassLoader().getResource(FILENAME); + InputStream certInputStream = resource.openStream(); + return new String(certInputStream.readAllBytes(), StandardCharsets.UTF_8); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 5f66774cad06b..d5515a8c5157d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.Dictionary; +import java.util.Objects; import java.util.Set; import javax.jmdns.ServiceInfo; @@ -28,12 +29,14 @@ import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.thing.ThingRegistry; 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.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,13 +69,20 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { // TODO: Implement SSL certificate validation private static final HttpClient httpClient = new HttpClient(new SslContextFactory.Client(true)); - + // TODO: Get from configuration private boolean autoDiscoveryEnabled = true; + protected final ThingRegistry thingRegistry; + + @Activate + public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + @SuppressWarnings("null") @Override public Set getSupportedThingTypeUIDs() { - return Collections.singleton(HueSyncBindingConstants.THING_TYPE_SYNCBOX); + return Collections.singleton(HueSyncBindingConstants.THING_TYPE); } @Override @@ -81,25 +91,23 @@ public String getServiceType() { } @Override - public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { + public @Nullable DiscoveryResult createResult(ServiceInfo service) { if (this.autoDiscoveryEnabled) { - String qualifiedName = serviceInfo.getQualifiedName(); + ThingUID uid = getThingUID(service); + if (Objects.nonNull(uid)) { + try { + String qualifiedName = service.getQualifiedName(); - logger.debug("HueSync Device found: {}", qualifiedName); + logger.debug("HueSync Device found: {}", qualifiedName); - try { - String[] addressses = serviceInfo.getHostAddresses(); - if (addressses.length == 0) { - logger.warn("Incomplete mDNS device discovery information - {} ignored.", qualifiedName); - return null; - } - String request = String.format("https://%s:%s/%s", serviceInfo.getHostAddresses()[0], - serviceInfo.getPort(), DEVICE_INFO_ENDPOINT); - ContentResponse response = HueSyncDiscoveryParticipant.httpClient.GET(request); + String request = String.format("https://%s:%s/%s", service.getHostAddresses()[0], service.getPort(), + DEVICE_INFO_ENDPOINT); + ContentResponse response = HueSyncDiscoveryParticipant.httpClient.GET(request); - logger.debug("Device information for {}: {}", qualifiedName, response); - } catch (Exception e) { - logger.error("Unable to query device information for {}: {}", qualifiedName, e); + logger.debug("Device information for {}: {}", qualifiedName, response); + } catch (Exception e) { + logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e); + } } } return null; @@ -107,7 +115,14 @@ public String getServiceType() { @Override public @Nullable ThingUID getThingUID(ServiceInfo service) { - return null; + String id = service.getName(); + String[] addressses = service.getHostAddresses(); + + if (addressses.length == 0 || id == null || id.isBlank()) { + logger.warn("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); + return null; + } + return new ThingUID(HueSyncBindingConstants.THING_TYPE, id); } @Activate diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem b/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem new file mode 100644 index 0000000000000..789591c582c9c --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBwDCCAWagAwIBAgIBATAKBggqhkjOPQQDAjA2MQswCQYDVQQGEwJOTDEUMBIG +A1UECgwLUGhpbGlwcyBIdWUxETAPBgNVBAMMCHJvb3QtaHNiMCAXDTE3MDEwMTAw +MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjA2MQswCQYDVQQGEwJOTDEUMBIGA1UECgwL +UGhpbGlwcyBIdWUxETAPBgNVBAMMCHJvb3QtaHNiMFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAEr9FgOxnsonsrnUZr3C4ggST7YCR9wISvDuwlNdZcAz4HiVCNmAAP +tAnAFDG0U19Rmc4MfRYBMO8GrOHrOkZ7sKNjMGEwHQYDVR0OBBYEFCK+VIWZqp++ +DHqmGLWEZHYdH9v7MB8GA1UdIwQYMBaAFCK+VIWZqp++DHqmGLWEZHYdH9v7MA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gAMEUC +IAFDI0Q4IOPxV7cY4wSVOJAn4y5AdZwrItJ1XuNpmCltAiEA5c6wcu6qmF596uyA +r7xLnr3/F5zJxrE3AyLD4t+5oKs= +-----END CERTIFICATE----- From beadc4564ca91e5d8361eeeeb5fd2206189d1514 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 15 Feb 2024 00:23:39 +0100 Subject: [PATCH 008/128] =?UTF-8?q?=F0=9F=8F=AD=20Inbox/Thing=20Factory=20?= =?UTF-8?q?(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../internal/HueSyncBindingConstants.java | 2 +- .../huesync/internal/HueSyncHandler.java | 29 ++++++++----- .../HueSyncTrustManagerProvider.java | 12 ++++++ .../HueSyncDiscoveryParticipant.java | 43 ++++++++++++------- .../factory/HueSyncHandlerFactory.java | 29 ++++++++++--- .../src/main/resources/OH-INF/addon/addon.xml | 12 ++++++ .../resources/OH-INF/i18n/huesync.properties | 26 +++++++++-- 7 files changed, 118 insertions(+), 35 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index a8371f795aef7..a26783af1440e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -29,7 +29,7 @@ public class HueSyncBindingConstants { public static final String BINDING_ID = "huesync"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE = new ThingTypeUID(BINDING_ID, "box"); + public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "box"); // List of all Channel ids public static final String CHANNEL_1 = "channel1"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java index aed66db1c7fac..25ee0a4cb5f4f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.huesync.internal; -import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.*; +import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.CHANNEL_1; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,7 +30,8 @@ * sent to one of the channels. * * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * @author Patrik Gfeller - Integration into official repository, update to 4.x + * infrastructure */ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { @@ -64,16 +65,22 @@ public void initialize() { config = getConfigAs(HueSyncConfiguration.class); // TODO: Initialize the handler. - // The framework requires you to return from this method quickly, i.e. any network access must be done in + // The framework requires you to return from this method quickly, i.e. any + // network access must be done in // the background initialization below. - // Also, before leaving this method a thing status from one of ONLINE, OFFLINE or UNKNOWN must be set. This + // Also, before leaving this method a thing status from one of ONLINE, OFFLINE + // or UNKNOWN must be set. This // might already be the real thing status in case you can decide it directly. - // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN - // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // In case you can not decide the thing status directly (e.g. for long running + // connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the + // real status asynchronously in the // background. - // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. - // the framework is then able to reuse the resources from the thing handler initialization. + // set the thing status to UNKNOWN temporarily and let the background task + // decide for the real status. + // the framework is then able to reuse the resources from the thing handler + // initialization. // we set this upfront to reliably check status updates in unit tests. updateStatus(ThingStatus.UNKNOWN); @@ -96,9 +103,11 @@ public void initialize() { // Logging to INFO should be avoided normally. // See https://www.openhab.org/docs/developer/guidelines.html#f-logging - // Note: When initialization can NOT be done set the status with more details for further + // Note: When initialization can NOT be done set the status with more details + // for further // analysis. See also class ThingStatusDetail for all available status details. - // Add a description to give user information to understand why thing does not work as expected. E.g. + // Add a description to give user information to understand why thing does not + // work as expected. E.g. // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, // "Can not access device as username and/or password are invalid"); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java index a600690c755f4..2f81e5893a114 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ package org.openhab.binding.huesync.internal.connection; import java.io.IOException; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index d5515a8c5157d..3006a2bb01643 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -1,5 +1,3 @@ -package org.openhab.binding.huesync.internal.discovery; - /** * Copyright (c) 2024-2024 Contributors to the openHAB project * @@ -12,6 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ +package org.openhab.binding.huesync.internal.discovery; import java.util.Collections; import java.util.Dictionary; @@ -23,10 +22,11 @@ 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.util.ssl.SslContextFactory; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; +import org.openhab.binding.huesync.internal.factory.HueSyncHandlerFactory; 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.thing.ThingRegistry; @@ -67,6 +67,8 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { private static final String SERVICE_TYPE = "_huesync._tcp.local."; private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; + private final HueSyncHandlerFactory factory = new HueSyncHandlerFactory(); + // TODO: Implement SSL certificate validation private static final HttpClient httpClient = new HttpClient(new SslContextFactory.Client(true)); // TODO: Get from configuration @@ -82,7 +84,7 @@ public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) @SuppressWarnings("null") @Override public Set getSupportedThingTypeUIDs() { - return Collections.singleton(HueSyncBindingConstants.THING_TYPE); + return Collections.singleton(HueSyncBindingConstants.THING_TYPE_UID); } @Override @@ -96,15 +98,25 @@ public String getServiceType() { ThingUID uid = getThingUID(service); if (Objects.nonNull(uid)) { try { - String qualifiedName = service.getQualifiedName(); - - logger.debug("HueSync Device found: {}", qualifiedName); - - String request = String.format("https://%s:%s/%s", service.getHostAddresses()[0], service.getPort(), - DEVICE_INFO_ENDPOINT); - ContentResponse response = HueSyncDiscoveryParticipant.httpClient.GET(request); - - logger.debug("Device information for {}: {}", qualifiedName, response); + /* + * String qualifiedName = service.getQualifiedName(); + * + * logger.debug("HueSync Device found: {}", qualifiedName); + * + * String request = String.format("https://%s:%s/%s", + * service.getHostAddresses()[0], service.getPort(), + * DEVICE_INFO_ENDPOINT); + * ContentResponse response = + * HueSyncDiscoveryParticipant.httpClient.GET(request); + * + * logger.debug("Device information for {}: {}", qualifiedName, response); + */ + + // return new HueSyncDiscoveryResult(uid, Map.of()); + + DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid).withLabel(service.getName()); + + return builder.build(); } catch (Exception e) { logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e); } @@ -122,7 +134,7 @@ public String getServiceType() { logger.warn("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); return null; } - return new ThingUID(HueSyncBindingConstants.THING_TYPE, id); + return new ThingUID(HueSyncBindingConstants.THING_TYPE_UID, id); } @Activate @@ -143,7 +155,8 @@ protected void modified(ComponentContext componentContext) { @SuppressWarnings("null") private void updateService(ComponentContext componentContext) { - Dictionary properties = componentContext.getProperties(); + Dictionary properties = componentContext.getProperties(); + String autoDiscoveryPropertyValue = (String) properties .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 6866b8d58a9a9..a53f055bda145 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -12,45 +12,62 @@ */ package org.openhab.binding.huesync.internal.factory; -import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.THING_TYPE_SYNCBOX; - import java.util.Collections; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.HueSyncBindingConstants; import org.openhab.binding.huesync.internal.HueSyncHandler; +import org.openhab.core.config.core.Configuration; 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; /** - * The {@link HueSyncHandlerFactory} is responsible for creating things and thing + * The {@link HueSyncHandlerFactory} is responsible for creating things and + * thing * handlers. * * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * @author Patrik Gfeller - Integration into official repository, update to 4.x + * infrastructure */ @NonNullByDefault @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) public class HueSyncHandlerFactory extends BaseThingHandlerFactory { + @Activate + public HueSyncHandlerFactory() { + } + @SuppressWarnings("null") - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_SYNCBOX); + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections + .singleton(HueSyncBindingConstants.THING_TYPE_UID); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } + @Override + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + + return Objects.nonNull(thingUID) ? super.createThing(thingTypeUID, configuration, thingUID) : null; + } + @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (THING_TYPE_SYNCBOX.equals(thingTypeUID)) { + if (HueSyncBindingConstants.THING_TYPE_UID.equals(thingTypeUID)) { return new HueSyncHandler(thing); } diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml index d8bd8447a95ca..8aed8234a5493 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml @@ -7,4 +7,16 @@ Hue HDMI Sync Box Binding Binding for the Hue HDMI Sync Box. + + + mdns + + + mdnsServiceType + _huesync._tcp.local. + + + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 0c2f44015a92d..a4eef2efeaa30 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -1,3 +1,23 @@ -# FIXME: please add all English translations to this file so the texts can be translated using Crowdin -# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations -# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html +# add-on + +addon.huesync.name = Hue HDMI Sync Box Binding +addon.huesync.description = Binding for the Hue HDMI Sync Box. + +# thing types + +thing-type.huesync.sample.label = huesync Binding Thing +thing-type.huesync.sample.description = Sample thing for huesync Binding + +# thing types config + +thing-type.config.huesync.sample.hostname.label = Hostname +thing-type.config.huesync.sample.hostname.description = Hostname or IP address of the device +thing-type.config.huesync.sample.password.label = Password +thing-type.config.huesync.sample.password.description = Password to access the device +thing-type.config.huesync.sample.refreshInterval.label = Refresh Interval +thing-type.config.huesync.sample.refreshInterval.description = Interval the device is polled in sec. + +# channel types + +channel-type.huesync.sample-channel.label = Example Temperature +channel-type.huesync.sample-channel.description = Sample channel for huesync Binding From c72c5e3c9cc03790471b5ec91c26092a4a848e8f Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 16 Feb 2024 21:27:57 +0100 Subject: [PATCH 009/128] =?UTF-8?q?=F0=9F=94=8E=20device=20discovery=20imp?= =?UTF-8?q?rovements=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../internal/HueSyncBindingConstants.java | 6 +-- .../huesync/internal/HueSyncHandler.java | 23 ++++---- .../HueSyncDiscoveryParticipant.java | 51 ++++++++++++------ .../factory/HueSyncHandlerFactory.java | 25 +++++---- .../resources/OH-INF/i18n/huesync.properties | 20 +++---- .../resources/OH-INF/thing/thing-types.xml | 54 ++++++++----------- 6 files changed, 93 insertions(+), 86 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index a26783af1440e..a8152e40585e0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -27,10 +27,8 @@ public class HueSyncBindingConstants { public static final String BINDING_ID = "huesync"; - - // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "box"); - // List of all Channel ids - public static final String CHANNEL_1 = "channel1"; + public static final String PARAMETER_HOST = "host"; + public static final String PARAMETER_PORT = "port"; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java index 25ee0a4cb5f4f..09f319d353bd9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.huesync.internal; -import static org.openhab.binding.huesync.internal.HueSyncBindingConstants.CHANNEL_1; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.thing.ChannelUID; @@ -21,7 +19,6 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,18 +43,18 @@ public HueSyncHandler(Thing thing) { @Override public void handleCommand(ChannelUID channelUID, Command command) { - if (CHANNEL_1.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - // TODO: handle data refresh - } + // if (CHANNEL_1.equals(channelUID.getId())) { + // if (command instanceof RefreshType) { + // // TODO: handle data refresh + // } - // TODO: handle command + // // TODO: handle command - // Note: if communication with thing fails for some reason, - // indicate that by setting the status with detail information: - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - // "Could not control device at IP address x.x.x.x"); - } + // // Note: if communication with thing fails for some reason, + // // indicate that by setting the status with detail information: + // // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // // "Could not control device at IP address x.x.x.x"); + // } } @Override diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 3006a2bb01643..422ac897281ae 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -14,6 +14,8 @@ import java.util.Collections; import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -21,10 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; -import org.openhab.binding.huesync.internal.factory.HueSyncHandlerFactory; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -65,12 +64,12 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { * Service·Name·and·Transport·Protocol·Port·Number·Registry */ private static final String SERVICE_TYPE = "_huesync._tcp.local."; - private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; - - private final HueSyncHandlerFactory factory = new HueSyncHandlerFactory(); + // TODO: move to API + // private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; // TODO: Implement SSL certificate validation - private static final HttpClient httpClient = new HttpClient(new SslContextFactory.Client(true)); + // private static final HttpClient httpClient = new HttpClient(new + // SslContextFactory.Client(true)); // TODO: Get from configuration private boolean autoDiscoveryEnabled = true; @@ -93,6 +92,7 @@ public String getServiceType() { } @Override + @SuppressWarnings("null") public @Nullable DiscoveryResult createResult(ServiceInfo service) { if (this.autoDiscoveryEnabled) { ThingUID uid = getThingUID(service); @@ -113,10 +113,14 @@ public String getServiceType() { */ // return new HueSyncDiscoveryResult(uid, Map.of()); + Map properties = new HashMap<>(2); - DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid).withLabel(service.getName()); + properties.put(HueSyncBindingConstants.PARAMETER_HOST, service.getHostAddresses()[0]); + properties.put(HueSyncBindingConstants.PARAMETER_PORT, service.getPort()); - return builder.build(); + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(service.getName()) + .withProperties(properties).build(); + return result; } catch (Exception e) { logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e); } @@ -134,18 +138,33 @@ public String getServiceType() { logger.warn("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); return null; } + + // Matcher matcher = PHILIPS_SYNCBOX_PATTERN.matcher(qualifiedName); + // matcher.matches(); // we already know it matches, it was matched in + // getThingUID + + // String vendor = "Philips"; + // String model = "Hue Play HDMI Sync Box"; + // String friendlyName = "Philips Hue HDMI Sync Box"; + // String hostname = (matcher.group(1) + "-" + matcher.group(2)).toLowerCase(); + // String serial = matcher.group(2); + + // Map properties = new HashMap<>(2); + // properties.put(PARAMETER_HOST, hostname); + // properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial); + // properties.put(Thing.PROPERTY_VENDOR, vendor); + // properties.put(Thing.PROPERTY_MODEL_ID, model); + + // logger.debug("thing properties: {}", properties); + + // return DiscoveryResultBuilder.create(thingUID).withProperties(properties) + // .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withLabel(friendlyName).build(); return new ThingUID(HueSyncBindingConstants.THING_TYPE_UID, id); } @Activate protected void activate(ComponentContext componentContext) { - try { - httpClient.start(); - - updateService(componentContext); - } catch (Exception e) { - logger.error("Unable to activate mDNS discovery participant: {}, Exception: {}", SERVICE_TYPE, e); - } + updateService(componentContext); } @Modified diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index a53f055bda145..3662c5b738839 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -13,22 +13,23 @@ package org.openhab.binding.huesync.internal.factory; import java.util.Collections; -import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; import org.openhab.binding.huesync.internal.HueSyncHandler; -import org.openhab.core.config.core.Configuration; +import org.openhab.core.io.net.http.HttpClientFactory; 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; /** * The {@link HueSyncHandlerFactory} is responsible for creating things and @@ -43,8 +44,12 @@ @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) public class HueSyncHandlerFactory extends BaseThingHandlerFactory { + private final HttpClient httpClient; + @Activate - public HueSyncHandlerFactory() { + public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception { + httpClient = new HttpClient(new SslContextFactory.Client(true)); + httpClient.start(); } @SuppressWarnings("null") @@ -56,12 +61,14 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } - @Override - public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, - @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + // @Override + // public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration + // configuration, + // @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { - return Objects.nonNull(thingUID) ? super.createThing(thingTypeUID, configuration, thingUID) : null; - } + // return Objects.nonNull(thingUID) ? super.createThing(thingTypeUID, + // configuration, thingUID) : null; + // } @Override protected @Nullable ThingHandler createHandler(Thing thing) { diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index a4eef2efeaa30..3964df1bd8bf7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -5,19 +5,13 @@ addon.huesync.description = Binding for the Hue HDMI Sync Box. # thing types -thing-type.huesync.sample.label = huesync Binding Thing -thing-type.huesync.sample.description = Sample thing for huesync Binding +thing-type.huesync.box.label = Hue HDMI Sync Box +thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. # thing types config -thing-type.config.huesync.sample.hostname.label = Hostname -thing-type.config.huesync.sample.hostname.description = Hostname or IP address of the device -thing-type.config.huesync.sample.password.label = Password -thing-type.config.huesync.sample.password.description = Password to access the device -thing-type.config.huesync.sample.refreshInterval.label = Refresh Interval -thing-type.config.huesync.sample.refreshInterval.description = Interval the device is polled in sec. - -# channel types - -channel-type.huesync.sample-channel.label = Example Temperature -channel-type.huesync.sample-channel.description = Sample channel for huesync Binding +thing-type.config.huesync.box.group.connection.label = Connection Settings +thing-type.config.huesync.box.host.label = Network Address +thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. +thing-type.config.huesync.box.port.label = Port +thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 45aca2ce239a5..40c8e19d28508 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,45 +4,37 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - - + - - Sample thing for huesync Binding - - - - + + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI + inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful + smart light that responds to and reflects the content you watch or listen to. + + Philips + - + + + + + network-address - - Hostname or IP address of the device + + Network address of the HDMI Sync Box. + true - - password - - Password to access the device - - - - Interval the device is polled in sec. - 600 + + + + Port of the HDMI Sync Box. true + 443 + true + - - - Number:Temperature - - Sample channel for huesync Binding - From 7b4fffcfe36b6a7ac8bdc3fcad68b2c9ee1a21ef Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 17 Feb 2024 23:29:11 +0100 Subject: [PATCH 010/128] =?UTF-8?q?=F0=9F=92=AC=20Basic=20JSON=20de-serial?= =?UTF-8?q?ization=20added=20to=20Thing-Handler=20to=20get=20device=20info?= =?UTF-8?q?rmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../internal/HueSyncConfiguration.java | 19 ++---- .../huesync/internal/HueSyncHandler.java | 54 ++++++++++++++-- .../internal/api/dto/HueSyncDeviceInfo.java | 60 ++++++++++++++++++ .../dto/HueSyncDeviceInfoCapabilities.java | 29 +++++++++ .../connection/HueSyncConnection.java | 63 +++++++++++++++++++ .../HueSyncDiscoveryParticipant.java | 41 +----------- .../factory/HueSyncHandlerFactory.java | 18 +----- 7 files changed, 211 insertions(+), 73 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java index 975e69f9a131e..6f2861e438c41 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java @@ -12,21 +12,12 @@ */ package org.openhab.binding.huesync.internal; -import org.eclipse.jdt.annotation.NonNullByDefault; - /** - * The {@link HueSyncConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x infrastructure + * TODO: Description ... + * + * @author Patrik Gfeller - Initial contribution */ -@NonNullByDefault public class HueSyncConfiguration { - - /** - * Sample configuration parameters. Replace with your own. - */ - public String hostname = ""; - public String password = ""; - public int refreshInterval = 600; + public String host; + public Integer port; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java index 09f319d353bd9..32ea0007a878b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java @@ -12,8 +12,15 @@ */ package org.openhab.binding.huesync.internal; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -37,8 +44,16 @@ public class HueSyncHandler extends BaseThingHandler { private @Nullable HueSyncConfiguration config; - public HueSyncHandler(Thing thing) { + private HttpClient httpClient; + + private HueSyncConnection connection; + + private HueSyncDeviceInfo deviceInfo; + + @SuppressWarnings("null") + public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) { super(thing); + this.httpClient = new HttpClient(new SslContextFactory.Client(true)); } @Override @@ -57,9 +72,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { // } } + // TODO: Check if we can go without the "null" warning ... + @SuppressWarnings("null") @Override public void initialize() { - config = getConfigAs(HueSyncConfiguration.class); + this.config = getConfigAs(HueSyncConfiguration.class); // TODO: Initialize the handler. // The framework requires you to return from this method quickly, i.e. any @@ -83,11 +100,24 @@ public void initialize() { // Example for background initialization: scheduler.execute(() -> { - boolean thingReachable = true; // - // when done do: - if (thingReachable) { + try { + this.connection = new HueSyncConnection(this.httpClient, this.config); + this.connection.start(); + + this.deviceInfo = this.connection.getDeviceInfo(); + + Map properties = editProperties(); + + properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); + properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceInfo.firmwareVersion); + + updateProperties(properties); + updateStatus(ThingStatus.ONLINE); - } else { + } catch (Exception e) { + // TODO: Log message ... + // TODO: thing status details ... updateStatus(ThingStatus.OFFLINE); } }); @@ -108,4 +138,16 @@ public void initialize() { // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, // "Can not access device as username and/or password are invalid"); } + + @Override + public void dispose() { + super.dispose(); + + try { + // TODO: Check if we have to unregister openHAB form the Hue HDMI Sync Box + this.httpClient.stop(); + } catch (Exception e) { + // TODO: Handle ... + } + } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java new file mode 100644 index 0000000000000..a9c687ee8e256 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto; + +/** + * HDMI Sync Box Device Information DTO + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +public class HueSyncDeviceInfo { + /** Friendly name of the device */ + public String name; + /** Device Type identifier */ + public String deviceType; + /** + * Capitalized hex string of the 6 byte / 12 characters device id without + * delimiters. Used as unique id on label, certificate common name, hostname + * etc. + */ + public String uniqueId; + /** + * Increased between firmware versions when api changes. Only apiLevel >= 7 is + * supported. + * + * TODO: Check minimal API level ... + */ + public int apiLevel; + /** + * User readable version of the device firmware, starting with decimal major + * .minor .maintenance format e.g. “1.12.3” + */ + public String firmwareVersion; + /** + * Build number of the firmware. Unique for every build with newer builds + * guaranteed a higher number than older. + */ + public int buildNumber; + public boolean termsAgreed; + /** uninitialized, disconnected, lan, wan */ + public String wifiState; + public String ipAddress; + public HueSyncDeviceInfoCapabilities capabilities; + public boolean beta; + public boolean overheating; + public boolean bluetooth; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java new file mode 100644 index 0000000000000..7e62aa14bfda1 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto; + +/** + * HDMI Sync Box Device Information Cababilities DTO + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +public class HueSyncDeviceInfoCapabilities { + /** The total number of IR codes configurable */ + public int maxIrCodes; + /** The total number of Presets configurable */ + public int maxPresets; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java new file mode 100644 index 0000000000000..0ae72a1029dfd --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.connection; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.openhab.binding.huesync.internal.HueSyncConfiguration; +import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Handles the connection to a Hue HDMI Sync Box using the official API. + * + * @author Patrik Gfeller - Initial Contribution + */ +public class HueSyncConnection { + private static final String REQUEST_FORMAT = "https://%s:%s/%s"; + private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; + + private static final ObjectMapper ObjectMapper = new ObjectMapper(); + + private @NonNull HttpClient httpClient; + private @NonNull HueSyncConfiguration config; + + public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfiguration config) { + this.httpClient = httpClient; + this.config = config; + } + + public void start() throws Exception { + this.httpClient.start(); + } + + @SuppressWarnings("null") + public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionException, TimeoutException, + JsonMappingException, JsonProcessingException { + String request = String.format(REQUEST_FORMAT, this.config.host, this.config.port, DEVICE_INFO_ENDPOINT); + + ContentResponse response = this.httpClient.GET(request); + + // TODO: Create private method to handle response code etc .... + String json = response.getContentAsString(); + + return ObjectMapper.readValue(json, HueSyncDeviceInfo.class); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 422ac897281ae..f189be692087e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -65,12 +65,9 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { */ private static final String SERVICE_TYPE = "_huesync._tcp.local."; - // TODO: move to API - // private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; // TODO: Implement SSL certificate validation // private static final HttpClient httpClient = new HttpClient(new // SslContextFactory.Client(true)); - // TODO: Get from configuration private boolean autoDiscoveryEnabled = true; protected final ThingRegistry thingRegistry; @@ -98,21 +95,9 @@ public String getServiceType() { ThingUID uid = getThingUID(service); if (Objects.nonNull(uid)) { try { - /* - * String qualifiedName = service.getQualifiedName(); - * - * logger.debug("HueSync Device found: {}", qualifiedName); - * - * String request = String.format("https://%s:%s/%s", - * service.getHostAddresses()[0], service.getPort(), - * DEVICE_INFO_ENDPOINT); - * ContentResponse response = - * HueSyncDiscoveryParticipant.httpClient.GET(request); - * - * logger.debug("Device information for {}: {}", qualifiedName, response); - */ - - // return new HueSyncDiscoveryResult(uid, Map.of()); + logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(), + service.getHostAddresses()[0], service.getPort()); + Map properties = new HashMap<>(2); properties.put(HueSyncBindingConstants.PARAMETER_HOST, service.getHostAddresses()[0]); @@ -139,26 +124,6 @@ public String getServiceType() { return null; } - // Matcher matcher = PHILIPS_SYNCBOX_PATTERN.matcher(qualifiedName); - // matcher.matches(); // we already know it matches, it was matched in - // getThingUID - - // String vendor = "Philips"; - // String model = "Hue Play HDMI Sync Box"; - // String friendlyName = "Philips Hue HDMI Sync Box"; - // String hostname = (matcher.group(1) + "-" + matcher.group(2)).toLowerCase(); - // String serial = matcher.group(2); - - // Map properties = new HashMap<>(2); - // properties.put(PARAMETER_HOST, hostname); - // properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial); - // properties.put(Thing.PROPERTY_VENDOR, vendor); - // properties.put(Thing.PROPERTY_MODEL_ID, model); - - // logger.debug("thing properties: {}", properties); - - // return DiscoveryResultBuilder.create(thingUID).withProperties(properties) - // .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withLabel(friendlyName).build(); return new ThingUID(HueSyncBindingConstants.THING_TYPE_UID, id); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 3662c5b738839..3f93c592f822e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -17,8 +17,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; import org.openhab.binding.huesync.internal.HueSyncHandler; import org.openhab.core.io.net.http.HttpClientFactory; @@ -44,12 +42,11 @@ @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) public class HueSyncHandlerFactory extends BaseThingHandlerFactory { - private final HttpClient httpClient; + private final HttpClientFactory httpClientFactory; @Activate public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception { - httpClient = new HttpClient(new SslContextFactory.Client(true)); - httpClient.start(); + this.httpClientFactory = httpClientFactory; } @SuppressWarnings("null") @@ -61,21 +58,12 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } - // @Override - // public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration - // configuration, - // @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { - - // return Objects.nonNull(thingUID) ? super.createThing(thingTypeUID, - // configuration, thingUID) : null; - // } - @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (HueSyncBindingConstants.THING_TYPE_UID.equals(thingTypeUID)) { - return new HueSyncHandler(thing); + return new HueSyncHandler(thing, this.httpClientFactory); } return null; From 294deb3d6c07f846f07e4076d1c8104c9578f0ac Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 24 Feb 2024 17:33:18 +0100 Subject: [PATCH 011/128] =?UTF-8?q?=F0=9F=94=90=20SSL=20Handshake=20&=20?= =?UTF-8?q?=F0=9F=94=8E=20Discovery=20working=20-=20=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../internal/HueSyncConfiguration.java | 9 +- .../huesync/internal/HueSyncHandler.java | 103 +++++++----------- .../connection/HueSyncConnection.java | 4 +- .../HueSyncTrustManagerProvider.java | 26 ++--- .../HueSyncDiscoveryParticipant.java | 7 +- .../factory/HueSyncHandlerFactory.java | 6 +- .../resources/OH-INF/i18n/huesync.properties | 5 + 7 files changed, 66 insertions(+), 94 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java index 6f2861e438c41..5befa522f34f6 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.huesync.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** - * TODO: Description ... + * Binding configuration parameters, * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncConfiguration { - public String host; - public Integer port; + public String host = ""; + public Integer port = 443; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java index 32ea0007a878b..701184bdffcb4 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java @@ -12,20 +12,27 @@ */ package org.openhab.binding.huesync.internal; +import java.io.IOException; +import java.security.cert.CertificateException; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory.Client; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,69 +47,50 @@ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { + @SuppressWarnings("null") private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class); - private @Nullable HueSyncConfiguration config; + private HueSyncConfiguration config; + private @Nullable HueSyncConnection connection; + private @Nullable HueSyncDeviceInfo deviceInfo; + private @Nullable ServiceRegistration serviceRegistration; private HttpClient httpClient; - private HueSyncConnection connection; + public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) + throws CertificateException, IOException, Exception { + super(thing); - private HueSyncDeviceInfo deviceInfo; + this.config = getConfigAs(HueSyncConfiguration.class); - @SuppressWarnings("null") - public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) { - super(thing); - this.httpClient = new HttpClient(new SslContextFactory.Client(true)); + @SuppressWarnings("null") + HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.config.host, + this.config.port); + + this.serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext() + .registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, null); + + SslContextFactory context = new Client.Client(); + + this.logger.debug("SSL context - Alias: {}", context.getCertAlias()); + this.logger.debug("Context: ", context.dumpSelf()); + + this.httpClient = httpClientFactory.getCommonHttpClient(); } @Override public void handleCommand(ChannelUID channelUID, Command command) { - // if (CHANNEL_1.equals(channelUID.getId())) { - // if (command instanceof RefreshType) { - // // TODO: handle data refresh - // } - - // // TODO: handle command - - // // Note: if communication with thing fails for some reason, - // // indicate that by setting the status with detail information: - // // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - // // "Could not control device at IP address x.x.x.x"); - // } + // TODO: Implementation ... } - // TODO: Check if we can go without the "null" warning ... @SuppressWarnings("null") @Override public void initialize() { - this.config = getConfigAs(HueSyncConfiguration.class); - - // TODO: Initialize the handler. - // The framework requires you to return from this method quickly, i.e. any - // network access must be done in - // the background initialization below. - // Also, before leaving this method a thing status from one of ONLINE, OFFLINE - // or UNKNOWN must be set. This - // might already be the real thing status in case you can decide it directly. - // In case you can not decide the thing status directly (e.g. for long running - // connection handshake using WAN - // access or similar) you should set status UNKNOWN here and then decide the - // real status asynchronously in the - // background. - - // set the thing status to UNKNOWN temporarily and let the background task - // decide for the real status. - // the framework is then able to reuse the resources from the thing handler - // initialization. - // we set this upfront to reliably check status updates in unit tests. updateStatus(ThingStatus.UNKNOWN); - // Example for background initialization: scheduler.execute(() -> { try { this.connection = new HueSyncConnection(this.httpClient, this.config); - this.connection.start(); this.deviceInfo = this.connection.getDeviceInfo(); @@ -116,27 +104,12 @@ public void initialize() { updateStatus(ThingStatus.ONLINE); } catch (Exception e) { - // TODO: Log message ... - // TODO: thing status details ... + this.logger.error("Unable to initialize handler for {}({}): {}", this.thing.getLabel(), + this.thing.getUID(), e); + updateStatus(ThingStatus.OFFLINE); } }); - - // These logging types should be primarily used by bindings - // logger.trace("Example trace message"); - // logger.debug("Example debug message"); - // logger.warn("Example warn message"); - // - // Logging to INFO should be avoided normally. - // See https://www.openhab.org/docs/developer/guidelines.html#f-logging - - // Note: When initialization can NOT be done set the status with more details - // for further - // analysis. See also class ThingStatusDetail for all available status details. - // Add a description to give user information to understand why thing does not - // work as expected. E.g. - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - // "Can not access device as username and/or password are invalid"); } @Override @@ -144,10 +117,14 @@ public void dispose() { super.dispose(); try { - // TODO: Check if we have to unregister openHAB form the Hue HDMI Sync Box - this.httpClient.stop(); + + if (this.serviceRegistration != null) { + this.serviceRegistration.unregister(); + this.serviceRegistration = null; + } } catch (Exception e) { - // TODO: Handle ... + this.logger.error("Unable to properly dispose handler for {}({}): {}", this.thing.getLabel(), + this.thing.getUID(), e); } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 0ae72a1029dfd..2bff350e014dd 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -37,7 +37,7 @@ public class HueSyncConnection { private static final ObjectMapper ObjectMapper = new ObjectMapper(); private @NonNull HttpClient httpClient; - private @NonNull HueSyncConfiguration config; + private HueSyncConfiguration config; public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfiguration config) { this.httpClient = httpClient; @@ -54,8 +54,6 @@ public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionE String request = String.format(REQUEST_FORMAT, this.config.host, this.config.port, DEVICE_INFO_ENDPOINT); ContentResponse response = this.httpClient.GET(request); - - // TODO: Create private method to handle response code etc .... String json = response.getContentAsString(); return ObjectMapper.readValue(json, HueSyncDeviceInfo.class); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java index 2f81e5893a114..737f370709df2 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -13,9 +13,6 @@ package org.openhab.binding.huesync.internal.connection; import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import javax.net.ssl.X509ExtendedTrustManager; @@ -23,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.io.net.http.PEMTrustManager; import org.openhab.core.io.net.http.TlsTrustManagerProvider; -import org.openhab.core.io.net.http.TrustAllTrustManager; /** * Provides a {@link PEMTrustManager} to allow secure connections to a Hue HDMI @@ -34,32 +30,24 @@ */ @NonNullByDefault public class HueSyncTrustManagerProvider implements TlsTrustManagerProvider { - private static final String FILENAME = "hsb_cacert.pem"; - private final String hostname; + private final String host; + private final Integer port; private X509ExtendedTrustManager trustManager; - public HueSyncTrustManagerProvider(String hostname) throws IOException, CertificateException { - this.hostname = hostname; - - String certificate = readCertificateStringFromResource(); - this.trustManager = (certificate != null) ? new PEMTrustManager(certificate) - : TrustAllTrustManager.getInstance(); + public HueSyncTrustManagerProvider(String host, Integer port) throws IOException, CertificateException { + this.trustManager = PEMTrustManager.getInstanceFromServer("https://" + host); + this.port = port; + this.host = host; } @Override public String getHostName() { - return this.hostname; + return this.host + ":" + this.port; } @Override public X509ExtendedTrustManager getTrustManager() { return this.trustManager; } - - private String readCertificateStringFromResource() throws IOException { - URL resource = Thread.currentThread().getContextClassLoader().getResource(FILENAME); - InputStream certInputStream = resource.openStream(); - return new String(certInputStream.readAllBytes(), StandardCharsets.UTF_8); - } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index f189be692087e..5c68df60c6ec8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -65,9 +65,6 @@ public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { */ private static final String SERVICE_TYPE = "_huesync._tcp.local."; - // TODO: Implement SSL certificate validation - // private static final HttpClient httpClient = new HttpClient(new - // SslContextFactory.Client(true)); private boolean autoDiscoveryEnabled = true; protected final ThingRegistry thingRegistry; @@ -98,7 +95,7 @@ public String getServiceType() { logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(), service.getHostAddresses()[0], service.getPort()); - Map properties = new HashMap<>(2); + Map properties = new HashMap<>(); properties.put(HueSyncBindingConstants.PARAMETER_HOST, service.getHostAddresses()[0]); properties.put(HueSyncBindingConstants.PARAMETER_PORT, service.getPort()); @@ -120,7 +117,7 @@ public String getServiceType() { String[] addressses = service.getHostAddresses(); if (addressses.length == 0 || id == null || id.isBlank()) { - logger.warn("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); + logger.info("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); return null; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 3f93c592f822e..8b0adceb5f103 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -63,7 +63,11 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (HueSyncBindingConstants.THING_TYPE_UID.equals(thingTypeUID)) { - return new HueSyncHandler(thing, this.httpClientFactory); + try { + return new HueSyncHandler(thing, this.httpClientFactory); + } catch (Exception e) { + // TODO: Implementation ... + } } return null; diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 3964df1bd8bf7..22acd31ab97bb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -15,3 +15,8 @@ thing-type.config.huesync.box.host.label = Network Address thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. thing-type.config.huesync.box.port.label = Port thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. + +# thing types config + +thing-type.config.huesync.box.commonName.label = SSL Certificate Common Name +thing-type.config.huesync.box.commonName.description = NThe Common Name (AKA CN) represents the server name protected by the SSL certificate. The certificate is valid only if the request hostname matches the certificate common name. From eb0600025c2347bc4ed9d2d08ebadf143caee292 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 25 Feb 2024 17:48:45 +0100 Subject: [PATCH 012/128] =?UTF-8?q?=F0=9F=94=93=20Registration=20(acquire?= =?UTF-8?q?=20API=20token)=20-=20wip=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 41 +++++------------- .../doc/bridge1.png | Bin 0 -> 62342 bytes .../doc/bridge2.png | Bin 0 -> 84447 bytes .../src/main/feature/feature.xml | 1 + .../internal/HueSyncBindingConstants.java | 6 +-- .../internal/api/dto/HueSyncDeviceInfo.java | 2 - .../{ => config}/HueSyncConfiguration.java | 3 +- .../connection/HueSyncConnection.java | 2 +- .../HueSyncTrustManagerProvider.java | 1 - .../HueSyncDiscoveryParticipant.java | 10 ++--- .../factory/HueSyncHandlerFactory.java | 6 +-- .../{ => handler}/HueSyncHandler.java | 36 ++++++++------- .../resources/OH-INF/i18n/huesync.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 9 ++++ .../src/main/resources/hsb_cacert.pem | 12 ----- 15 files changed, 55 insertions(+), 76 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/doc/bridge1.png create mode 100644 bundles/org.openhab.binding.huesync/doc/bridge2.png rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{ => config}/HueSyncConfiguration.java (87%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{ => handler}/HueSyncHandler.java (79%) delete mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 3d7c99d153c63..9d350b861504b 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -1,11 +1,10 @@ # HueSync Binding -_Give some details about what this binding is meant for - a protocol, system, specific device._ +This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. +The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/) (not via a Hue Bridge). -_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ -_You can place such resources into a `doc` folder next to this README.md._ - -_Put each sentence in a separate line to improve readability of diffs._ +![Play HDMI Sync Box](doc/bridge1.png) +![Play HDMI Sync Box](doc/bridge2.png) ## Supported Things @@ -18,11 +17,6 @@ _Note that it is planned to generate some part of this based on the XML files wi ## Discovery -_Describe the available auto-discovery features here._ -_Mention for what it works and what needs to be kept in mind when using it._ - -## Binding Configuration - The beinding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devies in the local network. The LED on the Sync Box must be white or red. This indicates that the device is connected to the Network. @@ -35,7 +29,7 @@ If the device is not discovered you can check if it is properly configured and d ::: tab Linux ```bash -avahi-browse --resolve _huesync._tcp +$ avahi-browse --resolve _huesync._tcp + wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local = wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local hostname = [XXXXXXXXXXX.local] @@ -46,30 +40,15 @@ avahi-browse --resolve _huesync._tcp ::: -::: tab Windows - -::: - :::: -_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ -_In this section, you should link to this file and provide some information about the options._ -_The file could e.g. look like:_ - -``` -# Configuration for the huesync Binding -# -# Default secret key for the pairing of the huesync Thing. -# It has to be between 10-40 (alphanumeric) characters. -# This may be changed by the user for security reasons. -secret=openHABSecret -``` - -_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ +## Thing Configuration -_If your binding does not offer any generic configurations, you can remove this section completely._ +To enable the binding to communicate with the device, a registration is required. +Once the registration process is completed, the acquired token will authorize the binding to communicate with the device. +After initial discovery and thing creation the device will stay offline. +To complete the authentication you need to pressed the registration button on the sync box for 3 seconds. -## Thing Configuration _Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ _This should be mainly about its mandatory and optional configuration parameters._ diff --git a/bundles/org.openhab.binding.huesync/doc/bridge1.png b/bundles/org.openhab.binding.huesync/doc/bridge1.png new file mode 100644 index 0000000000000000000000000000000000000000..e7795d60d0c7ce95363b039ab6d8fd5a47d73f25 GIT binary patch literal 62342 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 R2crRBsxP$^KPy;;{~zP(%sl`A literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.huesync/doc/bridge2.png b/bundles/org.openhab.binding.huesync/doc/bridge2.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d24ed0e470127cc4593a80e567d96193e677f1 GIT binary patch literal 84447 zcmeFaWmII%wk3+YyOX%PySuwf;_mM5t_6jA;qGpQOHoizI22wK?$&(gJNL*vr~BRe z-s{n0^r-v+J0sSfvDRE`M?`M+Orn()r4Zrp;6Ok?5M`vrRY5>NMc$9kVZh#hqQ;B- z@$09Ly0)7t&=cU~;%H%QXAW@lb}|Q;ds$n6fOvh6S6xLM0w6qF#w2WMA;gKPlDdZ` z7+p!jsgx|G?Ctg^y6#*)wRB0bNIEs%7pP!Pk;|5t0fd{T`N6?-#6KX6C9g9&LUIF^C)7&m}y`t$87LmgPl~M~2!W#w zfrGAW`7W&G6)>N@7bjI+RmN<6a?j-*#2szPs^g@HTp*A>6tkG2QkV?8*#Q{{?K!g z$vlmPi~Mt>DZwe?t+x;* zG0S0J!&#td-_{2w5D43T*-(hf|%vE*8{=V(~ErG&rq~uZ*n0~Pc#Ksx3>W> z;6XH?#s^**D=pYKg#u zPCJbJBq~IS_c`)iZpge%N*p6kQJ9DtM4|vfJ&0Br{8$*=3e>&;-0g1C1_U8^J|I{j z7?&A3qaRKi9N)Oj$PIc61_{J+3rs&4{~RQ*AKnX`cnkhHNC*Z(1b{;TRtP`{3xz>} zEfSe}H6ICvNPks;`JQ!v4xaZiJnnh@wMtFmBLzp}$0S=ftegvO~%Wspi7xAs?Y$f;bC|<~XmhzlVb&3`ygF?;3;2htM!%T14m=bAABdkMITt zXro|7j2Js>qh&o_ zFyw(kCvF1B&;1upq&W!XgHl`a-1vfU2Lq7ju=g1H2$=(zP8|C1--ZyK$kq^yf|v#~ zw-oQ;*5L1kz|R%DaEOAXwtdgJ`9Xz3%D2nU>7T&@Aejaox6QpEUqJ#x`uaDw5xw9y zB0RSQAmW5c>S6dp!iF)E)LB43+2# zksJ|r03Iqs6sI_J5xO(vd}Mn>doYe@96%EF4iyYR94=Y{EeV4hQ%0OrHXAG#4eIe zR+Ho*Emgwyh43i+S^^3XnhYG1Hlt?+&5Dy3vjDgzeNKWLBQ;}bM9>MZmdKOHlWGO5 zCS@iWj2W5!0vXDxja^O5jytwJSI6HVY?sUYy=v8s}yjwGdE>caHrkHK<&HSj9Yct1p z(9<{%Dc|Ckxq(AwHw<2++&D!s%OcnL&+`(8CT@_OPzH$(;%!B%^9F~8*BsXX9|(bn zfS`b|fRH?~+2YND%|fAqZ}Xe;dWS+dK_VtjIA{?uzyT){FBVZ8mN*Paq~bW0&xghc z%w;%qafG9=yAcZ}m+gF1w zVc3SZeC9P3s25xowxi~R!%q~5Q~11ZeAOVajAlpCisXy58e;$11Nc-Az6|_H?LqJa z^91ok&=qAcS_}NvAXML1&&Lgc6ic>U!L3Qar3gY5lR7-Ut?rb~Es(=9gJv1&I@q!6 z=M>zotk2E}ni(=VOgMtMrMNrc%+U^|&-RP77XDjgS8!K2{*b`1!07bOzO$nLHxgk8gkb3cv}G862$B$P zk%$8EIlv*RJd7p;bO_zR$1Noo5^+>Cm``E!qD1f4r)&jf4h1{tQ<+!3*++9Bny%DLV>7%3Cz(D6(PSVVNDZ9l0I+9a`tyc9CbX5NU0+EO9dDlzSNy za_bZ{na~m?R*ID*q$Gan-V(7AX;m8d@BU~yf}#cI z11Aop4n+uM4aEv238e@H55*5<8X}3Hjv$Y4izr5rKrlyeM~Fwj#;s-6x5NRq&oYlN zf36>^AFQ9LAFm&ZG9hPWWpiPLV~t^(V5?*~X31v#&dST?!v@77$xO*y#t1~t!`LJt&v?RzgUK+R`UtEBJV4R z4L3(#w&Zjnma5#RmamdFd@Yeap+1q1NsnQVDV^dk(k~(}Lt)0G%q%Ro(hE z^auPaM?CSNg$j1XY=A;ane;*t?tHcN47;N)Geb;<{A3AXdHS4uM9GJDILW|^+xvk<_3Kr zu5rf@>%e84d9r!>9{wcf`&=;>+4ebf@iT{%IQfBpJb-qs9G6;S`{KV)uHZ(V2Sj#^x3_jt4wSd%c9{3;_ndamcVP~s ze~kU$o>7@_jwlWYdw|QSi)qKFHT|`sHJ3HKHOaNsHHWp=wdA$& zwUsrAwe+=vcE0vHm(87kv6r|f#5<{9h(!IN2Za~smr+;LmsVFFF1XLVE+DRkFH0_d zT%?_Uy=cC4yIT9vG08T~Hrq6Km3zR%6p=YGIYKuzrKg$XN&-(0APSl0Ahe?0AB#|E8rF9mF)T9x%Bzw+5WlbdFUDW z!SZ4AGI0NmA_$`Z#t8-nehkhE?gPv+ycaA1S`@l8HZ=-0wiU`D${}_;!@2ZUc)xG3 zBUm70Lx_AxcgRkNY=}=tMhIg_KuB^(BxE979#%EF9p+C|0yICsz5IF2R_B%@Ng7Hh zN@miBq>!Y9r0}HFq&np=L7A+c0 z8%-JQ5KR&ti8f2IA@`gVMn0aBlVXx0oAQ{#nL?BjNsdn5M4=>;m)uRg!o)+%!_Yzf zpyQ$kM>aw~!bCw1(tDO@RAsdF6jmEn`jr%Y8_)sJ7KttG9AtYxe@ ztkJEdtcj1LkJOKLzhHh*__F?G=nMK6rc$j^E^`#tI97hv6xL%_N>*qyKl389gfWaU z_3`Gh>#?{o`SI?Q2kI+5EQU#XIshG66@@Ly2`xT3KGjDGU)kF@yRM%cD_xCB=9y-F zImD$O?IBpE;XV~SqV6L}j$V!CBAc1mXExAeA* zyCmdfr3@iTBg#O-Ubz720MP)$*U)Ds$Y!vnkb;n$kc5!*kVw%e(Ud`zaMtiEI0LvL zxKy|+IAY8IIui^XjBpH0j2jGlj2?_33^j}^OghYun7){fbc%Gnn$Jb;#p3Ea%0+aa zsB!6dXdh@ssLiQ!)hpF(wD6QW>)6Yii<%4L)Gbu^wK6o8R9ZD}m60?At8?n~3)iaG zs{Ew9B_HB1we|!s8{yhwN@5~nvSMOolVn3=Yh^!8c&9t0>(Q#w#?qe9@>Pm!C~7!s zaA>G$SS|`LdMy$xqAj{s8dSEam#brIeADp!8fBAXgJyGVV`0N+BVrS6Begj36>rh! zt3#DSRgcP38PZppasW;bDPWLLChw{o*Vu$8x_xAB`_UL?1@w34;9vDUTrv5K%Rv%#`jvNHVIUCUGZ zAbJ(L@5qpY)rDmsr!c8LDM6=ACqt)HrCKF%Qn_SMqf>)f!(boBS=Cz68q*rx+T@Ys zQFP043U`WiTJ_zt#j-`Kxy_B4x0<(zmzI~3cZOGlSBBS{H=VbJ_l5V)E$^iI=y_@6 zsPQD`r1B*Dr1L1^xa<(?aOt@9WMeLO&cF0Y$5Sh@`g1W=ae1+2v8OVVER)(<^9E0k zL(i%Yo6n?=(2dF?yFaSGxj(wU<%{tP`HSC+%?sxX=nM4Y)nopRC&Cz1Hxw&WCzJ=2 zAQTJK3REdnJX8;qDO3{_9aJBJa|A&IS_EE1Mnql&YQ#}QIRZXH7orq`B|;`5SKORr zwWNupjpV-MFG--JwWNgPjbyH5TU=RuMqB`1ahy*4NZexFbo_e!P~1YC5?(&XFIFO! zJQhRF4fcT#^;vTY?DF&q{0gM%>eR0Qo4}fQo8&BpEEX)}Eb#ZOTsd8xT@hXB zTph2huIR5+uPm;r_V1>RvI99cn~dvG>Xn>S}Uo3TifJc4%U1Vplt8E9myL zK03rVTeOtv*=p9qN@@ic>QCSg)>RB?7@o^gzErFO1% zqjvIF!g}?3_PUX8OixfxQBQQwNB?&J2>%BERR6vg@F&P8MqB?3)?RzjHszXZ61(S+IrV8T(tNP-PXNpn{%)A=j~+f*33!mmc!1k zUqnZFr-s`B^ZsN0qy8f=iO?frb7CVygS!P%IZ}C2HB#lD2@`n|mGY9Prc-}?+Os~u z-SS>;8oSD|%s&91m6#z-t>)Sxs7~HVgSlZ~_ zh~9wQu_{X)Y+>wQyw(}81e(Qnyr4j^hE0>XU#BmiyzLjidK z$CgFETfa@eB7j4gU72o9keQm9p`N>*;@o1(ZOdj$5rspVUYc%^xGe?1>R-#b5r8HJgLsey@% zN{Wh$x{OMQT1A~D>y<1b>mbD>ohC(=q!h=J%uDRX;=z0nbfJo&k>!`=n&6@3r=_ao zt)x44_$vB!)=IQlxY?#z!Ohss#!bde@7n3w?%D@|HG)l2aFl(NkCJ3xT;6o*=EG-} zDi+@c`i7JSs0N3I`3CQXn+E>|^oGfXRp&To6K7s$Mdxj29%l~cb?3WX!_W33sk;(8 zEjxs}V!KT{RXcpUXQNERj+9AgVQB1VKhZ_eSkYzs~@KKPIQkU|WGwh|iv>ayyAYD*oXMwS)76`K{06_S;e z6^<2w74(&}Mo(95S2I_6S3eh|gR~#b2W#Wr=}lR3B{K-_>4h_+^Tmz(Bk@ZaSSh%{F;8 zEjLa#*_GzWb>_a|A*j}C*6ZBs8KC-l^c?%(d5L}5mcT}UE#%;|cI7pAk@}g5fS5-x z(62a16`)yQv}K55q^v%t%dC3dXk476Ppw&DbZCfU$D582k{d23Y&-y zOQo3G?zD3otwq%ugA+?0gAm&tlM)jX`#p9v#t_>+-;9^W{mYM%+3~bgb1J6%?m(BL zA2T#dYA)sUijX<7LK^;+2VZ}DT3k4{u3eZYw^xeHn-MSweEfctwQw@BY!YlTZE~=4 zxD&Fozq7rAJt8DWO>vDjgjSbQl9G~=km5_>DEFGtm@$#An*o(!k+GC^o35B1Krfbl zk#3zvNF7VdXSk%!sefB!SDd5DXy~B+%OIe=ySlr{ulv~mvSO=Rxw5_nriNR0UYSMt zqIyS~Zc5_eZzZYrBILXiF_T6vS@CV?;$n7DNd|dqh)2M?8<2hI}<%OHamU{nw5% z*S47+-n%FM)9y9zs;kJEgFHv>z^mu0EByGlc-44|c+mK)cuTxGEtjcsFV`(u%kqw#Hn-M-U-Z}4zVR3y3 z)-RTG)U`Lk<`nN#_ZVuOe;seVb{%%TblqS*u~W!kzzDU+#BJnGb~K$$ z3#yjCuW@B)jcn6&0ewSlZs9xIQq%j*|I1sOo6bARyNtWrYsI^OQ?fgm+pHVNo4woI zv#!Oqh2UQnZas|7;zx;l15_WQZLir86?m%D2)BpZ)=O!0)2J{9rGdL``F_;cK5xRE+u$j~ebSC>D zxe>pQU(A)^Fv#`baJ+Wio#oTCzu)rFm5U47IutJSHgrCe3YrN~5g~wJ5P3dQJ_0P# zB@!=EGGYTE6u1FA1txB%Z69yj4!I8LwOO@cIP$E;bhtli9@I#+ElEj}rI2Bfk&r!* z)sT5gZEQ-eV|Cg)t=@U;pEk8cIx=l+^bGk+T_rr_rtvL$)~;uDHE~SvR`UB8vj;T0 zT~8cdAKtNyvaGPIHjFi-nr4ntjtXv4Y@~PFI&)qBy6Cd^JlizritM5CnEhcm*He6# zeHOpr>q+cxblAJ?|Ms+UH@n&A|8~mF&&XWM^@}Z!XM~(o`JnLI5E=3;KY?HSxszvzrKsomd+sggU%O?WO%pej zOv@z770N=(ugaL@H}hYaL-UhAG~~d3r|E>4Y)yBbW-kZxlZQZ zbL-@0>L#l$oF?tglTc-x_e(9*YU6M8@Toz2RCxOTpy-)xr8>p zY*co;`}^M8pRNwH)n#r`6jJP#Zk6uJoyw)k8D)N&jG0uNlzCsVxH7OeM9v^KaeLr> zGT(mfbVYj_ZK`e3^?Lf`KlkpxOy9-QoyW|@Tuc9tcL~K8D6xoz`Nf1p?Hn8~#=c(a&w`f}ye_4`3nzGGlOTY^ERcAH_MA(KI0=c|eT0sJO*C{wV} zWnXik_x0YB)_1MNx?e_#e)jjgkM1}4+aIsFx*VN1Y5NfU^zM=Tu^$~C1+IPP?`qGK z`raM~9>fkJ&l1;&u><&@FK(z7l=ku+gcbrgpS!PI-9zh1nt*-D^(rmSuDdssE^2NI zCTuQEUQLcy5BoD%ZyDQ~=bGD^2TM~+OH4CH-~0Rm311d(Z7;vIZ8mS(^a%u7zB)Ym zzulGFN7#?pXP$mNZCdqg)p|pD8+&zrPQ5O_ojuRr>s#(i3ABG3eXDu&y*m3y_!Y_j zvkjF+x!=L#%phYoU8ijWeurM?Y9C)8^ljMP)a~G1%-!Ry;jhydUqh2klsEpFS+K1S zU_pBjAR3DxQ4nCl7x!;GOc$FogHKPxVWSWC5s=T{NQv&jiBZ5sMb`Q(zz2oj|5h^f z*7CyC!YVB=6tP+g0)jSa{r)(Kwt_sbsiQq3(9F@qoYBkP>HRqq5Dt+^Y( z#N5)_L4fqEtB(|5Z6-jf#i78g;3Q^lWi9RFVy@<+sBY?GYszCrDkKQU@5TELU~ldQ z1bEroIk@tA36TB;m-qenk7g!Pz+X(Q8!kXpI9Iq@da^M(x>z!?@bK_3F|#tUvNF6|Ft~a5m>J z7Di^Ke>8s&?%!K`0iFJxv4XAFi@B@0o2wDf$;r;z z{JpmR^zQHQR@Sa=jxOFt<__vyl+y#H-kOIV8N>M*NldRkiQa*2s@I$0<> ziik*XTS}?CSMs0f`)fM?XWajrf&Vl2?{mVxM)_YQ|IeEIr@;Om5F5we$Y&Q56;Tn9 zka&;nUkCfYiE!3`4euWaw*=bT1OL_HzsmN%$>x8s&HSHU|2oM3O)RthYk2>_va1!) z#mW5t!<6#>$TRz2*=Do&^MK9bUmf~C#y8tvrV%MoOC1qe>;L6cA}RYml{o$vr;0?s*#G9L`H#Z3`0MLm!T*mD{Qqj6=4bk23--S3{>!TS4-4y` zM*pyHdpG{G?R`J^zP)7n+y3&e3-5b6{&&9qx}g8>90CCNJ;;BH-`{lort81Oz<%Yaof6MrHb^ZTF7u?_PQ2piZfam-DC#+|#see`rSq1-A3;k9L{Zf6xHl1QQ z&|RgA8PqR{bxZCAj&I^RB{P1Jc#J5D#p#vCKEnUVXmU?#P~vFU8MXa~NdwK=UrhYP zkL7PX!hUhZ8?={lef#JjrsBmX+S@zcb(q;JZ3ItCImd2~umL}pN3UAwq9euUskDP}k7s z*gbJGxs2N7_}G8=J&NV2aeTv$>Q~24%PPTdNuP-U(b+A_t_^SUdX2<&@{kot^iRI zZaKXttscHN&PraxC!6Qr@q1r*wOo6lO9PDNBoN-tL`M9JP&~{r9l^4G#oqNwWy|~K zeLX_d(pr*`a^S!N8yUULC}>{!Ra@;~XV2cbKCo}$>h4Y^gQgN6GR}V&qmKvTej5E9 zX^=T;rycEH1STw^_uvUC{?flBSueB%1_i)8QvmUxypm0N{-O+)+T7RD)6mdy5oLRJ z_nkOkMb7ZH=KCPKg8vuwt~0&&w7(&aby|;gsj){BbI5R*;b|=(6hJZbdN4;|Ic+cf zByZuhE?z`iZ*Of}vEk%e;^G{xU3ai^u&=SN=FpoqY(ws9TO>e|l*vYxg2n12@*Dj! z?c?97Tk{0=r2ub4Be=rv8NixH4jYFZ1BHnIu6u~|p?i$YS_n!Sd%Bw%0Cb|k@gNI@ zt2Q?_M;B~9Dvj%2w61lm7CaBW3w%lJ{XxOxd)qE&`h|X9aN!F!%QK?F%(yJ+nS9OM zPt0#=KD*AX&Y!fxyFe5!2}mg&?!;wmbWa1i7FHdaC4df&4(r~Z2^$CX4S1T??cCqL z@g&S6th2|koa-a1j5r1=9q^$eL|o-#9<|11;`|yxC009| zpj-K&j8MUIN}|5>XeIHm1kpX1MFva{EoGzehrJ>3b0C`>k9VG>E`bT6st!&l{|L)g z?7YTlu0@BNnZ4`Y0CL9;bifv$(Z$4pd@;vvH-yz}c-)Tnf&@aKSn5n}t_bTCM#LEd z)-j?&xqubB`O1o88{g{mDH|Of{>RlSI{X?7t!vAwm1AV6q3w!qn2_HIk)VoSmFKp- zJL>%p59(EV=;MCq+UX-(A-ag@Ayk9Keo-`XHvrX6=j0aXS8TydQ3ixKcne1%kL`iU z52|X^Rs&yRh&B1qKa$?oUlomj&-7Bk8xMGc#4~h|2VsNBjR|GHQm-K4_W1Bk zH)@-eWI8VDlcw+K^0=7L{V{g$W6Ny5aAWIT5mB%7K>;D#e*254G{ThRq@BGx-s$a< zhp(+1R-VZ!Cs*65ZPjYLMHNX*)B1@!Wd6`RGj4o}3NJ<~E1ZB=_@#Uc1D{}i}qn(u>XpS*^_-oT+AIL8k(Wsc%j9N(@1M2T}1;oo*>C|n^ z9??k{*=oQ1E9D`4RqYos(nYhkMhv&F3o+g27U7I(RM>mBR?yuIV7?0I@X8s*5UgZv zaE3;-;=5r}4l$PbWn|Ty?jMhSlB@gR`*7#~**4%F(s`OPM>-xMweXJ7VE7zz-a!rg zl)2YcWdG+J4XvxrOfEHU#Adjv5L<47b?cYYn*C8pQ&Ur4->L;W#NiVA7QPd`BEFAc zGY)NhhqtLfxHgoFVjoE-^uf%~Gg2`Ad2xa`5cWx@zwj%?&@+@c zHGFifCR7#|c^QMO{0d7ro6`*=PxL@}>k?d=Mi19PiNa8^;aXl%T%VAPJV-t*&J!u3 zkPT`bOwkO&?QzvV?{rlV%JwPAo56lMyQW6c_vJjf^z5Y<`?=q?u5Q*QA*9Tmc^cvn zM9LgHqXXo9&ge-YJZqAQZ7NtO$}u}jUzL4>RWsQ!Ke8aCK2V?$T4iYJO^z8{!>XaF zY-G{iwByw4X{qrj7C3Z!Vo=Fz{P7cUSRCWf{F8AZOwV%UE9l)oK)A<6Wx6awLWdKE zW>PqiSFeszMo7jtL}PX3AOe&hZfE2vK9~#_eHNdR&Tz z|D)JsZsLn#^4(>%9>%&zoz92kixhhNy@xRjF|y-tN@goC#&aL#1Yj~C;&r4u7Umj9 zi3f2uAs=DlW#Wuw3*Zjr@U%6rrezSbXV#8H2t zU!qQt_KC+Kk8B2>=4CTAPnt99p8ooNYao>{y>UKxV)#07K+rvy7lF|vB7YCNwgC^q z`4;%OzEZkpgU0@hFlEq}Oox@?BYw}T6S<8mxBMCJx~|Up@I$DiYw5ggV&gouttW5I zud$K_X8WdZ>HF$~I@K@KSi|>K(JM6c*u$3;oP}GBXx~_M=0%}*5K^hz5XRsX-c{rA z%8)jso!oda$vn(uWDF^^Uz)k_?Duu}rXF7_QV;6mo2scS+Ifo*mKkUZIy;ES?_s}O z6dXl~M94d%mS(9SB0#}cP`ZL*Oet27(OW0;(07kvtB~1h&yOv3$nw)=Fu0J=5q{AZ z#>UY2I#Fzr2C@c1JA(bOM3VPAC8jm}C-sr$+1OyMv6wAshE9_*pR6;cedS}^@Su@$ zMpAyWZGo%-#$~x8T#ixZCyGdNoW8{a%rrh?ee)+FLzko&3-gKfrAN>}?SULqZ=BIJm0&H&Ym9bmgj78X^(C-b zGiI|wATXU)EBVC9M8pe5uq43H?b!}xe~A3(1{#rxIIP$(*%Q9bd`cK4hdksUU@O+* zyr3RZ!$eL@A{F0tUa=6mbZ>r>%xeaYxN6PiE5Bz(h@6%|vJafIA-0oxaKSBN#$?fb z{|-+^i-~u{Ilga0a$@wbL)6ZS4Zqf>nK}2k8Iz_+m~pkvaCquj(t#AxNi^FbaAD1a zAKtQvISrtA>GRn*IDJO_=$_R?Mk;kgu-#ewRX9=9Cjz(B&CL79*!70ys|IdZd5*Zg zUnTtHjMOVwk~^1fQry`CrbKKGZNXCybmMm$#@;v)?au=$PI zA*f(eP$km_A=u*{J%8%IDDY#(sdcogsk3+EU5~%9A2?XjtZG=dX7~N5H|^j!cS4c- z3n0_6jFAumKB`_Up=&Fl{|Sz$G=ToGNS5;a9HPY;L(?f6ner1;1m=3fd1EECH14>Q zcg7IRNNYK@7-gp6zSH>*-<@}3KCP@~c@MurL};(f0_iD5@oDw4Vyxr`WLgQ*QKuBd z5F7&80@)59bs1bG8+iXT3J{`wC~Z`=g&z&V-|n9DMEaEj(p4`)(5X<3)#!qMK- z9jjs18Y&udS?TSv3_%?z5y2)gUn8^f=NlC-<#{RK?+dAakmE-Gj0zEBOP{p45OIJV zYWs1t#G=j?K8k~$BzO-#r2lI6lZ|ms2;3D=E}F^0(5^`hfSIEP-CYU`OuA>Qp7877^&?KzVeq^qV5vws|F zxw(O=XzA@}X$@Xa)9hk+2~aYor~eUo9v()LUWyaY77Kq?`9=O12rQbZOKZ%WUCR%pLVR?}*My3G+iZ@eau+ zU3`K73cy(o7W~53*_nYFiDM};sOpVQ0iu1zQ5_;AN&}5Y)WVea2x3cT33h3Vq$n`Z zXla~|L1FZv&}1DHrHU)uz_jTn_y2Lo?;A`L1i?(w7PTFfkqGT?v#^jSJE`pF|fh(sI76RuC?dHuaPWX zv2w#(J(Vn8JwxnT50DDtIYf*s5?aVPu(lTPIi_8-AF|P}(XeIBXLleS`*?2^r3++T`3VUU^$APD3vpNx)QbuhCJW{YbGYByg}1FbtT<&Y z^SqY6Z(olJk11=1PtmW{O%#L;Na5aP1oJL4FvnjYh!Kni1n(NUFzslCee7pSQUXXK znvtsWrMH1$+QCp)FwX(P8?qtwN3U`+G$r;Wiv4nJkAX3gQ@l_!#cnk9cN8rYI;4#> z(;bzwsf+P!9Wr@0KdIiX1D}KGi2dr#cEK#Zc#CblXACka&glKaN2~6~)%f_+(OXTu zpUZ5PnyqbmxgLS021_~kFR#M#IyxRmkHv<8X_WoRE(X-8)XWGYA()|@!xT#&mp?#k z1PQ<>#{2~9BINHdtl)w{k4m&Mn96zNax(7c7C}EZ167V%ku+4;-9AL72O-RK z9ULr*P6T=<2XEFxw?A-|_tsr3m5|3Bpx+sI{MG*KEw<09MbGu>Dl>=Mdu1<^VTx&L zW=6t!FJ`jty$HL{`~zPTHUlF;PUp5wC4WX2pha+~)E6F3YikD%V8{Wrb3f`O&|Vdn zK&_zOe|?LvXfh^4xhRT@Av8rwTYfV+$4_+vqHLu>FVDm?EoW>6FNN=47ittv8-Fqh z#4(XebfR(NzgFG%qn0xfP7@wclLD1@Zb*jUOPe710qH7Z^8rDafipT{KTky zutW_c1T>>TF21DFHJT-zqIs18+IXVyh-?hTSjPfpL+YLbFh59*6;85VqiK1#(H}AB z;i_h~-c%d-^RD}nN2<265fNL|2p?gxJ$j^b-45=AJ)1BO(Wa)(!LHiAwz{UK#*w{M zcYUBBwgw{r8GY;+!AHuO=BHlW;xk{laf^!Xkn2WI1rxS0NflZY>u}8j+q_Zgr4x_>wOPD{9dR<=vgp6tpE-< zY4OsMCS<>(|3cl%{p`z6p@1FY{M+VocD&WWWTpFkDi{Z)^?=ItUs3G*&d+8jP-p&U zzD!-$Up9Rw-`bm2eAbm-E)|Gh4?^X%80qzOs~8(pZSbn?z$?jM1eW)c{KLY(gUq#5 zmJK(f?m9t;NYbUB1lTd)VuKOJhrqxH!x{(&MS+gd2}4fsd~QJgDRxAZs!hxo)->b} z=Kqx^A!L9~iZJguTAp|!*WI$8JVIjy?8?*@WK2uC6AVnZDYr(w$;QkT40+gD1};c)AJIw-+(-q>2S*Y`g-M{qqj*3s5fyI} z(}FM|!0it3)JCTjau(}~^ERD7R?McCfp9t&@OhJO6c|?z2QdqkNx}Jm+sYmG1Cx<2 z-GX|S3O@S2(BsPT>LejO+SB`(yW<1k6|aX7B04#l@5C`=>ckp2Z<6unVT`ru(r)Q5?iV&q&(!@}QRw*(YU(+%D?{H_mM)3o;X46Wm*Cv?EQdRU!edb_(@b2+gQ(1nQ= zb|Z2_ivZs|r&U{}+wC`0#{i$n!6InrmrOB`tJ)TM86nx{D&`iz_|z=#M2q&Mn-ni% zfx*RXI8A~LLiG@pz=YyXt&k|6R_#~6SOU#cNQ#5_2}BHP5L=K+p6A-*P%7sEL5dB+ zs4)mT0^Mm4FSX-bqWH52hG>GQw~N;}F1Lp4*xXq8-20vn7IXfQv|zy`DSB*1N?$%$ zL2}&qgS$L)8m2@FV)%sw3Y_VfbadY1bIsc&^qJ37o8W=>Cmd{0^R&HD-?Ep%`l;rz z!ua@jbSlL$b}iZ)>O8~{T@ZN0UL9Q0dw=5#;oUh{VFF+i+gCxSru^urF#_xQpi*j$ zn$Hty?mrPTCD?1J?Q>}@Fl^`DK430M`-7~;9~EGy0SjrZ_$c%%ohQ;Fn|l4)!^zOe zPrd;2kQ+;w#s(rH4>cOZ!HLe`fexC*mC`mqwz^YNbJA5=a5JBJFj~y-FGaB5)h=#aRMkCvJ(%gf11Dxg>)Q#( z{R*vX1FK0MPRR@t1|P#+z*`pN0BEXfrdjMh=DbO0%9fHXwpr5u&umkeZ^T(&PSUBV^NAF{bPi`?%YpM@p6K(xfLaZMi z$vPKE%DKgPa^H%A2gD{KCx%oPj*q{eJ4d(da=o2m?}pHR6^{18X2m zpT)!Z*SQ9BF=wkLJi{UpAZi=SD!dyVBB5({p3<4KVM8jb?UW3On!8T9o`?AY?|{hO zudcQBb{ybMg2Tn|;J`-7rj;|QP?7Hr$l383)q0Xcy;D+MT-A#QmX_EOL1v&CVYG+y zCI&)t^WZ1gvP|&q=n~q3_|Z38u%_^6SA|Xvps#7ccoWPCDTZ+H6v0o}wn{(uO!l`@ zLVaiZrDpeNmKbpeS$W$A{Lh1$!EEK08%Q#R5w_I=*sV@0sn7>V2T0k>HAuE;maw|i)|{ONqk&QYP0=i-Y*l396xx~hZ4(&6=72~qoG`a3mkO*tx9-%Pne8fCL)T*PKs}8aonUbsu5nv^^ z{qIo-a!=bav@2^bw7(dJnU&=mo9hRQVt!#cNrt}Pq;{MOa5~35My6fk2miUh#(4~Q zd>u#Ou_XYB3m*gD)qrcl5Rx2LH@l7@AvQR17%M%q!-Eta*@A;zNW*X#7&cE1rq1d8 zhukejK_KhNPL^%e>ZU#`>TWiQsv<*Rf?l`M1%}Cg8(DA&&U)QC!8pQzZ;@YWQ=^$P=p>3 zNStwv#5H>ku?8=9jssrsXaILq!bBP!*C4P8Y&gJQ=SmY}`x{J%a_Ny!6N$1w`Bynl z1&h&QD9cpXSmyLg(+jd(!3x{i>1eDYXlf#2Z?wM{9t;L2Ph9frZ+`O+|GU?ny6yH8 zCr{>SSD+mRSrjfofl!xPiHZGEE;+FZgqY(hgvyq)$&>}ODc#A80pxkMpU)O zi<&X9eM&BeQdQoO`7Tn7Ooob7FA3p*Aripe;GihqVD^#8au-=Gg@Cv0!l96xA=jCO zN=Nv$MT+2|8yP$5I)ib0rJs7nH@23g^4lHI3`#Qd?3lnsOjxG9$c`#BN zL$5EF07M2iYhWQ+aOOPVSpcF1k*RzQuu!at*o1s5M1RX#0m3}uz5%QpSfKFn>bWx@ z8|7qpI6tGKK0}p|q38qs)%?pXXpDx=HgE?U(B8PFQ#Go<<$7BOThWUGk%nc17j(Kf zW1I}8P`)LhQe?3KI&1nLy|wFKtyi0*aeJfv#qek}>UO&?deMu&^;@6&xW_&2GoSg) zl~>%qTJ^JR-2_<>918q7gOmC?e3sK_OMyuZ*-s}GFHfOO1otpE1_->u5eDwzT=aEG2~mKl;nIk^|G*3-qcGHPoV&BA48=Q_<78z)YqGF(9`G{-*rvMLW zaq6LxXlQ^87z3x2&~;+LvVo1{7ug(S{RdPE`XRR|a>$gFVt}Q{06upYa(&`~ZRxIu z_Og0M^&J=!;tdC>4YViA1$#jiDoo%32Q+btK|zV(%~Cia;ZLGD%&k+MOUO^?%pwbl zkt`r#G$Iy{Dn(dw%ZgsWS*^wJ3BzQ@D9S1!6O9^VMOdWhs1 zG!>xppiOi<42*#RkJnL7`7oV$=n74spn?1_YUN=($YVT`=?2xxbFLPQvJ*$pd1oMl zs4UcZ3mBmov<4SktYOzylmHwFY*brA2)YwIHr8geEpc)iHF!iT#Fz9h zj;PZkx%Waif(#z02>l2TFUzQyIH-vff!o+%r3&NPq%yr#7)dbNX)Xgx94Ok(DVhbt zAAFvxn_3@%&=DY52qHC46zD8Nhs_Qd!KlEcWMUSJKP`=51LHA<+`$oJ5(e^1bCp>% z2(3Uv@TDO(xh+%EV%d)}G4_;5EJbCb+R_?o3*8&-JBBCe_ulrl$3O1TuX*(kEzB++ zKXItvyQ_#sVG!GK^idJ^_5KIO7_{l}c0*rM);iJAYs~s?F5q=N1>2E0t=c zT3uLJTwGYJ$;I5#Ol@v)d8s?dKl0&^{@INmXl)D@=Z@CGc`V?ta>h10_u%=$H11dd zH=GVTee^C!rDA5rW;vce)Eg~qZrt8!C3Cg7;5LGo#83%*@;k>7LcGrtm zf|RNVzzvpJ=q)?bRSCxh)r<@c4!6PhAKWbmVtej!Kg#-6}20~OraXY~T=Mx8s zJi-3)WSFuDg^|L;^`Dn^V9OjftVFH-TZXc9G5|~F9YzpIHOhr)Cf6LuAHBB!m3Osj zl5Ga5ERGQ@4P&k$m@UZ6iNxnY_XUQv?UY8|tA6mcPkiFjp7YFS-+t=!B`1%k**0xk z_E1bvG`>)O4i2ipT^hx4ys)qwMfEtIiz{=rh2^kb_iNR#RtqIkKB26ZDg~8oquuWG z`i*W=N_gyJ9=14l@Q>d0=c{*YEzL?%UXlV&@Vvl@Szs+FNCE+G*g zf2SNMLfQ>JMh1qi&qsa)+_kD#iPC^)9pS~Izc&s=qmKLx?{v1$;$ip<77nnN3>BWP zaM=>e+YS*N3+pbzG7~`>#o6Vx6Iq9`uk0-G1k(=RNoOkN(}wM~@wWufXG><}j|*YOr_VMgb$k z2E@x?vSzy#SF&)BM^V=34t&wPiwl+6*#Jj*p`R<2Uc29k1kDvb>^F$!=jKOQdgTL8 zyy&Ih`hoZS&F4RN+8(CCIOa= zmWN^D2&)&s@;lN&aKSKBt%WpP8rX59nU8UWGo2C$#bFI>5kpbPl3fE)wXba}7=>>e zHN~PBWc<};PetuUnugzHZPQdXTkpy;HCuUoVRT<$7zzaAIWeHV`Lp-`#Ys zLr0Gtml*6drXykEAd0cM&Rqa{_obA=PvlOFR4gq0q>z=dl#wP$G*io}wW10SWR8Vd zkn@pU&#!ie=Ft3HSPdTf$OjxhdCkW^`IS>=*8Hf3r-bk<0rvU@e)Q=3gb8>O90h>E zDZ_iNS9LiCRzmbZ_&(5QX|N8#IK9w zQ4OaD;eS(SJRV7)Pf&Ujh9hjEVx>zvUL7h543tR=1c0(9Dp3c?iXm+20X}H|7$TPK zEn7M^Rb{7%^jgzS22GPjS zzbOjX=CZLAxK?+c)!Z@J{@P5_bT`?)dfHzM3kgSd3vbbobB*f_`+4c5mktJf-7tZ- zGs%0y0S>In!c`B?^10m1P$dAzeLhewjSdSxO@p*w4837d9hthIa^x2s8rJQ>DD95o zh5Yu_5YM!D8-t`DguTND1_|DwRPgK@oUhzr#qkBK%d!lfU@$FC1<^?M@JE-Ya1*Ib zYgCXUc?M*O#u{ZsZy1ZA-e@iKlEX<>!8Q^eT~rLh1Z76LJz2M$9okG)Za2U0yOM1ah|>v(ABDD<fH;87y5P#sEe z_~Bn|818Yn&2E?}0dq*fSocrUgS%kNPBhP!Mwn{LRqjCELvraHlY0P4`+ ziTbCAar;I4i(wh9xrL>-eB##4PUlyj{TQP?NeVoEV@$Kt(s9Jl;+Jh8GYy{Ok?|EB z5+b6UFK4F}D#R&X`?Z^!0&A`1l+oBpCsGuEQC;}ZgtW{`@paTAO z59)>tuhy$xD8hAdImvK}M&z}(K;&8IRdkVs8SN6=5Q{xl>b@OP*f0lvW-G%W6UNRe z9^NvvlHJOO7LF|$vcaiQl{id?8+^f@qG*DEVQM%4Z9)thTz!nUR*9018>P3lTNShG z4&wM9WownT;)x3sS&lyKFgh zQ?IFc*s|B|r8uRGM!O-iqr&$jC6)SR^K*~NXO>5StYc?*P&TO7YJEUc*gekj(0V*! zN5R>Ocl+~&WEhWvuXE#j%GCzO_~9+c1s>cjOh}-QvIMHlq#hi^qN36?>10W(7!8?ivlY_(hM zX0osh`Ibd0cph}Z#wj+8apZ_byKj;#0sL@_=jOn#B}?J2$Mb4j}Y@6ZRM-FC#RWUAmf|O&lBv*^QJBu{TrE@x1Gr9)aaIN_)IdzUwr>mZh zw&YE%hsTZJVB4z;rv1gRVe7@>Y=*NR`ts&-9=-4}m)~#Ja!7!O#-ORn#L8O*q-8eD zH{(ik@$f^2W??uAI=x)e@gLi_5GGIx;{H^oA2H*Wpacf|q8}gP)ZPbOe zVd15zH%d*G&3Z+W^s`YX8FmFfc$958^hU!#;D(&37sz6JRPp)J3{ka#iJ=~|A`Xjc zoXTyz&R3}8+2u-9;pP!e$n$JHfSTfs=_$@qK$oH!iesD`qKS(T=~JeLHq|6{z~P00 zYNd8XK8y~H1?U^g`AF=hAC{**%SaZq$-$HWsB#-)Xq0LUS_5sT1BB2qtDVJ-?=&eT&PDX&UI^_khOq(+g4O2Mf;YJ9*3~-? zU-r(A-~P}`>(^g(sA6#V+_+hqB(SLQM}y6k+dtHAZ!Ii8+V_sniE2)9^gP4)X;DUL zfKxemGZh#R_GuwzZHNh1Rs#oN$(rXp9fF0h!CPCE2Xd?*@eU^ebl*fHzXnegT0n5#Z8OlOO1fb)5x=wzKKMku_l9u5=82xJ+LZsDH33w;sG;o&mWe1M$~0^rx}%1+g{?+m z>UhO{DJ;+lHtiCkN^r9P*5E-n>QL#SWmg{Jd4)I@XO+IkGT}(&I>lgD7H+?sW1y!< zVelX|Jp=k*ClX5))zxQ@i>=VJH!eLVkohL4yw(1z5u|~oAwQA)9%z3tENx6iqswQ_ z<;Hya5l23|apxt64m{zBPyUtne&FHvyL@JD0Y~R{<}q#*FsZS?G(VGdJ6SfEjV~2z z75K^d`V$#!Kk@O41RR5yPZk!~Zo#6BFHFEhNgIMu7UPjpP6gk=h&hDA7ITarxdf*a z_U!^3ae7>E2op{x=ubih&xqUkBn~l8mSG4U%R!VARIS<(Do++kkfTz3^FafLR6Fxp zCPH|{D4WHO6pOG7syxm#`bAruVKzvYC%TI=&gfXYtYhkKlJw4TAPXIbtL&JFqN>$~ zaQM(cJ^SFw(CoH8pr))2hhfR|oJ|N+BHci16RF z1DQ~V=HfR&>Lwip*{-2d{x5HLC9PR>Mr9j~PFaRhwK!;9Hx@Kv&Lr(Eegr`_>!u z>ec$IpLq4}|LYxp@c;Z%?Z}DlO#GlD=3`l^9JU*c!vinOi(Y^5so~(uQM5QWdmxNs zcy1+$C`E6Cv|wz$=fgmgaXNG;z@J)P?vty;fIH$zJI@ktFMuRkkc|MXqc==@> zRVoWhODqRFs+?PnxY~XWy5tR1=kZp{)&OSUKSpZB$|Oj@gNA{_#P)< zORe_fz-Z?tIb4TBTdpI6U>#F_PRi6&tF{YlknQ3)!Iz5KyPUi{!^{`oCyfB!e1 zi7PP%c4wxu$-{vLobD)ayhe0yh}Tqju&Hr?R}`j)cWe}Cl8q7^-pFuumVz zSa3!~QCP3nX6I)QEFYblor$AZPI@+U-B< zdH<$TpU1oGc$U>NJLCnJ!LC#tr?NYiDOImvxn{XlKe$#Y;QCGun?SB+x1Un|*jt4H zS`OCs>yj;oEFa3YT+!*R#je(LtHa#N6XM@(z?gDEEX0J8c1w05cFJRFbyWRyy0qwL4l(!LzGRU(Eez#^udK7>wJ_SF4DrGOO zy`4&PpHit=)vSue!cMQrVk{gtwoo?9({dFFHc&4#7}qL+ra9TwxyvonQMYLku1swZ z?m2wgG$d6K#9SB~s}jcrDkjJ6#K`M&6dyZbrr)P zsjodMmbH0%e`vY+p2>|W#+-&Fws_OP3x56A-+aYomqlT?xVRv=ovqL8%zAE8Z#Xw2 zG0~WBPJ_;SlarL*vmFpr zr>L{mHC>mJ{W5Nv&;ad=Ef&q0Z2X={i$Cvd=S%yGVOf{f>-Cqu^rbi5^w*#H%x9ke z{O7Y{3G2mcLY<@bo{3V~!vo@E0J+0EBJ=!t&wlpKq!X0$a4?w)QwY~H`PX1*IZ@o0 za>G65V>(%su}Es0k@a*=-28i`Qsv}RJ&@2fGAfJ3f9i5T$DU+xvE(e*2Et9Bz0lHF z#RQ?{Ol)DRw>!wD)XV88nlKZAlh?R4JGt&0GgOSnK#480-x@%~l?~8?8`$$!LAgU~ z@s6$!#JN(&X>4bA{RzuZ>ytd(i-=J_8r60fwTvhJ^i|;J>($|{jLA`TN{mS zjp}sUc-Nf4lY=`s{~RPK0{i$23?(6-x0rBFD(bV(iD4jpz6i4%JUfRu4@%{Dmno9s zjH1EMcAA7yJPAM5n5og%hyp&A32j_{@IDUwL@?Tbl6;C0ND`dhCY!OLKA-tUEKLEV zq55?TXQOTXkG;FQzP}@M&y;_#mWME~0x45;XpE4P%FH>sS z$f7nz|VW=}@D5%5E2>z2wkyN++U9vK85M z(_~XHYYA%&#=Q=LYrx$T2Z9X%``Jn4xH7L@8KK*@8YZ)Om1rsWD!a8s=R^BzOB-yu z`h%^_%6wg}ANtUT96xsaJvYAlagTlM{LD;Pjq&>!do@lJ<^^=8Gt7(~JsiU+HR`UU zsXdIi`Z{EcGdv1QYn~>b^K;?MHQ^+;ZC}(HbxoNVxN!nv6(`h(rE*xF4 zYcPh6nRaJ38+c)~&w7|l;t!hiAHn)PaB%tGzTy?1`sYuLlH{tZt{(LJipjlDfsZa8 zSuVreD$6+T-Z>OH>*PXk0@pw))A{NX9o;e$es#~B9^fr^e0w#EozxM$oYH<)AELAP zxU}|qr+Tp%LG1XF#FVO~OC5`kjj|2g(ZQ)W8^uDc2DeWGyIvv!;nudZSS^*LZ7<~- zQo6CxaIM?sBB}^ld^~G%Sbx?ymnzRPL>oXQ&&w%78q$t%qb302k-!ceK4|R30Nd=g z7mg;c%Uf3!uX)Rkp)Q8(sQ+$gx1`{>$#B|-_e5u-dXKd4dN>Tiesewe;-G%bH~ADx zGDI)=?w3eZyVu^CdM`w_@zhqtE&9%1McbV;6_|#EbCbNjdA)V);Cz&u-~QmOe}8J_ z%A>RY`P&}xcc+^F{&#NbuWY>Pd5`)(p8KfDaxBAgy6wl3UReeohw_%Qi6$ts>s+b@ zuI4eh(WYeZxD=bkMk#A4t7$^;BZk6uP5+J~4SQPBjua@<<@Hr*mmzEsb>(cllAW{lHnN)N3HEM z#;jqjSw44e@D?QWRcG~l2oq-Dyj2aO)Hmi|a`Q3#a)=ok^OtwFMEiffQ~db5|LIG{ z{KOyL^vfUm)c@KTT>A1~`iEv;7U)lT`J4XRO`m+%Et|jet2bV8)unHH@8ACLjbHq! z-}&gB;ml9I=~XXw04`uYj5j4&^omQ@BQsNZfiW}Wxpt5<4ocG%5Qw&3yFEp$8Nj%3tL-Z_4F5e zn?-=N@4|^_@sPhXYYs2{r)OOH=ttad?ezM$UH8z|m%j3oKll6Z`S>ZVNahA_an;u@ zOnkJ>+Hl+aiC(w~)#+y>c?nNED!Tv&V_TIxM7^T{WjU6)Y&pt_7N{0tWH09_JDcb! zo(iF}c*2$C$OfVk6sH(=Q9{->E;H%}Ft%lCEI^2Z>0GN2t2U`Cl$}DWZ5t4!eZnx% zI>BOsK0tic5rpH^?E#T?v}Ckf0^24Da#yRYbGtv1-mlFHI#)-g?>$F zpD}EpAO^jz34)pB1GjIj{Lu&hygVe0->$Knuop^J^0_05QIebs5#7 zE=!|X>k7~-gi~*7KK;^6y5hyP^IW?q)p%I^<8}38NDxqNg^6TrgtG_W@^&AtZw=m3 zgJ-*lNi2FxU8;ImVayAkiyD2$B-7aL`g~6beuc@75Pt`prWDrR{2t3T9Uu)V<)Zc) z)(gO1(&UXBQC_=o(`z?Bb_8t#)@IY_HUF@_iKoM`X8-^o07*naRI2Um*Dvk+dRT^} z7$v4XNEho#B|iRutN!T=Uwq(^ql*i3L4lJrI%XG0&V&=5m|dLrqS~iFy=G#wFjw{B z`X@j6*uZyY|F?gcF}OVJS?s5iX!C1nKq9ILPNd zoo2^B^RC1b^lTesZgZWprYqMFIrI!=s+Eva%u=250Wfu}89O5f;O+NcbBeW;RHF?``kD{6O`0f98c=Tokw7`mVS9 z*yhkY{h1%?woPm8Os5)U-Q4sx{=?7zkJo(nw|vvXFMD|GUA|a(_VXY7YfnFnUtu)n z7he56pKFgEamC`(7OPrQHO4P{Dp%S`Cd**rEh{N@)G$_V(m=AKma+?JV*Ss1$7gz> z@Fw#*G?G~CG6dvuO;o9ok7048*HmYA4Yi z$ewYLEC*f3K)DbKa5lr$#u#^Vfy7beNVrkD0NO7M>-6V&-s|+cW?2j_pgX3W9ce*bo2h z_dorbdGlXi`|b1d)yEw4|NQMgbje)(%Gvm59 zfA$S0k4jTf^(sc4eklw-wGkVnbFO>%3#@$K;&wD=MBN)8VFpL4bTHHj;takyR)O* zz%5V_*&O@tHXdb8us@q;^e4l#wF)90U~!rFh6L>hV8^~1;ug(S+~HsV9prqk0|x>4 zWRoXaszekQfZ{mFiW}r*RTw6BZ@(}OY>B$uksQ-Laf*|DKC8OBArD8p@%}UnFU0hP zYqb5su+qZ({FyUnp8AaI|K>yQUp{obNw)sw3ujJT^}v-oZaZ}NpuT$Pd^!#yU*d@q z>GII84*Y7(m?h8r@qc;cgH9ed#NN{{SvvJwZ_xB_{oW_b_x-Mh)DXJ<*rF>5gh!Ha z*b4&)sjocacu*=keflmux)|UWKS2!CzZ0Z5AZuCoi-uzIS<+so$9jHnMRrtG%w8i5 zMCn!D@rq`*Wdgk_3(%19w{6u#hKVQ?#WL~QF53VrvdS;Z!#l`R`Z6LKKYF5)p#-ClqS3^_foIei6v|kve)bxTNSXw^tk()m7fNQ_`=8yc{;rV4#-~7kB zZo4dkV|b)9y))}Q5Lt|yi+D4D{tz>;cfLR)XomHbHl87QzmNtZp$UdmPWtrLw){79UnhI2a3#+wM zQM+=^BF+R5z_N5$kX@OcN|SeXAbFkZnpF?SG9A!`D6~<_Be7PGXo8vJAzT-FF{nt|E10M7MM5QpTO0H1myITT7>a&1w72@Yj`Tl53k}R$( z*z5pQwKRk0mkoY(h2cEF$o&z$Dmi4#@DmgV!TQ$rjW_=3)1LO!e!s^Bi^2gP2NYC> zy%i~i#~x%#s%&y(U7yhxa3{+h43)yYpjvgxQ2qE6+`N1EQXboL`b%}w8 z6SvT@?8$CxOD@5Xsw^FiMmONMa9PH=7`xjw%rrFy?wNM9LRskS(!OqKe=#f_Z#P?y zeDX7{c<`g&_51%gN9Xj4&x znpi$gK{9!vE^1Y(+K`>JLv&eDW!JL?UJ0h+Q=&}HZ~TA-@*xoruW9hD50vSG8L@1d zCemgaZ7g%5tu*7xy^V=}VATwpo?X>u+0pc9nA`d(rhNQd9Ua?NM5z3*Eh~#YUxLut z8MaTfzZe#hRprLU`uAOT-3LE%^H!_v&CLeaJn(P->Z8wkG^#}cae;~n~PFGNL^5iAo@$KJ!^vGe& z67s?Ja@xs9PD@ObZkcR?h!1}8T4QYvGR$Muei(}00_p?Cbx; zDqbAu%SEwo_kO5I-3+5Hr=@X+dq2mE{cUYi!;gMV+|amol4K$YM;{?}2vjWWDC?|S zOS^}XY$NX`g((xco2lFLqJ773eP-rM|NP1APUo>tdh!Que784$AS)`~We@y+KYPl2 z<&#HdXU{gM&L%fYoKTN?ii2ILS7$2rReKUY02=$PcDvh=f89p2nT(QNuh(cc(lpg~ z21H>bBNkUGbF*^?4<4*kD~AprURqk1o0+ZE>rtr61H%tl?s=n8BE%p2BtKI(L4}gT z92Ss8g}z=Poof}h6*pCu>zoR3Nq`Mt6Epo`P1J zPIQm6C`P$1r-R-D>j;@T>{=K$DIvct$zLK5frx%XaTV+AupiWI5F|wl9TK z(jMiCrnhE@(S9(xtfJtLu)15cW!wNBk3!?qSFnC0avr7D%Z~dS1iwDijtRF^2 zSSy0s;^CvC)?%@`=XTq{LVD#XKMZ<<-rN4~Kd5BIX18TRuVV08nLN*HLNl{-bMr@z z9$j2qTt2W|iDC~gj;^Zux;x@nbH9>|NG?I;mIrmpK!|aLjSE$H>f9i=z1Ot*(J-RQ z>H^KLXyYFRmu9dnW(G~dG`0|%7;Y-Pwk|6zcLKw<6kDL@G&vW~3`UD-``d*we-#xn zOLv$oX)(LpE022C0=AbE8LgUC!0C8UVw8ypQIbatj@Q5(5{OhJE{J5ChvDoik%3z+ z9N!wK*XXcA&OM`r1ke^(F z2(Y5MNNpOm1&Bq!kBC<1xQ24&txSl7`a|W+LDix;HLoqiQ&UnC+5fDFjxYiMxm_I^ zt09gYt6IiZeQ5j2FQ&z$X)86ovaTj)BUNOQ_uaQ9MKmoV)}i3QtCM)Ljb*D$uvQI* zNE^4@66mJZ?rUi(oLo~-h7K~YYw+87WsIY-XwY@~ zy-sZQeM0+-VFOP>9?Z|=k&hqmhRrW*6%=0JM-@Ma&MBPko{-DO@xH9+4@Wgw60dCC z{FdK-@-O@(V(D8|(Wd;NIuT5P!(ry1`-7!g^%|D6y0$SG4up~61eUr2 z;McIVFoB^ri(Sy8(w+(yG@s8xXfv5x(XEjLJfmFq=>1}F*)S_2NW4S7efAnTCc*|dX;Fcslef!2MpYXVsKH~mg`N~(q zIG(B34j(=w#o)JEmKIyB7Jn!hq@FMWOs#F@68oP##z=&-C3?+^DO^ktlc;e_74P!$ zk6o~9A(~1J0b8u5vIT?ln8r4O+^X(njH^}u1dC>%+u8O3*Q!g)kD9n10imteYgXl| zAJgQIR-!&li7R(un9=665f)i&2~!ohf?2HxL~|3_(L|G>cym7Ch&wKst;R-U*jC$Y zcAecv5D&eePS$VHk8tg$4H;MY4MqEfVI_ShdzyN+nPn4sgZ8k9swRj`6lHvo&R02V z#1^ljXdw#ke9v!e-SrPif8&ua`$g~2(y4#^wQHXAV7;2^&~O7}$LvCI3+;kA27t*TpNY>0@Jh zy`X-@ujM)P89SWCWMfn>;ENaMBAbC-MqLvn?3J_@UVQCnzc9?fE*H!w*#h&5|9flVG+8WQHO>Va&_%Bd!*rR-WZ-j`S|Np3ULZ;fVX`qG$FhqHeGUhG zS`4DAS5IIPqE6G2>ruHqZepX*D%(2;T?H?(1niu<>zWuB0F`2sttXYqocf#i#z!*| zqP%AeYNPEl5ze;j2@ub=6?@3)n*^KL0-=}L2(+6Wv(LKhAlqymLXJ1Ae4lPi8>o%f zwcp(k8fz=RTbdY*-HFY;K-w=1PxRe0N!A@Cm3o|nffrT0s2bMlm3rN;S2^qFL6e+e zIN`RMEPvFuyn;rkKX@T#ycP(3)M^FeCBj#wL2IdJ$&Mt!_{bZ z?waf4IqCn>fx|~muCH(Od;J2xkeD`_&8^L?Z+Z+C!-iALpY1rmc1{yHjaMbirl-s% zKzAI9rXtCyh+-_SEwa_});mQwEN>6c)5N+rP3YQ`>T1IUt!}Nv7H6-uB>t`qjy0Xt zq}|9uSc%%z208|x^c;D*NM^~30Eh~18KIYDtBhM~ajPwd#wPOJ2%==9h7%08l!QgO z7%r=0Qj`m$UGI1Dk}Xk7ajo7<$0umIK;~BK;jAc2XsV0ZJ;fe~w$;5(CA9Ek+{xVG z#S@nu8E&t@Wm>75O6-Rr(wt|jsX8zf&?c-QOv50f7^+s%1NLN9cl}H4JO4#yeD#)_ zjQNU$+;Zy1PkixDA2#>chhO=`f4$@O%a#w^aob%t-*T%&_1c3^{nNjG;^UvJ&&>K> zu(+^v`0(MuaFAw+b|JxUu8vPCk-u=Gi(@j7Tf&l^l>fjj6HgO)G<1C;NVW6%7S^7_!BW07Coby#(R?}iGd zahE#?k+vr~QebV>Ar?`Lu^BxDe@Vm}k72l?##t-@&Mq-o*~aQ$hN~Xt72<7sJ-`eN zQaG!0aQs#lzmqh2XJCmX+-Q(C7qwXi2l$cO4aP3zfLOWUiU`C&X%5~2^O;!)R~{BYDv^84|~>i z+&PNp4nO*2c+i-cOCSHn-u7)KpMA`OULvNo=+tEs{p@wmn=aMFaVVc1fl~U~u-G)_ zBmeTbIFG|!b{Kaj+cM&UEJYw$Gna~3m&0UgDk#QST>)-%i(Z>U3qn?2*rlx1%m6l% zp%`9M+cQ0a2tSWbAcpA!y?j8An50p_64(x{+SrAV>UKU#QP`%OJqk#PlK9W2Ly+D~ zswzqX741w0-!pzt*cIUEk?WiXrb`v9g%GgFRVePs`VPgmSp299Z`o2$OJ?t;5M;GG zlbzk2335%?p>C(BY~$UQv>RC_K06{?T>c@`P7> z&kcY4H=k^84}-X#i`YzZH^@6OpOY+uxZBVh7{6o8tT}n&N&oq0wL7ziE|m}Cuc?e zo(S@3wERQ+)qi1U3vJfqlM>X^*DAhGq z;o@|LRe+KN`@lTtq0PwcHj@^D58>(TQIvvLe;^PAF{74Muz_}9bpXpOytAbBme&RE zrczAMxxy#O)pB=)aXhCg{;FwzFzg12GOzF!4sIv@2R`!m&wAktzU$kr|I_zGq z(TpEdl#9TXoP~QNO`@YNTp0V&d^n3BZ0GWV)Hh~xP;{D|t@VvN&#Zjqj=Sz$-CA#U znrj=qcB|3tt~Xkn-QlR0n864u#3(kEDqfEnSBiAgq|I^0s&UNpH~q@*OK@?zpSn?r%3+>l=5iZFaVs zZIcX5S^(blS%6a(&XPr}cR_5mYNGkzfZPgtv0rThQB|Q4u@cq&ilmRlN-rw42oKHV zd6s!Go|@2URrroO3AR&6pu>tRC9q0)+9|0pC#TDn%Xzyov(8bO43<^m!LKkK7`y zV%0grY=w9NN~ew$;9LKqB-`Z)+P&-dNT``urLT(()^XaUiVLOv!mw?iPG#EjvVj*> zg6hm4zxVyeue!!NbSR2_zk-F&c?}MGZaFs$D{0`p{FSdUqirf-QK2)YS~syTt8o9& zF?hfIpr}?1UJ&im85|dZ{x*<&hxB8@0?l$2HXa@cr^6Jl(F^>gg@qu=6Zk@N9vvbS zAkL}h*d)(Rl)I;9puRweTijCa+vO{(*SywT)pAv*+hfo0xRSl>;+97wG^6H)vmE7H zmFnp}FpFbX>=mxHG(;Bkep|a5<}{1a=Hf3E9&+kR`lXkhGUnlyO@;aD34bP0F7W@7=x6$P_^=bFwgO#;=;>Q!?l3?8|g!Xyd~c# z>}^%dsrYvl>`ELB+n2qNZMLyB>?mL5@L7eqK5DnmgZ2%>B?(`oy<*S`;=_PY9G+fX zuPz9{3d6V4k^RaJ-(Bk=Z37$(;g5uabHgB5I(Bquj)#>xURqdGaxchb1+I!o_{0w( z>r*s?OpBK|=tBS=Uf#q9lK7%58lb)jgFFhPT=|G*&ZDqMJuJ~^vyuT+ouC2SEg~~a z%!YP=+^)B%N$1QeR76=kAit)eT;Uaeo*Qs;IgudORyqu$HCW*yJupdGQDD=^68cU zo zzTGs$mK#-${t}R`5ba77&=9Mf@5#eYed$FZl0pK5VC0FR9Taf1u*u{hdxh*;5)t8e zP1)0?Drc+Y0vlZRlNUCpvQ-IOEz#CyIj$_1F-!y`wJJnRYA{l$&%&%B^osfZ91$w&^;HyBuvXnFTsR5E5k z`OFP+8fJcmsIWJXauO3}e!c4P_XVJHv5gXvyA;OC00jj%Mo&aH)vQm4R^;JRDZ
z5B5Tbz^D{J&pcSy)IS=6cmtkeQkEyhrPBZaAOJ~3K~#%d_hB>0kuXQms*&gw{ytC!V-p0v`#zEXP9gO2zhX{B>^d8l12JwXB> zmIMfba5ETU3jMr1_Vi$crh7*@>$ z6_aUEm|s4EWR4FG_Ko-*?ClQs_jmWUhx>bz(J0T0(RiFsjJB-~h6kA3uFH@!*j13C zPIZ;0snI39)xqZ0nF|;1K701u=K7k6xi=o;IxSF!ghV@3SeF9XSaSN4-8^Xq+scdL z*d}E_vtTAteJDu#u)mWxA>Q#}Su%{~BNrxd^4jaKpS^VHpZ>G|@-M&p#Fd*jvbC)& zO$=xkf6Vd@}3 zhG7R&CmYKgD%Y6JX<~-ZVK0q(y&%hiI89epiZsP1*Xo$|o^q%Z1^ka<bo-83E94gnr^CFr?dbE%qU!TzSE5y%;^PIrNMZ z{OhLwfJnKrp(gsK#aIZo(%?VvD*NTgSVfcp?D;ZWUEyyW<0=K*>5Z!vk|>T+*gkbt z7CG-aGClohG#-tMvN#xyZrr#rKG+}b?%uj_Z8|)dj)q2drv_&_E{!!@mNhT>uj?{S z3zGaotc)!hN7WAec@H5} zCc42WN{j>V&^-ZYBzU~exU zj|{@-;J`>Uv|{Hr?$@7x{-qaR*t+}f2OoK4^Yodq(F0TrsFl{zf@T0`C^OpgpzSMU z6hG+`ZFuAC#E=Yx+$Vsg$*_PSU6vTT=dKGE%j(A0zx|D0_{GnD{hNlr#|x=zx7-Hi)kxxC0m)s zEoXwjc%p~W{rfr z%3CRz7#5;uB7&=!tgu&;tGrvnND^wl)5y_&M++`E0MWq0ndmJZZJIz*jg=x_RfqkFIitep#=_{v-R+(I{k`FV5zM`V1Kb`u9*>Ny?rq;1W3)P+ zPR51lai>Pz7NVU^38RGeQEV)sz>HX>N7K5Xe9-37ghnOM5HZ8x0R0b|7PC`2l)%ap z0x^Xq!&uvO@o2G*H}H<>j%=le*{t0wZ+zqOrT2aCBX`|@|K51Y4JWhEc#@B_iRYqP zaBLti5=(%6OdHr_Y#eRO6Tot0*p|@zHO+he{Ke;9dHK~V*LU}ZqjB|%zxn~R0yvJFMlNMUg(FKE5W7FFQYiqyvg)jcjZ~tE(`{iGL_v^P_dGW>1eC{{@`fFbc zR#tE|59V$`Baq27F~-0YF`3H3+-({}SrDcCGDKp=!&TO+uo%`4r%7JqFtC}gt=Vje z@c}4=qa`#uwQ?n2TYym5ssX%-uQa4U6BnA9n<6RP&`!*r>a@ZXy|BE*<=n1#fZ6$^ z=Ev9_)4)2ZfNl%{6jX{um1gPM$|^q25Jp*=mig3lYeqngHC0S=%n=PI+nfp-9Sq0& z2fI7Bh6hHA?r!hy@9gXvDVZ~q4XeV} zcD!4P8wuc=F#92?iS%(I6Cn?G)N+C#AfZ4-%v6Cnn2wMa|Cu1-y5tSsdaq}!`R_me zSQ(TT-hTfSrI(C7kd$OKgkyZ00usR={jb@37NMr3Q$=#d9q1!I(1 z{#p#nkzotMj7D;Q?tPEcPd)$iGtb?;^xD_H@%4M(_Rv8TVJ)v4<3vDu80A^#Y1aRp zfBSF3(_3FNg8TcwpPW1Y#4E2FhKhSVXi>@sh)K4>*<2`+hy$n;d|;#(Vw?ss2E$>J z#%VTKTU}XO9Uer5chjCv+Zj67!Ywj7cD?l+MEH{-@FZ99vIP17xf=nA0?ECa3dI{l)|hVf5l#tZ^D++m#VG46q}by9Wn52m9mU zc>C6Nl@~^5?(J>w@9vnfsIh)W=KpwX2BijbJRT!`7M#hZ_#{qP^?Na_X+zn1K+O|~ z&Jh7+(ZQ?2z_6Lgu$A7$f%0kvEx?z4!t9zNx-F3kl`bl2kglU*U2tWoBlRnX)?@0a>GQu z($Aj${&SnBPW{X;eClt$`pwraz4F@i?fcH29;>41=%wQV1e0UIIO}5?IZG%=M>YKr2mIkXUv}92_*wR%=EdrTjA7=jHO1A;>p1$ZMWN&Z(Q$O+W zm%sA03-5g7qo4WA*T4F;pr2u85*s$qF!Q^u4z#L<7tGe`YzO2*W{68nh2rXIlm4_U z1DTXY9yslY(H;!m5S}TAzHu51R>FZ9`u55whCEiaVd(OvQ9=7RuH8y@-*{&Zt=ANR z`XFQxY-#9i3&-YXt!#=nlqdX4|hDOwkF)XZB;@ghWRiLHf zpkB|CsNaY1Lx3HNi%3+t5l19bx0i=YnXRhyj{~ldG#)x^w07wM(yU-F@%=cyhEPg?>`bCC%1m z>9&$;GlfvWwe{`5a%C8O3zq)U$DVoSU5~u~+2>w5SJt2Y^_nrhp@lBL!F* zmEBaukq=n|!wdBNMlf?Q3p;f5VVIqQv1K?QOTrjy4|OyM!Ymo|(v`K+bhB|#nemy? zvT0U^X=QMdxK0!42{G4Q)kR(yOFS_{>ESryRTeUfTTwZLVmrVsEG$Yu_K<~SDR{UV zV7n9iYC9f7Sjhc`R8%yCq3Wn$q4eyDdG5+EulT~_cw)pZFLU!VBhKA2AMNdpN29Wi z%AzbMLrW~sQ1P%AgCEUv^f5|t z5R@Z2%BvA!bez&G)PBN(P6D0V`3SG-tG1yc_?T=2fStUz0CY8>@aARI;e81bPajRe z^H3*rK^1ap{kb1J`;RW1H=SdF5BfBDJH#btYt$pEHeXRzQVhEl7J9-o;6hrU~!FGK8(7K^67mG)c3b5$iGzizqD3fD+Ok zL+?R>i&@2~2k8sKq{ojfl@wYj1?=QDt@Tw(2B^ud7D@YH&Y%j7R!v^hPqJqxw_liG;<(xF7-SaRye!F(;^jbeeY` zP)>C!U#lc{Jgv_RPx~Hi-PVFu{)^E(Ks9SzteEcZ@4x=atM@(l(7usjSRgJ1{?Vl2 z>qJw*)=o)gsp2@+PpElu4qA%02g{XVQ>;OM<;u%1e(PJ0zyGIy`b(exlh6LA|M>aK zR}QXSKYRX6P8&jW8YpmrN>JGu!X#<$?1f1*NLB`>v#8=#5lGXe8u2b@8`G9fjemia zWaDSlKlbsl>x1FSpL}y1sp`);1IjjKQrg2(n?6umcon@?qmL!~OIt!?Sv0L|phM-L zN>TG7%B#$bDvSmlPm0NSG#MFnTHy;%Ij+`c@65HYIFqc%Bc}JkWE6HA)-rtHo(Y~l z03|uo3C@9sim%A1FpY#!Bwbw?A`S%>nE|9DLq*e2zY85s&}t1(I*uFj5OIC;0CO8P zkGsG6@=JH$b8i}&Zjy82b0BO)uSEmU*}XdUIT5(H$A{(0a6qmNiu{SEpIW)Zvb17fe1d?ZM;D+>=!5gVYs5jP)?!QNmL3qKK|^uF}IfRcuRC|3qt90gSwx@mtKAO!i9^yff?~l+VV%2^o|QTwCha@Dy`+UgK&qibQv!4 z{Pfo5`aN%d`NgY~_4EDB&72yH+bb}O!2Rt~t7a&5*s#h2Sg^eK5zDK%s=C0wAtP^C zy~%=66|4^Oa*BigbsUVw*nky8>!&t1w+63Wy&0GwlgQvKW~U(-vR?vooZJ-=Qzg^i zD-0TMw)?G{?$Zi*ExoG4jjJj#2J>jYm<;pjG%ibHu%)m?(6!eTI^h7Rd+kl(G~9!S z+(!Z6C&K~~P)q@b=E}e)`W`9}l}R1tTKTk~Ni_xVLf&eaq4BaohWJ&Sga_mC3oktP z==(nqx*-S{#L;kwwn^MQ+$}qS+3MZ5Chd%tFY4X(N=YJU9 z_s)3hEXHo&*$_r-q?RZ>v{J`q8VWJ~n9=ZXx2|sX&YTVMNgVf$@ZJ0H+h4hS_5C0F z(DTndfBo`}pM3A5J7IC{*7ir<{qFzj*FOBO|KqpLUA+6=yHEe0|LzbU*Flj)n8eKDyA?>*9|UhQIDOT z@WTRFAnI=#EKP=4rJ}0L5VS1oZ+-I{ANjyXzQ48c%;Qfd>EP7nR=+nGr#%V>kC$LQ zQq@8?DGReyf9`*4rui@mORN|1PD`)^=8C&j7MYPAO*S0B z)B1~k^f5VwrFvDutiKIqh#)Y{ZjSST6?S>OiEz>srJFlxtlNepNhYJ|OE0|e?nfSp za0p6v>=>dPBWyB&wTga@*F+t{G4(t#R(_p~fAJHaetW<7gXe!Z$%4F}Ts(W%-+uE4 zlbz`cPrmT3Pk#FP_TInx?ccs|{%jETFJB-2kN?}3E?>PeiJ~$uf@~11tQ0T?xP2O{ zs+h-yJ)U5@ND^ku7g;$4yP!CM*JPrj&3L_xr=xO+`@MKeTqw#_w-z-KyWk0`6=N%b zUXa+~lv5X_MZ-Dtq*e)}M(uF#g$6i4B-snsH+H}_g8R#rPpEFslhBwOHt!Bd9n}_F z%mzybmOM$~?OQi?w{LBp-WrcF=0dTki5&xYb@%B;W_BU$%3)%%(R393rdDn_GVB;L z43jK-mkTkJ7Zaj}854@ao=+ zmp^aDpVx2fUcHfH<~i$M-`^EI{WKKKMc1qxsvxqJG@~ zM8K19b?B>&DN2}OQc973c%n1asETaeA#HRi4YPx4N97}QyZp;PVAB8-2+&vD$^d^Y z0Atb#2Pz~cl;*}o1eAsttx5@s3}_VG7-I%Fp|9XM=g`)HW=fD;k|-MG`D?GevT5JL>oU z=JCg)D*oah|MC4F{FwkV_#=aYv!@u+hCDGEVyh||?=WrL0q}Q>lP^y{{&*0ddhV%b zg277A?@vaPcyp_)F?)9T@>Q(poV|dFC)h+hcvvTIPC*hN3`txiVVqW&6|U1T z>KVB#QV5*bzJA71N0C)238hm@6>^JJl;`o2yK7WvpgHC~1d7mXn2a96=DsjA+;$w;SCcxs}?9M<9BE!d7_S*B$*TeB=KmIAh zZwJNn$)}%;(+uBg39y$Q*W6L47)NQ+k(V|!LC}g}Sl|F#n4H?oR#&qN7t*a$(V!n2 za~yU@fh-`oVgyI+taeQ9tBI zZ(YW&pmfySwjpnpkzpPLSzUyq{V*S6MTr_o)~1ntB}(+9(2nN4iPI`!@vty2FBB4> z$A{>e#ZoUQoyEOFmu(wXUKr&^A4~&c&s<%<_}mYJa?0gDRBNVN@@u^q4q@jzhNbCY z>1u*tI-Op8;DIu#pMUj*&;7=4{P9=*dRi7y4->wnEZq=lOki{CKUs$4;A}M~x`;5B zL?kIEjw_NF>{G#pMmlmhS!`YiD*qSu}sZ=YJAvFwgTrM!pi^(_%O3aBhVn4$M#|9w+o`)tepHu`F z(!rS*!%3pE0p|m&AMnAS)K9vqA7emW`dK>K-?{q6rSlixR+JMC>cC+{2>rv3CjoSc zbjb5g<$Gx|Y?(QU6J4{JyluVbelcBZ*X9(bztJ_DdjtRG)6oMd#s zcs#s*{py8_cTe*PD<$J`#V>%6olW<2hn2uVKrU8qN-C8)dYqxyBR)-*q z+)52Gn;^msTN?;9Z`zCM@!0%u6bZ#eSz%$|Nr^!DwdI_N9>CL9-189(<16DaCZ}j^ zWb3E&Q9Lg|k?C*`26&GqmtQ<{_Dq^YdCqfPLf#HCDS0%wv6O8P7jk>BtT`$dOM?c@1g=6n0;pgS+CJ7FY~$vjW?EkSh~#9G{%z_J%_5TxHR(1;3b(dzoj!fK;8h*Q zr@$3ip?R8q*}UTW31xNa)YielL0J}?8=C=fM&t3;))oriE#r?Fph4;ArRnJ4;PUI2 z(l~`Dt!WAJYuK%A_kCI2*^*_LsyG;rs^PFO?7hDBjjw<8wVT&}@;&d(i=3aqrD_au zHHAtM-ryO42oU6^>d;_3+$+qA>0n$gE4myJ@dYK!AY*n6M}Me(@%0lqzku3uqS}m3 zjc19Q#9?D&w++X#w)0>w!jUgaLE%K7tmTk4tU2pujCX!+6G$>aU6s>GIT>O9HiZt# z7si2WRGOytNA2ppTGsJ@7;8}IMA{@7>+Et$2j(%3jj}jui0`~ZbbGzt*j}Txxv_Ec z#tqX{XV0ECRl?5nuwGf|-`c*pwzg{gCzFXGnkZoli}QF(phZc~py&XD+%FKqDt%dl ze!qA5((6T;$M~{{j&X=x%P(m5Z1IU%T5gY%HX{E>EJe3(mcd=tCx+$O(g0J=GA}mn zK9~2x(ePHdmPPC9&wl^ec=O7+RoqqwQ-wAzWGdoC5fL}Eg_u&nt`3|lGGbT?DZ#|7 zDgJ`+7{MYWOz|KUun5G~g0#x5%+eDe05r+5`i(FC8X#SeC zVC=RCg9T+rDv?Mlt?Kr%Ez2UJ^AFl=hAcgkM;b{4wF6C4x%5KxA88b&c@ap z(Z^|uPZ62{GZ){j&adUPZGaZkg#O`xeCp+mOP7o)PVk*4aEZ!o3vmv#bVG17N1X_k zBExb4B3xD7Us=1fy|cEmb$aqe8=@(5L2fZ=l^ul z**X2d8JO|&@!D}_P;14QgX21y()}tdDd7knzj^?=8L3Q@l*O!gp)_MVvcL@Kj$^O} z@DBty6;hQ+6cp3R^=ntTib7W8SS=7WqjYEyK|Ex1h}$Y`xQlYqW(!92Q z>iJjSxc~0AJ#hD3kG=5RU~R41-{0BY+Z&D|-0;kcrnzgH4oo%yXs-AGa==?eN8m`2 zmLQT0_BgITZzJ3e=nSM4;JB_cGkP=w$LYlA;=(9mOvm|5J|7TFB=e&XEzu}HFelxf!?A&Mq+9%9U`NtJ%Zgd>G=~PuQ2n8m9Wa6zAWiGrqg6qLD zWLSV=1N%4K-#K{np$C6(`IY^meAhj18{NE-r0HZl;gnX!8=>WsWwNBh6(^bEvQ{W%p{eDpnGe>k z)-3H(_&*p3@y&yDI2_)*aU+Yd6Ibjt(Kiv+FNKeFZ4#jv9tJI1lG(^kZw}05-8ThT za2~Pjt*t-y)H4^(oZsKsdHJF{WB7&(Y03PJk2=VSV0LWL-j646gw^Qs}W@+!q}2#3JDmbf!GR<^efWXbUQwd++`%CKKL zG9SmW%^{$A+rlx)H5ZnAU>L-gF7G5OSkC1>3@oF{ z9tj9=Tp@zdZBP^>)W9Xy-nbDH{3M2pJTPO;a|F`kaWG8*q->FzN>b4l>fm;d zfZdo#A%l>#n-+(Bq7RW%%>gcSw(_k>u_VUY%qNp-GL0+jFqzvPb3F<3@q5(~1KyA$FgW>+}{?7W^`gEEjXl_D|g@g>sYwYG) z)x=YWkHlDz*g{yI9tMOwFK{p#{`KEI@xGt^nP-3Sv|*u-eezTJa4$%3gL{CTjI!mz z)C0Shu}e!$X!&>v&1*Yf!byFZodzAy&Ow(mBnY|NG7vn*Qh{_o4bWi5wn3Vdg(gQA z1r#jHe1gw*ViimmZ;uOe*<}_MsdeQBPqeJ8sb>dlI;4+Q*nw1*N!oWCpy?}Ee+?Bs zFR-B}(}PKA%}&pv@z8wm@MUer{T9I?K?{NDyBaqolvgj)R_?4)!tW$vJsfwIHsm@4 z9PjZ3zq;*7{-oo`v}RyQG8{_EA!b6tjn$3!J^0Y~9{cWxKl-!z!NLA$^zi#WfIEuf zBCPU{qspaa!;CNUNqK=O?ya!C`!`5M1>`E~WUkz~Qi{3vQroaGVI1B*TFa$;e%= zKe+kQ%dfuh!pA=TbKiOVyAS`wBiFCrc>VH~Bub0Aj?-S4W>J#G7#MR%j7!~dmWZ?Q z`g3W*EFrrcmv{uhh5)OG&SyoD@VP0wAkoa8e#YkkgUJL(j$yMl4Iw=3-WL7ZXl?Ki zRfI&a_?rugg@=!4*Q&YcgBVy{m#6(Hs>b7+U`~bPS~aJjyWl8V zbDuA5!(aiwbb;NOBQld|^u9xCoF{_i$uJ71sFbQ|=zu=F{t2s`x(Q_2^g zf9Y0@(|BM1<~KIZ-V2I{=laL zIFMeQY8A3G!7g0{RUF;0W5RM}*c3U>r)MvouGUW9ym52;`bxZUdVe_Cf8)x=N?MJg z5})|R*fPfXp|mO!jOEI>j-x>QaEhLl6B>$DdD{X^aL13D#%fv-1lKIt2(p&t@>6-| z#!nolD)PF_`8jZS+Lllx8)>niwy+L^d03K>fGwbSU^W!bhI`=2$+ngZ!|h~yK^7gc zKLSpG`XdLf>_V~7YjzvJD@qK+xEGaOwjeUFjq8EW7^-jOhcF+{bDsc~WlKxDS6nbG z^YJhI{KwZe*MglJ^=ODIuHpcWE)*Z$C%mPj{@#1(82DHNAeO%b1#tACw z)Z($AOBlv({ea;}3{%I>T5%_ddky$4QtXVzz7F0(Qs_GNDWGJHuJm7&juYrt&+4HnK43-sU7PaJwVKWy!Dw>`lA1xBcW(-~H8J{cJkuRijZg zEsQCgr3v(1obVTi+;oYRVh)J|`RE_MI0lm8kdv6v5wBlo7@ofPQBdHoKf1Zg1a3#; zhEO&Z4@AlD5DNQXaH@xGKMu)@%eaV$U*3uLW}3YhEZiIMvq3X=E{NERHS z4o_aKm%!pFsgDO<100j9UH^VKw9p!7H?GC9{@}V8#mB*qG&BEk^;#qwT3o#xWg5P6 zo~ zJm*hh(HsJ@XnS~JYjBN}(bJ+-3)n)t4;bH&Vh(*=B9OiSslbYbY%JhgUnUm`Xb0L@ ztw?pVg~N%IFol&*|86}A7JnEpaGj*-rEAw7+_`c8{SQ9z#FJ+>HZI*V zW8{32Pfrh4!XgdI%8V<~Ru#Y7k|cGVM{$xQDNdh5jJd-@T9Y#e_{-EolEuAx*$VxY zkt`nwu8^(O<|MF7OcC_If)}fXogI{AEix<@iAW-JX&c`b_=0qVP|bmp;=jf4CKpdy z>rOyK8l9l@>Lj^cX|E~eb(Up=!78>8R`nDM5TXKPVF`_H8BJ?P;cwNn2@{xU*6_|M zvTPX+@;rar{TC1Ndboe}UGIL^m%sScVDlcMov&|C2XPVgdUal4F$uHNRgh2XAjb_m zK`C?4APH%Je^igyYA;`k_25L!DQ0>o4wdg3H)EmAxQ`!sV%`fa1Y~XDh@r-#F3Msu ztx9YPMO2nYx?eA!g-|UP)V2)IS38nZ^F}w*B=gXAJ!VOc1CFqFm?HPE0;FtGN*lDe z^;+4xcE_QMx}#Su%=Pp^xyeecY0OFvLQ^@KbbDFW>-CNF8oSxxjK^a$`t@7QB_zh% z-!Cj(hI?7>xtCsf=%+uGZy&t);!D5u8=w2)*B-}+FO17DEOD&~)@f>(vvFX?8Kv<_ zj3i?ZCL7Su6`L*VSPseIb^xnKXoFMPGG@*qpfD2Y-t04|MrohFgd!Jt*iYfN}4EGD<=5_4U!U+^)PBYW9H z00PSB%~StLnv%HRChfdXYFHBHw}j{d|eN2(KjfXCA+wtj{}#8@4gdQmJCacLVQRtdhOEnfAZ`9^lu;k*2;Rey1x4T z=f3~Whu-t=QP zppx5^_HYQvMQA#kg9*ijH_^y8t6B)cdY z*(*7^6Z=}rbbX0K&hlCoxJyLhW5Y!p_YC0nFI=ygrJ3$7p=!NK4@A@j5G%!zVt0bHE2}{Dx2ovutY^kK$+KOHv>x3T1Tx5!Irk`1NL>P zqYsIU1}>&kbSCQE8k@G<$7@kfP&@T#MW34*)sT~5>nlF<@Vn4rQxE?bQ5S<`j;G3o zmrV`8Un3`Rl$ZuJqfO{I9?1*m@F6l{X)9GY58rH*_ zmM@edrlxR*yXtT-i*xfB0>8)6EABO$%`!zbmf8uI3xJwTGJMmY|_=fYu(c`R6l z3~SYe9ARDDJ=sZ5_n%KEU;NV7e&sX2w6VJScTfK?*w{?s8rwN4p5qFzBP-yzr8d71 z>&Cd-gp?0uleBcSx)8+X(ag{kuDR&1-a;R|;xczMi=Zj4Ga8+*e(x4-Y7q<94lVg--Vm2WfDmddB8v_cDmi)^Nbwy?T-=`~Mmn<;RxUKB z5mwWRAJ3ad1ATa5&|qgOf;xC2@^=FKp?1|Btb&1 z%@b2XbQMpd^A|vF@h)WYv?c;QL2nLer%O-ya8&!0ioEs z!ru`W@@3W+Tbi<7zmEZ`^uhs$kr5Bx8oz^s;gp|TGss3W$&k5Hb|`dA@U64ww=zAA6%Zuy8Z45U!noykjFky|Ff zDkz#J88Fo5g7(Hoa!0lMi+;}VsrodYo~tq55M?j;xfXyQD7f%ZUrEB{ zi{3?AD-;1n1NZ`bV$g{EBkn^B>wzywEsoUS#wnEQ0%~BreYbibL5e5mdzcZ@;U}|2 zmZU|@=rqGL5_C+KrKQd}Ng!78F%UH(none8Yru?AF^z;y(+GQP24X`d|^8N*0&xDw$AjkpqH91D8!fb zj0~4)5T|B59484r42&)tA4jb1=Rc6FNBH9d8nx>eSV|%UlF5I-q*aJ)*gVogQXj*T z6{j)LNF08_-7QYHfh>58&@^}rK#(g8`PAo{89`;7Lzsl@)fUSRu+EwhAjJBeXjF<4 zxZj9V6vIq{%!a(|j6+RHS)Tm%QV#q&7!ILVYg)Okv`Aarw5B*BjhRk3t2_fA->WQ05Jd3ex^&WUSOfR?lccW$Je z1407oTPew52o z=g*!!yLIZ+Lk~T4?b@|xzWe zj81_6;<>wk-Xo~4UcF()*XH-+li%Av*f-sd*zhJSO_NinHXnWTeK&9398V@0zNIX8 zYX>O5Wrc>uovPRk)fCpIq7q#l6$j7~E&*8D5=R5#kEhl3n$QVH#LJQ4pb-slj999; zP`wU&QJT~b)cNTminHk;$pWm_D-w!W=v)HL%MJq#ez;;_-r6OxsT@D;y)j1YgkI_YTYfm&?Lm- z9M3P~;$CRKkbC2d7J|u&I2fB1ITQ~CmWwb*5uTC--sJF&N;^sweAAA#_*xgCR!T~3!4S$wZs`Wc2K{Ygf?az?uts{hkctO=ore z+SO;CdFt-F@8LF8wvDX?|G3J{1rQG_%z76O|9fTd8~#veu%ro&7i$hIO@;~Dns|m} zfQ!g-C`zUZV~*Ht2YxITIVPa_=+GkK31aHa z9AHOgEL^p^x@tyK-}~P8-v9ph_4~ca6#ECPe0IS1ac*^R+F9M5wrdFhLoj?K^TC4v zG2`xE=+S8z&{UCa40%317!DKegfM}IUybm}0F}bXVIs*JaspZM#88G7k7TQ5ARav` zpGO6lQs7kd$)TH|fa_Db1fB7)fC8ZRk(HjhGPT~{-#4ULSy>IR+r_txfML>KSzq7T z-8p^wv@Ry&AIgLA0XjG2CZra#S%3*>L2#Zu#1UfnBYWC?ZCF%dcB<$*_#&Kzth@1# z4a=2bk{3^Lh1dq2gh4Ov_0zOx22v5$1EFhcZ1lhvej?0N$Ep;lXad=|gX`mKT1qxW zg3@3fba0?T78xln6+1;?{Z)V^$XJ}KYXGT0R==y4FJCqs|K3L)SsAPt3r(h8*~SZ7 z4Tk{sc};f4Rp1}yjvRkUvV@4&Cvff! zHI{9LpeqBxc53n39A)TAdfPGxPc}tz{%;-}(rydE6m&Qoa!X|@baVcoK8Y}$j)%ih z949hdp9_XKoB$8gB*t{A^xI84I{{WBw-uS8GL_(7#YAi{q`7S+yYc6E&Ul;zmMg<@ z8w|f6hL3w}i*P5Gi}k&+_>i>Cx5x)c&rYjm%390m>LJ(%^eu#jbEWk!%| ztWo6;Km733t5^5-_KYMG6GWC=OzjLg46TfP6yy5#=HAH$q^4ES>#a~eVc)?A3YM+T zBrUdO123VP0-U88l&QBcVbAna?blfEMJGcRQ1VMJPeCAKXgH!$KzR4)?Z9$nSco8; z9Fa33oM|YMpj=6Di8N#jn<&lSsEDJ=e)Q1TY87M~J1#*Rs)dG>%4f=ahk{BG5gTms zXyb*XG=RbaaLuN9k?z7SH4N#=gw9&=EnNQx={|04m)**=A&@x!YBb- zc#GV$R|!MdC~!`YNFnWn5VU1eQd%oO9}W+l116t;`7i&6-}4Qn*=1#cCbogsltz}3=_cFQY+L3dwwF!2w=q#vkfRaF6TDnEctO!i6rqD38psA zAugWvJqqS< z4U1}er6R-emD=b=gJ49ft1Rhac%V_-P9LSt*O_PXf8QHcbi+H)60NxTArV!xHEFLk9v11l{B+PI);ca84 zGNyf|QCKEniP>oV^eu;FMkj~-RvKTm2U`Y^f*`SwQXL{!EF>m>P&UjbRk#H##2QNy z%Ic`;>(X)n03ZNKL_t*a^suNXu-YCtr0Q9}?pebmYuXHLOAc5BwxPTJhd{$0hr$9h zG?|nfmI6ygF;0wZ7@-{w4@|7p)m0<9MsAHn-MV#aFc?tM!rh(SbLY>CU`X6M2a-43 z+1X7J-V3_`jG@!-5AM6~zJr5i|njcu1N8 zY@>Q@echxWK9qknc7r0!=~9w3^;O2_D$L&RPOr3BwhV)%+9P)+iR<91y`%gs}U8MIp@7ogX`C?-+S*pCXt<; zofxMD)5Y~w0(ZR^0tCi%BEe!BvgBrzlCP|+o;`c+{`(&={fjBvbUHO%4`&>T-QC^c zaKw!$O*$f-wDuNX-SQ)IYtg`KO{UqSxCvb#S7||x^EbE#Eo~Ei9*aC~= zPGGq*j73LI+8JvmuB$Ap!h_w>%^T5$3tJoOL0M!*DuTL*>I4(dRTAP(cpM?6z=5cA zv!ZD4cAeE-H`J$ZgVG?NEHi|2jYLl&E}f*i<1aJ@W7lqX9<#GdU^7-^Y*FEGQDp;l z@t!`i2S;$TEoOrw%n%&P2d;L`;X)dh7b0kezYh-fjSL%wbM5-|L4PnB4Nb3WJdGq9 zGcv^9taLJ&%5G$dWn{z1@`Vc*jCH-XwmuqNl^~twnGc6mc?M=GF$1nKjlFFQ1fe-f zK#KrI6pbIh*fye=47yAZc-9B~;V_+4zt^K71_72Jp?yspmnNSzSG$}gBB^=5g^hM% zSgs5U4Ppv#QdenNg~hZehBse(DU0LxKKg<2WSZCYq^_rd5n%Hx3!FkU7B$I@r6mz! z&bwN%qHs6~JsRdp{$iMuG#dw9Y2E|N23EHC!7k#9`e(tPhY%vp(skDfk&f} zQG;gOXwuPa+Q8Bszt#eW)JVlgC|RQ$ZN@J(WLPAp6+v6ozWN=6dl5-Cotn|-yoUfQ zQi6aUfxrk7&BiBr{R9~%c`z%V*mn%emEiz3FfM;g$~p~$9_}lerbeKr#T(y$=1gz! z@OvMCI&RUk03_f;^~@E#Ya6wqdE@gfKZ-0 z;V~PB>4`2vlfEqWG+~R2=5j)T+Ce9RIVHBRNRs_1%-t@f%F#19k!jQ|fYkWRsFSRg@?Vjn8DEOF3OleqeH4hx=>+4<#M z+=xfQl4ZEYq8nF`FsFqvWnnDkA`YU}IQ-UMeL0_we(Iw?b7gmLZP3eyWtpVYDCa2% z5wUrVj6ntnM{XlI$%ld}1LwLCNINYICmR6adawo&k&rZkG!CC#0QCg{3CY4@fQd+U z_u8DAwltrl$&r(kOlvaJcyv*6T0V(uFu^>vfqpx-xqYy{H08Wb}itRcynkzUNzhdSPYekR!~&fB|uy5;a-ri{l0S36%I z#lw4LWxUO2M0RA5W^`^fdPTbiw70q2E$;U*D~9g@z@MRNYB8q#w^C_|+B^|rLsi@@ zrYomc{@a1&%COY5#=>@mT}QSFT*ks(K7L<7|OM zw-2$XUERrmsJA$XL^O$^>jkjAvBg=Vgq#=$5h=Bn^%!cK)(W$}v9&Ok$Bli62xY9l zfP%-=xb)2&U4o(bu*5Dg+jy?b?iK%=SC2)yKuq^0Xi6844_pF%DyWelevQhNQyd{SE&$Msz*nt;DtlId0Ej@F-Ik5EyDh3> z2&Ld5aYM{hTVVR6mSl1gSgs6ffuyZ*Epwsw2$H{}G+GU_KmU_I*c%Q%`I%q&-g7U6 zxMqoK$8H$x(b^>`q=8CgN`9yV5*i&WrDs0a=J7cI$v0GV9>8YXXv@%Y0<+>|z@jn) zesj=zRL{lu05AW;K?)BuzrAnSW#uQMAF#5%&7;PLaso>SRtjP&wS`EFw&y z)$EIZ_#Xyw`tFZ@eEi0h(X@c-TwJA^?8q_mLa@w@M8xf0J2<}2Tn5tlNkQ!59}SM1 zD(__ck-&aj!A*hA1zXcOq7op~#wFidZflUV-URWYf*es7WP6C-(Oi-zp zFe}{52XQ%q@#3_=FR_TF=MgZyIWMfQI8h0aVag#OPLwa4PspPkF^)+?0_h`hyC57Q zHR||F5UWYDwc8P8W3P$7C?_{`2th~_C5=PYC(-d@F?FA&WYVO6B)k8D=q1tS>frZ&_uqV?4F1v2ePaJ$A3@qjYERis5Z_fns(d{`b?Z5DdlFy^ zN{s@S@v+mp)7)hp=73meg8jgCN}IfNrFTIF0XYn8Y>;JvjzwzkLePVaC zR|bW!8Io{~0gYt4H6&X@CG(tmJI^^VR}H#lL*Fx;r?F8IvKhK@@F>1b(e$>CA1i;y z2e}6L-S$2i2Xqy~3>?aJ9<(%{=WYNJv$&%XUoE^O#vVS zhgGbF)xb`t3chMz5UG>c49q9_vEZ$c;egg}sM5#Ns8VPA!KYY^aP|k;nf3mk|G~eT zgyn-D`0)O%TWIoTrbqr-F0$L2+2{KR^PyhMm$NV*8Kp&C5?qVj37VTZS`-16r+%bB zm~0-Hx-Z;HJ;%2ZCzq&WU{X?Kle)C#tS(TvOFZH6JpmEg2-j97(5SGA#QA|duA$43OY5k|a_wPPkR(IX=j#sz$ za-1K;(Zx=3oDH!JGlw=aU{x!qMH|m=txCRm*Wl{lr`@7pZr<|J}2-;k- zw!X(G?g?R0iXH#Mbd2JX(Xo-E6DlC$NaElPO<2h-HcDF$xN#P;h&d!9tQo&=ggsu7 z-oF*%qbdoelSI?%{UPu-sgX}?4RB^buBXH|kknKDG9B??Mfu28dUXP{plZk?B_qTe)@coPl}_Rk2Q1go?k-Y&AU&qR_6j#2htfrOg=GhYUZ!8S^rv~>mIUz4Cj{^o01#1x$ zJO|z|-kH{jVkfYac$Si0qWCW~cd>38mMp`V%0d+kf%80jVz3*ZdCz%zr_a?~Io;vvliG^($LXHN*T@+rV%T|FA}!8vBZ z;a9q5s+2`gz>{U>Symx$>Vnu{l|7P4Bg$!(^7{<<))671#)aK$RcavBZaBVCNivIo zGT6^|=z=XcpR>vwd{CBXF+rqJq}UEtrLbB}*9-)-9J8TLCz{x7z(ZLqqE;9^14r}h zwxqEU-^U5iHJF#iE|Zk?AvJW^!YzX46-_GAynCM7LouDeMwIGz@E5^yW!MUs(Xr|F z&09tcR##WC?Utn_g!*PtY9Y#!zl21j+%B!2y+7vAx}y9_stlx9jD%rJxeY^V@jr&BDJ zPF~xCP0<+2a2g0&V|*K6h7BfSExbcw&XF3`sK%7o*xX!SU*Fl;84UU?m1~?1lcHG6 zrRl$|yV0D-0aVp#*BNG@VFTR;=^`3DK?WoHzxSv4868F6)@nA4Wv&>ZA7#Ep* zM4R>}G%0sI2q%HXP++DPpe7i(Nu|hx7NuF6Wzpo>;zqxnB<`$&65h6%sTX^D#7Km; z-k=oWmY#UU*MNnKbnt*sA&@4=<_CG?zovsCslWZFL9&R#Oi^(~v#1i)#KQGESK&R+)46@m;{aVTb@9*zlxpL*6d+sS?$TnFVG*e_(?m9^0sF>~@?B3cu zbzV55-J}*z7oHh@2po>VP<9K8aHM1iEsA1wb?u>t-u1)hemI#-j4JC$Zv%&CN+837 zX$jhd?AOqY!j0l9s2Fxealc2MNf4t~tX*B6p-*h@4-pBMIauKUkP@EoQEkmBQT4a` zY82VlK%%%bV3}Hf+cbi2I|V{SbEyMTFLm4Wg$Q_?PfdVH+)ft-j$Cy5ZVgc+n(gp& z!r`RRD~4eDIopIaWr@nT>Y-Z2;Wq`F11NdBT0Rl<{dQrwGORVH1E*nM2y zSj{jxarU@T!oD z%s4_Z#ey1^9zAe80fYum^)>vZSmbQ^JWo+nMor#x_dVy%oVj&t+t}FC=|m2}i*%Uu zsU88SCM#})RqnDeV`)*Ukgo`r;6vF`pL3!|N{_VAg;|kuTqVE+ad1kWE&x8VIvQ}B zgBD1IEyPqUyHqRrHl8$r`uKWSP#iz|ikG22Cc7>IJJ<$O+p&2SW+#umllXk?4+{6#Zs3NELkM#6%I zbm$v!zp$*jYLM~ffdId01ZI7LWy^2~7UWEjZj;7a$Q#vnc6JOCZEUQAH9ac0h*_2l zdH$i7Ws}h`lsc2XbLw$hQ0q5};AHyk#2b^l&FIR}h(4X>MvTs%KX2Z7>l#jqt4(Na z1PBX-Ggf&M$w8NfT@b4D(0Fneyo@?csX~0Qfr=4A!+#<@jH9B1CmwzY1wni?%(_`7 zoPeegEf#A#sufX&wm^$H(^!E=v#>qC*y3+8r}fk*B5F303vHgwD`d~gdK)cJlSI%} zdR>rs*}vT^Bssb@f{Uo36f_U_O$0wZ6c2wY-Xd?kJOqHR^8(v)OOxSR$5n9RT9ztH z)0M%B(N{578jU}1TEqUHJkJG9XPa3OqnhyTgUPrm3Ym}<_VcGnq}KLL4LQ*+N_Evn zCHaYwY^)cd zjM~G9hX;LluaM1N3v9tEVG(UQ?C^S7aR^-X9zy(#Wl5p97j+hRavx_%zaNfV%+LVl4?TwRVOLJw;m z)H-AXXD(NUYh0mKma*JMoNsJyT)lSH*qm#txMf=^W?p##ij=y=h@}~1PA0wn0R5ZM zb&b6gQ1HO*0klSo!mb_h5b+d#?w~nWV>36DtL2!XgfhByspx|8G({z0xh@1){O_7n zG;jdfmL3$kSOr&%W{b}jQ5#uCstFG92qxze6ctbCN=qwgORJC>NFNDxU~}sFcVPj$ z9RiHe5ZzC}mkRaQ_Cs~7OTC?pDKcA`er)iTVpItPRrxOYx}e5w&2RN`2uNK^6HtG8 zQKXzrc5wRXw-D5Cz6O@O1GbDfEJ(8Alo?;aFRKDK(CHL#(^!7<)v9~6<6fE!+sx$i za}~c*y|J-z@#2LmSFRX=PLjCdfuWGbXOblH4KZxQWYvUci(*pO1-y`slVK%~o#kn9 zrbh4bs;jbnw+{8IVFtQ4c(g+0rHSN5@gh88dWx-!FGy=dkv{4}F2?vRV_QY^G~YB> zQ9^bB$O2z=ZCX(>mRId)n4WVu@xCo+^V%`M@*>?rl(`^nCj9M5fJAVus!uyzB))bU zi#|?z9WwtkZoX-LHgV@>&d$3fdvMG#zgg6F3V1X=ewYWIZ%Es+c_&EDf+wws_fl?9 z1+!Ykb_Cq147chw2c(!nE8NrjwtL@ZN_+Y8;8DZ+$d1 z{`Hf#Q4~v19D+DlBN?MMQGBRp%65maph4%bpaVb3Tz5z6{Dc}D3w#lOysCNZLJ`!G zUU`{G#5pJ>7TJnu7srLlD`;+m90vZ{marij?q}hi&f%vPdgT#tq4-!W^l%xKVy0Lv zK^&3i+B2bD3U?&KenN8rwT1>RdFS{zGlJUd-E+@9Cg|$btJCQu%X;N_oZ|Lv>;yEg zlkr3ZncJtsd^!y(Y_~RvE#f0q40*9`A{9dGMqu#H&DBgnlF?4incJK#1BETAJ=2;m zPQT45h!Z|IWvtr{QSlbZl<0!PRDA)q4uaMKE^9$`Uj4l89A+ve0PjW3-SPKded+CJ zJVo$dYf{z|(Pji|rAy>Yf*hJk7MYFE&90q@CZ)Xt0X}{~2>2_7(${us_Tj~|KrcZP zhoojhx>2MZWju^h;e?#FtZQ61o8?CoxFZ=p5};imUbxLN=a_gBhW&o_j(6N|dYv~e zUp7AckZizx3Q>~Ay?(D84pC*s%_n23M=-Jo8-Sr5mwgRdKBPn-jNYZ3HiFWHWNy$X zYtAL%f+@X$%S1{bZ{pj8S)x0HmZv=JOmh?JTn=m=t_+>PfPubGtAw7kJH~R*rQi&L zgJT6zkes&!b?sH(;m3oFX(PKR0(23}*4Kdsn1_hBMrh?i|4yvdD||E$FFjiZ7u-UZ zG(4QyT!<#^J10jULc5&7!Eqq>D|e9ybz8QBWy>%tg|bT>j5bbFe9W@fOYgt`9j3Hb zuDsFj_v08}6-aP3d9ty-kzh;aju{t3ag63*zIG}O#L8rn?UK*PJJxRh35Rv9{c>)G_oJlSIX1v@*5hY8PE&iG$$Knndi2K5IJ-rvA;KKrmX!%~Lcd9uo0T2zmG?Fp4h0S!vv( z3tv8UR)w$PsJ-5NSu!knNc{uyiRoab(`heDs}y@ZHaAbb=RNO9;`rM2>+9>8kzUTu z88MDlSJ#Z_?(FV_+?6B!lO`e-bq&TRTZ4e}IZ$!a&xuG6_JkoeNU!rYGrCvm4WYuk5Syl+D|c#B5p(R;xB1I1aY& z_7(l3Z4nHA1ap2OHq_&E^`@Vn173kirv|i3V7=%|bRu^UP6iDPThiDq91--7Wbb>; z0g3OZ?|C~)HThQZ{g8${S_~(>;&u#nEE)EB?lzznpiR&2EHd0*mG$b%+I!yf$afxl zY&aaPt*y%~OGQzdK4^UdUkTaS-jx9^tq15OQ*3Uq#tmGowp7a!LaQhJAGSBz-U|<6 zg!qZr%EVJcM5U0nqw_`2!bK$8S7?Z3cBz^-fyg&E7bU561oC(wWEXLSq@dn5S4mwi z4Vh^iSA1uFXuqO?K0)Bow6EO@bre7?+4!x2CBkNyPoKW7;~!Tbho*7u;Lt_xrW3a3 z$vOx#Px;G#GpWf_TyC$Wd-}w&+07XmSTrBG31!mw)AD2_d1|-@)tuk5yX732TOjyC zrDMtPZ3Ejvtq^RAjbZ4>f-%k>Mq!es55N227hZgEcXu~Q)^K8u6GAmUy|rd)GHri2 zuc@l?p#6I&9F9~&)FG0RxN3X%gb-4WYAo8R^aycPP+H?mpkp+ncQ-KwrehVds{_)~ zvU$QePs)KKsZr@sphdSMj;0pco=mzAmmPgri^lqbTU~8ftcQJ&WHs?Nu&HOjmBZCu znRo^?unC!i6KG1_>pZMh_)NME#Ea4-g}2B)(n6S<rW!Qk#c#Y;$`P5q1a?`hj>lIpxA3AA- z37^!qcc$u;r~0?+z^RmAIgZ)CGPv&<4@z+=9oJ zve^DKm2*g6>8of>N8a?&etM-m42|9a^Ywjd+X|}5n|~aAGf0wdr`FZLiaFD!w+C!5 zN=CsgrG4pC#bEgM|xL|QnUemjrI(6!S`yVj=2L}h{ zX@<>LuU;LEMkutDUCDG}VztP9Y|vQvONQZ7dr7i{GKL`5js+or6+OgT!XR=r_EtMI zKNp85e)_Y2+bn{FC2@V%zsQyh$UG`CBZ}K3(xIG}#w>KA0n_12_DU7`c_m_4<10}u ziUs>BnCh@-UOFTT!y2%OHw>|} znZ#s90<|Q~5b9WJaPeVwpqWJu7~*A(dMz@W=lKN^#Zq!6s8RT6Abi4ULiHLxJNSDT1O3O}HWMz=>xNC%`9(ox4U zgaew`&!T}v8_#auTH1J+g^VIx?d?cYvm+5zEP%^psw&4mt>hi|-51Bv^UuGKB-1lz z&Wy(s?!JWmZP^vSBL)_2K-M6D{ksTiD^V&MU6AII!ErP0q~L1m0(@zNpRY`sk=XQ# z$%d)LI%TfHB>~w*TZ#EJJ5vaa+v2-)ykfLC2GGu0eEeh^nCC}eJI)LC^8!XUZ$)UH z`qzuqL1>=#|D2%NG`pUSLnpw9WK$FHqktk_&>e!G(7a^3KGbM3d&EIeiM7>=Td6C% zD_JVl;p8`YJgnJ>^+*)+AIBbw0$Wd58j59H!j#T&^Ak66g#`}NUc7kG=;3SEt{Fu= z81yHTNnpD`2W`^eKQe9cIlw7Hltjh2@$)eSIL1Uug2Re*aoWJ49ApeHit9yDQUE~* zqx4g;2e`5LOs`>$1NX-2N#ki`jwI>U7DW)bkUmmlO+b*2HQ{^+v|FJ`T+1e86DG4& zvQ!ad`6J=&)D~+wi_PbuXbti#nh*YaH;iDTEKE%iRN#TeCYWhO4?3+e3r0eDb0icT zI4j`MrAjxV$e=CYs;?2@q)3b$^PV@D z++q?MF*ZfLaq~u&Wzq==r5zQ>s4YO6YRZC+uC_C1mjCBL(>fHB(2CG}$`ei2hvme! zelAN^}#i#n&^wvN}|ZN6Un**e#*YP z19RifLI+Knm2CqMmo0lB7N$h;dt{f0IP_H>a7$9~@DcRssa8ck8jbd~ef0r8l*!qk zIY1PfrZ^9wsDNw3E+9*a1(cgHJmm^B27!mYx`;NGSk7&57Rspd8B}6Vg<=;wP$bF;5+t*7Qqr$p+-yuCf=+_-LM=7~+b)vf*_=XuxHSVV zNDD*2tFD4bNpQ{LbULls>PBP0nl6eYP9(AsS6)Vg3y{~>HjI4l?(W&HGw^EX6@yMB zC|v78s0MvjM%`=Jdw8^1+?8COVoCr9m!jyGj>Cxa4C3ET*+%D6c)6PeFs@k>p_E2oJC^r<$e}BXBQk0NfVHl#mwE>36FLtdFg{VNF>m8DXYYAdx5TcH* zj3B78oNGkb+JdTJ_Yooh7hG)=OK8b@=Yu){%Htuqa4wyBeJu&3Xh6nMU6*<(fq#{G zDyd-9ZpPn)`(5ItmT<%yb8LKZ`L03L&8@rL?r=DC1o%o5ry0gsl`q$ce%04q>0gYwBG@dIxp9|-ab@nT_B9}&osUUxpy&(i?S$od~BC({*PX+{Kl(E+^IO7e>Qg4~$ zRGTkRZg2KC6Mh48HlG=D5N7gZEI6)94nbyxi;1B-a=gsdW21ms9BpC=4p^y9*%OsE3GIwN-`8M5Rn(h ziT8ZSD1D<=@Y~Eyyp4@JNA@;DzZgze&84g0c-an90FEF=iB$WF4bJxxmHTykjwDp&$t zZy@tf;!BnwE|_4jHg;IvjAdr1m->|{%%+ujxg0=1ArLi)=K!$xqJ($^)ixBAxYd#g zRnU#4ePk?Pn2tCqrMS<|ct0z{EwmTax&i(BDo9z0~h{2&9xEx1GpRS`zS35?JXB2r~z zL$VfNAf8pGFu7piOIs-cGMaQQm@)HA)74F*cB}=oZ(ZVgZc&7PA*MY|`$0?4+4#!% zj6&pmK!eWJk9wX7tecOhs8FCWiAoO;OIS-#h1y1Vu#nHVa0H%HC?3q>*LDE-EW4iO zOYzj6J|^HTH$Sj-feoYaeESv}qOSEt6Cj}_V$&~FE#UiN8IyGu)SA?X>$Oc_Kqx1khfbAO8A(0G0A!- zhJZm2`e;mMn4D)CIYs<>;yj~a%%Tfq_5AZ#Ajtb9_P|BX1!8-=qzG}jsF(L9!^-3*&Z$HE{C?*8|sfKYmUak+R=N7W4c?3kEI zW@7@w-cG9rHi9ve&c8jk0MS@fg7~Pmy`B0&L0n`_@rC4Cbt>aTRqXE)Fu~K6(!Nz7 z!&goC$*TmNuIg}kh#NF+@;seQr_=dtHh=c{s}9o}49=%hYK;f-K_EO%oTE+cVZ&;O z_14SgEJWjLDGCA(Hiyr(~*Esmk>>$ z0W2u;pFl=p8=o~&M^IQ&;=8c~8#z*|UB#GILEPXmWW^CnWHB+q^;r{@uD*yfma;@i zK3JuH&`|AJdJhKJ9m{BV4PrQhgx}(Wdb9Z)XNRd|8~ovdoE zy4TIlCuEyRt;DWiu{`Jzkh+%raH;<5n$L;;6lrI$<<24 zL`G9w*sD#fdF_}?R8F1^5D&dE)mjK`zS^MHLcC%xL98AT`Zhi)mBbMW!`ADI_>t(&d%0wYkOz3e|WIJx95!Z z^5x6p@pyN4e6YXY@AtdCUaQlcPEL=HkHem`eo$8aLJzNSv945YIEK)0aXW_mni8l2 z8K=;?@(X~|$z<}>Q&06e-FLj>9l!pGPn@4mdfhIj!@lwwpAy_dO8A@b6ZUy$U@DEX zqbZA0S?oE53=}9T^+hf*%3cQal`#vPBak>u@n%_|)>W?)h&Y(hiU;oB%Q!vv3|T@f z&RB#XHCV<8Im87io`+*7D#1O#sx~a)WxX+y;UbMEsPfbZh^KTRv`-Tmk-tmIiIvYoz%Si(ldm(bUCcFG81mPD;CL&;wG z3@7JYFs?$;z6DrO@13d(W-8q3q66Fs&01JlikHn|3>T*HbvZp*F=Nh3nJhJS*HY^` zmJ#GxEzTySs4_zDSD?XMr}9BNfTqC?snZI*ey`K(_Iv%oU~uKimBUMiyF1RC?(XjF zY;SG5)WKlrpn*?(+O8~akv5uc<#>8_dYf(*M<)1*yp2ZF*{w_609ePtbagFiv^QEy zi<7WYEwx}A4XMFsMZ?MhGFoq#KP+j|s=OPx+yvwKtJ5EU>sxQ$yz!aOeCEoP%Unjf z9>rc9-K<=2hA){cc?3j*W0t^l6T8c#qO;1jR;UZAMs((_2j2^1RId)e88BBOZf&qF z)^s{Yg%BzAe*iAs>ywfw55{n7^3d+#as2|Mx}_A=j@yt?GJn-Rie(EB6nYd5k;hgF zSSkH(A?}3&)Z1``VYH-$HL!wx2?FQ5pfJFxiEOAvuQnSS3!D$6Va@S6H<{>!E(bL9 zf6kr;gU#XAmb=nG9qb$(T|PK~cXig=C$!rgnSiRo=_kDNjhi6MQk+dD=?gFX zt)Kjf>o=}{?sK1W6O1JXyzss+GUU+(R5#^J6Ot|Yy4_F{&DKrIT0j+PSl~llAr33- zG{xO5JupQJ!uN|pt|Fk0xrW(v%R?R`CkaNWR?OTbLl&j0cKKeC$cnlsC@pk-f<%ht zDlfk;hC88I3$dIMuH^G#L zK0ZCoj*lS&wH)S(8FjPO#Aa2Cf+hOmG@Nd#FfqN5p;SfB%!RG z-fn%Wg$p(u!U~i?b}Pe!oAnJ8oUqJzVXIpS0HJQ(zTN4z-~BT`bA0>w(f|JF;o+gn z+--Lftx4RBIzjVi4d+)R74C!j9u4xCgvQ*$`P!0Q3OtO~Nr)HV;vW_=BEXw?(@vw6 z6q%SQia~C@o*)iFR~U*X?^)5pF62WdCW6PNwz@8q7x!SOI4Lb^r9?B;vOTYcs)TnA zYWXsjz>ZRju$k(hAiSaxp;BeSGQ~JcXormJMk!uGH|SjJ`P6yTnNvyE={oJ?QakOA z^PkQvN2Af@%a;e6181(6E?sgSb+EbVX1{KF41-b^aOsOx931w-8UE?nnL5U76(;Ha zGK)Z4@dpwgK~VN(7HM{D$-d%57@ZcqqCw+{M+BaQ&lZSXzzr>Er>JR?$m=Sad0Pz_ z*{zaf&r8mtL)hURXJD3d*CehjM#HV+Op}wB~UlT;n=|nQ8GDQ81La}3>cuB=%!p9 zSoP^l@R9%)4Jq9iJDp86mkuJa4vlHLY@W@>Cuu)z_MD;4lWx-3YIpb2&hD%^nm0yS zvz;~eTb-?T`zz_$e`sC*gWl=1)!49EcBD)k>GIgBa5*XORF9!1NX8=1oB&sUiE%3m z^{7fgtDz+40|XgZ8A~YP5v4ONA!EUyPMVwWrsIN|Gv%GtCEm4$&CMkqPHlAYQN50H zo-o(p&`szx`*ky6XS)vXpuf3^7d&yX>Ac7ic8)itW_W_tR(z?qG~5YEVTF)`SnB}% z5Q3Be6Q$sX{!xDx%_*X=s3JVFvsF?St3nt6f;M4f9P8PY+Uqec9+^Zo9IgQp?~s$C z4JVwsEWMpCY0t}OgIZU0gD03R_}Mf!G=AgBCqMk551$<09*wp(By@w$*G3`yst6VZ z)QlSstY!fgX|{2`_@Nu!zn1Kc8|~e!IZhk>S#tH-@%h5JyX7EBclURl8*jqlr~N_a zORd@aT2I{QW$m;H)lu`sI=0$Q3gL26K8cO-f!j(x1?ts&0w(5NOAvuN>&1YToQoE# zFzV<9lM2Z`U7}m5o}Qj0wgUoaVz;)_-|Rd8iJ9(jFdmODU%ujOwb$!9H#^!Mb$eY$ zqqj9Fqvq1Ql|k2R&)8wUE&qvrpxH_c$s+@%WM)WSRHfU0D2ZD=;RUNq4ShJ@P_=FWzv^B=yo`FERF z9&Y!KZ%wkvVt#UVtl*-_noon2d1{U+7Rk1A9fzVLB5%x_snTrp}ih9vr%rDkj6D(Qt@ms+;=awo9v( za7<6I!kTjzc?us(FJh+YJt*$dAYiQ`-+(qlx(oxYt5i`^V9V}lf->V`0skzDI9Y&J z3|_1?uC^p3Cgv9X(R-ttLveyAoY%pwm4#J+!h0<RQhHQ5Y<7hr5^mdWA1{A+ zWBlso=Gpbr)=73Yom_9WzxSX0{p;7Cx%RuCX*JsqzyI&N=9NcZ{m1|GQe)RG%-xFF zDSmVAl)nKD(=^K0pj?*~TSbsBq7+qpaI!!Rg~?+Eb==gltfWOL9n!==Mf%kuu`IKE zq#=fC(RTn)Gn9c{9BnrhFpBuTkPgTbV%vkB(8Wd2peDGr=xA$%jU_Hk_xJam#cpqH zZw@xy!nNz~A@5PjK!gQz-qshV2{Sa;hUa43`BCAdcVJLk_hDJ}82ESV}cKRA5Q{i3^@Zc#eKZtrk+XJ?F+>c|=DcyH`X*O_r^ zqva;R4L&!XU=54|Pgrv~bH!LcS@P8gR9H%qasYr{m@RnY5``b|y;3t^M2gF4QPhJPp^!!w?BjfI9^Y2I z+7HJ~6@##Ou3SRb)H7~I;vgYjT8Jme{9|NpPtPxAn(P+F2;~Y8vIvVW(y%s|0 zjNBX;KGvl>o6!W=^|f=PH1@gE0OwlIk8xH}eF@#ClXSFr<)9y;f~uLni#G^to>EZm z$U!Ne?F@cc`H^E66vNWA%2Ud^a| z)aVhT9eAlik`D!(!NoRwA*dPxdEEwY+?o6JHg8c>3a^8A1Xr(J`?0tD*yMcj+rRbO zmktlz+GhbX<|})!OPR&mRSR5EV1QiWz}IwRZ}-~St;zN0lGna6`NOXym&VT|x6{sG z_QG+44>lqTM+OBhICS}PKp3hLbz89kLMnB}rYDs?4PfNbKt^)*+^@A;3EH8|xO1E0 zq&{qQunCtsaMRu0-GjqJH(hd*;o;U+e-kGS`~6Ms=|WY(!S;T$8SWq~H3+LT)WYc* zUMR!urCeo|2=Ew-Q@3dbwBkIK-5u7hJ_{}}E73#*=yUD#`e?wL2QDV%d}2!aA8Jli zx|wcFhMzKr5LO3R(flcJvuH{)E@W8)xeXg%T3Vj&M-D+i#v%r>L@67VD-PT;zL}mZ zviTGvu?wD`Pp@9R{+74=__gP+fA+Kg`S9?d)!N8#f)M*U;niEAnq|Rg<{+YS&XVQn z3s?W#U--Vqzxc7Q|IK&b9yVV446hur7T%;5Mu9U{N zv06KlHXxhhuU=!4IpL7!#1JQh{S2PJ@pnB)&(p zXrnUWZ7PT4{kTjw=$*yRX7iJi(|5k}oleyree??l2m5RQX9fBwsH6c`%7D@WLnMd- z{hW2v{?$MIYVv1SzyEzd{XahZp?1>$U#~iP%?BR&#^3uVTgSJWjgcnvWWhzAwRXg< zQ1w#cy+VfLcom%18rC>i_7*y_4pOi3UP@(An=f^`KEu~DxTyqihz*{nhZT0aZoa!U z91ezCdwb4K$8It_qF)^jhix|n!gf-NN0jW0k}J{im`w0gjo**u>d)L@YDaX;ElMK_ zwNNz7D*YC58IaApRxK|}d<3b#XIjJ=Fd_`D2^}mK*rvdIuX;7}000uzNklE>)S z9~BRVxP^iUa+l=UST@>t@gFA2&9reT>;CC`fAQ77{BwWx-~Mj$^{10=@9IDJk_Q;X%2jFuw`!VOL-`}E^ynTeXPrEtx#_`F? zU@&~oBR~6#AN_^ruU^Glg|d0dSq%lio|bJ|R;n|_^e}UJmd%%=w4dBO`EPIk>D`CE zx4$*G_Lav6$z+_2ZqIK`Had-s-p<@jxif5p&a$+#vBbRBe`N4QgNg;)h0YBI3)8U< zMSV4EUGT9jYzNJ7&QwjxBfw4PHpjc;@!r@?e0O$XvN-uL8sS{wa0n94T3uV|&MFVR zI@dZoIXw;PlyDMDQBt=6X76Bj*J`(k3*rXt=l|R)?3!}lIMI>AJdb%)*dZyEiZDv{ z0gn%AM)NVwLIRILf@OhTl4Ze|2};Z(YRr^o?9sWii79O@}Bp6-~%7HapSsEOuVeeZ;Oa?rD4$;StFfKv&r=KaWZcw zy_2M|-5NK}=BF=wbNH$&%%ww;Pv18?8)C-0QDkO-b&V4Iaq6Qcz2a`#^h!WnyhAe@QY8BQxMQ%UEpTFJc=|J z44kn#M>^Ub9ULCu{wgiD*Xy_1tp?6Bpa}5ciDEoPM^f*Ko6e3hh^v2bc-Ipkos0V855 zO`I{1x%o3R#ZBZWe#@EhY=%`A4z4EGZ`^qJm9PBS_rCXoAN;`0n>V+&x3q4Ipj77+ z&u%WW^ZBy(rZ??w^=@u14_@;ZlUvi~AieeU)zO=NVE**;$;nwd+@F8>kCXkKt-YP( zYO-wN#%j876KTw-F*cY5#{ICeeObFKxCGQL9PUfK-%%^@AsD_447X=+2!c<*u|bw@ z3F=IBu!;9P=`wma6#LMW?gb@8gy%4za>{XMDiZO@J48Jkt~?sF$BC z+YOc!08AKLg3bw4QyRH(2v?&g2Zig&{5%VsOb$0p^Z7B43G}I)bC80&JIccRMnhDd z5yKmX{*qJ(NVTe2qOhXsnLNpZFP>r1pehj)fqETKNmU-HeF5(qtAq!nf!+N=y$uW0 zG*ZSOv@lfz7BXQsE1+=5A`VOVA9-T*WGiXSQoQ-jQ90#vX8eXXzTsW(e)q5b>c^ap z_Ih0uBwhe(#>_S$vPCPIPiDi{fB*USJ@oXA>*K%laBJu2_=|tCc=Rzm`;fMt{nF!` zz5cw_PoH~k@yyk4{Ly1y9!*c%crZ;39=AI<=AAvF*-;A}GDroO#iZyV2)58N76^WLMQqupH>#+Fjg`P^QQs}B{5!OqQ~UD(YRj!%vq zKn9ysCi0l>$BhCBZ@`#@QVTzi1#o&CCcrt6oj|cD=+h%wMlD&hQ%}Hdh%r%i72&z~ zXaPY}xADj4ii_qS0{|O8l;p!Py%3(flmvY0hieGps}R#vWeAW`W7pVS80jL&>W~*g zP3dbuhf6O8DRk-w8`PB`>lKYyX(g;FcSh6Aym=$Kk+?bXhBH_`Z=7OL;o1edpTHeA zo_Y4!zw#qLa{KnJU;D(bH8(bzDL!l}R@_%s>FSB>tht!?o73l?X+QkX_BUT>Uj6cO zkNw`Dkv30mHyg?Gk3Hr_mt(ge?6uF6#go_OpC3(rD}DN`-)$~az1oq(fsN&1cxdR2 zNB&e9`wlNUT1HQI%y3!>w^doSw9{$hEiHH+b<_D&o%?kT_VD0fo3r2HVBnUT-EOzv z>+$tzbC{c3Yw*a^$?1u6v{0;EjjWZ?2)Gri$D+OB`=u0Tv(^#BkcDf!olDK8GYrb-{cz4Fs#*-Cf@mm|JFx(e-s<>W?Ldr6h?3f;)Cg}K zo%(jD2x8##RwX6mH7tNCSH=Qrkp}r3qz2YyEP*{g9O4QGjEpBnhHEg(+CYc~W6t!F z0EhGloejX`a=Fl30rl=Nr*`ArolpJ7Z~V*O{LM>;hYi@fn?24O@Go@i8u+nWGyT!C zoxh!48Jwq+AW$oqyJ7Cu2 z5<~kyS*Z0AZxNDxVt1X>;nX|THcip_&r6pM-3<6(|G=%@x14v~-EnKzcDI8GaJ#Kr zHO@E7?9m{o1-O}NdHqrV#>RRTHit4iYo135meYGbs86(%4b}n@Q$|u1IaprHFB)dn zNFi5ms&B-+mZG?3d^HN^>v<`wN{o?aq9ImFHMt{#$}m(1af218ie=%Fr+M%+EV%+u zf(Hlj(x3w6P8ZY)kcMaL1q#R45C|qJh{*&Z5M{t9kkU~=n-}n9__xH73)u-Q8RpD$ zc}pB3aJ}W6*QY-9so(im|JpgVjn;-U1}^e~Dj={(mhH*%4YRH9nYM4urq9jKZnT%z zy32VpJzGxC7E`zMUAFMdSSM*LyKe5;>@3n#=iH-lqSdsd0H5$Rn6hHFc1+3&%tWE`992*wImK>*MMH>#* z8^CgLe@Bm_frv(pm*(&pV|xaaPro?N*zfiK(J%ed=Rg1Zd;9y7$wc3tNzg9&%E&2D zGMzUjGy1W{w2ij{v=?|X))HGSBEW-kT=EVX7Rm z7+~h9U0_R!xiSg}mcX5hLb=tjIx%2Wa-uHq@&_o1l#ewYsxntGF{CTj!|S0^;>EdE za$Ym@E!s#7E4fLO`b^2)*|1Jxgm^Hva9tHmD)w9=Tlg1i(LVi4wy>>=lM1BE7JOB& zwka2Q=!|ZR=Y*!`=dG4Av*t%W@{#}f5KxP(9` zQIQqll}TBoTdZVADMo}L#L+0R0#Ts|MtiGkpvb@87}mg=BH}M(m0(S-Re%ij{8GD* zR)xAkumu|YXHv041=48CM97lSmN0RWqVmkBC)_Z@9P~yA_ zLXFA^#p=X>CiaW-JBM!z8%{S-1PSI|jp%cnA%K$7~t5(@fg)Z*vQ zEyZ?g1Q7(9xFcg!hPymoU~!8Mt`xZyVyg;O1wu52n5;CxD~VkNBPh1#1>lzk_h6HE z3u~mRoyy)bsL$#keULVs7CmsBB)iQBF@gEpC>j%_c)(4VI9EZm6sN&p=+bF8*wOY^ z|Kxvv@e5zL@`_jJMJ?N-?M}M`0rd7f?vOdBIypOAa+7Siz?0>4u;6K{Mst`20Gj_Z zP4I~!B-EHlc%PR6go`^XAqzlBrQ$3a{AUR-&u=4Jd0Pk=lF3mcN=X%fIGLdm^o|Q8 zI_4ketpj5cBDLWXH26%sw(JVsF99eCppfRl%e{(!$X$xY_m*BfSS3~Rz-_p;aR7I3 z!*RUohhvEGu`vf^HJX-2fed9JPGqGRA#kEIcHoZ5)@VCKr<2KvTk-P4P8fqkLmOW5 zvlV$u|>qig}ZdyeS}K3&l2!UTro+ zQOa1rm^CX{=<&krHyT5kHy^61%&P!NqokadD#E8m5fJH!;J5pb`UdJ;zkZ% z?Y+nkb!COec-KJ?^`dUb@3M#%`noQQ5(^||NJSflGb6vep=gS6PDCnhS@?vWIgVO5 zp`;;N*G+;rY1MkT1O;x9D^Azux)WG4TVCzBR>FM>Q-TL?!?l2Fu4sh1Lds_?B}z%4 zRo!2eJFU#U10YF_P@i6bB5&>u0p*st&ed|B3`0+FoRAt1-YA_?!ORNz9|WjvWX!b< zRRt2{G@_R(guFH8D&}=Y8-ux~6;x}QAT*Jht5kYKm`7K2YZ*$}>+EioR2M=D?PC5a z)0Y6)+}!ajj9D6jW5Smh3eOKGzN^;Av?eKudfK$GXwp)*wYS-*1Tf8{I21)$@FD^1 z7cX$*r!096P@c5~)kD4J`(}ym6j=pVjF$j9)Z9YASwT6jlI2tzwu&hMA22KqnnjzA zgn(B8H<+F z_kE4a4ahaw%ke5`E2*doQ8FOq{uI`Yo(e}9H=1If>Zb9s_yWs|K!TUC1nVc`4ZZi6 zO7#L*LLCM3qp*fTWrha04J) zhCNc0b0xPs12#p@;jY{Nz6B`5gXQ+JLtSm^{qX&RjvvHeeIwPW6D8UlH83hXG=yMA zjR;w5bv=ia4<$b!xCg~rljR-^l7?1Ug^<=8VR2kx^Q_{Q*{(r->ixj#edX`Kiv{(5 zxCHkwnyv(xa6LC$1+Rdt?!|E>!R%h}3XqyB%iY$fTJ(jm(ty@*!>-ASq}8xyO@0sD z?jK&3LRZ4KlrZ-Xb+vi1{O|*Z+D+P$x-`}e0sxtD!p)hxw&U->9l*Di0e^eI9og`z zj=480SjBDSJ+ajg7Ik+HD!t~o)_S-*YQQ~AR#W)l^83B-888v3Y#9h&MFzOrhI#Pt zvJ3G48{Cl%f6D^3G``8PQZ=ive(HYza1S;tc(7LVV7Yw openhab-runtime-base + openhab-transport-mdns mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java index a8152e40585e0..344e48bfc70aa 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java @@ -19,12 +19,12 @@ * The {@link HueSyncBindingConstants} class defines common constants, which are * used across the whole binding. * - * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x - * infrastructure + * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault public class HueSyncBindingConstants { + /** Minimal API Version required. Only apiLevel >= 7 is supported. */ + public static final Integer MINIMAL_API_VERSION = 7; public static final String BINDING_ID = "huesync"; public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "box"); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java index a9c687ee8e256..59149e57a2074 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java @@ -35,8 +35,6 @@ public class HueSyncDeviceInfo { /** * Increased between firmware versions when api changes. Only apiLevel >= 7 is * supported. - * - * TODO: Check minimal API level ... */ public int apiLevel; /** diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java similarity index 87% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java index 5befa522f34f6..f4f0550fce66a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.huesync.internal; +package org.openhab.binding.huesync.internal.config; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -21,6 +21,7 @@ */ @NonNullByDefault public class HueSyncConfiguration { + public String apiAccessToken = ""; public String host = ""; public Integer port = 443; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 2bff350e014dd..2af0d5cbebd03 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -18,8 +18,8 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; -import org.openhab.binding.huesync.internal.HueSyncConfiguration; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java index 737f370709df2..fab3d521c6b22 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -26,7 +26,6 @@ * Sync Box * * @author Patrik Gfeller - Initial Contribution - * Based on the hue binding implementation by Christoph Weitkamp */ @NonNullByDefault public class HueSyncTrustManagerProvider implements TlsTrustManagerProvider { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 5c68df60c6ec8..e3c8badb8fc03 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -43,10 +43,7 @@ * The {@link HueSyncDiscoveryParticipant} is responsible for discovering * the remote huesync.boxes using mDNS discovery service. * - * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x - * infrastructure - * + * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") @@ -92,7 +89,7 @@ public String getServiceType() { ThingUID uid = getThingUID(service); if (Objects.nonNull(uid)) { try { - logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(), + logger.trace("HDMI Sync Box {} discovered at {}:{}", service.getName(), service.getHostAddresses()[0], service.getPort()); Map properties = new HashMap<>(); @@ -117,7 +114,8 @@ public String getServiceType() { String[] addressses = service.getHostAddresses(); if (addressses.length == 0 || id == null || id.isBlank()) { - logger.info("Incomplete mDNS device discovery information - {} ignored.", id == null ? "[name: null]" : id); + logger.debug("Incomplete mDNS device discovery information - {} ignored.", + id == null ? "[name: null]" : id); return null; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 8b0adceb5f103..ad42b987cd05f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.HueSyncBindingConstants; -import org.openhab.binding.huesync.internal.HueSyncHandler; +import org.openhab.binding.huesync.internal.handler.HueSyncHandler; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -34,9 +34,7 @@ * thing * handlers. * - * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x - * infrastructure + * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault @Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java similarity index 79% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 701184bdffcb4..0a073e6547429 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.huesync.internal; +package org.openhab.binding.huesync.internal.handler; import java.io.IOException; import java.security.cert.CertificateException; @@ -19,9 +19,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.ssl.SslContextFactory.Client; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; import org.openhab.core.io.net.http.HttpClientFactory; @@ -29,6 +28,7 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; import org.osgi.framework.FrameworkUtil; @@ -40,17 +40,21 @@ * The {@link HueSyncHandler} is responsible for handling commands, which are * sent to one of the channels. * - * @author Marco Kawon - Initial contribution - * @author Patrik Gfeller - Integration into official repository, update to 4.x - * infrastructure + * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { + /** the key for the api version property */ + static final String PROPERTY_API_VERSION = "apiVersion"; + /** the ky for the network state property */ + static final String PROPERTY_NETWORK_STATE = "networkState"; + @SuppressWarnings("null") private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class); private HueSyncConfiguration config; + private @Nullable HueSyncConnection connection; private @Nullable HueSyncDeviceInfo deviceInfo; private @Nullable ServiceRegistration serviceRegistration; @@ -63,18 +67,12 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) this.config = getConfigAs(HueSyncConfiguration.class); - @SuppressWarnings("null") HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.config.host, this.config.port); this.serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext() .registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, null); - SslContextFactory context = new Client.Client(); - - this.logger.debug("SSL context - Alias: {}", context.getCertAlias()); - this.logger.debug("Context: ", context.dumpSelf()); - this.httpClient = httpClientFactory.getCommonHttpClient(); } @@ -98,11 +96,20 @@ public void initialize() { properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceInfo.firmwareVersion); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, this.deviceInfo.firmwareVersion); + + properties.put(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", this.deviceInfo.apiLevel)); + properties.put(HueSyncHandler.PROPERTY_NETWORK_STATE, this.deviceInfo.wifiState); updateProperties(properties); - updateStatus(ThingStatus.ONLINE); + // TODO: Check API Version ... + + if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING); + } else { + updateStatus(ThingStatus.ONLINE); + } } catch (Exception e) { this.logger.error("Unable to initialize handler for {}({}): {}", this.thing.getLabel(), this.thing.getUID(), e); @@ -117,7 +124,6 @@ public void dispose() { super.dispose(); try { - if (this.serviceRegistration != null) { this.serviceRegistration.unregister(); this.serviceRegistration = null; diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 22acd31ab97bb..4db473040f149 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -10,6 +10,8 @@ thing-type.huesync.box.description = Sync your smart lights to your on-screen TV # thing types config +thing-type.config.huesync.box.apiAccessToken.label = API Access Token +thing-type.config.huesync.box.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. thing-type.config.huesync.box.group.connection.label = Connection Settings thing-type.config.huesync.box.host.label = Network Address thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 40c8e19d28508..f47bf839dd946 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -33,6 +33,15 @@ 443 true + + password + + To enable the binding to communicate with the device, a registration is required. Once the registration + process is completed, the acquired token will authorize the binding to interact with the device. After initial + discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 + seconds to grant the binding the required permissions. + false + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem b/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem deleted file mode 100644 index 789591c582c9c..0000000000000 --- a/bundles/org.openhab.binding.huesync/src/main/resources/hsb_cacert.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBwDCCAWagAwIBAgIBATAKBggqhkjOPQQDAjA2MQswCQYDVQQGEwJOTDEUMBIG -A1UECgwLUGhpbGlwcyBIdWUxETAPBgNVBAMMCHJvb3QtaHNiMCAXDTE3MDEwMTAw -MDAwMFoYDzk5OTkxMjMxMjM1OTU5WjA2MQswCQYDVQQGEwJOTDEUMBIGA1UECgwL -UGhpbGlwcyBIdWUxETAPBgNVBAMMCHJvb3QtaHNiMFkwEwYHKoZIzj0CAQYIKoZI -zj0DAQcDQgAEr9FgOxnsonsrnUZr3C4ggST7YCR9wISvDuwlNdZcAz4HiVCNmAAP -tAnAFDG0U19Rmc4MfRYBMO8GrOHrOkZ7sKNjMGEwHQYDVR0OBBYEFCK+VIWZqp++ -DHqmGLWEZHYdH9v7MB8GA1UdIwQYMBaAFCK+VIWZqp++DHqmGLWEZHYdH9v7MA8G -A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gAMEUC -IAFDI0Q4IOPxV7cY4wSVOJAn4y5AdZwrItJ1XuNpmCltAiEA5c6wcu6qmF596uyA -r7xLnr3/F5zJxrE3AyLD4t+5oKs= ------END CERTIFICATE----- From 4b66cda92c766e956e42d178aef336718f0f7b48 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 29 Feb 2024 18:09:33 +0100 Subject: [PATCH 013/128] =?UTF-8?q?=F0=9F=94=A7=20Prototype=20to=20use=20r?= =?UTF-8?q?esource=20strings=20for=20loggger,=20exceptions=20&=20UI=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- ...ngConstants.java => HueSyncConstants.java} | 4 +- .../HueSyncDiscoveryParticipant.java | 10 ++--- .../exceptions/HueSyncApiException.java | 24 ++++++++++ .../factory/HueSyncHandlerFactory.java | 6 +-- .../internal/handler/HueSyncHandler.java | 26 +++++++++-- .../internal/util/HueSyncLogLocalizer.java | 45 +++++++++++++++++++ .../resources/OH-INF/i18n/huesync.properties | 5 +++ 7 files changed, 106 insertions(+), 14 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{HueSyncBindingConstants.java => HueSyncConstants.java} (89%) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java similarity index 89% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 344e48bfc70aa..d60935dbd4c79 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncBindingConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -16,13 +16,13 @@ import org.openhab.core.thing.ThingTypeUID; /** - * The {@link HueSyncBindingConstants} class defines common constants, which are + * The {@link HueSyncConstants} class defines common constants, which are * used across the whole binding. * * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault -public class HueSyncBindingConstants { +public class HueSyncConstants { /** Minimal API Version required. Only apiLevel >= 7 is supported. */ public static final Integer MINIMAL_API_VERSION = 7; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index e3c8badb8fc03..8b86ede857c45 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.HueSyncBindingConstants; +import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -74,7 +74,7 @@ public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) @SuppressWarnings("null") @Override public Set getSupportedThingTypeUIDs() { - return Collections.singleton(HueSyncBindingConstants.THING_TYPE_UID); + return Collections.singleton(HueSyncConstants.THING_TYPE_UID); } @Override @@ -94,8 +94,8 @@ public String getServiceType() { Map properties = new HashMap<>(); - properties.put(HueSyncBindingConstants.PARAMETER_HOST, service.getHostAddresses()[0]); - properties.put(HueSyncBindingConstants.PARAMETER_PORT, service.getPort()); + properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]); + properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort()); DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(service.getName()) .withProperties(properties).build(); @@ -119,7 +119,7 @@ public String getServiceType() { return null; } - return new ThingUID(HueSyncBindingConstants.THING_TYPE_UID, id); + return new ThingUID(HueSyncConstants.THING_TYPE_UID, id); } @Activate diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java new file mode 100644 index 0000000000000..400abb094913c --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.exceptions; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +public class HueSyncApiException extends Exception { + + public HueSyncApiException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index ad42b987cd05f..364d42baef0cf 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -17,7 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.HueSyncBindingConstants; +import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.handler.HueSyncHandler; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; @@ -49,7 +49,7 @@ public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactor @SuppressWarnings("null") private static final Set SUPPORTED_THING_TYPES_UIDS = Collections - .singleton(HueSyncBindingConstants.THING_TYPE_UID); + .singleton(HueSyncConstants.THING_TYPE_UID); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -60,7 +60,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (HueSyncBindingConstants.THING_TYPE_UID.equals(thingTypeUID)) { + if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) { try { return new HueSyncHandler(thing, this.httpClientFactory); } catch (Exception e) { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 0a073e6547429..15fad793ab4b8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -23,6 +23,8 @@ import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; +import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; +import org.openhab.binding.huesync.internal.util.HueSyncLogLocalizer; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.openhab.core.thing.ChannelUID; @@ -101,24 +103,40 @@ public void initialize() { properties.put(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", this.deviceInfo.apiLevel)); properties.put(HueSyncHandler.PROPERTY_NETWORK_STATE, this.deviceInfo.wifiState); - updateProperties(properties); + this.updateProperties(properties); - // TODO: Check API Version ... + this.checkCompatibility(); if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING); } else { updateStatus(ThingStatus.ONLINE); } + } catch (HueSyncApiException e) { + // TODO: Refacture - simplify use of logger ... + this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), + this.thing.getLabel(), this.thing.getUID(), + HueSyncLogLocalizer.getResourceString(e.getMessage())); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { - this.logger.error("Unable to initialize handler for {}({}): {}", this.thing.getLabel(), - this.thing.getUID(), e); + this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), + this.thing.getLabel(), this.thing.getUID(), + HueSyncLogLocalizer.getResourceString(e.getMessage())); updateStatus(ThingStatus.OFFLINE); } }); } + private void checkCompatibility() throws HueSyncApiException { + throw new HueSyncApiException("@text/api.minimal-version"); + + // if (this.deviceInfo != null && this.deviceInfo.apiLevel < + // HueSyncBindingConstants.MINIMAL_API_VERSION) { + // throw new HueSyncApiException("@text/api.minimal-version"); + // } + } + @Override public void dispose() { super.dispose(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java new file mode 100644 index 0000000000000..6ad62f5b74acc --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.util; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +public class HueSyncLogLocalizer { + + private static final Locale locale = Locale.ENGLISH; + + private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLogLocalizer.class) + .getBundleContext(); + private static final @Nullable ServiceReference<@NonNull TranslationProvider> serviceReference = bundleContext != null + ? bundleContext.getServiceReference(TranslationProvider.class) + : null; + + // TODO: Resolve warning ... + private static final TranslationProvider translationProvider = serviceReference != null + ? bundleContext.getService(serviceReference) + : null; + + public static String getResourceString(String key) { + key = key.replace("@text/", ""); + + return translationProvider != null ? translationProvider.getText(bundleContext.getBundle(), key, key, locale) + : key; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 4db473040f149..d7272eb388fd9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -22,3 +22,8 @@ thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. thing-type.config.huesync.box.commonName.label = SSL Certificate Common Name thing-type.config.huesync.box.commonName.description = NThe Common Name (AKA CN) represents the server name protected by the SSL certificate. The certificate is valid only if the request hostname matches the certificate common name. + +# api exceptions + +api.minimal-version = Only devices with API level >= 7 are supported +logger.initialization-problem = Unable to initialize handler for {} ({}): {} From 2d3cfc9bf135bc24391880f5b3d7648017fad433 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 3 Mar 2024 00:10:07 +0100 Subject: [PATCH 014/128] =?UTF-8?q?=F0=9F=94=93=20Registration=20(acquire?= =?UTF-8?q?=20API=20token)=20-=20wip=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 5 ++ .../HueSyncRegistrationRequest.java | 24 ++++++ .../connection/HueSyncConnection.java | 57 +++++++++++--- .../internal/handler/HueSyncHandler.java | 77 +++++++++++++------ .../resources/OH-INF/i18n/huesync.properties | 12 +-- 5 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index d60935dbd4c79..b9fa00feef498 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -23,6 +23,8 @@ */ @NonNullByDefault public class HueSyncConstants { + public static final String APPLICATION_NAME = "openHAB"; + /** Minimal API Version required. Only apiLevel >= 7 is supported. */ public static final Integer MINIMAL_API_VERSION = 7; @@ -31,4 +33,7 @@ public class HueSyncConstants { public static final String PARAMETER_HOST = "host"; public static final String PARAMETER_PORT = "port"; + + public static final Integer REGISTRATION_INITIAL_DELAY = 3; + public static final Integer REGISTRATION_DELAY = 1; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java new file mode 100644 index 0000000000000..3bd660aeeabb5 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.registration; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +public class HueSyncRegistrationRequest { + /** User recognizable name of registered application */ + public String appName; + /** User recognizable name of application instance. */ + public String instanceName; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 2af0d5cbebd03..6d13ce51f265c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -13,13 +13,22 @@ package org.openhab.binding.huesync.internal.connection; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.MimeTypes; +import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -31,12 +40,23 @@ * @author Patrik Gfeller - Initial Contribution */ public class HueSyncConnection { - private static final String REQUEST_FORMAT = "https://%s:%s/%s"; - private static final String DEVICE_INFO_ENDPOINT = "api/v1/device"; + /** + * Request format: The Sync Box API can be accessed locally via HTTPS on root + * level (port 443, /api/v1), resource level /api/v1/ and in some + * cases subresource level /api/v1//. + */ + private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; + + private static final String API = "api/v1"; + private static final String RESOURCE = "device"; + private static final String REGISTRATIONS = "registrations"; private static final ObjectMapper ObjectMapper = new ObjectMapper(); + private final Logger logger = LoggerFactory.getLogger(HueSyncConnection.class); private @NonNull HttpClient httpClient; + + private HueSyncDeviceInfo deviceInfo; private HueSyncConfiguration config; public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfiguration config) { @@ -44,18 +64,37 @@ public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfigu this.config = config; } - public void start() throws Exception { - this.httpClient.start(); - } - @SuppressWarnings("null") public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionException, TimeoutException, JsonMappingException, JsonProcessingException { - String request = String.format(REQUEST_FORMAT, this.config.host, this.config.port, DEVICE_INFO_ENDPOINT); - ContentResponse response = this.httpClient.GET(request); + String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, RESOURCE); + + ContentResponse response = this.httpClient.GET(uri); String json = response.getContentAsString(); - return ObjectMapper.readValue(json, HueSyncDeviceInfo.class); + logger.trace("getDeviceInfo: {}", json); + + this.deviceInfo = ObjectMapper.readValue(json, HueSyncDeviceInfo.class); + + return this.deviceInfo; + } + + public void registerDevice() + throws JsonProcessingException, InterruptedException, TimeoutException, ExecutionException { + + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = this.deviceInfo.uniqueId; + + String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, REGISTRATIONS); + String json = ObjectMapper.writeValueAsString(dto); + + ContentResponse response = this.httpClient.newRequest(uri).method(HttpMethod.POST) + .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) + .content(new StringContentProvider(json)).timeout(500, TimeUnit.MILLISECONDS).send(); + + logger.trace("registerDevice", response); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 15fad793ab4b8..2a2693fc09365 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -15,10 +15,13 @@ import java.io.IOException; import java.security.cert.CertificateException; import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; @@ -33,6 +36,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; @@ -57,12 +61,16 @@ public class HueSyncHandler extends BaseThingHandler { private HueSyncConfiguration config; + private @Nullable BundleContext context; + private @Nullable ScheduledFuture registrationJob; + private @Nullable HueSyncConnection connection; private @Nullable HueSyncDeviceInfo deviceInfo; private @Nullable ServiceRegistration serviceRegistration; private HttpClient httpClient; + @SuppressWarnings("null") public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException, Exception { super(thing); @@ -72,8 +80,9 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.config.host, this.config.port); - this.serviceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext() - .registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, null); + this.context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + this.serviceRegistration = this.context.registerService(TlsTrustManagerProvider.class.getName(), + trustManagerProvider, null); this.httpClient = httpClientFactory.getCommonHttpClient(); } @@ -106,42 +115,66 @@ public void initialize() { this.updateProperties(properties); this.checkCompatibility(); + this.checkRegistration(); - if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING); - } else { - updateStatus(ThingStatus.ONLINE); + if (this.thing.getStatus() == ThingStatus.OFFLINE) { + this.startRegistrationJob(); } } catch (HueSyncApiException e) { - // TODO: Refacture - simplify use of logger ... - this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), - this.thing.getLabel(), this.thing.getUID(), - HueSyncLogLocalizer.getResourceString(e.getMessage())); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + this.logInitializationException(e); + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { - this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), - this.thing.getLabel(), this.thing.getUID(), - HueSyncLogLocalizer.getResourceString(e.getMessage())); - - updateStatus(ThingStatus.OFFLINE); + this.logInitializationException(e); + this.updateStatus(ThingStatus.OFFLINE); } }); } - private void checkCompatibility() throws HueSyncApiException { - throw new HueSyncApiException("@text/api.minimal-version"); + private void logInitializationException(Exception e) { + this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), + this.thing.getLabel(), this.thing.getUID(), HueSyncLogLocalizer.getResourceString(e.getMessage())); + } + + @SuppressWarnings("null") + private void startRegistrationJob() { + this.logger.info("Starting registration job for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.deviceInfo.uniqueId); + + this.registrationJob = scheduler.scheduleWithFixedDelay(() -> { + try { + this.connection.registerDevice(); + } catch (Exception e) { + // TODO: ... + } + }, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_DELAY, TimeUnit.SECONDS); + } + + private void checkRegistration() { + if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "@text/thing.config.huesync.box.registration"); + } else { + updateStatus(ThingStatus.ONLINE); + } + } - // if (this.deviceInfo != null && this.deviceInfo.apiLevel < - // HueSyncBindingConstants.MINIMAL_API_VERSION) { - // throw new HueSyncApiException("@text/api.minimal-version"); - // } + private void checkCompatibility() throws HueSyncApiException { + if (this.deviceInfo != null && this.deviceInfo.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { + throw new HueSyncApiException("@text/api.minimal-version"); + } } + @SuppressWarnings("null") @Override public void dispose() { super.dispose(); try { + if (this.registrationJob != null && !this.registrationJob.isDone()) { + this.registrationJob.cancel(true); + this.registrationJob = null; + } + if (this.serviceRegistration != null) { this.serviceRegistration.unregister(); this.serviceRegistration = null; diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index d7272eb388fd9..9c50908a5e26f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -18,12 +18,14 @@ thing-type.config.huesync.box.host.description = Network address of the HDMI Syn thing-type.config.huesync.box.port.label = Port thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. -# thing types config - -thing-type.config.huesync.box.commonName.label = SSL Certificate Common Name -thing-type.config.huesync.box.commonName.description = NThe Common Name (AKA CN) represents the server name protected by the SSL certificate. The certificate is valid only if the request hostname matches the certificate common name. - # api exceptions api.minimal-version = Only devices with API level >= 7 are supported + +# registration + +thing.config.huesync.box.registration = Device registration pending. Please press the HDMI Sync Box device button for 3 seconds. + +# logger (to keep text in sync with on-screen messages, log messages will always be in locale.english) + logger.initialization-problem = Unable to initialize handler for {} ({}): {} From 871e286fcf78e80430234b31719e5e2531a7f618 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 7 Mar 2024 18:14:04 +0100 Subject: [PATCH 015/128] =?UTF-8?q?=F0=9F=94=93=20Registration=20(acquire?= =?UTF-8?q?=20API=20token)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- .../dto/registration/HueSyncRegistration.java | 22 +++++++++ .../internal/config/HueSyncConfiguration.java | 1 + .../connection/HueSyncConnection.java | 19 +++++++- .../internal/handler/HueSyncHandler.java | 47 +++++++++++++++---- .../resources/OH-INF/i18n/huesync.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 8 ++++ 6 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java new file mode 100644 index 0000000000000..3882739a70ece --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2024-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.registration; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +public class HueSyncRegistration { + public String registrationId; + public String accessToken; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java index f4f0550fce66a..0470af5aca882 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java @@ -21,6 +21,7 @@ */ @NonNullByDefault public class HueSyncConfiguration { + public String registrationId = ""; public String apiAccessToken = ""; public String host = ""; public Integer port = 443; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 6d13ce51f265c..a5e9078c3e2f7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -17,14 +17,17 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.slf4j.Logger; @@ -80,7 +83,17 @@ public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionE return this.deviceInfo; } - public void registerDevice() + /** + * Try to register the application with the device. + * + * @return null || HueSyncRegistration + * + * @throws JsonProcessingException + * @throws InterruptedException + * @throws TimeoutException + * @throws ExecutionException + */ + public @Nullable HueSyncRegistration registerDevice() throws JsonProcessingException, InterruptedException, TimeoutException, ExecutionException { HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); @@ -95,6 +108,8 @@ public void registerDevice() .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) .content(new StringContentProvider(json)).timeout(500, TimeUnit.MILLISECONDS).send(); - logger.trace("registerDevice", response); + return (response.getStatus() == HttpStatus.OK_200) + ? ObjectMapper.readValue(response.getContentAsString(), HueSyncRegistration.class) + : null; } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 2a2693fc09365..f8ab0a4ebac7e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -23,11 +23,13 @@ import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; import org.openhab.binding.huesync.internal.util.HueSyncLogLocalizer; +import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.openhab.core.thing.ChannelUID; @@ -50,6 +52,9 @@ */ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { + /** the device registration id */ + static final String REGISTRATION_ID = "registrationId"; + static final String API_TOKEN = "apiAccessToken"; /** the key for the api version property */ static final String PROPERTY_API_VERSION = "apiVersion"; @@ -62,7 +67,7 @@ public class HueSyncHandler extends BaseThingHandler { private HueSyncConfiguration config; private @Nullable BundleContext context; - private @Nullable ScheduledFuture registrationJob; + private @Nullable ScheduledFuture registrationTask; private @Nullable HueSyncConnection connection; private @Nullable HueSyncDeviceInfo deviceInfo; @@ -103,7 +108,7 @@ public void initialize() { this.deviceInfo = this.connection.getDeviceInfo(); - Map properties = editProperties(); + Map properties = this.editProperties(); properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); @@ -140,9 +145,33 @@ private void startRegistrationJob() { this.logger.info("Starting registration job for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - this.registrationJob = scheduler.scheduleWithFixedDelay(() -> { + this.registrationTask = scheduler.scheduleWithFixedDelay(() -> { try { - this.connection.registerDevice(); + if (this.thing.getStatus() == ThingStatus.OFFLINE) { + HueSyncRegistration registration = this.connection.registerDevice(); + + if (registration != null) { + Map properties = this.editProperties(); + + properties.put(HueSyncHandler.PROPERTY_API_VERSION, + String.format("%d", this.deviceInfo.apiLevel)); + properties.put(HueSyncHandler.REGISTRATION_ID, registration.registrationId); + + this.config.registrationId = registration.registrationId; + this.config.apiAccessToken = registration.accessToken; + + Configuration configuration = this.editConfiguration(); + + configuration.put(HueSyncHandler.REGISTRATION_ID, this.config.registrationId); + configuration.put(HueSyncHandler.API_TOKEN, this.config.apiAccessToken); + + this.updateConfiguration(configuration); + this.updateProperties(properties); + this.updateStatus(ThingStatus.ONLINE); + + this.registrationTask.cancel(false); + } + } } catch (Exception e) { // TODO: ... } @@ -151,10 +180,10 @@ private void startRegistrationJob() { private void checkRegistration() { if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/thing.config.huesync.box.registration"); } else { - updateStatus(ThingStatus.ONLINE); + this.updateStatus(ThingStatus.ONLINE); } } @@ -170,9 +199,9 @@ public void dispose() { super.dispose(); try { - if (this.registrationJob != null && !this.registrationJob.isDone()) { - this.registrationJob.cancel(true); - this.registrationJob = null; + if (this.registrationTask != null && !this.registrationTask.isDone()) { + this.registrationTask.cancel(true); + this.registrationTask = null; } if (this.serviceRegistration != null) { diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 9c50908a5e26f..07bafe3439277 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -17,6 +17,8 @@ thing-type.config.huesync.box.host.label = Network Address thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. thing-type.config.huesync.box.port.label = Port thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. +thing-type.config.huesync.box.registrationId.label = Application Registration Id +thing-type.config.huesync.box.registrationId.description = The id of the API registration. # api exceptions diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index f47bf839dd946..b1151677ee663 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -33,6 +33,13 @@ 443 true + + + + + The id of the API registration. + false + password @@ -42,6 +49,7 @@ seconds to grant the binding the required permissions. false + From 267fc75cc113eac7c82286d2db791fba11d24635 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 13 Mar 2024 00:23:41 +0100 Subject: [PATCH 016/128] =?UTF-8?q?=E2=8F=AB=20pom=20version=20change=20to?= =?UTF-8?q?=204.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml index a292435f94341..ae1063d65926b 100644 --- a/bundles/org.openhab.binding.huesync/pom.xml +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -7,11 +7,11 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.1.2-SNAPSHOT + 4.2.0-SNAPSHOT org.openhab.binding.huesync openHAB Add-ons :: Bundles :: Hue Play HDMI Sync Box Binding - + \ No newline at end of file From f18346ffcc811c732c9467e393caccee8be28478 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 13 Mar 2024 00:29:51 +0100 Subject: [PATCH 017/128] =?UTF-8?q?=F0=9F=93=9C=20linter=20fix=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml index ae1063d65926b..a7750775ada2f 100644 --- a/bundles/org.openhab.binding.huesync/pom.xml +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -14,4 +14,4 @@ openHAB Add-ons :: Bundles :: Hue Play HDMI Sync Box Binding - \ No newline at end of file + From 142f8f9fa3c50057372f38bd9660fc8cf7a25ee2 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 13 Mar 2024 01:11:06 +0100 Subject: [PATCH 018/128] =?UTF-8?q?=F0=9F=93=9C=20linter=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller Signed-off-by: Patrik Gfeller --- bundles/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/pom.xml b/bundles/pom.xml index 22170b613997d..b821c23d7feaf 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -487,7 +487,7 @@ target/dependency - + @@ -708,4 +708,4 @@ - \ No newline at end of file + From 57b8ca7d44997aa0240a8adea5127352f3e8c083 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 16 Mar 2024 23:52:52 +0100 Subject: [PATCH 019/128] =?UTF-8?q?=F0=9F=93=9C=20...=20temporary=20refact?= =?UTF-8?q?oring/removal=20of=20log=20message=20consistency=20mechanism=20?= =?UTF-8?q?to=20avoid=20linter=20error=20=F0=9F=A4=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 2 +- .../internal/api/dto/HueSyncDeviceInfo.java | 2 +- .../dto/HueSyncDeviceInfoCapabilities.java | 2 +- .../dto/registration/HueSyncRegistration.java | 2 +- .../HueSyncRegistrationRequest.java | 2 +- .../internal/config/HueSyncConfiguration.java | 2 +- .../connection/HueSyncConnection.java | 6 ++-- .../HueSyncTrustManagerProvider.java | 2 +- .../HueSyncDiscoveryParticipant.java | 10 ++++--- .../exceptions/HueSyncApiException.java | 2 +- .../factory/HueSyncHandlerFactory.java | 2 +- .../internal/handler/HueSyncHandler.java | 20 ++++++++----- .../HueSyncLocalizer.java} | 13 +++++---- .../internal/log/HueSyncLogFactory.java | 29 +++++++++++++++++++ 14 files changed, 67 insertions(+), 29 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/{util/HueSyncLogLocalizer.java => i18n/HueSyncLocalizer.java} (85%) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index b9fa00feef498..4f74a13cf2896 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java index 59149e57a2074..0775bc0334d88 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java index 7e62aa14bfda1..b4337aa428e83 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java index 3882739a70ece..5444f93c9ac27 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java index 3bd660aeeabb5..1de01c1d22b28 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java index 0470af5aca882..0ffaab20a447d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index a5e9078c3e2f7..dfa8f70a3b2ba 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -30,8 +30,8 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -55,7 +55,7 @@ public class HueSyncConnection { private static final String REGISTRATIONS = "registrations"; private static final ObjectMapper ObjectMapper = new ObjectMapper(); - private final Logger logger = LoggerFactory.getLogger(HueSyncConnection.class); + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); private @NonNull HttpClient httpClient; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java index fab3d521c6b22..8ff98533a2ea8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 8b86ede857c45..e1ae06632947d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -24,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -37,7 +38,6 @@ import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link HueSyncDiscoveryParticipant} is responsible for discovering @@ -49,7 +49,7 @@ @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { @SuppressWarnings("null") - private Logger logger = LoggerFactory.getLogger(HueSyncDiscoveryParticipant.class); + private Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); /** * @@ -101,7 +101,9 @@ public String getServiceType() { .withProperties(properties).build(); return result; } catch (Exception e) { - logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e); + // TODO ... + // logger.error("Unable to query device information for {}: {}", + // service.getQualifiedName(), e); } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index 400abb094913c..ef0723e48c9ff 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 364d42baef0cf..77c3c4c309ec1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index f8ab0a4ebac7e..f42b4af22ddec 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -28,7 +28,7 @@ import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; -import org.openhab.binding.huesync.internal.util.HueSyncLogLocalizer; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; @@ -42,7 +42,6 @@ import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link HueSyncHandler} is responsible for handling commands, which are @@ -62,7 +61,7 @@ public class HueSyncHandler extends BaseThingHandler { static final String PROPERTY_NETWORK_STATE = "networkState"; @SuppressWarnings("null") - private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class); + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); private HueSyncConfiguration config; @@ -136,8 +135,11 @@ public void initialize() { } private void logInitializationException(Exception e) { - this.logger.error(HueSyncLogLocalizer.getResourceString("@text/logger.initialization-problem"), - this.thing.getLabel(), this.thing.getUID(), HueSyncLogLocalizer.getResourceString(e.getMessage())); + // TODO: Use EN local string to format log & exception message ... + + // this.logger.error("@text/logger.initialization-problem", + // this.thing.getLabel(), this.thing.getUID(), + // HueSyncLocalizer.getResourceString(e.getMessage())); } @SuppressWarnings("null") @@ -209,8 +211,10 @@ public void dispose() { this.serviceRegistration = null; } } catch (Exception e) { - this.logger.error("Unable to properly dispose handler for {}({}): {}", this.thing.getLabel(), - this.thing.getUID(), e); + // TODO: ... + // this.logger.error("Unable to properly dispose handler for {}({}): {}", + // this.thing.getLabel(), + // this.thing.getUID(), e); } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java similarity index 85% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 6ad62f5b74acc..94cd67b2bdb41 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/util/HueSyncLogLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024-2024 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.huesync.internal.util; +package org.openhab.binding.huesync.internal.i18n; import java.util.Locale; @@ -21,17 +21,20 @@ import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; -public class HueSyncLogLocalizer { +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +public class HueSyncLocalizer { private static final Locale locale = Locale.ENGLISH; - private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLogLocalizer.class) + private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); private static final @Nullable ServiceReference<@NonNull TranslationProvider> serviceReference = bundleContext != null ? bundleContext.getServiceReference(TranslationProvider.class) : null; - // TODO: Resolve warning ... private static final TranslationProvider translationProvider = serviceReference != null ? bundleContext.getService(serviceReference) : null; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java new file mode 100644 index 0000000000000..f62d183ec96cd --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to use [en] resource files to log messages to be consistent + * with exception message displayed on screen. + * + * @author Patrik Gfeller - Initial contribution + */ +public class HueSyncLogFactory { + + public static Logger getLogger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } +} From 2d981b5df579ebc8787167d05e1fce04fb780e1b Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 17 Mar 2024 18:21:10 +0100 Subject: [PATCH 020/128] =?UTF-8?q?=F0=9F=93=A6=20Device=20information=20D?= =?UTF-8?q?TO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../org.openhab.binding.huesync/.gitignore | 1 + .../dto/device/HueSyncDetailedDeviceInfo.java | 54 +++++++++++++++++++ .../HueSyncDetailedDeviceInfoUpdate.java | 38 +++++++++++++ .../device/HueSyncDeteiledDeviceInfoWifi.java | 29 ++++++++++ .../connection/HueSyncConnection.java | 4 +- .../resources/OH-INF/thing/thing-types.xml | 4 +- 6 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/.gitignore create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java diff --git a/bundles/org.openhab.binding.huesync/.gitignore b/bundles/org.openhab.binding.huesync/.gitignore new file mode 100644 index 0000000000000..528dc4eeda31a --- /dev/null +++ b/bundles/org.openhab.binding.huesync/.gitignore @@ -0,0 +1 @@ +scripts \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java new file mode 100644 index 0000000000000..d3c63d1cf8ffd --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +import java.util.Date; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; + +/** + * HDMI Sync Box Device Information DTO - Extended information (only available + * to registered clients) + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo { + public HueSyncDeteiledDeviceInfoWifi wifi; + public HueSyncDetailedDeviceInfoUpdate update; + + /** UTC time when last check for update was performed. */ + public Date lastCheckedUpdate; + /** + * Build number that is available to update to. Item is set to null when there + * is no update available. + */ + public @Nullable Integer updatableBuildNumber; + /** + * User readable version of the firmware the device can upgrade to. Item is set + * to null when there is no update available. + */ + public @Nullable String updatableFirmwareVersion; + /** + * 1 = regular; 0 = off in powersave, passthrough or sync mode; 2 = dimmed in + * powersave or passthrough mode and off in sync mode + */ + public int ledMode; + /** none, doSoftwareRestart, doFirmwareUpdate */ + public String action; + public String pushlink; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java new file mode 100644 index 0000000000000..3173a079eaa28 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +/** + * HDMI Sync Box Device Information DTO - Automatic Firmware update + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ + +public class HueSyncDetailedDeviceInfoUpdate { + /** + * Sync Box checks daily for a firmware update. If true, an available update + * will automatically be installed. This will be postponed if Sync Box is + * passing through content to the TV and being used. + */ + public boolean autoUpdateEnabled; + /** + * TC hour when the automatic update will check and execute, values 0 – 23. + * Default is 10. Ideally this value should be set to 3AM according to user’s + * timezone. + */ + public int autoUpdateTime; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java new file mode 100644 index 0000000000000..ab2dfe1217574 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +/** + * HDMI Sync Box Device Information DTO - Wifi connection information + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +public class HueSyncDeteiledDeviceInfoWifi { + /** Wifi SSID */ + public String ssid; + /** 0 = not connected; 1 = weak; 2 = fair; 3 = good; 4 = excellent */ + public int strength; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index dfa8f70a3b2ba..7408ba7ca0ac6 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -51,7 +51,7 @@ public class HueSyncConnection { private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; private static final String API = "api/v1"; - private static final String RESOURCE = "device"; + private static final String DEVICE = "device"; private static final String REGISTRATIONS = "registrations"; private static final ObjectMapper ObjectMapper = new ObjectMapper(); @@ -71,7 +71,7 @@ public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfigu public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionException, TimeoutException, JsonMappingException, JsonProcessingException { - String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, RESOURCE); + String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, DEVICE); ContentResponse response = this.httpClient.GET(uri); String json = response.getContentAsString(); diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index b1151677ee663..ed633833caa33 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -38,7 +38,7 @@ The id of the API registration. - false + true password @@ -47,7 +47,7 @@ process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. - false + true From 9f64cc90401f74b91d347b43464350de46b4b0cf Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 21 Mar 2024 22:08:59 +0100 Subject: [PATCH 021/128] =?UTF-8?q?=F0=9F=91=A4=20Added=20myself=20as=20ow?= =?UTF-8?q?ner=20of=20the=20huesync=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index e9ea09599fb49..768f73de87de8 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -157,7 +157,7 @@ /bundles/org.openhab.binding.hpprinter/ @cossey /bundles/org.openhab.binding.http/ @J-N-K /bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg -/bundles/org.openhab.binding.huesync/ @openhab/add-ons-maintainers +/bundles/org.openhab.binding.huesync/ @pgfeller /bundles/org.openhab.binding.hydrawise/ @digitaldan /bundles/org.openhab.binding.hyperion/ @tavalin /bundles/org.openhab.binding.iammeter/ @lewei50 From 9b66703dbd0aa21dcc5235574b94ea2aa9df3a16 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 22 Mar 2024 01:22:34 +0100 Subject: [PATCH 022/128] =?UTF-8?q?=F0=9F=94=A7=20refactoring=20to=20simpl?= =?UTF-8?q?ify=20API=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../connection/HueSyncConnection.java | 72 +++++++++++++++---- .../internal/handler/HueSyncHandler.java | 4 ++ 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 7408ba7ca0ac6..42b64f993655a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -20,6 +20,7 @@ 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.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -34,7 +35,6 @@ import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -51,8 +51,11 @@ public class HueSyncConnection { private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; private static final String API = "api/v1"; - private static final String DEVICE = "device"; - private static final String REGISTRATIONS = "registrations"; + + private static class ENDPOINTS { + public static final String DEVICE = "device"; + public static final String REGISTRATIONS = "registrations"; + } private static final ObjectMapper ObjectMapper = new ObjectMapper(); private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); @@ -67,18 +70,32 @@ public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfigu this.config = config; } + /** + * TODO: Add log, debug and trace information ... + * + * @return + * + * @throws InterruptedException + * @throws ExecutionException + * @throws TimeoutException + */ @SuppressWarnings("null") - public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionException, TimeoutException, - JsonMappingException, JsonProcessingException { + public @Nullable HueSyncDeviceInfo getDeviceInfo() + throws InterruptedException, ExecutionException, TimeoutException { + + ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); - String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, DEVICE); + if (response.getStatus() == HttpStatus.OK_200) { + String payload = response.getContentAsString(); - ContentResponse response = this.httpClient.GET(uri); - String json = response.getContentAsString(); + logger.trace("getDeviceInfo: {}", payload); - logger.trace("getDeviceInfo: {}", json); + try { + this.deviceInfo = ObjectMapper.readValue(payload, HueSyncDeviceInfo.class); + } catch (JsonProcessingException e) { - this.deviceInfo = ObjectMapper.readValue(json, HueSyncDeviceInfo.class); + } + } return this.deviceInfo; } @@ -101,15 +118,40 @@ public HueSyncDeviceInfo getDeviceInfo() throws InterruptedException, ExecutionE dto.appName = HueSyncConstants.APPLICATION_NAME; dto.instanceName = this.deviceInfo.uniqueId; - String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, REGISTRATIONS); - String json = ObjectMapper.writeValueAsString(dto); + String payload = ObjectMapper.writeValueAsString(dto); + ContentResponse response = this.executeJsonRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, payload, 500); - ContentResponse response = this.httpClient.newRequest(uri).method(HttpMethod.POST) - .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) - .content(new StringContentProvider(json)).timeout(500, TimeUnit.MILLISECONDS).send(); + // TODO: Check if "this.httpClient.setAuthenticationStore(null)" makes sense to + // be used in this context ... return (response.getStatus() == HttpStatus.OK_200) ? ObjectMapper.readValue(response.getContentAsString(), HueSyncRegistration.class) : null; } + + private ContentResponse executeGetRequest(String endpoint) + throws InterruptedException, ExecutionException, TimeoutException { + String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, endpoint); + + return httpClient.GET(uri); + } + + private ContentResponse executeJsonRequest(HttpMethod method, String endpoint, String payload, + Integer timeoutInMillisecons) throws InterruptedException, TimeoutException, ExecutionException { + + String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, endpoint); + + Request request = this.httpClient.newRequest(uri).method(method); + + if (!payload.isBlank()) { + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) + .content(new StringContentProvider(payload)); + } + + if (!this.config.apiAccessToken.isBlank()) { + request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.config.apiAccessToken); + } + + return request.timeout(timeoutInMillisecons, TimeUnit.MILLISECONDS).send(); + } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index f42b4af22ddec..2ab96d3c298fd 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -172,9 +172,13 @@ private void startRegistrationJob() { this.updateStatus(ThingStatus.ONLINE); this.registrationTask.cancel(false); + + this.logger.info("Device registration for {} complete - Id: {}", this.deviceInfo.name, + registration.registrationId); } } } catch (Exception e) { + logger.error(""); // TODO: ... } }, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_DELAY, TimeUnit.SECONDS); From 6e840ac7b5fa8df2479c99db809095c53c9288b6 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Mar 2024 20:59:35 +0100 Subject: [PATCH 023/128] =?UTF-8?q?feat(unregistration):=20=F0=9F=91=8B=20?= =?UTF-8?q?Device=20unregistration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/AUTHORS | 15 +++ bundles/org.openhab.binding.huesync/README.md | 8 +- .../connection/HueSyncConnection.java | 113 ++++++++++++------ .../exceptions/HueSyncApiException.java | 11 ++ .../internal/handler/HueSyncHandler.java | 57 +++++---- 5 files changed, 137 insertions(+), 67 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/AUTHORS diff --git a/bundles/org.openhab.binding.huesync/AUTHORS b/bundles/org.openhab.binding.huesync/AUTHORS new file mode 100644 index 0000000000000..0361fda4d4585 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/AUTHORS @@ -0,0 +1,15 @@ +# This is the list of huesync's significant contributors. +# +# This does not necessarily list everyone who has contributed code, +# To see the full list of contributors, see the revision history in +# source control. + +@pgfeller - Patrik Gfeller + +# The binding used work from the following developvers as reference +# and base for the implementation: +@HHomey-GER - Marco Kawon (https://github.com/Homey-GER/openhab-addons) + +@andrewfg - Andrew Fiddian-Green (hue binding) +@cweitkamp - Christoph Weitkamp (hue binding) + diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 9d350b861504b..f61eb05cd2e36 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -24,9 +24,8 @@ If the LED is blinking blue, you need to setup the device using the official [Hu If the device is not discovered you can check if it is properly configured and discoverable in the network: -:::: tabs - -::: tab Linux +
+ Linux (Ubuntu based distributions) ```bash $ avahi-browse --resolve _huesync._tcp @@ -38,9 +37,8 @@ $ avahi-browse --resolve _huesync._tcp txt = ["name=Sync Box" "devicetype=HSB1" "uniqueid=XXXXXXXXXXX" "path=/api"] ``` -::: +
-:::: ## Thing Configuration diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 42b64f993655a..2e83a7e76bb34 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -35,6 +35,7 @@ import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -49,8 +50,8 @@ public class HueSyncConnection { * cases subresource level /api/v1//. */ private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; - private static final String API = "api/v1"; + private static final Integer TIMEOUT_MILLISECONDS = 500; private static class ENDPOINTS { public static final String DEVICE = "device"; @@ -65,68 +66,85 @@ private static class ENDPOINTS { private HueSyncDeviceInfo deviceInfo; private HueSyncConfiguration config; + /** + * + * @param httpClient + * @param config + */ public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfiguration config) { this.httpClient = httpClient; this.config = config; } /** - * TODO: Add log, debug and trace information ... * * @return - * - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException */ - @SuppressWarnings("null") - public @Nullable HueSyncDeviceInfo getDeviceInfo() - throws InterruptedException, ExecutionException, TimeoutException { + public @Nullable HueSyncDeviceInfo getDeviceInfo() { + try { + ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); - ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + if (response.getStatus() == HttpStatus.OK_200) { + this.deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); + } - if (response.getStatus() == HttpStatus.OK_200) { - String payload = response.getContentAsString(); + } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { + this.logger.error(e.getMessage()); + } - logger.trace("getDeviceInfo: {}", payload); + return this.deviceInfo; + } + /** + * + * @return + */ + public boolean unregisterDevice() { + if (!this.config.apiAccessToken.isBlank() && !this.config.registrationId.isBlank()) { try { - this.deviceInfo = ObjectMapper.readValue(payload, HueSyncDeviceInfo.class); - } catch (JsonProcessingException e) { + String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.config.registrationId; + ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); + return response.getStatus() == HttpStatus.OK_200; + } catch (InterruptedException | TimeoutException | ExecutionException e) { + this.logger.error(e.getMessage()); } } - - return this.deviceInfo; + return false; } /** * Try to register the application with the device. * * @return null || HueSyncRegistration - * - * @throws JsonProcessingException - * @throws InterruptedException - * @throws TimeoutException - * @throws ExecutionException */ - public @Nullable HueSyncRegistration registerDevice() - throws JsonProcessingException, InterruptedException, TimeoutException, ExecutionException { + public @Nullable HueSyncRegistration registerDevice() { + HueSyncRegistration registration = null; + + try { + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = this.deviceInfo.uniqueId; + + String json = ObjectMapper.writeValueAsString(dto); - HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json); - dto.appName = HueSyncConstants.APPLICATION_NAME; - dto.instanceName = this.deviceInfo.uniqueId; + if (response.getStatus() == HttpStatus.OK_200) { + registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); + } + } catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) { + this.logger.error(e.getMessage()); + } - String payload = ObjectMapper.writeValueAsString(dto); - ContentResponse response = this.executeJsonRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, payload, 500); + return registration; + } - // TODO: Check if "this.httpClient.setAuthenticationStore(null)" makes sense to - // be used in this context ... + // #region - private - return (response.getStatus() == HttpStatus.OK_200) - ? ObjectMapper.readValue(response.getContentAsString(), HueSyncRegistration.class) - : null; + private T deserialize(String json, Class type) throws JsonMappingException, JsonProcessingException { + return ObjectMapper.readValue(json, type); } private ContentResponse executeGetRequest(String endpoint) @@ -136,8 +154,26 @@ private ContentResponse executeGetRequest(String endpoint) return httpClient.GET(uri); } - private ContentResponse executeJsonRequest(HttpMethod method, String endpoint, String payload, - Integer timeoutInMillisecons) throws InterruptedException, TimeoutException, ExecutionException { + private ContentResponse executeRequest(HttpMethod method, String endpoint) + throws InterruptedException, TimeoutException, ExecutionException { + return this.executeRequest(method, endpoint, ""); + } + + /** + * Executes a REST API call, with an optional json payload. + * + * @param method + * @param endpoint + * @param payload + * + * @return + * + * @throws InterruptedException + * @throws TimeoutException + * @throws ExecutionException + */ + private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) + throws InterruptedException, TimeoutException, ExecutionException { String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, endpoint); @@ -149,9 +185,12 @@ private ContentResponse executeJsonRequest(HttpMethod method, String endpoint, S } if (!this.config.apiAccessToken.isBlank()) { + // TODO: Check if httpClient.setAuthenticationStore makes sense ... request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.config.apiAccessToken); } - return request.timeout(timeoutInMillisecons, TimeUnit.MILLISECONDS).send(); + return request.timeout(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS).send(); } + + // #endregion } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index ef0723e48c9ff..4d7756c2a7a19 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -12,13 +12,24 @@ */ package org.openhab.binding.huesync.internal.exceptions; +import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; + /** * * @author Patrik Gfeller - Initial contribution */ public class HueSyncApiException extends Exception { + private String key; public HueSyncApiException(String message) { super(message); + + if (message.startsWith("@text")) { + key = message; + } + } + + public String getLogMessage() { + return this.key == null ? this.getMessage() : HueSyncLocalizer.getResourceString(key); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 2ab96d3c298fd..cad09b84d0c1e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -66,7 +66,7 @@ public class HueSyncHandler extends BaseThingHandler { private HueSyncConfiguration config; private @Nullable BundleContext context; - private @Nullable ScheduledFuture registrationTask; + private @Nullable ScheduledFuture deviceRegistrationTask; private @Nullable HueSyncConnection connection; private @Nullable HueSyncDeviceInfo deviceInfo; @@ -122,32 +122,28 @@ public void initialize() { this.checkRegistration(); if (this.thing.getStatus() == ThingStatus.OFFLINE) { - this.startRegistrationJob(); + this.startDeviceRegistrationTask(); } } catch (HueSyncApiException e) { - this.logInitializationException(e); + this.logger.error(e.getLogMessage()); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { - this.logInitializationException(e); + this.logger.error(e.getMessage()); this.updateStatus(ThingStatus.OFFLINE); } }); } - private void logInitializationException(Exception e) { - // TODO: Use EN local string to format log & exception message ... - - // this.logger.error("@text/logger.initialization-problem", - // this.thing.getLabel(), this.thing.getUID(), - // HueSyncLocalizer.getResourceString(e.getMessage())); - } - @SuppressWarnings("null") - private void startRegistrationJob() { + private void startDeviceRegistrationTask() { + if (this.deviceRegistrationTask != null) { + return; + } + this.logger.info("Starting registration job for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - this.registrationTask = scheduler.scheduleWithFixedDelay(() -> { + this.deviceRegistrationTask = scheduler.scheduleWithFixedDelay(() -> { try { if (this.thing.getStatus() == ThingStatus.OFFLINE) { HueSyncRegistration registration = this.connection.registerDevice(); @@ -171,15 +167,14 @@ private void startRegistrationJob() { this.updateProperties(properties); this.updateStatus(ThingStatus.ONLINE); - this.registrationTask.cancel(false); + this.deviceRegistrationTask.cancel(false); this.logger.info("Device registration for {} complete - Id: {}", this.deviceInfo.name, registration.registrationId); } } } catch (Exception e) { - logger.error(""); - // TODO: ... + this.logger.debug(e.getMessage()); } }, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_DELAY, TimeUnit.SECONDS); } @@ -205,20 +200,32 @@ public void dispose() { super.dispose(); try { - if (this.registrationTask != null && !this.registrationTask.isDone()) { - this.registrationTask.cancel(true); - this.registrationTask = null; + if (this.deviceRegistrationTask != null && !this.deviceRegistrationTask.isDone()) { + this.deviceRegistrationTask.cancel(true); } if (this.serviceRegistration != null) { this.serviceRegistration.unregister(); - this.serviceRegistration = null; } + } catch (Exception e) { - // TODO: ... - // this.logger.error("Unable to properly dispose handler for {}({}): {}", - // this.thing.getLabel(), - // this.thing.getUID(), e); + this.logger.error(e.getMessage()); + } finally { + this.deviceRegistrationTask = null; + this.serviceRegistration = null; + + this.logger.info("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); + } + } + + @Override + public void handleRemoval() { + super.handleRemoval(); + + if (this.connection != null && !this.connection.unregisterDevice()) { + this.logger.error( + "It was not possible to unregister {} ({}). You may use id: {} Key: {} to manually re-configure the thing, or to manually remove the device via API.", + this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, this.config.apiAccessToken); } } } From de14fc1220965174b500be49dc5f6de482c53799 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Mar 2024 21:15:03 +0100 Subject: [PATCH 024/128] =?UTF-8?q?fix(linter):=20=F0=9F=94=8E=20Format=20?= =?UTF-8?q?should=20be=20constant.=20Use=20placeholder=20to=20reduce=20the?= =?UTF-8?q?=20needless=20cost=20of=20parameter=20construction.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncConnection.java | 6 +++--- .../binding/huesync/internal/handler/HueSyncHandler.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 2e83a7e76bb34..fdad4560ea61d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -89,7 +89,7 @@ public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfigu } } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { - this.logger.error(e.getMessage()); + this.logger.error("{}", e.getMessage()); } return this.deviceInfo; @@ -107,7 +107,7 @@ public boolean unregisterDevice() { return response.getStatus() == HttpStatus.OK_200; } catch (InterruptedException | TimeoutException | ExecutionException e) { - this.logger.error(e.getMessage()); + this.logger.error("{}", e.getMessage()); } } return false; @@ -135,7 +135,7 @@ public boolean unregisterDevice() { registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); } } catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) { - this.logger.error(e.getMessage()); + this.logger.error("{}", e.getMessage()); } return registration; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index cad09b84d0c1e..0116cd69368fb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -128,7 +128,7 @@ public void initialize() { this.logger.error(e.getLogMessage()); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { - this.logger.error(e.getMessage()); + this.logger.error("{}", e.getMessage()); this.updateStatus(ThingStatus.OFFLINE); } }); @@ -209,7 +209,7 @@ public void dispose() { } } catch (Exception e) { - this.logger.error(e.getMessage()); + this.logger.error("{}", e.getMessage()); } finally { this.deviceRegistrationTask = null; this.serviceRegistration = null; From 70bb80b52c0233664e3529fa82c35b294f706fa8 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Mar 2024 21:20:42 +0100 Subject: [PATCH 025/128] =?UTF-8?q?fix(linter):=20=F0=9F=94=8E=20Format=20?= =?UTF-8?q?should=20be=20constant.=20Use=20placeholder=20to=20reduce=20the?= =?UTF-8?q?=20needless=20cost=20of=20parameter=20construction.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/handler/HueSyncHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 0116cd69368fb..2c5ca79f8ea65 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -125,7 +125,7 @@ public void initialize() { this.startDeviceRegistrationTask(); } } catch (HueSyncApiException e) { - this.logger.error(e.getLogMessage()); + this.logger.error("{}", e.getLogMessage()); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { this.logger.error("{}", e.getMessage()); @@ -174,7 +174,7 @@ private void startDeviceRegistrationTask() { } } } catch (Exception e) { - this.logger.debug(e.getMessage()); + this.logger.debug("{}", e.getMessage()); } }, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_DELAY, TimeUnit.SECONDS); } From 36b8d0cff9ed20b3950fdf0e6e043c0aa85de649 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 29 Mar 2024 23:35:14 +0100 Subject: [PATCH 026/128] =?UTF-8?q?refactor:=20=F0=9F=93=9C=20code=20moved?= =?UTF-8?q?=20and=20group=20areas=20added=20to=20improve=20overview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../internal/handler/HueSyncHandler.java | 96 ++++++++++--------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 2c5ca79f8ea65..b886d031de5c7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -51,13 +51,11 @@ */ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { - /** the device registration id */ + static final String REGISTRATION_ID = "registrationId"; static final String API_TOKEN = "apiAccessToken"; - /** the key for the api version property */ static final String PROPERTY_API_VERSION = "apiVersion"; - /** the ky for the network state property */ static final String PROPERTY_NETWORK_STATE = "networkState"; @SuppressWarnings("null") @@ -91,49 +89,7 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) this.httpClient = httpClientFactory.getCommonHttpClient(); } - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // TODO: Implementation ... - } - - @SuppressWarnings("null") - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - - scheduler.execute(() -> { - try { - this.connection = new HueSyncConnection(this.httpClient, this.config); - - this.deviceInfo = this.connection.getDeviceInfo(); - - Map properties = this.editProperties(); - - properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); - properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, this.deviceInfo.firmwareVersion); - - properties.put(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", this.deviceInfo.apiLevel)); - properties.put(HueSyncHandler.PROPERTY_NETWORK_STATE, this.deviceInfo.wifiState); - - this.updateProperties(properties); - - this.checkCompatibility(); - this.checkRegistration(); - - if (this.thing.getStatus() == ThingStatus.OFFLINE) { - this.startDeviceRegistrationTask(); - } - } catch (HueSyncApiException e) { - this.logger.error("{}", e.getLogMessage()); - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (Exception e) { - this.logger.error("{}", e.getMessage()); - this.updateStatus(ThingStatus.OFFLINE); - } - }); - } - + // #region private @SuppressWarnings("null") private void startDeviceRegistrationTask() { if (this.deviceRegistrationTask != null) { @@ -194,6 +150,52 @@ private void checkCompatibility() throws HueSyncApiException { } } + // #endregion + + // #region Override + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // TODO: Implementation ... + } + + @SuppressWarnings("null") + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + + scheduler.execute(() -> { + try { + this.connection = new HueSyncConnection(this.httpClient, this.config); + + this.deviceInfo = this.connection.getDeviceInfo(); + + Map properties = this.editProperties(); + + properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); + properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, this.deviceInfo.firmwareVersion); + + properties.put(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", this.deviceInfo.apiLevel)); + properties.put(HueSyncHandler.PROPERTY_NETWORK_STATE, this.deviceInfo.wifiState); + + this.updateProperties(properties); + + this.checkCompatibility(); + this.checkRegistration(); + + if (this.thing.getStatus() == ThingStatus.OFFLINE) { + this.startDeviceRegistrationTask(); + } + } catch (HueSyncApiException e) { + this.logger.error("{}", e.getLogMessage()); + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (Exception e) { + this.logger.error("{}", e.getMessage()); + this.updateStatus(ThingStatus.OFFLINE); + } + }); + } + @SuppressWarnings("null") @Override public void dispose() { @@ -207,7 +209,6 @@ public void dispose() { if (this.serviceRegistration != null) { this.serviceRegistration.unregister(); } - } catch (Exception e) { this.logger.error("{}", e.getMessage()); } finally { @@ -228,4 +229,5 @@ public void handleRemoval() { this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, this.config.apiAccessToken); } } + // #endregion } From 6f9b46a084acddc8da3ebece4a12bd20e28fcf08 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 31 Mar 2024 23:31:22 +0200 Subject: [PATCH 027/128] =?UTF-8?q?refactor:=20=F0=9F=93=9C=20refactoring?= =?UTF-8?q?=20to=20simplify=20maintentance=20and=20pending=20implementatio?= =?UTF-8?q?n=20tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 5 +- .../connection/HueSyncConnection.java | 123 +++++------ .../HueSyncDiscoveryParticipant.java | 2 +- .../exceptions/HueSyncApiException.java | 18 +- .../internal/exceptions/HueSyncException.java | 39 ++++ .../internal/handler/HueSyncHandler.java | 191 ++++++++---------- .../tasks/HueSyncRegistrationTask.java | 62 ++++++ 7 files changed, 260 insertions(+), 180 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 4f74a13cf2896..83d32a255e81c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -35,5 +35,8 @@ public class HueSyncConstants { public static final String PARAMETER_PORT = "port"; public static final Integer REGISTRATION_INITIAL_DELAY = 3; - public static final Integer REGISTRATION_DELAY = 1; + public static final Integer REGISTRATION_INTERVAL = 1; + + public static final String REGISTRATION_ID = "registrationId"; + public static final String API_TOKEN = "apiAccessToken"; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index fdad4560ea61d..8f3843b088617 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.huesync.internal.connection; +import java.io.IOException; +import java.security.cert.CertificateException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNull; +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; @@ -30,8 +33,11 @@ import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; -import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; @@ -59,73 +65,57 @@ private static class ENDPOINTS { } private static final ObjectMapper ObjectMapper = new ObjectMapper(); + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); private @NonNull HttpClient httpClient; + private @NonNull String host; + private @NonNull Integer port; - private HueSyncDeviceInfo deviceInfo; - private HueSyncConfiguration config; + private @Nullable String apiAccessToken; + private @Nullable String registrationId; - /** - * - * @param httpClient - * @param config - */ - public HueSyncConnection(@NonNull HttpClient httpClient, @NonNull HueSyncConfiguration config) { + private ServiceRegistration tlsProviderService; + + @NonNullByDefault + public HueSyncConnection(String host, Integer port, HttpClient httpClient) + throws CertificateException, IOException { + + this.host = host; + this.port = port; + + HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); + BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, + null); this.httpClient = httpClient; - this.config = config; } - /** - * - * @return - */ public @Nullable HueSyncDeviceInfo getDeviceInfo() { + HueSyncDeviceInfo deviceInfo = null; + try { ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); if (response.getStatus() == HttpStatus.OK_200) { - this.deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); + deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); } - } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { this.logger.error("{}", e.getMessage()); } - return this.deviceInfo; - } - - /** - * - * @return - */ - public boolean unregisterDevice() { - if (!this.config.apiAccessToken.isBlank() && !this.config.registrationId.isBlank()) { - try { - String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.config.registrationId; - ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); - - return response.getStatus() == HttpStatus.OK_200; - } catch (InterruptedException | TimeoutException | ExecutionException e) { - this.logger.error("{}", e.getMessage()); - } - } - return false; + return deviceInfo; } - /** - * Try to register the application with the device. - * - * @return null || HueSyncRegistration - */ - public @Nullable HueSyncRegistration registerDevice() { + public @Nullable HueSyncRegistration registerDevice(String id) { HueSyncRegistration registration = null; try { HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); dto.appName = HueSyncConstants.APPLICATION_NAME; - dto.instanceName = this.deviceInfo.uniqueId; + dto.instanceName = id; String json = ObjectMapper.writeValueAsString(dto); @@ -133,6 +123,9 @@ public boolean unregisterDevice() { if (response.getStatus() == HttpStatus.OK_200) { registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); + + this.apiAccessToken = registration.accessToken; + this.registrationId = registration.registrationId; } } catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) { this.logger.error("{}", e.getMessage()); @@ -141,15 +134,38 @@ public boolean unregisterDevice() { return registration; } + public boolean unregisterDevice() { + if (this.isRegistered()) { + try { + String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; + ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); + + return response.getStatus() == HttpStatus.OK_200; + } catch (InterruptedException | TimeoutException | ExecutionException e) { + this.logger.error("{}", e.getMessage()); + } + } + return false; + } + + public void stop() { + this.tlsProviderService.unregister(); + } + // #region - private + private boolean isRegistered() { + return this.apiAccessToken != null && this.apiAccessToken.isBlank() != false && this.registrationId != null + && this.registrationId.isBlank() != false; + } + private T deserialize(String json, Class type) throws JsonMappingException, JsonProcessingException { return ObjectMapper.readValue(json, type); } private ContentResponse executeGetRequest(String endpoint) throws InterruptedException, ExecutionException, TimeoutException { - String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, endpoint); + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); return httpClient.GET(uri); } @@ -159,23 +175,10 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint) return this.executeRequest(method, endpoint, ""); } - /** - * Executes a REST API call, with an optional json payload. - * - * @param method - * @param endpoint - * @param payload - * - * @return - * - * @throws InterruptedException - * @throws TimeoutException - * @throws ExecutionException - */ private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) throws InterruptedException, TimeoutException, ExecutionException { - String uri = String.format(REQUEST_FORMAT, this.config.host, this.config.port, API, endpoint); + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); Request request = this.httpClient.newRequest(uri).method(method); @@ -184,9 +187,9 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint, Strin .content(new StringContentProvider(payload)); } - if (!this.config.apiAccessToken.isBlank()) { - // TODO: Check if httpClient.setAuthenticationStore makes sense ... - request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.config.apiAccessToken); + if (this.apiAccessToken != null && !this.apiAccessToken.isBlank()) { + // TODO: Check if we can use httpClient.setAuthenticationStore ... + request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.apiAccessToken); } return request.timeout(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS).send(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index e1ae06632947d..2b982670666ff 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -101,7 +101,7 @@ public String getServiceType() { .withProperties(properties).build(); return result; } catch (Exception e) { - // TODO ... + // TODO Handle exception // logger.error("Unable to query device information for {}: {}", // service.getQualifiedName(), e); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index 4d7756c2a7a19..8f021fea7ba07 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -12,24 +12,14 @@ */ package org.openhab.binding.huesync.internal.exceptions; -import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; +import org.slf4j.Logger; /** * * @author Patrik Gfeller - Initial contribution */ -public class HueSyncApiException extends Exception { - private String key; - - public HueSyncApiException(String message) { - super(message); - - if (message.startsWith("@text")) { - key = message; - } - } - - public String getLogMessage() { - return this.key == null ? this.getMessage() : HueSyncLocalizer.getResourceString(key); +public class HueSyncApiException extends HueSyncException { + public HueSyncApiException(String message, Logger logger) { + super(message, logger); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java new file mode 100644 index 0000000000000..9b2fece2a95b6 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.exceptions; + +import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; +import org.slf4j.Logger; + +/** + * Base class for all HueSyncExceptions + * + * @author Patrik Gfeller - Initial Contribution + */ +public abstract class HueSyncException extends Exception { + private String key; + + public HueSyncException(String message, Logger logger) { + super(message); + + if (message.startsWith("@text")) { + key = message; + } + + logger.error(this.getLogMessage()); + } + + private String getLogMessage() { + return this.key == null ? this.getMessage() : HueSyncLocalizer.getResourceString(key); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index b886d031de5c7..383471110dab0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -15,10 +15,11 @@ import java.io.IOException; import java.security.cert.CertificateException; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; @@ -26,21 +27,17 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; -import org.openhab.binding.huesync.internal.connection.HueSyncTrustManagerProvider; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; -import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; /** @@ -49,145 +46,133 @@ * * @author Patrik Gfeller - Initial contribution */ -@NonNullByDefault public class HueSyncHandler extends BaseThingHandler { + private static final String PROPERTY_API_VERSION = "apiVersion"; + private static final String PROPERTY_NETWORK_STATE = "networkState"; - static final String REGISTRATION_ID = "registrationId"; - static final String API_TOKEN = "apiAccessToken"; - - static final String PROPERTY_API_VERSION = "apiVersion"; - static final String PROPERTY_NETWORK_STATE = "networkState"; - - @SuppressWarnings("null") private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); - private HueSyncConfiguration config; - - private @Nullable BundleContext context; private @Nullable ScheduledFuture deviceRegistrationTask; - - private @Nullable HueSyncConnection connection; private @Nullable HueSyncDeviceInfo deviceInfo; - private @Nullable ServiceRegistration serviceRegistration; - private HttpClient httpClient; + private @NonNull HueSyncConnection connection; + private @NonNull HueSyncConfiguration config; - @SuppressWarnings("null") - public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) - throws CertificateException, IOException, Exception { + protected class HueSyncProperties { + + }; + + public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException { super(thing); this.config = getConfigAs(HueSyncConfiguration.class); - HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.config.host, - this.config.port); + HttpClient httpClient = httpClientFactory.getCommonHttpClient(); - this.context = FrameworkUtil.getBundle(getClass()).getBundleContext(); - this.serviceRegistration = this.context.registerService(TlsTrustManagerProvider.class.getName(), - trustManagerProvider, null); - - this.httpClient = httpClientFactory.getCommonHttpClient(); + this.connection = new HueSyncConnection(this.config.host, this.config.port, httpClient); } // #region private - @SuppressWarnings("null") - private void startDeviceRegistrationTask() { - if (this.deviceRegistrationTask != null) { - return; + private void stopTask(@Nullable ScheduledFuture task) { + try { + if (task != null && !task.isDone()) { + task.cancel(true); + } + } catch (Exception e) { + // TODO: Handle exception ... + } finally { + task = null; } + } + + private @Nullable ScheduledFuture executeTask(Runnable task, long initialDelay, long interval) { + return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); + } + + @SuppressWarnings("null") + private void registerDevice() { - this.logger.info("Starting registration job for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.logger.info("Starting device registration for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - this.deviceRegistrationTask = scheduler.scheduleWithFixedDelay(() -> { - try { - if (this.thing.getStatus() == ThingStatus.OFFLINE) { - HueSyncRegistration registration = this.connection.registerDevice(); + if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "@text/thing.config.huesync.box.registration"); - if (registration != null) { - Map properties = this.editProperties(); + Runnable task = new HueSyncRegistrationTask(connection, deviceInfo, () -> thing.getStatus(), + (registration) -> setRegistration(registration)); - properties.put(HueSyncHandler.PROPERTY_API_VERSION, - String.format("%d", this.deviceInfo.apiLevel)); - properties.put(HueSyncHandler.REGISTRATION_ID, registration.registrationId); + this.deviceRegistrationTask = this.executeTask(task, HueSyncConstants.REGISTRATION_INITIAL_DELAY, + HueSyncConstants.REGISTRATION_INTERVAL); + } else if (this.deviceInfo == null) { + this.updateStatus(ThingStatus.OFFLINE); + } else { + this.updateStatus(ThingStatus.ONLINE); + } + } - this.config.registrationId = registration.registrationId; - this.config.apiAccessToken = registration.accessToken; + @SuppressWarnings("null") + private void setRegistration(HueSyncRegistration registration) { + this.stopTask(deviceRegistrationTask); - Configuration configuration = this.editConfiguration(); + addProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); + addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceInfo.apiLevel)); - configuration.put(HueSyncHandler.REGISTRATION_ID, this.config.registrationId); - configuration.put(HueSyncHandler.API_TOKEN, this.config.apiAccessToken); + this.config.registrationId = registration.registrationId; + this.config.apiAccessToken = registration.accessToken; - this.updateConfiguration(configuration); - this.updateProperties(properties); - this.updateStatus(ThingStatus.ONLINE); + Configuration configuration = this.editConfiguration(); - this.deviceRegistrationTask.cancel(false); + configuration.put(HueSyncConstants.REGISTRATION_ID, this.config.registrationId); + configuration.put(HueSyncConstants.API_TOKEN, this.config.apiAccessToken); - this.logger.info("Device registration for {} complete - Id: {}", this.deviceInfo.name, - registration.registrationId); - } - } - } catch (Exception e) { - this.logger.debug("{}", e.getMessage()); - } - }, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_DELAY, TimeUnit.SECONDS); - } + this.updateConfiguration(configuration); + this.updateStatus(ThingStatus.ONLINE); - private void checkRegistration() { - if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "@text/thing.config.huesync.box.registration"); - } else { - this.updateStatus(ThingStatus.ONLINE); - } + this.logger.info("Device registration for {} complete - Id: {}", this.deviceInfo.name, + registration.registrationId); } private void checkCompatibility() throws HueSyncApiException { if (this.deviceInfo != null && this.deviceInfo.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { - throw new HueSyncApiException("@text/api.minimal-version"); + throw new HueSyncApiException("@text/api.minimal-version", this.logger); } } - // #endregion + private void addProperty(@NonNull String key, @Nullable String value) { + if (value != null) { + Map<@NonNull String, @NonNull String> properties = this.editProperties(); - // #region Override - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // TODO: Implementation ... + properties.put(key, value); + + this.updateProperties(properties); + + } } - @SuppressWarnings("null") + // #endregion + + // #region Override @Override public void initialize() { updateStatus(ThingStatus.UNKNOWN); scheduler.execute(() -> { try { - this.connection = new HueSyncConnection(this.httpClient, this.config); - this.deviceInfo = this.connection.getDeviceInfo(); - Map properties = this.editProperties(); - - properties.put(Thing.PROPERTY_SERIAL_NUMBER, this.deviceInfo.uniqueId); - properties.put(Thing.PROPERTY_MODEL_ID, this.deviceInfo.deviceType); - properties.put(Thing.PROPERTY_FIRMWARE_VERSION, this.deviceInfo.firmwareVersion); - - properties.put(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", this.deviceInfo.apiLevel)); - properties.put(HueSyncHandler.PROPERTY_NETWORK_STATE, this.deviceInfo.wifiState); + Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { + addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); + addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); + addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); - this.updateProperties(properties); + addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); + }); this.checkCompatibility(); - this.checkRegistration(); - - if (this.thing.getStatus() == ThingStatus.OFFLINE) { - this.startDeviceRegistrationTask(); - } + this.registerDevice(); } catch (HueSyncApiException e) { - this.logger.error("{}", e.getLogMessage()); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { this.logger.error("{}", e.getMessage()); @@ -196,25 +181,22 @@ public void initialize() { }); } - @SuppressWarnings("null") + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // TODO: Implementation ... + } + @Override public void dispose() { super.dispose(); try { - if (this.deviceRegistrationTask != null && !this.deviceRegistrationTask.isDone()) { - this.deviceRegistrationTask.cancel(true); - } + this.connection.stop(); - if (this.serviceRegistration != null) { - this.serviceRegistration.unregister(); - } + this.stopTask(deviceRegistrationTask); } catch (Exception e) { this.logger.error("{}", e.getMessage()); } finally { - this.deviceRegistrationTask = null; - this.serviceRegistration = null; - this.logger.info("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); } } @@ -229,5 +211,6 @@ public void handleRemoval() { this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, this.config.apiAccessToken); } } + // #endregion } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java new file mode 100644 index 0000000000000..03d42292e99cb --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.handler.tasks; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; + +/** + * Task to handle device registration. + * + * @author Patrik Gfeller - Initial contribution + */ +public class HueSyncRegistrationTask implements Runnable { + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); + + private HueSyncConnection connection; + private HueSyncDeviceInfo deviceInfo; + + private Consumer action; + private Supplier status; + + public HueSyncRegistrationTask(HueSyncConnection connection, HueSyncDeviceInfo deviceInfo, + Supplier status, Consumer action) { + + this.connection = connection; + this.deviceInfo = deviceInfo; + this.status = status; + this.action = action; + } + + @Override + public void run() { + try { + if (this.status.get() == ThingStatus.OFFLINE) { + HueSyncRegistration registration = this.connection.registerDevice(deviceInfo.uniqueId); + + if (registration != null) { + this.action.accept(registration); + } + } + } catch (Exception e) { + this.logger.debug("{}", e.getMessage()); + } + } +} From 428b3e30309870be74254431cb41d49d6f0896e9 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 31 Mar 2024 23:38:57 +0200 Subject: [PATCH 028/128] =?UTF-8?q?fix(linter):=20=F0=9F=94=8E=20=20linter?= =?UTF-8?q?=20performance=20check=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/exceptions/HueSyncException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java index 9b2fece2a95b6..531fc716547f9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java @@ -30,7 +30,7 @@ public HueSyncException(String message, Logger logger) { key = message; } - logger.error(this.getLogMessage()); + logger.error("{}", this.getLogMessage()); } private String getLogMessage() { From 7f3a4e519511964dc4e0cd12db438195df4e5de4 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 6 Apr 2024 20:19:16 +0200 Subject: [PATCH 029/128] =?UTF-8?q?feat(infrastructure):=20=F0=9F=94=8C=20?= =?UTF-8?q?check=20online/offline=20state=20(poll)=20-=20wip=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../dto/device/HueSyncDetailedDeviceInfo.java | 1 - .../dto/{ => device}/HueSyncDeviceInfo.java | 7 +- .../internal/config/HueSyncConfiguration.java | 1 + .../connection/HueSyncConnection.java | 39 ++++++++--- .../internal/handler/HueSyncHandler.java | 69 +++++++++++++------ .../tasks/HueSyncRegistrationTask.java | 2 +- .../handler/tasks/HueSyncUpdateTask.java | 59 ++++++++++++++++ .../resources/OH-INF/i18n/huesync.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 8 ++- 9 files changed, 155 insertions(+), 33 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/{ => device}/HueSyncDeviceInfo.java (88%) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java index d3c63d1cf8ffd..7f116267bf646 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java @@ -15,7 +15,6 @@ import java.util.Date; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; /** * HDMI Sync Box Device Information DTO - Extended information (only available diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java similarity index 88% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java index 0775bc0334d88..e7f5f8e770dfb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java @@ -10,7 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.huesync.internal.api.dto; +package org.openhab.binding.huesync.internal.api.dto.device; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfoCapabilities; /** * HDMI Sync Box Device Information DTO @@ -31,7 +34,7 @@ public class HueSyncDeviceInfo { * delimiters. Used as unique id on label, certificate common name, hostname * etc. */ - public String uniqueId; + public @NonNull String uniqueId = ""; /** * Increased between firmware versions when api changes. Only apiLevel >= 7 is * supported. diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java index 0ffaab20a447d..08c87729b2117 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java @@ -25,4 +25,5 @@ public class HueSyncConfiguration { public String apiAccessToken = ""; public String host = ""; public Integer port = 443; + public Integer statusUpdateInterval = 10; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 8f3843b088617..8af4cb8f43984 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -30,7 +30,8 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; @@ -78,11 +79,13 @@ private static class ENDPOINTS { private ServiceRegistration tlsProviderService; @NonNullByDefault - public HueSyncConnection(String host, Integer port, HttpClient httpClient) - throws CertificateException, IOException { + public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, + String registrationId) throws CertificateException, IOException { this.host = host; this.port = port; + this.apiAccessToken = apiAccessToken; + this.registrationId = registrationId; HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); @@ -108,6 +111,25 @@ public HueSyncConnection(String host, Integer port, HttpClient httpClient) return deviceInfo; } + public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { + if (!this.isRegistered()) + return null; + + HueSyncDetailedDeviceInfo deviceInfo = null; + + try { + ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + + if (response.getStatus() == HttpStatus.OK_200) { + deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class); + } + } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { + this.logger.error("{}", e.getMessage()); + } + + return deviceInfo; + } + public @Nullable HueSyncRegistration registerDevice(String id) { HueSyncRegistration registration = null; @@ -152,13 +174,14 @@ public void stop() { this.tlsProviderService.unregister(); } - // #region - private - - private boolean isRegistered() { - return this.apiAccessToken != null && this.apiAccessToken.isBlank() != false && this.registrationId != null - && this.registrationId.isBlank() != false; + @SuppressWarnings("null") + public boolean isRegistered() { + return this.apiAccessToken != null && this.registrationId != null && !this.apiAccessToken.isBlank() + && !this.registrationId.isBlank(); } + // #region - private + private T deserialize(String json, Class type) throws JsonMappingException, JsonProcessingException { return ObjectMapper.readValue(json, type); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 383471110dab0..acba13bfb6017 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -23,12 +23,14 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; @@ -52,7 +54,9 @@ public class HueSyncHandler extends BaseThingHandler { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); - private @Nullable ScheduledFuture deviceRegistrationTask; + private @Nullable ScheduledFuture deviceRegistrationTask; + private @Nullable ScheduledFuture deviceUpdateTask; + private @Nullable HueSyncDeviceInfo deviceInfo; private @NonNull HueSyncConnection connection; @@ -65,11 +69,11 @@ protected class HueSyncProperties { public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException { super(thing); - this.config = getConfigAs(HueSyncConfiguration.class); - HttpClient httpClient = httpClientFactory.getCommonHttpClient(); - this.connection = new HueSyncConnection(this.config.host, this.config.port, httpClient); + this.config = getConfigAs(HueSyncConfiguration.class); + this.connection = new HueSyncConnection(httpClient, this.config.host, this.config.port, + this.config.apiAccessToken, this.config.registrationId); } // #region private @@ -89,34 +93,59 @@ private void stopTask(@Nullable ScheduledFuture task) { return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); } - @SuppressWarnings("null") - private void registerDevice() { + @SuppressWarnings({ "unchecked", "null" }) + private void startBackgroundTasks() { + Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, this.deviceInfo, + (deviceState) -> this.updateDeviceState(deviceState)); - this.logger.info("Starting device registration for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, - this.deviceInfo.uniqueId); + if (this.connection.isRegistered()) { + this.logger.debug("Device {} {}:{} is already registered", this.deviceInfo.name, this.deviceInfo.deviceType, + this.deviceInfo.uniqueId); + + this.startUpdateTask(statusUpdateTask); + } else { + this.logger.info("Starting device registration for {} {}:{}", this.deviceInfo.name, + this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - if (this.config.apiAccessToken.isEmpty() || this.config.apiAccessToken.isBlank()) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/thing.config.huesync.box.registration"); Runnable task = new HueSyncRegistrationTask(connection, deviceInfo, () -> thing.getStatus(), - (registration) -> setRegistration(registration)); + (registration) -> { + this.setRegistration(registration); + this.startUpdateTask(statusUpdateTask); + }); + + this.deviceRegistrationTask = (ScheduledFuture) this.executeTask(task, + HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_INTERVAL); + } + } + + @SuppressWarnings("unchecked") + private void startUpdateTask(Runnable updateTask) { + this.deviceUpdateTask = (ScheduledFuture) this.executeTask(updateTask, 0, + this.config.statusUpdateInterval); + } + + private void updateDeviceState(HueSyncDetailedDeviceInfo deviceState) { + ThingStatus currentStatus = this.thing.getStatus(); + logger.trace("Current status: {}", currentStatus); - this.deviceRegistrationTask = this.executeTask(task, HueSyncConstants.REGISTRATION_INITIAL_DELAY, - HueSyncConstants.REGISTRATION_INTERVAL); - } else if (this.deviceInfo == null) { - this.updateStatus(ThingStatus.OFFLINE); + if (deviceState != null) { + if (currentStatus != ThingStatus.OFFLINE) { + this.updateStatus(ThingStatus.ONLINE); + } } else { - this.updateStatus(ThingStatus.ONLINE); + if (currentStatus != ThingStatus.OFFLINE) { + this.updateStatus(ThingStatus.OFFLINE); + } } } - @SuppressWarnings("null") private void setRegistration(HueSyncRegistration registration) { this.stopTask(deviceRegistrationTask); addProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); - addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceInfo.apiLevel)); this.config.registrationId = registration.registrationId; this.config.apiAccessToken = registration.accessToken; @@ -146,7 +175,6 @@ private void addProperty(@NonNull String key, @Nullable String value) { properties.put(key, value); this.updateProperties(properties); - } } @@ -171,7 +199,7 @@ public void initialize() { }); this.checkCompatibility(); - this.registerDevice(); + this.startBackgroundTasks(); } catch (HueSyncApiException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { @@ -194,6 +222,7 @@ public void dispose() { this.connection.stop(); this.stopTask(deviceRegistrationTask); + this.stopTask(deviceUpdateTask); } catch (Exception e) { this.logger.error("{}", e.getMessage()); } finally { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 03d42292e99cb..239f855d74964 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -15,7 +15,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java new file mode 100644 index 0000000000000..c9b1fb2c3ce7d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.handler.tasks; + +import java.util.function.Consumer; + +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.slf4j.Logger; + +/** + * Task to handle device information update. + * + * @author Patrik Gfeller - Initial contribution + */ +public class HueSyncUpdateTask implements Runnable { + + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); + + private HueSyncConnection connection; + private HueSyncDeviceInfo deviceInfo; + + private Consumer action; + + public HueSyncUpdateTask(HueSyncConnection connection, HueSyncDeviceInfo deviceInfo, + Consumer action) { + + this.connection = connection; + this.deviceInfo = deviceInfo; + + this.action = action; + } + + @Override + public void run() { + try { + this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.deviceInfo.uniqueId); + + HueSyncDetailedDeviceInfo deviceStatus = this.connection.getDetailedDeviceInfo(); + + this.action.accept(deviceStatus); + } catch (Exception e) { + this.logger.debug("{}", e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 07bafe3439277..e16f6e0e1c57a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -19,6 +19,8 @@ thing-type.config.huesync.box.port.label = Port thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. thing-type.config.huesync.box.registrationId.label = Application Registration Id thing-type.config.huesync.box.registrationId.description = The id of the API registration. +thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval +thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. # api exceptions diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index ed633833caa33..b78371fd9c2a3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -33,7 +33,6 @@ 443 true - @@ -49,6 +48,13 @@ seconds to grant the binding the required permissions. true + + + Seconds between fetching values from the Hue Sync Box. + true + 10 + Seconds + From a76e4391dcb0a60c5aa850909f0c7d5a762897d9 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 12 Apr 2024 16:00:41 +0200 Subject: [PATCH 030/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../dto/HueSyncDeviceInfoCapabilities.java | 3 + .../dto/device/HueSyncDetailedDeviceInfo.java | 22 +++-- .../HueSyncDetailedDeviceInfoUpdate.java | 4 +- ...ava => HueSyncDetailedDeviceInfoWifi.java} | 16 +++- .../api/dto/device/HueSyncDeviceInfo.java | 22 +++-- .../dto/registration/HueSyncRegistration.java | 8 +- .../HueSyncRegistrationRequest.java | 8 +- .../connection/HueSyncConnection.java | 88 +++++++++++-------- .../exceptions/HueSyncApiException.java | 2 + .../internal/handler/HueSyncHandler.java | 68 +++++++------- .../tasks/HueSyncRegistrationTask.java | 2 + .../handler/tasks/HueSyncUpdateTask.java | 6 +- .../internal/i18n/HueSyncLocalizer.java | 23 ++--- .../internal/log/HueSyncLogFactory.java | 2 + 14 files changed, 170 insertions(+), 104 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeteiledDeviceInfoWifi.java => HueSyncDetailedDeviceInfoWifi.java} (71%) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java index b4337aa428e83..5cd5cd7e1940a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.huesync.internal.api.dto; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * HDMI Sync Box Device Information Cababilities DTO * @@ -21,6 +23,7 @@ * "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue * HDMI Sync Box API */ +@NonNullByDefault public class HueSyncDeviceInfoCapabilities { /** The total number of IR codes configurable */ public int maxIrCodes; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java index 7f116267bf646..79ad5f935d68a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java @@ -14,6 +14,7 @@ import java.util.Date; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** @@ -26,28 +27,31 @@ * "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue * HDMI Sync Box API */ +@NonNullByDefault public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo { - public HueSyncDeteiledDeviceInfoWifi wifi; - public HueSyncDetailedDeviceInfoUpdate update; + public @Nullable HueSyncDetailedDeviceInfoWifi wifi; + public @Nullable HueSyncDetailedDeviceInfoUpdate update; /** UTC time when last check for update was performed. */ - public Date lastCheckedUpdate; + public @Nullable Date lastCheckedUpdate; /** * Build number that is available to update to. Item is set to null when there * is no update available. */ - public @Nullable Integer updatableBuildNumber; + public int updatableBuildNumber; /** * User readable version of the firmware the device can upgrade to. Item is set * to null when there is no update available. */ - public @Nullable String updatableFirmwareVersion; + public int updatableFirmwareVersion; /** - * 1 = regular; 0 = off in powersave, passthrough or sync mode; 2 = dimmed in - * powersave or passthrough mode and off in sync mode + * 1 = regular; + * 0 = off in powersave, passthrough or sync mode; + * 2 = dimmed in powersave or passthrough mode and off in sync mode */ public int ledMode; + /** none, doSoftwareRestart, doFirmwareUpdate */ - public String action; - public String pushlink; + public @Nullable String action; + public @Nullable String pushlink; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java index 3173a079eaa28..87aefc36d94f1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.huesync.internal.api.dto.device; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * HDMI Sync Box Device Information DTO - Automatic Firmware update * @@ -21,7 +23,7 @@ * "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue * HDMI Sync Box API */ - +@NonNullByDefault public class HueSyncDetailedDeviceInfoUpdate { /** * Sync Box checks daily for a firmware update. If true, an available update diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java similarity index 71% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java index ab2dfe1217574..30ae3f8e2bc3a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeteiledDeviceInfoWifi.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.huesync.internal.api.dto.device; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * HDMI Sync Box Device Information DTO - Wifi connection information * @@ -21,9 +24,16 @@ * "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue * HDMI Sync Box API */ -public class HueSyncDeteiledDeviceInfoWifi { +@NonNullByDefault +public class HueSyncDetailedDeviceInfoWifi { /** Wifi SSID */ - public String ssid; - /** 0 = not connected; 1 = weak; 2 = fair; 3 = good; 4 = excellent */ + public @Nullable String ssid; + /** + * 0 = not connected; + * 1 = weak; + * 2 = fair; + * 3 = good; + * 4 = excellent + */ public int strength; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java index e7f5f8e770dfb..e229508d75475 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.huesync.internal.api.dto.device; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfoCapabilities; /** @@ -24,17 +25,18 @@ * "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue * HDMI Sync Box API */ +@NonNullByDefault public class HueSyncDeviceInfo { /** Friendly name of the device */ - public String name; + public @Nullable String name; /** Device Type identifier */ - public String deviceType; + public @Nullable String deviceType; /** * Capitalized hex string of the 6 byte / 12 characters device id without * delimiters. Used as unique id on label, certificate common name, hostname * etc. */ - public @NonNull String uniqueId = ""; + public @Nullable String uniqueId; /** * Increased between firmware versions when api changes. Only apiLevel >= 7 is * supported. @@ -44,17 +46,21 @@ public class HueSyncDeviceInfo { * User readable version of the device firmware, starting with decimal major * .minor .maintenance format e.g. “1.12.3” */ - public String firmwareVersion; + public @Nullable String firmwareVersion; /** * Build number of the firmware. Unique for every build with newer builds * guaranteed a higher number than older. */ public int buildNumber; + public boolean termsAgreed; + /** uninitialized, disconnected, lan, wan */ - public String wifiState; - public String ipAddress; - public HueSyncDeviceInfoCapabilities capabilities; + public @Nullable String wifiState; + public @Nullable String ipAddress; + + public @Nullable HueSyncDeviceInfoCapabilities capabilities; + public boolean beta; public boolean overheating; public boolean bluetooth; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java index 5444f93c9ac27..1dd7ed5b40b13 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.huesync.internal.api.dto.registration; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * * @author Patrik Gfeller - Initial Contribution */ +@NonNullByDefault public class HueSyncRegistration { - public String registrationId; - public String accessToken; + public @Nullable String registrationId; + public @Nullable String accessToken; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java index 1de01c1d22b28..d7651a6872f89 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java @@ -12,13 +12,17 @@ */ package org.openhab.binding.huesync.internal.api.dto.registration; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * * @author Patrik Gfeller - Initial Contribution */ +@NonNullByDefault public class HueSyncRegistrationRequest { /** User recognizable name of registered application */ - public String appName; + public @Nullable String appName; /** User recognizable name of application instance. */ - public String instanceName; + public @Nullable String instanceName; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 8af4cb8f43984..a631980a83918 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -18,7 +18,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -42,7 +41,6 @@ import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -50,11 +48,12 @@ * * @author Patrik Gfeller - Initial Contribution */ +@NonNullByDefault public class HueSyncConnection { /** * Request format: The Sync Box API can be accessed locally via HTTPS on root * level (port 443, /api/v1), resource level /api/v1/ and in some - * cases subresource level /api/v1//. + * cases sub-resource level /api/v1//. */ private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; private static final String API = "api/v1"; @@ -69,16 +68,15 @@ private static class ENDPOINTS { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); - private @NonNull HttpClient httpClient; - private @NonNull String host; - private @NonNull Integer port; + private HttpClient httpClient; + private String host; + private Integer port; private @Nullable String apiAccessToken; private @Nullable String registrationId; private ServiceRegistration tlsProviderService; - @NonNullByDefault public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, String registrationId) throws CertificateException, IOException { @@ -95,59 +93,57 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin this.httpClient = httpClient; } + @SuppressWarnings("null") public @Nullable HueSyncDeviceInfo getDeviceInfo() { HueSyncDeviceInfo deviceInfo = null; - try { - ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); - if (response.getStatus() == HttpStatus.OK_200) { - deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); - } - } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { - this.logger.error("{}", e.getMessage()); + if (this.responseReceived(response)) { + deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); } return deviceInfo; } + @SuppressWarnings("null") public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { if (!this.isRegistered()) return null; HueSyncDetailedDeviceInfo deviceInfo = null; - try { - ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); - if (response.getStatus() == HttpStatus.OK_200) { - deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class); - } - } catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) { - this.logger.error("{}", e.getMessage()); + if (this.responseReceived(response)) { + deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class); } return deviceInfo; } - public @Nullable HueSyncRegistration registerDevice(String id) { + public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { HueSyncRegistration registration = null; try { - HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + if (id != null) { + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); - dto.appName = HueSyncConstants.APPLICATION_NAME; - dto.instanceName = id; + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = id; - String json = ObjectMapper.writeValueAsString(dto); + String json = ObjectMapper.writeValueAsString(dto); - ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json); + ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json); - if (response.getStatus() == HttpStatus.OK_200) { - registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); + if (this.responseReceived(response)) { + registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); - this.apiAccessToken = registration.accessToken; - this.registrationId = registration.registrationId; + if (registration != null) { + this.apiAccessToken = registration.accessToken; + this.registrationId = registration.registrationId; + } + } } } catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) { this.logger.error("{}", e.getMessage()); @@ -182,15 +178,35 @@ public boolean isRegistered() { // #region - private - private T deserialize(String json, Class type) throws JsonMappingException, JsonProcessingException { - return ObjectMapper.readValue(json, type); + private boolean responseReceived(@Nullable ContentResponse response) { + if (response != null && response.getStatus() == HttpStatus.OK_200) { + return true; + } else { + // TODO: responseReceived - log error details ... + return false; + } + } + + private @Nullable T deserialize(String json, Class type) { + try { + return ObjectMapper.readValue(json, type); + } catch (JsonProcessingException | NoClassDefFoundError e) { + this.logger.error("{}", e.getMessage()); + + return null; + } } - private ContentResponse executeGetRequest(String endpoint) - throws InterruptedException, ExecutionException, TimeoutException { + private @Nullable ContentResponse executeGetRequest(String endpoint) { String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - return httpClient.GET(uri); + try { + return httpClient.GET(uri); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.logger.error("Endpoint: {} - {}", endpoint, e.getMessage()); + + return null; + } } private ContentResponse executeRequest(HttpMethod method, String endpoint) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index 8f021fea7ba07..a0cad91c1bb3e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -12,12 +12,14 @@ */ package org.openhab.binding.huesync.internal.exceptions; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; /** * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncApiException extends HueSyncException { public HueSyncApiException(String message, Logger logger) { super(message, logger); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index acba13bfb6017..92b805e2d9037 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -19,7 +19,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; @@ -48,6 +48,7 @@ * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncHandler extends BaseThingHandler { private static final String PROPERTY_API_VERSION = "apiVersion"; private static final String PROPERTY_NETWORK_STATE = "networkState"; @@ -59,8 +60,8 @@ public class HueSyncHandler extends BaseThingHandler { private @Nullable HueSyncDeviceInfo deviceInfo; - private @NonNull HueSyncConnection connection; - private @NonNull HueSyncConfiguration config; + private HueSyncConnection connection; + private HueSyncConfiguration config; protected class HueSyncProperties { @@ -93,10 +94,11 @@ private void stopTask(@Nullable ScheduledFuture task) { return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); } + // TODO: Remove "null" annotation ... @SuppressWarnings({ "unchecked", "null" }) private void startBackgroundTasks() { Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, this.deviceInfo, - (deviceState) -> this.updateDeviceState(deviceState)); + (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); if (this.connection.isRegistered()) { this.logger.debug("Device {} {}:{} is already registered", this.deviceInfo.name, this.deviceInfo.deviceType, @@ -127,39 +129,38 @@ private void startUpdateTask(Runnable updateTask) { this.config.statusUpdateInterval); } - private void updateDeviceState(HueSyncDetailedDeviceInfo deviceState) { + private void updateDeviceStatus(HueSyncDetailedDeviceInfo deviceState) { ThingStatus currentStatus = this.thing.getStatus(); + logger.trace("Current status: {}", currentStatus); - if (deviceState != null) { - if (currentStatus != ThingStatus.OFFLINE) { - this.updateStatus(ThingStatus.ONLINE); - } - } else { - if (currentStatus != ThingStatus.OFFLINE) { - this.updateStatus(ThingStatus.OFFLINE); - } + if (currentStatus != ThingStatus.OFFLINE) { + this.updateStatus(ThingStatus.ONLINE); } } private void setRegistration(HueSyncRegistration registration) { - this.stopTask(deviceRegistrationTask); + if (registration.registrationId != null && registration.accessToken != null) { - addProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); + this.stopTask(deviceRegistrationTask); + + addProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); - this.config.registrationId = registration.registrationId; - this.config.apiAccessToken = registration.accessToken; + Configuration configuration = this.editConfiguration(); - Configuration configuration = this.editConfiguration(); + configuration.put(HueSyncConstants.REGISTRATION_ID, this.config.registrationId); + configuration.put(HueSyncConstants.API_TOKEN, this.config.apiAccessToken); - configuration.put(HueSyncConstants.REGISTRATION_ID, this.config.registrationId); - configuration.put(HueSyncConstants.API_TOKEN, this.config.apiAccessToken); + this.updateConfiguration(configuration); + this.updateStatus(ThingStatus.ONLINE); - this.updateConfiguration(configuration); - this.updateStatus(ThingStatus.ONLINE); + this.logger.info("Device registration for {} complete - Id: {}", + this.deviceInfo != null + ? this.deviceInfo.name != null ? this.deviceInfo.name : "⚠️ unknown device ⚠️" + : "⚠️ unknown device ⚠️", - this.logger.info("Device registration for {} complete - Id: {}", this.deviceInfo.name, - registration.registrationId); + registration.registrationId); + } } private void checkCompatibility() throws HueSyncApiException { @@ -168,9 +169,9 @@ private void checkCompatibility() throws HueSyncApiException { } } - private void addProperty(@NonNull String key, @Nullable String value) { + private void addProperty(String key, @Nullable String value) { if (value != null) { - Map<@NonNull String, @NonNull String> properties = this.editProperties(); + Map properties = this.editProperties(); properties.put(key, value); @@ -190,12 +191,15 @@ public void initialize() { this.deviceInfo = this.connection.getDeviceInfo(); Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { - addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); - addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); - addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); - - addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); - addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); + // Redundant null check required to avoid warning during build/development ... + if (info != null) { + addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); + addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); + addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + + addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); + } }); this.checkCompatibility(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 239f855d74964..11f1582d2d223 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -15,6 +15,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; @@ -27,6 +28,7 @@ * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncRegistrationTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index c9b1fb2c3ce7d..79bb60ffe1816 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -14,6 +14,7 @@ import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; @@ -25,6 +26,7 @@ * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncUpdateTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); @@ -51,7 +53,9 @@ public void run() { HueSyncDetailedDeviceInfo deviceStatus = this.connection.getDetailedDeviceInfo(); - this.action.accept(deviceStatus); + if (deviceStatus != null) + this.action.accept(deviceStatus); + } catch (Exception e) { this.logger.debug("{}", e.getMessage()); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 94cd67b2bdb41..9cac796ab9d60 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -14,9 +14,10 @@ import java.util.Locale; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -25,24 +26,26 @@ * * @author Patrik Gfeller - Initial Contribution */ +@NonNullByDefault public class HueSyncLocalizer { private static final Locale locale = Locale.ENGLISH; private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); - private static final @Nullable ServiceReference<@NonNull TranslationProvider> serviceReference = bundleContext != null - ? bundleContext.getServiceReference(TranslationProvider.class) - : null; - - private static final TranslationProvider translationProvider = serviceReference != null - ? bundleContext.getService(serviceReference) - : null; + private static final ServiceReference serviceReference = bundleContext + .getServiceReference(TranslationProvider.class); public static String getResourceString(String key) { key = key.replace("@text/", ""); - return translationProvider != null ? translationProvider.getText(bundleContext.getBundle(), key, key, locale) - : key; + Bundle bundle = bundleContext.getBundle(); + @Nullable + TranslationProvider translationProvider = bundleContext.getService(serviceReference); + + String text = translationProvider != null ? translationProvider.getText(bundle, key, key, locale) : key; + + // TODO: Add log message in case of translation problem ... + return text != null ? text : key; } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java index f62d183ec96cd..64c20e3222785 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.huesync.internal.log; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +22,7 @@ * * @author Patrik Gfeller - Initial contribution */ +@NonNullByDefault public class HueSyncLogFactory { public static Logger getLogger(Class clazz) { From 072bee7837dd442abb4e40fc3d0482a75047c5f0 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 12 Apr 2024 16:35:06 +0200 Subject: [PATCH 031/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../exceptions/HueSyncTaskException.java | 27 ++++++++ .../internal/handler/HueSyncHandler.java | 63 ++++++++++--------- .../internal/i18n/HueSyncLocalizer.java | 3 +- 3 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java new file mode 100644 index 0000000000000..c18461c7063c3 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncTaskException extends HueSyncException { + public HueSyncTaskException(String message, Logger logger) { + super(message, logger); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 92b805e2d9037..914bd01e8b39c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -94,32 +94,34 @@ private void stopTask(@Nullable ScheduledFuture task) { return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); } - // TODO: Remove "null" annotation ... - @SuppressWarnings({ "unchecked", "null" }) + @SuppressWarnings("unchecked") private void startBackgroundTasks() { - Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, this.deviceInfo, - (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); - - if (this.connection.isRegistered()) { - this.logger.debug("Device {} {}:{} is already registered", this.deviceInfo.name, this.deviceInfo.deviceType, - this.deviceInfo.uniqueId); - - this.startUpdateTask(statusUpdateTask); - } else { - this.logger.info("Starting device registration for {} {}:{}", this.deviceInfo.name, - this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "@text/thing.config.huesync.box.registration"); - - Runnable task = new HueSyncRegistrationTask(connection, deviceInfo, () -> thing.getStatus(), - (registration) -> { - this.setRegistration(registration); - this.startUpdateTask(statusUpdateTask); - }); - - this.deviceRegistrationTask = (ScheduledFuture) this.executeTask(task, - HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_INTERVAL); + if (this.deviceInfo != null) { + HueSyncDeviceInfo device = this.deviceInfo; + Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, + (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); + + if (this.connection.isRegistered()) { + this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, + device.uniqueId); + + this.startUpdateTask(statusUpdateTask); + } else { + this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, + device.uniqueId); + + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "@text/thing.config.huesync.box.registration"); + + Runnable task = new HueSyncRegistrationTask(connection, device, () -> thing.getStatus(), + (registration) -> { + this.setRegistration(registration); + this.startUpdateTask(statusUpdateTask); + }); + + this.deviceRegistrationTask = (ScheduledFuture) this.executeTask(task, + HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_INTERVAL); + } } } @@ -200,12 +202,15 @@ public void initialize() { addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); } + + try { + this.checkCompatibility(); + this.startBackgroundTasks(); + } catch (HueSyncApiException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } }); - this.checkCompatibility(); - this.startBackgroundTasks(); - } catch (HueSyncApiException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Exception e) { this.logger.error("{}", e.getMessage()); this.updateStatus(ThingStatus.OFFLINE); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 9cac796ab9d60..b2d88ee9ab55d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -40,10 +40,11 @@ public static String getResourceString(String key) { key = key.replace("@text/", ""); Bundle bundle = bundleContext.getBundle(); + @Nullable TranslationProvider translationProvider = bundleContext.getService(serviceReference); - String text = translationProvider != null ? translationProvider.getText(bundle, key, key, locale) : key; + String text = translationProvider.getText(bundle, key, key, locale); // TODO: Add log message in case of translation problem ... return text != null ? text : key; From cb0c86c4e78e51b93e0aceadefce9506b652db4c Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 12 Apr 2024 16:44:20 +0200 Subject: [PATCH 032/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/handler/HueSyncHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 914bd01e8b39c..897e9239ee59f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -97,7 +97,8 @@ private void stopTask(@Nullable ScheduledFuture task) { @SuppressWarnings("unchecked") private void startBackgroundTasks() { if (this.deviceInfo != null) { - HueSyncDeviceInfo device = this.deviceInfo; + HueSyncDeviceInfo device = (HueSyncDeviceInfo) this.deviceInfo; + Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); From 4dd836e5b3769268929eeaa6663206e07c26165a Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 12 Apr 2024 16:54:08 +0200 Subject: [PATCH 033/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/handler/HueSyncHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 897e9239ee59f..57bd3ba4991e9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -99,7 +99,7 @@ private void startBackgroundTasks() { if (this.deviceInfo != null) { HueSyncDeviceInfo device = (HueSyncDeviceInfo) this.deviceInfo; - Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, + Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, (HueSyncDeviceInfo) device, (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); if (this.connection.isRegistered()) { From 4e1a812732a7e812f5faaa58f0cfac5b3cc7b614 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Mon, 15 Apr 2024 23:28:51 +0200 Subject: [PATCH 034/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20code=20split=20(single=20response)=20and=20better=20encapsul?= =?UTF-8?q?ation=20to=20simplify=20null=20handling=20and=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../connection/HueSyncConnection.java | 290 ++++++++++-------- .../internal/handler/HueSyncHandler.java | 10 +- .../handler/tasks/HueSyncUpdateTask.java | 8 +- 3 files changed, 172 insertions(+), 136 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index a631980a83918..b2f221c69c098 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.security.cert.CertificateException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -50,189 +49,224 @@ */ @NonNullByDefault public class HueSyncConnection { + /** - * Request format: The Sync Box API can be accessed locally via HTTPS on root - * level (port 443, /api/v1), resource level /api/v1/ and in some - * cases sub-resource level /api/v1//. + * + * @author Patrik Gfeller - Initial Contribution */ - private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; - private static final String API = "api/v1"; - private static final Integer TIMEOUT_MILLISECONDS = 500; - - private static class ENDPOINTS { - public static final String DEVICE = "device"; - public static final String REGISTRATIONS = "registrations"; - } + protected class HueSyncConnectionHelper { + private static class ENDPOINTS { + public static final String DEVICE = "device"; + public static final String REGISTRATIONS = "registrations"; + } - private static final ObjectMapper ObjectMapper = new ObjectMapper(); + /** + * Request format: The Sync Box API can be accessed locally via HTTPS on root + * level (port 443, /api/v1), resource level /api/v1/ and in some + * cases sub-resource level /api/v1//. + */ + private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; + private static final String API = "api/v1"; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); + private Integer port; + private String host; - private HttpClient httpClient; - private String host; - private Integer port; + private ServiceRegistration tlsProviderService; + private HttpClient httpClient; - private @Nullable String apiAccessToken; - private @Nullable String registrationId; + protected static final ObjectMapper ObjectMapper = new ObjectMapper(); - private ServiceRegistration tlsProviderService; + protected String apiAccessToken; + protected String registrationId; - public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, - String registrationId) throws CertificateException, IOException { + public HueSyncConnectionHelper(HttpClient httpClient, String host, Integer port, String apiAccessToken, + String registrationId) throws CertificateException, IOException { - this.host = host; - this.port = port; - this.apiAccessToken = apiAccessToken; - this.registrationId = registrationId; + this.host = host; + this.port = port; + this.apiAccessToken = apiAccessToken; + this.registrationId = registrationId; - HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); - BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); + BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); - this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, - null); - this.httpClient = httpClient; - } + this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), + trustManagerProvider, null); + this.httpClient = httpClient; + } - @SuppressWarnings("null") - public @Nullable HueSyncDeviceInfo getDeviceInfo() { - HueSyncDeviceInfo deviceInfo = null; + protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload, Class type) { + try { + ContentResponse response = this.executeRequest(method, endpoint, payload); - ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + if (response != null) { + return processedResponse(response, type); + } - if (this.responseReceived(response)) { - deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); + } + return null; } - return deviceInfo; - } - - @SuppressWarnings("null") - public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { - if (!this.isRegistered()) - return null; + protected @Nullable T executeGetRequest(String endpoint, Class type) { + try { + ContentResponse response = this.executeGetRequest(endpoint); - HueSyncDetailedDeviceInfo deviceInfo = null; + if (response != null) { + return processedResponse(response, type); + } - ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); + } - if (this.responseReceived(response)) { - deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class); + return null; } - return deviceInfo; - } - - public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { - HueSyncRegistration registration = null; - - try { - if (id != null) { - HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); - - dto.appName = HueSyncConstants.APPLICATION_NAME; - dto.instanceName = id; + protected boolean isRegistered() { + if (this.apiAccessToken.isBlank() || this.registrationId.isBlank()) { + return false; + } - String json = ObjectMapper.writeValueAsString(dto); + return true; + } - ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json); + protected boolean unregisterDevice() { + if (this.isRegistered()) { + try { + String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; + ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); - if (this.responseReceived(response)) { - registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class); + if (response.getStatus() == HttpStatus.OK_200) { + this.registrationId = ""; + this.apiAccessToken = ""; - if (registration != null) { - this.apiAccessToken = registration.accessToken; - this.registrationId = registration.registrationId; + return true; } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + this.logger.error("{}", e.getMessage()); } } - } catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) { - this.logger.error("{}", e.getMessage()); + return false; } - return registration; - } + protected void stop() { + this.tlsProviderService.unregister(); + } - public boolean unregisterDevice() { - if (this.isRegistered()) { - try { - String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; - ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); + private @Nullable T processedResponse(ContentResponse response, Class type) { + switch (response.getStatus()) { + case HttpStatus.OK_200: + return this.deserialize(response.getContentAsString(), type); + default: + logger.warn("HTTP Status: {}", response.getStatus()); + return null; + } + } - return response.getStatus() == HttpStatus.OK_200; - } catch (InterruptedException | TimeoutException | ExecutionException e) { + private @Nullable T deserialize(String json, Class type) { + try { + return ObjectMapper.readValue(json, type); + } catch (JsonProcessingException | NoClassDefFoundError e) { this.logger.error("{}", e.getMessage()); + + return null; } } - return false; - } - public void stop() { - this.tlsProviderService.unregister(); - } - - @SuppressWarnings("null") - public boolean isRegistered() { - return this.apiAccessToken != null && this.registrationId != null && !this.apiAccessToken.isBlank() - && !this.registrationId.isBlank(); - } + private ContentResponse executeRequest(HttpMethod method, String endpoint) + throws InterruptedException, TimeoutException, ExecutionException { + return this.executeRequest(method, endpoint, ""); + } - // #region - private + private ContentResponse executeGetRequest(String endpoint) + throws InterruptedException, ExecutionException, TimeoutException { + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - private boolean responseReceived(@Nullable ContentResponse response) { - if (response != null && response.getStatus() == HttpStatus.OK_200) { - return true; - } else { - // TODO: responseReceived - log error details ... - return false; + return httpClient.GET(uri); } - } - private @Nullable T deserialize(String json, Class type) { - try { - return ObjectMapper.readValue(json, type); - } catch (JsonProcessingException | NoClassDefFoundError e) { - this.logger.error("{}", e.getMessage()); + private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) + throws InterruptedException, TimeoutException, ExecutionException { - return null; - } - } + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - private @Nullable ContentResponse executeGetRequest(String endpoint) { - String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); + Request request = this.httpClient.newRequest(uri).method(method); - try { - return httpClient.GET(uri); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - this.logger.error("Endpoint: {} - {}", endpoint, e.getMessage()); + if (!payload.isBlank()) { + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) + .content(new StringContentProvider(payload)); + } - return null; + if (this.isRegistered()) { + request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.apiAccessToken); + } + + return request.send(); } + }; + + private HueSyncConnectionHelper helper; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); + + public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, + String registrationId) throws CertificateException, IOException { + + this.helper = new HueSyncConnectionHelper(httpClient, host, port, apiAccessToken, registrationId); } - private ContentResponse executeRequest(HttpMethod method, String endpoint) - throws InterruptedException, TimeoutException, ExecutionException { - return this.executeRequest(method, endpoint, ""); + public @Nullable HueSyncDeviceInfo getDeviceInfo() { + return this.helper.executeGetRequest(HueSyncConnectionHelper.ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); } - private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) - throws InterruptedException, TimeoutException, ExecutionException { + public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { + if (id == null || id.isBlank()) { + return null; + } - String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); - Request request = this.httpClient.newRequest(uri).method(method); + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = id; - if (!payload.isBlank()) { - request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) - .content(new StringContentProvider(payload)); + try { + String json = HueSyncConnectionHelper.ObjectMapper.writeValueAsString(dto); + HueSyncRegistration registration = this.helper.executeRequest(HttpMethod.POST, + HueSyncConnectionHelper.ENDPOINTS.REGISTRATIONS, json, HueSyncRegistration.class); + + if (registration != null) { + this.helper.registrationId = registration.registrationId != null ? registration.registrationId : ""; + this.helper.apiAccessToken = registration.accessToken != null ? registration.accessToken : ""; + } + + return registration; + } catch (JsonProcessingException e) { + this.logger.error("{}", e.getMessage()); } - if (this.apiAccessToken != null && !this.apiAccessToken.isBlank()) { - // TODO: Check if we can use httpClient.setAuthenticationStore ... - request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.apiAccessToken); + return null; + } + + public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { + if (this.helper.isRegistered()) { + return this.helper.executeRequest(HttpMethod.GET, HueSyncConnectionHelper.ENDPOINTS.DEVICE, "", + HueSyncDetailedDeviceInfo.class); } - return request.timeout(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS).send(); + return null; + } + + public boolean isRegistered() { + return this.helper.isRegistered(); } - // #endregion + public boolean unregisterDevice() { + return this.helper.unregisterDevice(); + } + + public void stop() { + this.helper.stop(); + } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 57bd3ba4991e9..b8965ec48b9ac 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -97,9 +97,9 @@ private void stopTask(@Nullable ScheduledFuture task) { @SuppressWarnings("unchecked") private void startBackgroundTasks() { if (this.deviceInfo != null) { - HueSyncDeviceInfo device = (HueSyncDeviceInfo) this.deviceInfo; + HueSyncDeviceInfo device = this.deviceInfo; - Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, (HueSyncDeviceInfo) device, + Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); if (this.connection.isRegistered()) { @@ -132,12 +132,14 @@ private void startUpdateTask(Runnable updateTask) { this.config.statusUpdateInterval); } - private void updateDeviceStatus(HueSyncDetailedDeviceInfo deviceState) { + private void updateDeviceStatus(@Nullable HueSyncDetailedDeviceInfo deviceState) { ThingStatus currentStatus = this.thing.getStatus(); logger.trace("Current status: {}", currentStatus); - if (currentStatus != ThingStatus.OFFLINE) { + if (deviceState == null) { + this.updateStatus(ThingStatus.OFFLINE); + } else { this.updateStatus(ThingStatus.ONLINE); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 79bb60ffe1816..1b70be8ac5e5d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -15,6 +15,7 @@ import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.connection.HueSyncConnection; @@ -34,10 +35,10 @@ public class HueSyncUpdateTask implements Runnable { private HueSyncConnection connection; private HueSyncDeviceInfo deviceInfo; - private Consumer action; + private Consumer<@Nullable HueSyncDetailedDeviceInfo> action; public HueSyncUpdateTask(HueSyncConnection connection, HueSyncDeviceInfo deviceInfo, - Consumer action) { + Consumer<@Nullable HueSyncDetailedDeviceInfo> action) { this.connection = connection; this.deviceInfo = deviceInfo; @@ -53,8 +54,7 @@ public void run() { HueSyncDetailedDeviceInfo deviceStatus = this.connection.getDetailedDeviceInfo(); - if (deviceStatus != null) - this.action.accept(deviceStatus); + this.action.accept(deviceStatus); } catch (Exception e) { this.logger.debug("{}", e.getMessage()); From 5e60e9797be0a857162167c3cec82598465d03c8 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Mon, 15 Apr 2024 23:57:02 +0200 Subject: [PATCH 035/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review=20..?= =?UTF-8?q?.=20=F0=9F=A4=94=20-=20wip=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../internal/connection/HueSyncConnection.java | 12 ++++++++++-- .../huesync/internal/handler/HueSyncHandler.java | 15 +++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index b2f221c69c098..ce16c9b712eae 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -237,8 +237,16 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin HueSyncConnectionHelper.ENDPOINTS.REGISTRATIONS, json, HueSyncRegistration.class); if (registration != null) { - this.helper.registrationId = registration.registrationId != null ? registration.registrationId : ""; - this.helper.apiAccessToken = registration.accessToken != null ? registration.accessToken : ""; + String registrationId = ""; + String registrationToken = ""; + + if (registration.registrationId != null) + registrationId = registration.registrationId; + if (registration.accessToken != null) + registrationToken = registration.accessToken; + + this.helper.registrationId = registrationId; + this.helper.apiAccessToken = registrationToken; } return registration; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index b8965ec48b9ac..9bf35dab6733a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -96,20 +96,19 @@ private void stopTask(@Nullable ScheduledFuture task) { @SuppressWarnings("unchecked") private void startBackgroundTasks() { - if (this.deviceInfo != null) { - HueSyncDeviceInfo device = this.deviceInfo; - + Optional.ofNullable(this.deviceInfo).ifPresent((device) -> { Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); if (this.connection.isRegistered()) { - this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, - device.uniqueId); + // TODO: Create helper to log nullable strings ... + // this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, + // device.uniqueId); this.startUpdateTask(statusUpdateTask); } else { - this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, - device.uniqueId); + // this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, + // device.uniqueId); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/thing.config.huesync.box.registration"); @@ -123,7 +122,7 @@ private void startBackgroundTasks() { this.deviceRegistrationTask = (ScheduledFuture) this.executeTask(task, HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_INTERVAL); } - } + }); } @SuppressWarnings("unchecked") From 048ce86fc4c20368ee7efd688adfba8bbd6b30a8 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 18 Apr 2024 10:49:21 +0200 Subject: [PATCH 036/128] =?UTF-8?q?refactor(code=20review):=20=F0=9F=94=8E?= =?UTF-8?q?=20improved=20null=20handling=20based=20on=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 1 + .../huesync/internal/HueSyncConstants.java | 5 ++ .../dto/device/HueSyncDetailedDeviceInfo.java | 2 +- .../api/dto/device/HueSyncDeviceInfo.java | 4 +- .../connection/HueSyncConnection.java | 49 ++++++--------- .../HueSyncDiscoveryParticipant.java | 25 ++++---- .../exceptions/HueSyncApiException.java | 2 + .../internal/exceptions/HueSyncException.java | 13 +++- .../exceptions/HueSyncTaskException.java | 2 + .../factory/HueSyncHandlerFactory.java | 1 - .../internal/handler/HueSyncHandler.java | 60 ++++++++++--------- .../internal/i18n/HueSyncLocalizer.java | 22 ++++--- .../src/main/resources/OH-INF/addon/addon.xml | 1 + .../main/resources/OH-INF/config/config.xml | 49 +++++++++++++++ .../resources/OH-INF/i18n/huesync.properties | 20 +++++++ .../OH-INF/thing/thing-types-channels.xml | 28 +++++++++ 16 files changed, 190 insertions(+), 94 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index f61eb05cd2e36..e37c77837f4eb 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -82,6 +82,7 @@ _*.sitemap examples are optional._ ```java Example thing configuration goes here. ``` + ### Item Configuration ```java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 83d32a255e81c..7afc0819d48fd 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -23,6 +23,11 @@ */ @NonNullByDefault public class HueSyncConstants { + public static class ENDPOINTS { + public static final String DEVICE = "device"; + public static final String REGISTRATIONS = "registrations"; + } + public static final String APPLICATION_NAME = "openHAB"; /** Minimal API Version required. Only apiLevel >= 7 is supported. */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java index 79ad5f935d68a..0c025eb9b9146 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java @@ -49,7 +49,7 @@ public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo { * 0 = off in powersave, passthrough or sync mode; * 2 = dimmed in powersave or passthrough mode and off in sync mode */ - public int ledMode; + public int ledMode = -1; /** none, doSoftwareRestart, doFirmwareUpdate */ public @Nullable String action; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java index e229508d75475..27b0434f7e81b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java @@ -41,7 +41,7 @@ public class HueSyncDeviceInfo { * Increased between firmware versions when api changes. Only apiLevel >= 7 is * supported. */ - public int apiLevel; + public int apiLevel = 0; /** * User readable version of the device firmware, starting with decimal major * .minor .maintenance format e.g. “1.12.3” @@ -51,7 +51,7 @@ public class HueSyncDeviceInfo { * Build number of the firmware. Unique for every build with newer builds * guaranteed a higher number than older. */ - public int buildNumber; + public int buildNumber = 0; public boolean termsAgreed; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index ce16c9b712eae..df3ce80d78bc3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.security.cert.CertificateException; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -28,6 +29,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; @@ -55,11 +57,6 @@ public class HueSyncConnection { * @author Patrik Gfeller - Initial Contribution */ protected class HueSyncConnectionHelper { - private static class ENDPOINTS { - public static final String DEVICE = "device"; - public static final String REGISTRATIONS = "registrations"; - } - /** * Request format: The Sync Box API can be accessed locally via HTTPS on root * level (port 443, /api/v1), resource level /api/v1/ and in some @@ -100,10 +97,7 @@ public HueSyncConnectionHelper(HttpClient httpClient, String host, Integer port, try { ContentResponse response = this.executeRequest(method, endpoint, payload); - if (response != null) { - return processedResponse(response, type); - } - + return processedResponse(response, type); } catch (InterruptedException | ExecutionException | TimeoutException e) { this.logger.error("{}", e.getMessage()); } @@ -114,10 +108,7 @@ public HueSyncConnectionHelper(HttpClient httpClient, String host, Integer port, try { ContentResponse response = this.executeGetRequest(endpoint); - if (response != null) { - return processedResponse(response, type); - } - + return processedResponse(response, type); } catch (InterruptedException | ExecutionException | TimeoutException e) { this.logger.error("{}", e.getMessage()); } @@ -218,7 +209,7 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin } public @Nullable HueSyncDeviceInfo getDeviceInfo() { - return this.helper.executeGetRequest(HueSyncConnectionHelper.ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); + return this.helper.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); } public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { @@ -233,22 +224,17 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin try { String json = HueSyncConnectionHelper.ObjectMapper.writeValueAsString(dto); - HueSyncRegistration registration = this.helper.executeRequest(HttpMethod.POST, - HueSyncConnectionHelper.ENDPOINTS.REGISTRATIONS, json, HueSyncRegistration.class); - - if (registration != null) { - String registrationId = ""; - String registrationToken = ""; - - if (registration.registrationId != null) - registrationId = registration.registrationId; - if (registration.accessToken != null) - registrationToken = registration.accessToken; - - this.helper.registrationId = registrationId; - this.helper.apiAccessToken = registrationToken; - } - + HueSyncRegistration registration = this.helper.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, + json, HueSyncRegistration.class); + + Optional.ofNullable(registration).ifPresent((obj) -> { + Optional.ofNullable(obj.registrationId).ifPresent((regId) -> { + this.helper.registrationId = regId; + }); + Optional.ofNullable(obj.accessToken).ifPresent((regToken) -> { + this.helper.apiAccessToken = regToken; + }); + }); return registration; } catch (JsonProcessingException e) { this.logger.error("{}", e.getMessage()); @@ -259,8 +245,7 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { if (this.helper.isRegistered()) { - return this.helper.executeRequest(HttpMethod.GET, HueSyncConnectionHelper.ENDPOINTS.DEVICE, "", - HueSyncDetailedDeviceInfo.class); + return this.helper.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class); } return null; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 2b982670666ff..f8225e6d4452d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import javax.jmdns.ServiceInfo; @@ -48,7 +49,6 @@ @NonNullByDefault @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { - @SuppressWarnings("null") private Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); /** @@ -71,7 +71,6 @@ public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) this.thingRegistry = thingRegistry; } - @SuppressWarnings("null") @Override public Set getSupportedThingTypeUIDs() { return Collections.singleton(HueSyncConstants.THING_TYPE_UID); @@ -83,7 +82,6 @@ public String getServiceType() { } @Override - @SuppressWarnings("null") public @Nullable DiscoveryResult createResult(ServiceInfo service) { if (this.autoDiscoveryEnabled) { ThingUID uid = getThingUID(service); @@ -101,9 +99,8 @@ public String getServiceType() { .withProperties(properties).build(); return result; } catch (Exception e) { - // TODO Handle exception - // logger.error("Unable to query device information for {}: {}", - // service.getQualifiedName(), e); + logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), + e.getMessage()); } } } @@ -134,19 +131,19 @@ protected void modified(ComponentContext componentContext) { updateService(componentContext); } - @SuppressWarnings("null") private void updateService(ComponentContext componentContext) { Dictionary properties = componentContext.getProperties(); String autoDiscoveryPropertyValue = (String) properties .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); - - if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) { - boolean value = Boolean.valueOf(autoDiscoveryPropertyValue); - if (value != this.autoDiscoveryEnabled) { - logger.debug("{} update: {} ➡️ {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, - autoDiscoveryPropertyValue, value); - this.autoDiscoveryEnabled = value; + if (Optional.ofNullable(autoDiscoveryPropertyValue).isPresent()) { + if (!autoDiscoveryPropertyValue.isBlank()) { + boolean value = Boolean.valueOf(autoDiscoveryPropertyValue); + if (value != this.autoDiscoveryEnabled) { + logger.debug("{} update: {} ➡️ {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, + autoDiscoveryPropertyValue, value); + this.autoDiscoveryEnabled = value; + } } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index a0cad91c1bb3e..29f300beda4f1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -21,6 +21,8 @@ */ @NonNullByDefault public class HueSyncApiException extends HueSyncException { + private static final long serialVersionUID = 0L; + public HueSyncApiException(String message, Logger logger) { super(message, logger); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java index 531fc716547f9..3c7a2acb60805 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.huesync.internal.exceptions; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; import org.slf4j.Logger; @@ -20,20 +23,24 @@ * * @author Patrik Gfeller - Initial Contribution */ +@NonNullByDefault public abstract class HueSyncException extends Exception { - private String key; + private static final long serialVersionUID = 0L; + + private Optional key = Optional.empty(); public HueSyncException(String message, Logger logger) { super(message); if (message.startsWith("@text")) { - key = message; + key = Optional.of(message); } logger.error("{}", this.getLogMessage()); } private String getLogMessage() { - return this.key == null ? this.getMessage() : HueSyncLocalizer.getResourceString(key); + Optional message = Optional.ofNullable(this.getMessage()); + return key.isPresent() ? HueSyncLocalizer.getResourceString(key.get()) : message.orElse(""); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java index c18461c7063c3..c0f5618735833 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java @@ -21,6 +21,8 @@ */ @NonNullByDefault public class HueSyncTaskException extends HueSyncException { + private static final long serialVersionUID = 0L; + public HueSyncTaskException(String message, Logger logger) { super(message, logger); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index 77c3c4c309ec1..eecaefa35bcba 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -47,7 +47,6 @@ public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactor this.httpClientFactory = httpClientFactory; } - @SuppressWarnings("null") private static final Set SUPPORTED_THING_TYPES_UIDS = Collections .singleton(HueSyncConstants.THING_TYPE_UID); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 9bf35dab6733a..d4921acabd8ea 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.security.cert.CertificateException; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -101,14 +102,13 @@ private void startBackgroundTasks() { (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); if (this.connection.isRegistered()) { - // TODO: Create helper to log nullable strings ... - // this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, - // device.uniqueId); + this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, + device.uniqueId); this.startUpdateTask(statusUpdateTask); } else { - // this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, - // device.uniqueId); + this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, + device.uniqueId); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/thing.config.huesync.box.registration"); @@ -144,32 +144,38 @@ private void updateDeviceStatus(@Nullable HueSyncDetailedDeviceInfo deviceState) } private void setRegistration(HueSyncRegistration registration) { - if (registration.registrationId != null && registration.accessToken != null) { + Optional id = Optional.ofNullable(registration.registrationId); + Optional token = Optional.ofNullable(registration.accessToken); + if (id.isPresent() && token.isPresent()) { this.stopTask(deviceRegistrationTask); - addProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); + addProperty(HueSyncConstants.REGISTRATION_ID, id.get()); Configuration configuration = this.editConfiguration(); - - configuration.put(HueSyncConstants.REGISTRATION_ID, this.config.registrationId); - configuration.put(HueSyncConstants.API_TOKEN, this.config.apiAccessToken); - + configuration.put(HueSyncConstants.REGISTRATION_ID, id.get()); + configuration.put(HueSyncConstants.REGISTRATION_ID, token.get()); this.updateConfiguration(configuration); - this.updateStatus(ThingStatus.ONLINE); - this.logger.info("Device registration for {} complete - Id: {}", - this.deviceInfo != null - ? this.deviceInfo.name != null ? this.deviceInfo.name : "⚠️ unknown device ⚠️" - : "⚠️ unknown device ⚠️", + String deviceName = "⚠️ unknown device ⚠️"; + Optional deviceInfo = Optional.ofNullable(this.deviceInfo); + + if (deviceInfo.isPresent()) { + deviceName = Optional.ofNullable(deviceInfo.get().name).orElse(deviceName); + } - registration.registrationId); + this.logger.info("Device registration for {} complete - Id: {}", deviceName, id.get()); } } private void checkCompatibility() throws HueSyncApiException { - if (this.deviceInfo != null && this.deviceInfo.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { - throw new HueSyncApiException("@text/api.minimal-version", this.logger); + try { + HueSyncDeviceInfo info = Optional.ofNullable(this.deviceInfo).orElseThrow(); + if (info.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { + throw new HueSyncApiException("@text/api.minimal-version", this.logger); + } + } catch (NoSuchElementException e) { + throw new HueSyncApiException("@text/api.communication-problem", logger); } } @@ -195,16 +201,12 @@ public void initialize() { this.deviceInfo = this.connection.getDeviceInfo(); Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { - // Redundant null check required to avoid warning during build/development ... - if (info != null) { - addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); - addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); - addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); - - addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); - addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); - } + addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); + addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); + addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); try { this.checkCompatibility(); this.startBackgroundTasks(); @@ -245,7 +247,7 @@ public void dispose() { public void handleRemoval() { super.handleRemoval(); - if (this.connection != null && !this.connection.unregisterDevice()) { + if (this.connection.unregisterDevice()) { this.logger.error( "It was not possible to unregister {} ({}). You may use id: {} Key: {} to manually re-configure the thing, or to manually remove the device via API.", this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, this.config.apiAccessToken); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index b2d88ee9ab55d..69ba5e719afdf 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -13,9 +13,9 @@ package org.openhab.binding.huesync.internal.i18n; import java.util.Locale; +import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.TranslationProvider; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -23,30 +23,28 @@ import org.osgi.framework.ServiceReference; /** + * * * @author Patrik Gfeller - Initial Contribution */ @NonNullByDefault public class HueSyncLocalizer { - private static final Locale locale = Locale.ENGLISH; - private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); private static final ServiceReference serviceReference = bundleContext .getServiceReference(TranslationProvider.class); + private static final Bundle bundle = bundleContext.getBundle(); public static String getResourceString(String key) { - key = key.replace("@text/", ""); - - Bundle bundle = bundleContext.getBundle(); - - @Nullable - TranslationProvider translationProvider = bundleContext.getService(serviceReference); + String lookupKey = key.replace("@text/", ""); + String missingKey = "⚠️ Missing Translation ⚠️: " + key; - String text = translationProvider.getText(bundle, key, key, locale); + Optional translationProvider = Optional + .ofNullable(bundleContext.getService(serviceReference)); - // TODO: Add log message in case of translation problem ... - return text != null ? text : key; + return translationProvider.isPresent() ? Optional + .ofNullable(translationProvider.get().getText(bundle, lookupKey, missingKey, locale)).orElse(missingKey) + : missingKey; } } diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml index 8aed8234a5493..b9afa2d7f4b11 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml @@ -6,6 +6,7 @@ binding Hue HDMI Sync Box Binding Binding for the Hue HDMI Sync Box. + local diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..bfb5d537f6b15 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,49 @@ + + + + + + + + + + network-address + + Network address of the HDMI Sync Box. + true + + + + + Port of the HDMI Sync Box. + true + 443 + true + + + + + The id of the API registration. + true + + + password + + To enable the binding to communicate with the device, a registration is required. Once the registration + process is completed, the acquired token will authorize the binding to interact with the device. After initial + discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 + seconds to grant the binding the required permissions. + true + + + + Seconds between fetching values from the Hue Sync Box. + true + 10 + Seconds + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index e16f6e0e1c57a..89ca6e188f6de 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -22,9 +22,29 @@ thing-type.config.huesync.box.registrationId.description = The id of the API reg thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. +# channel group types + +channel-group-type.huesync.test.label = Test Group +channel-group-type.huesync.test.description = Culpa occaecat aliquip tempor ipsum exercitation incididunt culpa sit mollit officia labore commodo. + +# channel types + +channel-type.huesync.mode.label = Sync Mode +channel-type.huesync.mode.description = Select the sync mode +channel-type.huesync.mode.state.option.powersave = Powersave +channel-type.huesync.mode.state.option.video = Video +channel-type.huesync.mode.state.option.music = Music +channel-type.huesync.mode.state.option.game = Game + +# thing types + +thing-type.huesync.huesync.label = Hue HDMI Sync Box +thing-type.huesync.huesync.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. + # api exceptions api.minimal-version = Only devices with API level >= 7 are supported +api.communication-problem = Communication problem with the device # registration diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml new file mode 100644 index 0000000000000..6a71ab46d7691 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml @@ -0,0 +1,28 @@ + + + + + + Culpa occaecat aliquip tempor ipsum exercitation incididunt culpa sit mollit officia labore commodo. + + + + + + + String + + Select the sync mode + + + + + + + + + + From 0ce8d19fcd7181a939fe03546f0bae222f13dbea Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 26 Apr 2024 19:52:52 +0200 Subject: [PATCH 037/128] =?UTF-8?q?refactor(connection):=20=F0=9F=93=9C=20?= =?UTF-8?q?Simplified=20authentication=20mechanism?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../HueSyncAuthenticationResult.java | 52 ++++ .../connection/HueSyncConnection.java | 274 +++++++----------- .../connection/HueSyncDeviceConnection.java | 102 +++++++ .../internal/handler/HueSyncHandler.java | 11 +- .../tasks/HueSyncRegistrationTask.java | 6 +- .../handler/tasks/HueSyncUpdateTask.java | 6 +- 6 files changed, 272 insertions(+), 179 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java new file mode 100644 index 0000000000000..2e5bca36ac813 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.connection; + +import java.net.URI; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.api.Authentication.Result; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncAuthenticationResult implements Result { + private String token; + private URI uri; + + public HueSyncAuthenticationResult(URI uri, String token) { + this.uri = uri; + this.token = token; + } + + @Override + public URI getURI() { + return this.uri; + } + + @SuppressWarnings("null") + @Override + public void apply(@Nullable Request request) { + if (Optional.ofNullable(request).isPresent()) { + if (!request.getHeaders().contains(HttpHeader.AUTHORIZATION)) { + request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.token); + } + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index df3ce80d78bc3..b205695a088c5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -13,6 +13,8 @@ package org.openhab.binding.huesync.internal.connection; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.security.cert.CertificateException; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -28,12 +30,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; -import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.osgi.framework.BundleContext; @@ -45,221 +42,160 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * Handles the connection to a Hue HDMI Sync Box using the official API. * * @author Patrik Gfeller - Initial Contribution */ @NonNullByDefault public class HueSyncConnection { - + public static final ObjectMapper ObjectMapper = new ObjectMapper(); /** - * - * @author Patrik Gfeller - Initial Contribution + * Request format: The Sync Box API can be accessed locally via HTTPS on root + * level (port 443, /api/v1), resource level /api/v1/ and in some + * cases sub-resource level /api/v1//. */ - protected class HueSyncConnectionHelper { - /** - * Request format: The Sync Box API can be accessed locally via HTTPS on root - * level (port 443, /api/v1), resource level /api/v1/ and in some - * cases sub-resource level /api/v1//. - */ - private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; - private static final String API = "api/v1"; - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); + private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; + private static final String API = "api/v1"; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); - private Integer port; - private String host; + private Integer port; + private String host; - private ServiceRegistration tlsProviderService; - private HttpClient httpClient; + private ServiceRegistration tlsProviderService; + private HttpClient httpClient; + private URI uri; - protected static final ObjectMapper ObjectMapper = new ObjectMapper(); + @Nullable + private HueSyncAuthenticationResult authentication; - protected String apiAccessToken; - protected String registrationId; + protected String registrationId; - public HueSyncConnectionHelper(HttpClient httpClient, String host, Integer port, String apiAccessToken, - String registrationId) throws CertificateException, IOException { + public HueSyncConnection(HttpClient httpClient, String host, Integer port, String token, String registrationId) + throws CertificateException, IOException, URISyntaxException { - this.host = host; - this.port = port; - this.apiAccessToken = apiAccessToken; - this.registrationId = registrationId; + this.host = host; + this.port = port; - HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); - BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + this.uri = new URI(String.format("https://%s:%s", this.host, this.port)); - this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), - trustManagerProvider, null); - this.httpClient = httpClient; - } - - protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload, Class type) { - try { - ContentResponse response = this.executeRequest(method, endpoint, payload); + this.registrationId = registrationId; - return processedResponse(response, type); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - this.logger.error("{}", e.getMessage()); - } - return null; - } + HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); + BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); - protected @Nullable T executeGetRequest(String endpoint, Class type) { - try { - ContentResponse response = this.executeGetRequest(endpoint); + this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, + null); + this.httpClient = httpClient; + this.setAuthentication(token); + } - return processedResponse(response, type); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - this.logger.error("{}", e.getMessage()); - } + protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload, Class type) { + try { + ContentResponse response = this.executeRequest(method, endpoint, payload); - return null; + return processedResponse(response, type); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); } + return null; + } - protected boolean isRegistered() { - if (this.apiAccessToken.isBlank() || this.registrationId.isBlank()) { - return false; - } + protected @Nullable T executeGetRequest(String endpoint, Class type) { + try { + ContentResponse response = this.executeGetRequest(endpoint); - return true; + return processedResponse(response, type); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); } - protected boolean unregisterDevice() { - if (this.isRegistered()) { - try { - String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; - ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); + return null; + } - if (response.getStatus() == HttpStatus.OK_200) { - this.registrationId = ""; - this.apiAccessToken = ""; + protected boolean isRegistered() { + return Optional.ofNullable(this.authentication).isPresent(); + } - return true; - } - } catch (InterruptedException | TimeoutException | ExecutionException e) { - this.logger.error("{}", e.getMessage()); - } - } - return false; + protected void setAuthentication(String token) { + if (!token.isBlank()) { + this.authentication = new HueSyncAuthenticationResult(this.uri, token); + this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication); } + } - protected void stop() { - this.tlsProviderService.unregister(); - } + protected void unsetAuthentication() { + if (isRegistered()) { + this.httpClient.getAuthenticationStore().removeAuthenticationResult(this.authentication); - private @Nullable T processedResponse(ContentResponse response, Class type) { - switch (response.getStatus()) { - case HttpStatus.OK_200: - return this.deserialize(response.getContentAsString(), type); - default: - logger.warn("HTTP Status: {}", response.getStatus()); - return null; - } + this.registrationId = ""; + this.authentication = null; } + } - private @Nullable T deserialize(String json, Class type) { + protected boolean unregisterDevice() { + if (this.isRegistered()) { try { - return ObjectMapper.readValue(json, type); - } catch (JsonProcessingException | NoClassDefFoundError e) { - this.logger.error("{}", e.getMessage()); - - return null; - } - } - - private ContentResponse executeRequest(HttpMethod method, String endpoint) - throws InterruptedException, TimeoutException, ExecutionException { - return this.executeRequest(method, endpoint, ""); - } - - private ContentResponse executeGetRequest(String endpoint) - throws InterruptedException, ExecutionException, TimeoutException { - String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - - return httpClient.GET(uri); - } - - private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) - throws InterruptedException, TimeoutException, ExecutionException { - - String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - - Request request = this.httpClient.newRequest(uri).method(method); + String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; + ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); - if (!payload.isBlank()) { - request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) - .content(new StringContentProvider(payload)); - } - - if (this.isRegistered()) { - request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.apiAccessToken); + if (response.getStatus() == HttpStatus.OK_200) { + this.unsetAuthentication(); + return true; + } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + this.logger.error("{}", e.getMessage()); } - - return request.send(); } - }; - - private HueSyncConnectionHelper helper; - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); - - public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, - String registrationId) throws CertificateException, IOException { - - this.helper = new HueSyncConnectionHelper(httpClient, host, port, apiAccessToken, registrationId); + return false; } - public @Nullable HueSyncDeviceInfo getDeviceInfo() { - return this.helper.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); + protected void stop() { + this.tlsProviderService.unregister(); } - public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { - if (id == null || id.isBlank()) { - return null; + private @Nullable T processedResponse(ContentResponse response, Class type) { + switch (response.getStatus()) { + case HttpStatus.OK_200: + return this.deserialize(response.getContentAsString(), type); + default: + logger.warn("HTTP Status: {}", response.getStatus()); + return null; } + } - HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); - - dto.appName = HueSyncConstants.APPLICATION_NAME; - dto.instanceName = id; - + private @Nullable T deserialize(String json, Class type) { try { - String json = HueSyncConnectionHelper.ObjectMapper.writeValueAsString(dto); - HueSyncRegistration registration = this.helper.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, - json, HueSyncRegistration.class); - - Optional.ofNullable(registration).ifPresent((obj) -> { - Optional.ofNullable(obj.registrationId).ifPresent((regId) -> { - this.helper.registrationId = regId; - }); - Optional.ofNullable(obj.accessToken).ifPresent((regToken) -> { - this.helper.apiAccessToken = regToken; - }); - }); - return registration; - } catch (JsonProcessingException e) { + return ObjectMapper.readValue(json, type); + } catch (JsonProcessingException | NoClassDefFoundError e) { this.logger.error("{}", e.getMessage()); + + return null; } + } - return null; + private ContentResponse executeRequest(HttpMethod method, String endpoint) + throws InterruptedException, TimeoutException, ExecutionException { + return this.executeRequest(method, endpoint, ""); } - public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { - if (this.helper.isRegistered()) { - return this.helper.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class); - } + private ContentResponse executeGetRequest(String endpoint) + throws InterruptedException, ExecutionException, TimeoutException { + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); - return null; + return httpClient.GET(uri); } - public boolean isRegistered() { - return this.helper.isRegistered(); - } + private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) + throws InterruptedException, TimeoutException, ExecutionException { - public boolean unregisterDevice() { - return this.helper.unregisterDevice(); - } + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); + + Request request = this.httpClient.newRequest(uri).method(method); + + if (!payload.isBlank()) { + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) + .content(new StringContentProvider(payload)); + } - public void stop() { - this.helper.stop(); + return request.send(); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java new file mode 100644 index 0000000000000..7ae188847a6bf --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.connection; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.slf4j.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Handles the connection to a Hue HDMI Sync Box using the official API. + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncDeviceConnection { + private HueSyncConnection connection; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); + + public HueSyncDeviceConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, + String registrationId) throws CertificateException, IOException, URISyntaxException { + + this.connection = new HueSyncConnection(httpClient, host, port, apiAccessToken, registrationId); + } + + public @Nullable HueSyncDeviceInfo getDeviceInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class) + : this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); + } + + public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class) + : null; + } + + public @Nullable HueSyncRegistration registerDevice(@Nullable String id) { + if (id == null || id.isBlank()) { + return null; + } + + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = id; + + try { + String json = HueSyncConnection.ObjectMapper.writeValueAsString(dto); + HueSyncRegistration registration = this.connection.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, + json, HueSyncRegistration.class); + + Optional.ofNullable(registration).ifPresent((obj) -> { + Optional.ofNullable(obj.accessToken).ifPresent((token) -> { + this.connection.setAuthentication(token); + }); + }); + return registration; + } catch (JsonProcessingException e) { + this.logger.error("{}", e.getMessage()); + } + + return null; + } + + public boolean isRegistered() { + return this.connection.isRegistered(); + } + + public boolean unregisterDevice() { + return this.connection.unregisterDevice(); + } + + public void stop() { + this.connection.stop(); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index d4921acabd8ea..e4a06446e4391 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -13,6 +13,7 @@ package org.openhab.binding.huesync.internal.handler; import java.io.IOException; +import java.net.URISyntaxException; import java.security.cert.CertificateException; import java.util.Map; import java.util.NoSuchElementException; @@ -28,7 +29,7 @@ import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; -import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask; @@ -61,20 +62,22 @@ public class HueSyncHandler extends BaseThingHandler { private @Nullable HueSyncDeviceInfo deviceInfo; - private HueSyncConnection connection; + private HueSyncDeviceConnection connection; private HueSyncConfiguration config; protected class HueSyncProperties { }; - public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException { + public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) + throws CertificateException, IOException, URISyntaxException { super(thing); HttpClient httpClient = httpClientFactory.getCommonHttpClient(); + httpClient.setName(this.thing.getUID().getAsString()); this.config = getConfigAs(HueSyncConfiguration.class); - this.connection = new HueSyncConnection(httpClient, this.config.host, this.config.port, + this.connection = new HueSyncDeviceConnection(httpClient, this.config.host, this.config.port, this.config.apiAccessToken, this.config.registrationId); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 11f1582d2d223..0386d9eca168a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; -import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.thing.ThingStatus; import org.slf4j.Logger; @@ -32,13 +32,13 @@ public class HueSyncRegistrationTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); - private HueSyncConnection connection; + private HueSyncDeviceConnection connection; private HueSyncDeviceInfo deviceInfo; private Consumer action; private Supplier status; - public HueSyncRegistrationTask(HueSyncConnection connection, HueSyncDeviceInfo deviceInfo, + public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDeviceInfo deviceInfo, Supplier status, Consumer action) { this.connection = connection; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 1b70be8ac5e5d..d552fa2e7f72a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; -import org.openhab.binding.huesync.internal.connection.HueSyncConnection; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -32,12 +32,12 @@ public class HueSyncUpdateTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); - private HueSyncConnection connection; + private HueSyncDeviceConnection connection; private HueSyncDeviceInfo deviceInfo; private Consumer<@Nullable HueSyncDetailedDeviceInfo> action; - public HueSyncUpdateTask(HueSyncConnection connection, HueSyncDeviceInfo deviceInfo, + public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDeviceInfo deviceInfo, Consumer<@Nullable HueSyncDetailedDeviceInfo> action) { this.connection = connection; From dbd0bcca0fd1c786add2fc462ab6871e055b22c9 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 30 Apr 2024 21:50:48 +0200 Subject: [PATCH 038/128] =?UTF-8?q?refactor(communication):=20=F0=9F=93=9C?= =?UTF-8?q?=20improved=20error=20handling=20and=20manual=20configuration?= =?UTF-8?q?=20(wip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 💣 preparation for connection exceptions - 📜 preparation for channel .xml's BREAKING CHANGE: Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 3 +- .../dto/HueSyncDeviceInfoCapabilities.java | 2 +- .../connection/HueSyncConnection.java | 52 +++++++++++++--- .../connection/HueSyncDeviceConnection.java | 15 +++-- .../HueSyncConnectionException.java | 33 +++++++++++ .../HueSyncConnectionExceptionType.java | 24 ++++++++ .../internal/handler/HueSyncHandler.java | 25 +++++++- .../main/resources/OH-INF/config/config.xml | 2 +- .../resources/OH-INF/i18n/huesync.properties | 49 +++++++++++---- .../OH-INF/thing/thing-types-channels.xml | 28 --------- .../resources/OH-INF/thing/thing-types.xml | 59 ++++--------------- 11 files changed, 186 insertions(+), 106 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java delete mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 7afc0819d48fd..c2b8cd951783a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -34,7 +34,8 @@ public static class ENDPOINTS { public static final Integer MINIMAL_API_VERSION = 7; public static final String BINDING_ID = "huesync"; - public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "box"); + public static final String THING_TYPE_ID = "huesyncthing"; + public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID); public static final String PARAMETER_HOST = "host"; public static final String PARAMETER_PORT = "port"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java index 5cd5cd7e1940a..c9208eee426a3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/HueSyncDeviceInfoCapabilities.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * HDMI Sync Box Device Information Cababilities DTO + * HDMI Sync Box Device Information Capabilities DTO * * @author Patrik Gfeller - Initial Contribution * diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index b205695a088c5..29244fab7d05c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.osgi.framework.BundleContext; @@ -67,9 +68,9 @@ public class HueSyncConnection { @Nullable private HueSyncAuthenticationResult authentication; - protected String registrationId; + protected String registrationId = ""; - public HueSyncConnection(HttpClient httpClient, String host, Integer port, String token, String registrationId) + public HueSyncConnection(HttpClient httpClient, String host, Integer port) throws CertificateException, IOException, URISyntaxException { this.host = host; @@ -77,7 +78,7 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin this.uri = new URI(String.format("https://%s:%s", this.host, this.port)); - this.registrationId = registrationId; + // this.registrationId = registrationId; HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); @@ -85,7 +86,6 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, null); this.httpClient = httpClient; - this.setAuthentication(token); } protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload, Class type) { @@ -117,6 +117,8 @@ protected boolean isRegistered() { protected void setAuthentication(String token) { if (!token.isBlank()) { + this.unsetAuthentication(); + this.authentication = new HueSyncAuthenticationResult(this.uri, token); this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication); } @@ -148,18 +150,45 @@ protected boolean unregisterDevice() { return false; } - protected void stop() { + protected void dispose() { this.tlsProviderService.unregister(); } private @Nullable T processedResponse(ContentResponse response, Class type) { - switch (response.getStatus()) { + int status = response.getStatus(); + /* + * 400 Invalid State: + * Registration in progress + * + * 401 Authentication failed: + * If credentials are missing or invalid, errors out. If credentials are missing, continues on to GET only the + * Configuration state when unauthenticated, to allow for device identification. + * + * 404 Invalid URI Path: + * Accessing URI path which is not supported + * + * 500 Internal: + * Internal errors like out of memory + */ + switch (status) { case HttpStatus.OK_200: return this.deserialize(response.getContentAsString(), type); + case HttpStatus.BAD_REQUEST_400: + logger.info("registration in progress: no token received yet"); + break; + case HttpStatus.UNAUTHORIZED_401: + logger.error("credentials missing or invalid"); + break; + case HttpStatus.NOT_FOUND_404: + logger.error("invalid device URI or API endpoint"); + break; + case HttpStatus.INTERNAL_SERVER_ERROR_500: + logger.error("hue sync box server problem"); + break; default: - logger.warn("HTTP Status: {}", response.getStatus()); - return null; + logger.warn("unexpected HTTP status: {}", status); } + return null; } private @Nullable T deserialize(String json, Class type) { @@ -198,4 +227,11 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint, Strin return request.send(); } + + public void updateConfig(HueSyncConfiguration config) { + if (!config.apiAccessToken.isBlank()) { + this.registrationId = config.registrationId; + this.setAuthentication(config.apiAccessToken); + } + } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 7ae188847a6bf..5203e83f5057e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -27,6 +27,7 @@ import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -42,10 +43,10 @@ public class HueSyncDeviceConnection { private HueSyncConnection connection; private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); - public HueSyncDeviceConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken, - String registrationId) throws CertificateException, IOException, URISyntaxException { + public HueSyncDeviceConnection(HttpClient httpClient, String host, Integer port) + throws CertificateException, IOException, URISyntaxException { - this.connection = new HueSyncConnection(httpClient, host, port, apiAccessToken, registrationId); + this.connection = new HueSyncConnection(httpClient, host, port); } public @Nullable HueSyncDeviceInfo getDeviceInfo() { @@ -96,7 +97,11 @@ public boolean unregisterDevice() { return this.connection.unregisterDevice(); } - public void stop() { - this.connection.stop(); + public void dispose() { + this.connection.dispose(); + } + + public void updateConfig(HueSyncConfiguration config) { + this.connection.updateConfig(config); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java new file mode 100644 index 0000000000000..e08825d3fec8d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncConnectionException extends HueSyncException { + public HueSyncConnectionExceptionType type; + + private static final long serialVersionUID = 0L; + + public HueSyncConnectionException(String message, HueSyncConnectionExceptionType type, Logger logger) { + super(message, logger); + + this.type = type; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java new file mode 100644 index 0000000000000..2547c8b81fccf --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public enum HueSyncConnectionExceptionType { + +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index e4a06446e4391..532c81f1b780d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -77,8 +77,7 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) httpClient.setName(this.thing.getUID().getAsString()); this.config = getConfigAs(HueSyncConfiguration.class); - this.connection = new HueSyncDeviceConnection(httpClient, this.config.host, this.config.port, - this.config.apiAccessToken, this.config.registrationId); + this.connection = new HueSyncDeviceConnection(httpClient, this.config.host, this.config.port); } // #region private @@ -199,6 +198,11 @@ private void addProperty(String key, @Nullable String value) { public void initialize() { updateStatus(ThingStatus.UNKNOWN); + // initialize is also called, if the thing configuration was updated + // TODO: Improve handling of configuration changes ... + this.config = getConfigAs(HueSyncConfiguration.class); + this.connection.updateConfig(this.config); + scheduler.execute(() -> { try { this.deviceInfo = this.connection.getDeviceInfo(); @@ -230,12 +234,27 @@ public void handleCommand(ChannelUID channelUID, Command command) { // TODO: Implementation ... } + /* + * TODO: Solve shutdown timing problem(s): + * + * 2024-04-30 21:38:43.874 [ERROR] [sync.internal.handler.HueSyncHandler] - The service has been unregistered + * {org.openhab.core.io.net.http.TlsTrustManagerProvider}={service.id=494, service.bundleid=249, + * service.scope=singleton} + * 2024-04-30 21:38:43.875 [INFO ] [sync.internal.handler.HueSyncHandler] - Thing HueSyncBox-... + * (huesync:huesyncthing:HueSyncBox-...) disposed. + * 2024-04-30 21:38:53.085 [TRACE] [rnal.handler.tasks.HueSyncUpdateTask] - Status update query for Sync Box + * HSB1:... + * 2024-04-30 21:38:53.111 [TRACE] [sync.internal.handler.HueSyncHandler] - Current status: UNINITIALIZED + * 2024-04-30 21:38:53.113 [WARN ] [.core.thing.binding.BaseThingHandler] - Handler HueSyncHandler tried updating + * the thing status although the handler was already disposed. + */ + @Override public void dispose() { super.dispose(); try { - this.connection.stop(); + this.connection.dispose(); this.stopTask(deviceRegistrationTask); this.stopTask(deviceUpdateTask); diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml index bfb5d537f6b15..9325742753c2d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml @@ -4,7 +4,7 @@ xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 89ca6e188f6de..b132b7551c3d8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -5,22 +5,26 @@ addon.huesync.description = Binding for the Hue HDMI Sync Box. # thing types -thing-type.huesync.box.label = Hue HDMI Sync Box -thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. +thing-type.huesync.huesyncthing.label = Hue HDMI Sync Box +thing-type.huesync.huesyncthing.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. # thing types config -thing-type.config.huesync.box.apiAccessToken.label = API Access Token -thing-type.config.huesync.box.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. -thing-type.config.huesync.box.group.connection.label = Connection Settings -thing-type.config.huesync.box.host.label = Network Address -thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. -thing-type.config.huesync.box.port.label = Port -thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. -thing-type.config.huesync.box.registrationId.label = Application Registration Id -thing-type.config.huesync.box.registrationId.description = The id of the API registration. -thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval -thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. +thing-type.config.huesync.thing.apiAccessToken.label = API Access Token +thing-type.config.huesync.thing.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. +thing-type.config.huesync.thing.group.connection.label = Connection Settings +thing-type.config.huesync.thing.host.label = Network Address +thing-type.config.huesync.thing.host.description = Network address of the HDMI Sync Box. +thing-type.config.huesync.thing.port.label = Port +thing-type.config.huesync.thing.port.description = Port of the HDMI Sync Box. +thing-type.config.huesync.thing.registrationId.label = Application Registration Id +thing-type.config.huesync.thing.registrationId.description = The id of the API registration. +thing-type.config.huesync.thing.statusUpdateInterval.label = Status Update Interval +thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. + +# channel group types + +channel-group-type.huesync.deviceGroup.label = Device Information # channel group types @@ -38,6 +42,25 @@ channel-type.huesync.mode.state.option.game = Game # thing types +thing-type.huesync.box.label = Hue HDMI Sync Box +thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. + +# thing types config + +thing-type.config.huesync.box.apiAccessToken.label = API Access Token +thing-type.config.huesync.box.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. +thing-type.config.huesync.box.group.connection.label = Connection Settings +thing-type.config.huesync.box.host.label = Network Address +thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. +thing-type.config.huesync.box.port.label = Port +thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. +thing-type.config.huesync.box.registrationId.label = Application Registration Id +thing-type.config.huesync.box.registrationId.description = The id of the API registration. +thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval +thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. + +# thing types + thing-type.huesync.huesync.label = Hue HDMI Sync Box thing-type.huesync.huesync.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml deleted file mode 100644 index 6a71ab46d7691..0000000000000 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types-channels.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Culpa occaecat aliquip tempor ipsum exercitation incididunt culpa sit mollit officia labore commodo. - - - - - - - String - - Select the sync mode - - - - - - - - - - diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index b78371fd9c2a3..d56f79a0a68b8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,60 +4,27 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - + + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful - smart light that responds to and reflects the content you watch or listen to. + smart light that responds to and reflects the content you watch or listen to. + + + Philips - - - - - - - network-address - - Network address of the HDMI Sync Box. - true - - - - - Port of the HDMI Sync Box. - true - 443 - true - - - - - The id of the API registration. - true - - - password - - To enable the binding to communicate with the device, a registration is required. Once the registration - process is completed, the acquired token will authorize the binding to interact with the device. After initial - discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 - seconds to grant the binding the required permissions. - true - - - - Seconds between fetching values from the Hue Sync Box. - true - 10 - Seconds - - - + host + + + + + + From 024b8f600b91f66899d9ce67c337350c1e33196f Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 4 May 2024 00:37:31 +0200 Subject: [PATCH 039/128] =?UTF-8?q?feat(channel):=20=F0=9F=94=A7=20firmwar?= =?UTF-8?q?e=20channel=20group=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - current firmware (read only) - available/latest firmware (read only) 📜 minor improvements and refactoring ... (wip) Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 9 +++ .../dto/device/HueSyncDetailedDeviceInfo.java | 2 +- .../connection/HueSyncConnection.java | 12 ++-- .../internal/handler/HueSyncHandler.java | 45 ++++++++------- .../resources/OH-INF/i18n/huesync.properties | 27 +++++++++ .../resources/OH-INF/thing/channel-types.xml | 57 +++++++++++++++++++ .../resources/OH-INF/thing/thing-types.xml | 15 +++-- 7 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index c2b8cd951783a..03d4b58923fea 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -28,6 +28,15 @@ public static class ENDPOINTS { public static final String REGISTRATIONS = "registrations"; } + public static class CHANNELS { + public static class DEVICE { + public static class INFORMATION { + public static final String FIRMWARE = "device-information#firmware"; + public static final String FIRMWARE_AVAILABLE = "device-information#available-firmware"; + } + } + } + public static final String APPLICATION_NAME = "openHAB"; /** Minimal API Version required. Only apiLevel >= 7 is supported. */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java index 0c025eb9b9146..07901ee77e671 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java @@ -43,7 +43,7 @@ public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo { * User readable version of the firmware the device can upgrade to. Item is set * to null when there is no update available. */ - public int updatableFirmwareVersion; + public @Nullable String updatableFirmwareVersion; /** * 1 = regular; * 0 = off in powersave, passthrough or sync mode; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 29244fab7d05c..49c1fe98c4bde 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -116,9 +116,9 @@ protected boolean isRegistered() { } protected void setAuthentication(String token) { - if (!token.isBlank()) { - this.unsetAuthentication(); + this.unsetAuthentication(); + if (!token.isBlank()) { this.authentication = new HueSyncAuthenticationResult(this.uri, token); this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication); } @@ -174,7 +174,7 @@ protected void dispose() { case HttpStatus.OK_200: return this.deserialize(response.getContentAsString(), type); case HttpStatus.BAD_REQUEST_400: - logger.info("registration in progress: no token received yet"); + logger.trace("registration in progress: no token received yet"); break; case HttpStatus.UNAUTHORIZED_401: logger.error("credentials missing or invalid"); @@ -229,9 +229,7 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint, Strin } public void updateConfig(HueSyncConfiguration config) { - if (!config.apiAccessToken.isBlank()) { - this.registrationId = config.registrationId; - this.setAuthentication(config.apiAccessToken); - } + this.registrationId = config.registrationId; + this.setAuthentication(config.apiAccessToken); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 532c81f1b780d..2a8f0a33301e3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -36,12 +36,14 @@ import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.State; import org.slf4j.Logger; /** @@ -83,9 +85,11 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) // #region private private void stopTask(@Nullable ScheduledFuture task) { try { - if (task != null && !task.isDone()) { - task.cancel(true); - } + Optional.ofNullable(task).ifPresent((job) -> { + if (!job.isCancelled() && !job.isDone()) { + job.cancel(true); + } + }); } catch (Exception e) { // TODO: Handle exception ... } finally { @@ -142,6 +146,15 @@ private void updateDeviceStatus(@Nullable HueSyncDetailedDeviceInfo deviceState) this.updateStatus(ThingStatus.OFFLINE); } else { this.updateStatus(ThingStatus.ONLINE); + + State firmwareState = new StringType(deviceState.firmwareVersion); + State firmwareAvailableState = new StringType( + Optional.ofNullable(deviceState.updatableFirmwareVersion).isPresent() + ? deviceState.updatableFirmwareVersion + : deviceState.firmwareVersion); + + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); } } @@ -196,10 +209,13 @@ private void addProperty(String key, @Nullable String value) { // #region Override @Override public void initialize() { + // TODO: Check if we need to handle enable/disable state ... + updateStatus(ThingStatus.UNKNOWN); - // initialize is also called, if the thing configuration was updated - // TODO: Improve handling of configuration changes ... + this.stopTask(this.deviceRegistrationTask); + this.stopTask(this.deviceUpdateTask); + this.config = getConfigAs(HueSyncConfiguration.class); this.connection.updateConfig(this.config); @@ -234,30 +250,15 @@ public void handleCommand(ChannelUID channelUID, Command command) { // TODO: Implementation ... } - /* - * TODO: Solve shutdown timing problem(s): - * - * 2024-04-30 21:38:43.874 [ERROR] [sync.internal.handler.HueSyncHandler] - The service has been unregistered - * {org.openhab.core.io.net.http.TlsTrustManagerProvider}={service.id=494, service.bundleid=249, - * service.scope=singleton} - * 2024-04-30 21:38:43.875 [INFO ] [sync.internal.handler.HueSyncHandler] - Thing HueSyncBox-... - * (huesync:huesyncthing:HueSyncBox-...) disposed. - * 2024-04-30 21:38:53.085 [TRACE] [rnal.handler.tasks.HueSyncUpdateTask] - Status update query for Sync Box - * HSB1:... - * 2024-04-30 21:38:53.111 [TRACE] [sync.internal.handler.HueSyncHandler] - Current status: UNINITIALIZED - * 2024-04-30 21:38:53.113 [WARN ] [.core.thing.binding.BaseThingHandler] - Handler HueSyncHandler tried updating - * the thing status although the handler was already disposed. - */ - @Override public void dispose() { super.dispose(); try { - this.connection.dispose(); - this.stopTask(deviceRegistrationTask); this.stopTask(deviceUpdateTask); + + this.connection.dispose(); } catch (Exception e) { this.logger.error("{}", e.getMessage()); } finally { diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index b132b7551c3d8..ad212811a8da5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -24,6 +24,33 @@ thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds betwe # channel group types +channel-group-type.huesync.device-information.label = Firmware +channel-group-type.huesync.device-information.description = Information about the installed device firmaware and available updates. + +# channel types + +channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version +channel-type.huesync.device-info-firmware-available.description = Latest available firmware version +channel-type.huesync.device-info-firmware.label = Firmware Version +channel-type.huesync.device-info-firmware.description = Installed firmware version + +# channel group types + +channel-group-type.huesync.device.label = Generic device information + +# channel types + +channel-type.huesync.device-info.label = Last Updated +channel-type.huesync.device-info.description = The date and time when the sensor was last updated. +channel-type.huesync.device-info.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS + +# thing types + +thing-type.huesync.huesyncthing.channel.test.label = test +thing-type.huesync.huesyncthing.channel.test.description = test + +# channel group types + channel-group-type.huesync.deviceGroup.label = Device Information # channel group types diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml new file mode 100644 index 0000000000000..ea02fcea628a2 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -0,0 +1,57 @@ + + + + + + String + + Installed firmware version + Text + + + + String + + Latest available firmware version + Text + + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index d56f79a0a68b8..2b3dc58d492b2 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -12,6 +12,9 @@ smart light that responds to and reflects the content you watch or listen to. + + + Philips @@ -20,11 +23,15 @@ host - - - + + + Information about the installed device firmaware and available updates. + + + + + - From 3399194dfe20ab274eb2570ce1ff9ee452570cf1 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 7 May 2024 21:54:42 +0200 Subject: [PATCH 040/128] =?UTF-8?q?fix(channel):=20=F0=9F=94=83=20keep=20p?= =?UTF-8?q?roperties=20and=20firmware=20channel=20information=20in=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - keep firmware property and channel in sync - README.md updated Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 29 +++++++++------- .../huesync/internal/HueSyncConstants.java | 4 +-- .../internal/handler/HueSyncHandler.java | 34 +++++++++++++------ .../main/resources/OH-INF/config/config.xml | 1 + .../resources/OH-INF/i18n/huesync.properties | 4 +-- .../resources/OH-INF/thing/thing-types.xml | 4 +-- 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index e37c77837f4eb..16b9227bedce4 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -39,7 +39,6 @@ $ avahi-browse --resolve _huesync._tcp - ## Thing Configuration To enable the binding to communicate with the device, a registration is required. @@ -47,29 +46,33 @@ Once the registration process is completed, the acquired token will authorize th After initial discovery and thing creation the device will stay offline. To complete the authentication you need to pressed the registration button on the sync box for 3 seconds. - _Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ _This should be mainly about its mandatory and optional configuration parameters._ _Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ -### `sample` Thing Configuration +### Thing Configuration `huesyncthing` -| Name | Type | Description | Default | Required | Advanced | -|-----------------|---------|---------------------------------------|---------|----------|----------| -| hostname | text | Hostname or IP address of the device | N/A | yes | no | -| password | text | Password to access the device | N/A | yes | no | -| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | +| Name | Type | Description | Default | Required | Advanced | +| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | +| host | text | IP address of the device | N/A | yes | no | +| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | +| registrationId | text | Application Registration Id | N/A | no | yes | +| apiAccessToken | text | API Access Token | N/A | no | yes | +| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | ## Channels -_Here you should provide information about available channel types, what their meaning is and how they can be used._ +### Channel Group `device-firmware` -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +#### Firmware + +Information about the installed device firmware and available updates. -| Channel | Type | Read/Write | Description | -|---------|--------|------------|-----------------------------| -| control | Switch | RW | This is the control channel | +| Channel | Type | Read/Write | Description | +| ------------------ | ------ | ---------- | --------------------------------- | +| firmware | String | R | Installed firmware version | +| available-firmware | String | R | Latest available firmware version | ## Full Example diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 03d4b58923fea..3f73d5c4818dc 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -31,8 +31,8 @@ public static class ENDPOINTS { public static class CHANNELS { public static class DEVICE { public static class INFORMATION { - public static final String FIRMWARE = "device-information#firmware"; - public static final String FIRMWARE_AVAILABLE = "device-information#available-firmware"; + public static final String FIRMWARE = "device-firmware#firmware"; + public static final String FIRMWARE_AVAILABLE = "device-firmware#available-firmware"; } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 2a8f0a33301e3..4245c23f49fa5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -153,6 +153,9 @@ private void updateDeviceStatus(@Nullable HueSyncDetailedDeviceInfo deviceState) ? deviceState.updatableFirmwareVersion : deviceState.firmwareVersion); + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceState.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceState.apiLevel)); + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); } @@ -165,7 +168,7 @@ private void setRegistration(HueSyncRegistration registration) { if (id.isPresent() && token.isPresent()) { this.stopTask(deviceRegistrationTask); - addProperty(HueSyncConstants.REGISTRATION_ID, id.get()); + setProperty(HueSyncConstants.REGISTRATION_ID, id.get()); Configuration configuration = this.editConfiguration(); configuration.put(HueSyncConstants.REGISTRATION_ID, id.get()); @@ -194,16 +197,27 @@ private void checkCompatibility() throws HueSyncApiException { } } - private void addProperty(String key, @Nullable String value) { + private void setProperty(String key, @Nullable String value) { if (value != null) { Map properties = this.editProperties(); - properties.put(key, value); - - this.updateProperties(properties); + if (properties.containsKey(key)) { + @Nullable + String currentValue = properties.get(key); + if (!(value.equals(currentValue))) { + saveProperty(key, value, properties); + } + } else { + saveProperty(key, value, properties); + } } } + private void saveProperty(String key, String value, Map properties) { + properties.put(key, value); + this.updateProperties(properties); + } + // #endregion // #region Override @@ -224,12 +238,12 @@ public void initialize() { this.deviceInfo = this.connection.getDeviceInfo(); Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { - addProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); - addProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); - addProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); + setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); - addProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); - addProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + // setProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); try { this.checkCompatibility(); this.startBackgroundTasks(); diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml index 9325742753c2d..2694fe53011dc 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml @@ -42,6 +42,7 @@ Seconds between fetching values from the Hue Sync Box. true + true 10 Seconds diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index ad212811a8da5..fd65088b97d9b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -24,8 +24,8 @@ thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds betwe # channel group types -channel-group-type.huesync.device-information.label = Firmware -channel-group-type.huesync.device-information.description = Information about the installed device firmaware and available updates. +channel-group-type.huesync.device-firmware.label = Firmware +channel-group-type.huesync.device-firmware.description = Information about the installed device firmaware and available updates. # channel types diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 2b3dc58d492b2..a4fa4dcd75501 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -13,7 +13,7 @@ - + @@ -25,7 +25,7 @@ - + Information about the installed device firmaware and available updates. From 4c726628f31f71b70c8886a7aa4f7968b05ddca7 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 8 May 2024 22:19:33 +0200 Subject: [PATCH 041/128] =?UTF-8?q?feat(dto):=20=F0=9F=93=BA=20HDMI=20Stat?= =?UTF-8?q?us=20DTO=20&=20Get=20API=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📜 includes some refactoring (rename of classes and device status callback type) Signed-off-by: Patrik Gfeller --- .../huesync/internal/HueSyncConstants.java | 1 + ....java => HueSyncDetailedDeviceStatus.java} | 6 +- ...=> HueSyncDetailedDeviceStatusUpdate.java} | 2 +- ...a => HueSyncDetailedDeviceStatusWifi.java} | 2 +- ...viceInfo.java => HueSyncDeviceStatus.java} | 2 +- .../api/dto/hdmi/HueSyncHdmiStatus.java | 39 +++++++++++ .../dto/hdmi/HueSyncHdmiStatusConnection.java | 65 +++++++++++++++++++ .../connection/HueSyncDeviceConnection.java | 23 +++++-- .../internal/handler/HueSyncHandler.java | 36 +++++----- .../tasks/HueSyncRegistrationTask.java | 6 +- .../handler/tasks/HueSyncUpdateInfo.java | 28 ++++++++ .../handler/tasks/HueSyncUpdateTask.java | 17 ++--- 12 files changed, 187 insertions(+), 40 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDetailedDeviceInfo.java => HueSyncDetailedDeviceStatus.java} (89%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDetailedDeviceInfoUpdate.java => HueSyncDetailedDeviceStatusUpdate.java} (96%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDetailedDeviceInfoWifi.java => HueSyncDetailedDeviceStatusWifi.java} (95%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceInfo.java => HueSyncDeviceStatus.java} (98%) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatus.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatusConnection.java create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateInfo.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 3f73d5c4818dc..a2bddb25ecbd1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -26,6 +26,7 @@ public class HueSyncConstants { public static class ENDPOINTS { public static final String DEVICE = "device"; public static final String REGISTRATIONS = "registrations"; + public static final String HDMI = "hdmi"; } public static class CHANNELS { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatus.java similarity index 89% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatus.java index 07901ee77e671..dfb29248036cf 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatus.java @@ -28,9 +28,9 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo { - public @Nullable HueSyncDetailedDeviceInfoWifi wifi; - public @Nullable HueSyncDetailedDeviceInfoUpdate update; +public class HueSyncDetailedDeviceStatus extends HueSyncDeviceStatus { + public @Nullable HueSyncDetailedDeviceStatusWifi wifi; + public @Nullable HueSyncDetailedDeviceStatusUpdate update; /** UTC time when last check for update was performed. */ public @Nullable Date lastCheckedUpdate; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusUpdate.java similarity index 96% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusUpdate.java index 87aefc36d94f1..1c04431f7fd39 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoUpdate.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusUpdate.java @@ -24,7 +24,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDetailedDeviceInfoUpdate { +public class HueSyncDetailedDeviceStatusUpdate { /** * Sync Box checks daily for a firmware update. If true, an available update * will automatically be installed. This will be postponed if Sync Box is diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusWifi.java similarity index 95% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusWifi.java index 30ae3f8e2bc3a..ffa780af97229 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceInfoWifi.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDetailedDeviceStatusWifi.java @@ -25,7 +25,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDetailedDeviceInfoWifi { +public class HueSyncDetailedDeviceStatusWifi { /** Wifi SSID */ public @Nullable String ssid; /** diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceStatus.java similarity index 98% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceStatus.java index 27b0434f7e81b..752f6ce12ee9d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceStatus.java @@ -26,7 +26,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceInfo { +public class HueSyncDeviceStatus { /** Friendly name of the device */ public @Nullable String name; /** Device Type identifier */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatus.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatus.java new file mode 100644 index 0000000000000..5282b14610994 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatus.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.hdmi; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncHdmiStatus { + public @Nullable HueSyncHdmiStatusConnection input1; + public @Nullable HueSyncHdmiStatusConnection input2; + public @Nullable HueSyncHdmiStatusConnection input3; + public @Nullable HueSyncHdmiStatusConnection input4; + + public @Nullable HueSyncHdmiStatusConnection output; + + /** x @ */ + public @Nullable String contentSpecs; + + /** Current content specs supported for video sync (video/game mode) */ + public boolean videoSyncSupported; + /** Current content specs supported for audio sync (music mode) */ + public boolean audioSyncSupported; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatusConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatusConnection.java new file mode 100644 index 0000000000000..166408f6a59fa --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiStatusConnection.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.hdmi; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncHdmiStatusConnection { + /** Friendly name, not empty */ + public @Nullable String name; + /** + * Friendly type: + * generic, + * video, + * game, + * music, + * xbox, + * playstation, + * nintendoswitch, + * phone, + * desktop, + * laptop, + * appletv, + * roku, + * shield, + * chromecast, + * firetv, + * diskplayer, + * settopbox, + * satellite, + * avreceiver, + * soundbar, + * hdmiswitch + */ + public @Nullable String type; + /** + * unplugged, + * plugged, + * linked, + * unknown + */ + public @Nullable String status; + /** + * video, + * game, + * music + */ + public @Nullable String lastSyncMode; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 5203e83f5057e..c384634a3d875 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -23,8 +23,9 @@ import org.eclipse.jetty.http.HttpMethod; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceStatus; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceStatus; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiStatus; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; @@ -49,15 +50,23 @@ public HueSyncDeviceConnection(HttpClient httpClient, String host, Integer port) this.connection = new HueSyncConnection(httpClient, host, port); } - public @Nullable HueSyncDeviceInfo getDeviceInfo() { + public @Nullable HueSyncDeviceStatus getDeviceInfo() { return this.connection.isRegistered() - ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class) - : this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceInfo.class); + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", + HueSyncDetailedDeviceStatus.class) + : this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceStatus.class); } - public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() { + public @Nullable HueSyncDetailedDeviceStatus getDetailedDeviceInfo() { return this.connection.isRegistered() - ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDetailedDeviceInfo.class) + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", + HueSyncDetailedDeviceStatus.class) + : null; + } + + public @Nullable HueSyncHdmiStatus getHdmiInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmiStatus.class) : null; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 4245c23f49fa5..ce874f4e84178 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -25,13 +25,14 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceStatus; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceStatus; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateInfo; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; @@ -55,14 +56,14 @@ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { private static final String PROPERTY_API_VERSION = "apiVersion"; - private static final String PROPERTY_NETWORK_STATE = "networkState"; + // private static final String PROPERTY_NETWORK_STATE = "networkState"; private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); private @Nullable ScheduledFuture deviceRegistrationTask; private @Nullable ScheduledFuture deviceUpdateTask; - private @Nullable HueSyncDeviceInfo deviceInfo; + private @Nullable HueSyncDeviceStatus deviceInfo; private HueSyncDeviceConnection connection; private HueSyncConfiguration config; @@ -105,7 +106,7 @@ private void stopTask(@Nullable ScheduledFuture task) { private void startBackgroundTasks() { Optional.ofNullable(this.deviceInfo).ifPresent((device) -> { Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, - (deviceStatus) -> this.updateDeviceStatus(deviceStatus)); + (deviceStatus) -> this.updateStatus(deviceStatus)); if (this.connection.isRegistered()) { this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, @@ -137,24 +138,27 @@ private void startUpdateTask(Runnable updateTask) { this.config.statusUpdateInterval); } - private void updateDeviceStatus(@Nullable HueSyncDetailedDeviceInfo deviceState) { + private void updateStatus(@Nullable HueSyncUpdateInfo update) { ThingStatus currentStatus = this.thing.getStatus(); logger.trace("Current status: {}", currentStatus); - if (deviceState == null) { + @SuppressWarnings("null") + HueSyncDetailedDeviceStatus deviceStatus = update.deviceStatus; + + if (deviceStatus == null) { this.updateStatus(ThingStatus.OFFLINE); } else { this.updateStatus(ThingStatus.ONLINE); - State firmwareState = new StringType(deviceState.firmwareVersion); + State firmwareState = new StringType(deviceStatus.firmwareVersion); State firmwareAvailableState = new StringType( - Optional.ofNullable(deviceState.updatableFirmwareVersion).isPresent() - ? deviceState.updatableFirmwareVersion - : deviceState.firmwareVersion); + Optional.ofNullable(deviceStatus.updatableFirmwareVersion).isPresent() + ? deviceStatus.updatableFirmwareVersion + : deviceStatus.firmwareVersion); - setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceState.firmwareVersion); - setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceState.apiLevel)); + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceStatus.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceStatus.apiLevel)); this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); @@ -172,11 +176,11 @@ private void setRegistration(HueSyncRegistration registration) { Configuration configuration = this.editConfiguration(); configuration.put(HueSyncConstants.REGISTRATION_ID, id.get()); - configuration.put(HueSyncConstants.REGISTRATION_ID, token.get()); + configuration.put(HueSyncConstants.API_TOKEN, token.get()); this.updateConfiguration(configuration); String deviceName = "⚠️ unknown device ⚠️"; - Optional deviceInfo = Optional.ofNullable(this.deviceInfo); + Optional deviceInfo = Optional.ofNullable(this.deviceInfo); if (deviceInfo.isPresent()) { deviceName = Optional.ofNullable(deviceInfo.get().name).orElse(deviceName); @@ -188,7 +192,7 @@ private void setRegistration(HueSyncRegistration registration) { private void checkCompatibility() throws HueSyncApiException { try { - HueSyncDeviceInfo info = Optional.ofNullable(this.deviceInfo).orElseThrow(); + HueSyncDeviceStatus info = Optional.ofNullable(this.deviceInfo).orElseThrow(); if (info.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { throw new HueSyncApiException("@text/api.minimal-version", this.logger); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 0386d9eca168a..e10c6ff720502 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -16,7 +16,7 @@ import java.util.function.Supplier; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceStatus; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; @@ -33,12 +33,12 @@ public class HueSyncRegistrationTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); private HueSyncDeviceConnection connection; - private HueSyncDeviceInfo deviceInfo; + private HueSyncDeviceStatus deviceInfo; private Consumer action; private Supplier status; - public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDeviceInfo deviceInfo, + public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDeviceStatus deviceInfo, Supplier status, Consumer action) { this.connection = connection; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateInfo.java new file mode 100644 index 0000000000000..1b385a6c3d975 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateInfo.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.handler.tasks; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceStatus; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiStatus; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncUpdateInfo { + public @Nullable HueSyncDetailedDeviceStatus deviceStatus; + public @Nullable HueSyncHdmiStatus hdmiStatus; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index d552fa2e7f72a..9955832ac090e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -16,8 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceInfo; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceInfo; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceStatus; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -33,12 +32,12 @@ public class HueSyncUpdateTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); private HueSyncDeviceConnection connection; - private HueSyncDeviceInfo deviceInfo; + private HueSyncDeviceStatus deviceInfo; - private Consumer<@Nullable HueSyncDetailedDeviceInfo> action; + private Consumer<@Nullable HueSyncUpdateInfo> action; - public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDeviceInfo deviceInfo, - Consumer<@Nullable HueSyncDetailedDeviceInfo> action) { + public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDeviceStatus deviceInfo, + Consumer<@Nullable HueSyncUpdateInfo> action) { this.connection = connection; this.deviceInfo = deviceInfo; @@ -52,9 +51,11 @@ public void run() { this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - HueSyncDetailedDeviceInfo deviceStatus = this.connection.getDetailedDeviceInfo(); + HueSyncUpdateInfo updateInfo = new HueSyncUpdateInfo(); + updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo(); + updateInfo.hdmiStatus = this.connection.getHdmiInfo(); - this.action.accept(deviceStatus); + this.action.accept(updateInfo); } catch (Exception e) { this.logger.debug("{}", e.getMessage()); From f7a6d9187419bfdaa964c8c1746fed9edb93378c Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 14 May 2024 23:26:41 +0200 Subject: [PATCH 042/128] =?UTF-8?q?feat(channel):=20Some=20HDMI=20input=20?= =?UTF-8?q?and=20ouput=20channels=20added=20(read=20only)=20-=20?= =?UTF-8?q?=F0=9F=94=A7=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🧹 Additional code clean up and refactorings to improve maintainability (⚙️ including refactoring of background job handling) Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 28 ++- .../huesync/internal/HueSyncConstants.java | 27 +++ .../HueSyncConnectionExceptionType.java | 2 +- .../internal/handler/HueSyncHandler.java | 203 ++++++++++-------- .../tasks/HueSyncRegistrationTask.java | 13 +- .../handler/tasks/HueSyncUpdateTask.java | 15 +- .../resources/OH-INF/i18n/huesync.properties | 20 ++ .../resources/OH-INF/thing/channel-types.xml | 61 ++++-- .../resources/OH-INF/thing/thing-types.xml | 29 ++- 9 files changed, 274 insertions(+), 124 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 16b9227bedce4..ee16e12c818b2 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -89,7 +89,33 @@ Example thing configuration goes here. ### Item Configuration ```java -Example item configuration goes here. +Group HueSyncBox "HueSyncBox" ["NetworkAppliance"] + +Group HueSyncBox_Firmware "Firmware" (HueSyncBox) ["Sensor"] + +Group HueSyncBox_Inputs "Inputs" (HueSyncBox) ["Receiver"] + +Group HueSyncBox_Input_1 "Input 1" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_2 "Input 2" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_3 "Input 3" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_4 "Input 4" (HueSyncBox_Inputs) ["Receiver"] + +Group HueSyncBox_Output "Output" (HueSyncBox) ["Screen"] + +String HueSyncBox_Firmware_Version "Firmware Version" (HueSyncBox_Firmware) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" } +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" (HueSyncBox_Firmware) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" } + +String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" (HueSyncBox_Input_1) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" } +String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" (HueSyncBox_Input_1) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" } +String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" (HueSyncBox_Input_2) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" } +String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" (HueSyncBox_Input_2) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" } +String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" (HueSyncBox_Input_3) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" } +String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" (HueSyncBox_Input_3) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" } +String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" (HueSyncBox_Input_4) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" } +String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" (HueSyncBox_Input_4) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" } + +String HueSyncBox_Device_hdmi_out_Name "Name - Output" (HueSyncBox_Output) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" } +String HueSyncBox_Device_hdmi_out_Type "Type - Output" (HueSyncBox_Output) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" } ``` ### Sitemap Configuration diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index a2bddb25ecbd1..6e912733f0b94 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -36,6 +36,33 @@ public static class INFORMATION { public static final String FIRMWARE_AVAILABLE = "device-firmware#available-firmware"; } } + + public static class HDMI { + public static class IN_1 { + public static final String NAME = "device-hdmi-in-1#name"; + public static final String TYPE = "device-hdmi-in-1#type"; + } + + public static class IN_2 { + public static final String NAME = "device-hdmi-in-2#name"; + public static final String TYPE = "device-hdmi-in-2#type"; + } + + public static class IN_3 { + public static final String NAME = "device-hdmi-in-3#name"; + public static final String TYPE = "device-hdmi-in-3#type"; + } + + public static class IN_4 { + public static final String NAME = "device-hdmi-in-4#name"; + public static final String TYPE = "device-hdmi-in-4#type"; + } + + public static class OUT { + public static final String NAME = "device-hdmi-out#name"; + public static final String TYPE = "device-hdmi-out#type"; + } + } } public static final String APPLICATION_NAME = "openHAB"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java index 2547c8b81fccf..53b3cc29e6717 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java @@ -20,5 +20,5 @@ */ @NonNullByDefault public enum HueSyncConnectionExceptionType { - + GENERIC } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index ce874f4e84178..0e532fbe33f30 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.security.cert.CertificateException; +import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; @@ -27,6 +28,7 @@ import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDetailedDeviceStatus; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceStatus; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiStatus; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; @@ -55,18 +57,20 @@ */ @NonNullByDefault public class HueSyncHandler extends BaseThingHandler { + private static final String JOB_REGISTRATION = "Registration"; + private static final String JOB_UPDATE = "Update"; + private static final String PROPERTY_API_VERSION = "apiVersion"; - // private static final String PROPERTY_NETWORK_STATE = "networkState"; private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); - private @Nullable ScheduledFuture deviceRegistrationTask; - private @Nullable ScheduledFuture deviceUpdateTask; + Map> tasks = new HashMap<>(); private @Nullable HueSyncDeviceStatus deviceInfo; + private @Nullable HueSyncDeviceConnection connection; - private HueSyncDeviceConnection connection; private HueSyncConfiguration config; + private HttpClient httpClient; protected class HueSyncProperties { @@ -76,11 +80,10 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException, URISyntaxException { super(thing); - HttpClient httpClient = httpClientFactory.getCommonHttpClient(); - httpClient.setName(this.thing.getUID().getAsString()); + this.httpClient = httpClientFactory.getCommonHttpClient(); + this.httpClient.setName(this.thing.getUID().getAsString()); this.config = getConfigAs(HueSyncConfiguration.class); - this.connection = new HueSyncDeviceConnection(httpClient, this.config.host, this.config.port); } // #region private @@ -102,40 +105,15 @@ private void stopTask(@Nullable ScheduledFuture task) { return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); } - @SuppressWarnings("unchecked") - private void startBackgroundTasks() { - Optional.ofNullable(this.deviceInfo).ifPresent((device) -> { - Runnable statusUpdateTask = new HueSyncUpdateTask(this.connection, device, - (deviceStatus) -> this.updateStatus(deviceStatus)); - - if (this.connection.isRegistered()) { - this.logger.debug("Device {} {}:{} is already registered", device.name, device.deviceType, - device.uniqueId); - - this.startUpdateTask(statusUpdateTask); - } else { - this.logger.info("Starting device registration for {} {}:{}", device.name, device.deviceType, - device.uniqueId); - - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "@text/thing.config.huesync.box.registration"); - - Runnable task = new HueSyncRegistrationTask(connection, device, () -> thing.getStatus(), - (registration) -> { - this.setRegistration(registration); - this.startUpdateTask(statusUpdateTask); - }); - - this.deviceRegistrationTask = (ScheduledFuture) this.executeTask(task, - HueSyncConstants.REGISTRATION_INITIAL_DELAY, HueSyncConstants.REGISTRATION_INTERVAL); - } + private void startBackgroundTasks(HueSyncDeviceStatus device, HueSyncDeviceConnection connection) { + Runnable update = new HueSyncUpdateTask(connection, device, (deviceStatus) -> this.updateStatus(deviceStatus)); + Runnable register = new HueSyncRegistrationTask(connection, device, () -> thing.getStatus(), (registration) -> { + this.setRegistration(registration); }); - } - @SuppressWarnings("unchecked") - private void startUpdateTask(Runnable updateTask) { - this.deviceUpdateTask = (ScheduledFuture) this.executeTask(updateTask, 0, - this.config.statusUpdateInterval); + this.tasks.put(JOB_REGISTRATION, this.executeTask(register, HueSyncConstants.REGISTRATION_INITIAL_DELAY, + HueSyncConstants.REGISTRATION_INTERVAL)); + this.tasks.put(JOB_UPDATE, this.executeTask(update, 0, this.config.statusUpdateInterval)); } private void updateStatus(@Nullable HueSyncUpdateInfo update) { @@ -151,18 +129,41 @@ private void updateStatus(@Nullable HueSyncUpdateInfo update) { } else { this.updateStatus(ThingStatus.ONLINE); - State firmwareState = new StringType(deviceStatus.firmwareVersion); - State firmwareAvailableState = new StringType( - Optional.ofNullable(deviceStatus.updatableFirmwareVersion).isPresent() - ? deviceStatus.updatableFirmwareVersion - : deviceStatus.firmwareVersion); + updateFirmwareInformation(deviceStatus); + updateHdmiInformation(update.hdmiStatus); + } + } - setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceStatus.firmwareVersion); - setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceStatus.apiLevel)); + @SuppressWarnings("null") + private void updateHdmiInformation(@Nullable HueSyncHdmiStatus hdmiStatus) { + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.NAME, new StringType(hdmiStatus.input1.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.TYPE, new StringType(hdmiStatus.input1.type)); - this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); - this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); - } + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.NAME, new StringType(hdmiStatus.input2.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.TYPE, new StringType(hdmiStatus.input2.type)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.NAME, new StringType(hdmiStatus.input3.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.TYPE, new StringType(hdmiStatus.input3.type)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.NAME, new StringType(hdmiStatus.input4.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.TYPE, new StringType(hdmiStatus.input4.type)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.NAME, new StringType(hdmiStatus.output.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.TYPE, new StringType(hdmiStatus.output.type)); + } + + private void updateFirmwareInformation(HueSyncDetailedDeviceStatus deviceStatus) { + State firmwareState = new StringType(deviceStatus.firmwareVersion); + State firmwareAvailableState = new StringType( + Optional.ofNullable(deviceStatus.updatableFirmwareVersion).isPresent() + ? deviceStatus.updatableFirmwareVersion + : deviceStatus.firmwareVersion); + + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceStatus.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceStatus.apiLevel)); + + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); } private void setRegistration(HueSyncRegistration registration) { @@ -170,7 +171,7 @@ private void setRegistration(HueSyncRegistration registration) { Optional token = Optional.ofNullable(registration.accessToken); if (id.isPresent() && token.isPresent()) { - this.stopTask(deviceRegistrationTask); + this.stopTask(this.tasks.get(JOB_REGISTRATION)); setProperty(HueSyncConstants.REGISTRATION_ID, id.get()); @@ -227,45 +228,64 @@ private void saveProperty(String key, String value, Map properti // #region Override @Override public void initialize() { - // TODO: Check if we need to handle enable/disable state ... + try { + updateStatus(ThingStatus.UNKNOWN); - updateStatus(ThingStatus.UNKNOWN); + this.tasks.values().forEach(task -> { + this.stopTask(task); + }); - this.stopTask(this.deviceRegistrationTask); - this.stopTask(this.deviceUpdateTask); + this.connection = new HueSyncDeviceConnection(httpClient, this.config.host, this.config.port); - this.config = getConfigAs(HueSyncConfiguration.class); - this.connection.updateConfig(this.config); - - scheduler.execute(() -> { - try { - this.deviceInfo = this.connection.getDeviceInfo(); - - Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { - setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); - setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); - - setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); - setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); - // setProperty(HueSyncHandler.PROPERTY_NETWORK_STATE, info.wifiState); - try { - this.checkCompatibility(); - this.startBackgroundTasks(); - } catch (HueSyncApiException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + Optional.ofNullable(this.connection).ifPresent((connection) -> { + this.config = getConfigAs(HueSyncConfiguration.class); + + Optional.ofNullable(this.connection).ifPresent((config) -> { + connection.updateConfig(this.config); + + if (!connection.isRegistered()) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "@text/thing.config.huesync.box.registration"); } + + scheduler.execute(() -> { + try { + this.deviceInfo = connection.getDeviceInfo(); + Optional.ofNullable(this.deviceInfo).ifPresent((info) -> { + setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId); + setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); + + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + + try { + this.checkCompatibility(); + this.startBackgroundTasks(info, connection); + } catch (HueSyncApiException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + e.getMessage()); + } + }); + + } catch (Exception e) { + this.logger.error("{}", e.getMessage()); + this.updateStatus(ThingStatus.OFFLINE); + } + }); }); - } catch (Exception e) { - this.logger.error("{}", e.getMessage()); - this.updateStatus(ThingStatus.OFFLINE); - } - }); + }); + // throw new HueSyncConnectionException("@text/exception.generic.connection", + // HueSyncConnectionExceptionType.GENERIC, this.logger); + } catch (Exception e) { + this.logger.error("{}", e.getMessage()); + this.updateStatus(ThingStatus.OFFLINE); + } } @Override public void handleCommand(ChannelUID channelUID, Command command) { - // TODO: Implementation ... + this.logger.info("Channel UID: {} - Command: {}", channelUID.getAsString(), command.toFullString()); } @Override @@ -273,14 +293,20 @@ public void dispose() { super.dispose(); try { - this.stopTask(deviceRegistrationTask); - this.stopTask(deviceUpdateTask); + Optional.ofNullable(this.connection).ifPresent((connection) -> { + connection.dispose(); + }); + + this.connection = null; - this.connection.dispose(); + this.tasks.values().forEach((task) -> { + this.stopTask(task); + }); } catch (Exception e) { this.logger.error("{}", e.getMessage()); } finally { this.logger.info("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); + this.connection = null; } } @@ -288,11 +314,14 @@ public void dispose() { public void handleRemoval() { super.handleRemoval(); - if (this.connection.unregisterDevice()) { - this.logger.error( - "It was not possible to unregister {} ({}). You may use id: {} Key: {} to manually re-configure the thing, or to manually remove the device via API.", - this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, this.config.apiAccessToken); - } + Optional.ofNullable(this.connection).ifPresent((connection) -> { + if (connection.unregisterDevice()) { + this.logger.error( + "It was not possible to unregister {} ({}). You may use id: {} Key: {} to manually re-configure the thing, or to manually remove the device via API.", + this.thing.getLabel(), this.thing.getUID(), this.config.registrationId, + this.config.apiAccessToken); + } + }); } // #endregion diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index e10c6ff720502..ae0cc43ba99a0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -50,11 +50,16 @@ public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice @Override public void run() { try { - if (this.status.get() == ThingStatus.OFFLINE) { - HueSyncRegistration registration = this.connection.registerDevice(deviceInfo.uniqueId); + if (!this.connection.isRegistered()) { + this.logger.info("Starting device registration for {} {}:{}", this.deviceInfo.name, + this.deviceInfo.deviceType, this.deviceInfo.uniqueId); - if (registration != null) { - this.action.accept(registration); + if (this.status.get() == ThingStatus.OFFLINE) { + HueSyncRegistration registration = this.connection.registerDevice(deviceInfo.uniqueId); + + if (registration != null) { + this.action.accept(registration); + } } } } catch (Exception e) { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 9955832ac090e..2c0d7e18542f1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -48,15 +48,16 @@ public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDeviceStatus @Override public void run() { try { - this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, - this.deviceInfo.uniqueId); + if (this.connection.isRegistered()) { + this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.deviceInfo.uniqueId); - HueSyncUpdateInfo updateInfo = new HueSyncUpdateInfo(); - updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo(); - updateInfo.hdmiStatus = this.connection.getHdmiInfo(); - - this.action.accept(updateInfo); + HueSyncUpdateInfo updateInfo = new HueSyncUpdateInfo(); + updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo(); + updateInfo.hdmiStatus = this.connection.getHdmiInfo(); + this.action.accept(updateInfo); + } } catch (Exception e) { this.logger.debug("{}", e.getMessage()); } diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index fd65088b97d9b..cd4202efd7d97 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -26,14 +26,30 @@ thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds betwe channel-group-type.huesync.device-firmware.label = Firmware channel-group-type.huesync.device-firmware.description = Information about the installed device firmaware and available updates. +channel-group-type.huesync.device-hdmi-connection-in.label = HDMI Input +channel-group-type.huesync.device-hdmi-connection-in.description = HDMI connection +channel-group-type.huesync.device-hdmi-connection-out.label = HDMI output +channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connection # channel types +channel-type.huesync.connection-name.label = Friendly name +channel-type.huesync.connection-type.label = Friendly type channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version channel-type.huesync.device-info-firmware-available.description = Latest available firmware version channel-type.huesync.device-info-firmware.label = Firmware Version channel-type.huesync.device-info-firmware.description = Installed firmware version +# channel types + +channel-type.huesync.connection-type-in.label = Friendly type +channel-type.huesync.connection-type-out.label = Friendly type + +# channel group types + +channel-group-type.huesync.device-hdmi-connection.label = hdmi +channel-group-type.huesync.device-hdmi-connection.description = HDMI connection + # channel group types channel-group-type.huesync.device.label = Generic device information @@ -91,6 +107,10 @@ thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between thing-type.huesync.huesync.label = Hue HDMI Sync Box thing-type.huesync.huesync.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. +# *** exceptions *** + +exception.generic.connection = "Unable to connect to device." + # api exceptions api.minimal-version = Only devices with API level >= 7 are supported diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index ea02fcea628a2..53f1242e4b089 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -3,15 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + + + + String + + Text + + + String + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index a4fa4dcd75501..e5abc67daf997 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -12,8 +12,17 @@ smart light that responds to and reflects the content you watch or listen to. + NetworkAppliance + + + + + + + + @@ -28,10 +37,28 @@ Information about the installed device firmaware and available updates. - + Group + + + HDMI connection + Receiver + + + + + + + + HDMI connection + Screen + + + + + From 520713de6cccac37d0c4e6d6803ce43896852f3a Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 18 May 2024 10:21:26 +0200 Subject: [PATCH 043/128] feat(channel): Additional channel `connection-status`added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📜 includes also documentation updates in README.md Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 109 +++++++++++------- .../huesync/internal/HueSyncConstants.java | 5 + .../internal/handler/HueSyncHandler.java | 5 + .../resources/OH-INF/i18n/huesync.properties | 2 + .../resources/OH-INF/thing/channel-types.xml | 23 +++- .../resources/OH-INF/thing/thing-types.xml | 2 + 6 files changed, 99 insertions(+), 47 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index ee16e12c818b2..4e8d9165e6fe3 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -1,11 +1,21 @@ # HueSync Binding This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. -The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/) (not via a Hue Bridge). +The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/). -![Play HDMI Sync Box](doc/bridge1.png) +![Play HDMI Sync Box](doc/bridge1.png) ![Play HDMI Sync Box](doc/bridge2.png) +- [HueSync Binding](#huesync-binding) + - [Discovery](#discovery) + - [Thing Configuration](#thing-configuration) + - [Thing Configuration `huesyncthing`](#thing-configuration-huesyncthing) + - [Channels](#channels) + - [Channel Group `device-firmware`](#channel-group-device-firmware) + - [Chanel Group `device-hdmi-connection-in` and `device-hdmi-connection-in`](#chanel-group-device-hdmi-connection-in-and-device-hdmi-connection-in) + - [Item Configuration](#item-configuration) + + ## Discovery @@ -41,15 +52,17 @@ $ avahi-browse --resolve _huesync._tcp ## Thing Configuration -To enable the binding to communicate with the device, a registration is required. -Once the registration process is completed, the acquired token will authorize the binding to communicate with the device. +To enable the binding to communicate with the device, a registration is required. +Once the registration process is completed, the acquired token will authorize the binding to communicate with the device. After initial discovery and thing creation the device will stay offline. To complete the authentication you need to pressed the registration button on the sync box for 3 seconds. -_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ + ### Thing Configuration `huesyncthing` @@ -65,8 +78,6 @@ _Note that it is planned to generate some part of this based on the XML files wi ### Channel Group `device-firmware` -#### Firmware - Information about the installed device firmware and available updates. | Channel | Type | Read/Write | Description | @@ -74,50 +85,61 @@ Information about the installed device firmware and available updates. | firmware | String | R | Installed firmware version | | available-firmware | String | R | Latest available firmware version | +### Chanel Group `device-hdmi-connection-in` and `device-hdmi-connection-in` + +Information about a HDMI input connection. + +| Channel | Type | Read/Write | Description | +| ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | String | R | Installed firmware version | +| type | String | R |
Friendly Type
  • generic
  • video
  • game
  • music
  • xbox
  • playstation
  • nintendoswitch
  • phone
  • desktop
  • laptop
  • appletv
  • roku
  • shield
  • chromecast
  • firetv
  • diskplayer
  • settopbox
  • satellite
  • avreceiver
  • soundbar
  • hdmiswitch
| +| status | String | R |
  • unplugged
  • plugged
  • linked
  • unknown
| + + ### Item Configuration -```java -Group HueSyncBox "HueSyncBox" ["NetworkAppliance"] - -Group HueSyncBox_Firmware "Firmware" (HueSyncBox) ["Sensor"] - -Group HueSyncBox_Inputs "Inputs" (HueSyncBox) ["Receiver"] - -Group HueSyncBox_Input_1 "Input 1" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_2 "Input 2" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_3 "Input 3" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_4 "Input 4" (HueSyncBox_Inputs) ["Receiver"] - -Group HueSyncBox_Output "Output" (HueSyncBox) ["Screen"] - -String HueSyncBox_Firmware_Version "Firmware Version" (HueSyncBox_Firmware) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" } -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" (HueSyncBox_Firmware) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" } - -String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" (HueSyncBox_Input_1) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" } -String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" (HueSyncBox_Input_1) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" } -String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" (HueSyncBox_Input_2) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" } -String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" (HueSyncBox_Input_2) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" } -String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" (HueSyncBox_Input_3) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" } -String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" (HueSyncBox_Input_3) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" } -String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" (HueSyncBox_Input_4) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" } -String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" (HueSyncBox_Input_4) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" } - -String HueSyncBox_Device_hdmi_out_Name "Name - Output" (HueSyncBox_Output) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" } -String HueSyncBox_Device_hdmi_out_Type "Type - Output" (HueSyncBox_Output) ["Property"] { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" } -``` - +| | | | | | | | +| ------ | ---------------------------------- | ------------------------- | ------------------------- | --------------------- | -------------------- | ----------------------------------------------------------------------------------- | +| Group | HueSyncBox | "HueSyncBox" | | | ["NetworkAppliance"] | | +| Group | HueSyncBox_Firmware | "Firmware" | | (HueSyncBox) | ["Sensor"] | | +| Group | HueSyncBox_Inputs | "Inputs" | | (HueSyncBox) | ["Receiver"] | | +| Group | HueSyncBox_Input_1 | "Input 1" | | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_2 | "Input 2" | | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_3 | "Input 3" | | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_4 | "Input 4" | | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Output | "Output" | | (HueSyncBox) | ["Screen"] | | +| String | HueSyncBox_Firmware_Version | "Firmware Version" | | (HueSyncBox_Firmware) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" } ` | +| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | | (HueSyncBox_Firmware) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" } ` | +| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | | (HueSyncBox_Input_1) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" } ` | +| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | | (HueSyncBox_Input_1) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" } ` | +| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | | (HueSyncBox_Input_1) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#status" } ` | +| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | | (HueSyncBox_Input_2) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" } ` | +| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | | (HueSyncBox_Input_2) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" } ` | +| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | | (HueSyncBox_Input_2) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#status" } ` | +| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | | (HueSyncBox_Input_3) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" } ` | +| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | | (HueSyncBox_Input_3) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" } ` | +| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | | (HueSyncBox_Input_3) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#status" } ` | +| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | | (HueSyncBox_Input_4) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" } ` | +| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | | (HueSyncBox_Input_4) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" } ` | +| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | | (HueSyncBox_Input_4) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#status" } ` | +| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | | (HueSyncBox_Output) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" } ` | +| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | | (HueSyncBox_Output) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" } ` | +| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output 1" | | (HueSyncBox_Output) | ["Property"] | {` channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#status" } ` | + + diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 6e912733f0b94..ec7f7130f3da3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -41,26 +41,31 @@ public static class HDMI { public static class IN_1 { public static final String NAME = "device-hdmi-in-1#name"; public static final String TYPE = "device-hdmi-in-1#type"; + public static final String STATUS = "device-hdmi-in-1#status"; } public static class IN_2 { public static final String NAME = "device-hdmi-in-2#name"; public static final String TYPE = "device-hdmi-in-2#type"; + public static final String STATUS = "device-hdmi-in-2#status"; } public static class IN_3 { public static final String NAME = "device-hdmi-in-3#name"; public static final String TYPE = "device-hdmi-in-3#type"; + public static final String STATUS = "device-hdmi-in-3#status"; } public static class IN_4 { public static final String NAME = "device-hdmi-in-4#name"; public static final String TYPE = "device-hdmi-in-4#type"; + public static final String STATUS = "device-hdmi-in-4#status"; } public static class OUT { public static final String NAME = "device-hdmi-out#name"; public static final String TYPE = "device-hdmi-out#type"; + public static final String STATUS = "device-hdmi-out#status"; } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 0e532fbe33f30..fe89549c88ae3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -138,18 +138,23 @@ private void updateStatus(@Nullable HueSyncUpdateInfo update) { private void updateHdmiInformation(@Nullable HueSyncHdmiStatus hdmiStatus) { this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.NAME, new StringType(hdmiStatus.input1.name)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.TYPE, new StringType(hdmiStatus.input1.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.STATUS, new StringType(hdmiStatus.input1.status)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.NAME, new StringType(hdmiStatus.input2.name)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.TYPE, new StringType(hdmiStatus.input2.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.STATUS, new StringType(hdmiStatus.input2.status)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.NAME, new StringType(hdmiStatus.input3.name)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.TYPE, new StringType(hdmiStatus.input3.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.STATUS, new StringType(hdmiStatus.input3.status)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.NAME, new StringType(hdmiStatus.input4.name)); this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.TYPE, new StringType(hdmiStatus.input4.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.STATUS, new StringType(hdmiStatus.input4.status)); this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.NAME, new StringType(hdmiStatus.output.name)); this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.TYPE, new StringType(hdmiStatus.output.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.STATUS, new StringType(hdmiStatus.output.status)); } private void updateFirmwareInformation(HueSyncDetailedDeviceStatus deviceStatus) { diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index cd4202efd7d97..8a18f2d5bc2e2 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -34,6 +34,8 @@ channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connect # channel types channel-type.huesync.connection-name.label = Friendly name +channel-type.huesync.connection-status.label = HDMI connection Status +channel-type.huesync.connection-status.description = Status of the HDMI input channel-type.huesync.connection-type.label = Friendly type channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version channel-type.huesync.device-info-firmware-available.description = Latest available firmware version diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index 53f1242e4b089..dbd763cf53fd3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -22,26 +22,41 @@ String Installed firmware version - Text
String Latest available firmware version - Text String - Text + + + + + String + + Status of the HDMI input + + + + + + + + + + + String - Text + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index e5abc67daf997..cedb95c34be32 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -50,6 +50,7 @@ +
@@ -59,6 +60,7 @@ + From a95d4016c17d9b649ddd37a13496f4ae297da5d5 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 19 May 2024 22:40:58 +0200 Subject: [PATCH 044/128] =?UTF-8?q?feat(channel):=20=E2=9E=95=20Additional?= =?UTF-8?q?=20channel=20`connection-lastSyncMode`=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 120 ++++++++++++++---- .../huesync/internal/HueSyncConstants.java | 5 + .../internal/handler/HueSyncHandler.java | 5 + .../resources/OH-INF/i18n/huesync.properties | 4 + .../resources/OH-INF/thing/channel-types.xml | 16 +++ .../resources/OH-INF/thing/thing-types.xml | 2 + 6 files changed, 124 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 4e8d9165e6fe3..eada6d6c4837b 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -93,7 +93,8 @@ Information about a HDMI input connection. | ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | String | R | Installed firmware version | | type | String | R |
Friendly Type
  • generic
  • video
  • game
  • music
  • xbox
  • playstation
  • nintendoswitch
  • phone
  • desktop
  • laptop
  • appletv
  • roku
  • shield
  • chromecast
  • firetv
  • diskplayer
  • settopbox
  • satellite
  • avreceiver
  • soundbar
  • hdmiswitch
| -| status | String | R |
  • unplugged
  • plugged
  • linked
  • unknown
| +| status | String | R |
Device connection status>
  • unplugged
  • plugged
  • linked
  • unknown
| +| mode | String | R |
Last sync mode used for this channel.
  • video
  • game
  • music
| -### Thing Configuration `huesyncthing` +### Thing Configuration "huesyncthing" | Name | Type | Description | Default | Required | Advanced | | -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | @@ -76,7 +80,7 @@ _Note that it is planned to generate some part of this based on the XML files wi ## Channels -### Channel Group `device-firmware` +### Group - "device-firmware" Information about the installed device firmware and available updates. @@ -85,17 +89,32 @@ Information about the installed device firmware and available updates. | firmware | String | R | Installed firmware version | | available-firmware | String | R | Latest available firmware version | -### Chanel Group `device-hdmi-connection-in` and `device-hdmi-connection-in` +### Group - "device-hdmi-connection-[in\|out]" Information about a HDMI input connection. | Channel | Type | Read/Write | Description | | ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | String | R | Installed firmware version | | type | String | R |
Friendly Type
  • generic
  • video
  • game
  • music
  • xbox
  • playstation
  • nintendoswitch
  • phone
  • desktop
  • laptop
  • appletv
  • roku
  • shield
  • chromecast
  • firetv
  • diskplayer
  • settopbox
  • satellite
  • avreceiver
  • soundbar
  • hdmiswitch
| | status | String | R |
Device connection status>
  • unplugged
  • plugged
  • linked
  • unknown
| | mode | String | R |
Last sync mode used for this channel.
  • video
  • game
  • music
| +### Group - "device-commands" + +| Channel | Type | Read/Write | Description | +| ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | String | R/W |
Hue Sync operation mode
  • powersave
  • passthrough
  • video
  • game
  • music
  • ambient
  • unknown (read only)
| + +#### mode + +- **video**
Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience. +- **music**
Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes. +- **game**
Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n +- **ambient** +- **passthrough** +- **powersave** +- **unknown**
The device reports a mode not known to the binding (e.g. intorduced by a new firmware). *NOTE: It is not possible to send this command to the device.* + -### Item Configuration +## Item Configuration
Groups From 0744fcd9974f14006b44ba83ab6a3658324e0b84 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 1 Jun 2024 10:38:33 +0200 Subject: [PATCH 051/128] =?UTF-8?q?fix(channel):=20=F0=9F=90=9ECommand=20e?= =?UTF-8?q?xecutors=20are=20not=20global,=20but=20are=20defined=20per=20de?= =?UTF-8?q?vice.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../openhab/binding/huesync/internal/HueSyncConstants.java | 7 ------- .../internal/connection/HueSyncDeviceConnection.java | 7 ++++++- .../binding/huesync/internal/handler/HueSyncHandler.java | 7 ++++--- .../src/main/resources/OH-INF/i18n/huesync.properties | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 9d88ccac3aca6..b9c909e6690c5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -12,13 +12,8 @@ */ package org.openhab.binding.huesync.internal; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.types.Command; /** * The {@link HueSyncConstants} class defines common constants, which are @@ -45,8 +40,6 @@ public static class INFORMATION { public static class COMMANDS { public static final String MODE = "device-commands#mode"; - - public static Map> EXECUTORS = new HashMap<>(); } public static class HDMI { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 78ee98e449b2c..6564961dcfb5b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -15,7 +15,10 @@ import java.io.IOException; import java.net.URISyntaxException; import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -32,6 +35,7 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequestDto; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.types.Command; import org.slf4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; @@ -45,13 +49,14 @@ public class HueSyncDeviceConnection { private HueSyncConnection connection; private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); + public Map> DeviceCommandsExecutors = new HashMap<>(); public HueSyncDeviceConnection(HttpClient httpClient, String host, Integer port) throws CertificateException, IOException, URISyntaxException { this.connection = new HueSyncConnection(httpClient, host, port); - COMMANDS.EXECUTORS.put(COMMANDS.MODE, command -> { + this.DeviceCommandsExecutors.put(COMMANDS.MODE, command -> { this.logger.info("Command executor: {}", command); }); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index e97e1d6703357..77845e06d3bdb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -26,7 +26,6 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.HueSyncConstants.CHANNELS.COMMANDS; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDto; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDtoDetailed; import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecutionDto; @@ -132,6 +131,8 @@ private void updateStatus(@Nullable HueSyncUpdateInfo update) { this.updateFirmwareInformation(deviceStatus); this.updateHdmiInformation(update.hdmiStatus); this.updateExecutionInformation(update.execution); + + // TODO: Handle Exception(s) } } @@ -323,8 +324,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } String commandId = channel.getUID().getId(); - if (COMMANDS.EXECUTORS.containsKey(commandId)) { - COMMANDS.EXECUTORS.get(commandId).accept(command); + if (this.connection.DeviceCommandsExecutors.containsKey(commandId)) { + this.connection.DeviceCommandsExecutors.get(commandId).accept(command); } else { this.logger.error("No executor registered for command {} - please report this as an issue", commandId); } diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 97ae060203e7b..014ae4cf275e7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -48,7 +48,7 @@ channel-type.huesync.device-info-firmware-available.description = Latest availab channel-type.huesync.device-info-firmware.label = Firmware Version channel-type.huesync.device-info-firmware.description = Installed firmware version channel-type.huesync.execution-mode.label = Hue Sync operation mode -channel-type.huesync.execution-mode.description = "video": Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience. "music": Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.\n "game": Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n "ambient", "passthrough", "powersave", "unknown:" The device reports a mode not known to the binding (e.g. intorduced by a new firmware). NOTE: It is not possible to send this command to the device. +channel-type.huesync.execution-mode.description = "video": Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience. "music": Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes. "game": Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n "ambient", "passthrough", "powersave", "unknown:" The device reports a mode not known to the binding (e.g. intorduced by a new firmware). NOTE: It is not possible to send this command to the device. # channel types From 77e1c103b75cd728ef13d8df23688000c9de93ef Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 2 Jun 2024 19:52:04 +0200 Subject: [PATCH 052/128] =?UTF-8?q?feat(channel):=20=E2=9E=95=20write=20su?= =?UTF-8?q?pport=20for=20"mode"=20channel=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 5 ++-- .../dto/execution/HueSyncExecutionDto.java | 23 +++++++++++++++---- .../connection/HueSyncConnection.java | 7 +++--- .../connection/HueSyncDeviceConnection.java | 12 +++++++++- .../resources/OH-INF/i18n/huesync.properties | 2 +- .../resources/OH-INF/thing/channel-types.xml | 6 ++--- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index add73a31186ff..62ea6450dcf7f 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -103,17 +103,16 @@ Information about a HDMI input connection. | Channel | Type | Read/Write | Description | | ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | String | R/W |
Hue Sync operation mode
  • powersave
  • passthrough
  • video
  • game
  • music
  • ambient
  • unknown (read only)
| +| name | String | R/W |
Hue Sync operation mode
  • powersave
  • passthrough
  • video
  • game
  • music
  • unknown (read only)
| #### mode - **video**
Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience. - **music**
Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes. - **game**
Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n -- **ambient** - **passthrough** - **powersave** -- **unknown**
The device reports a mode not known to the binding (e.g. intorduced by a new firmware). *NOTE: It is not possible to send this command to the device.* +- **unknown**
The device reports a mode not known to the binding (e.g. introduced by a new firmware). *NOTE: It is not possible to send this command to the device.* String Installed firmware version + String @@ -38,7 +25,6 @@ - String From e6e72d0249e7ce66a458fae335ca1a4f81eb2f1d Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 16 Jun 2024 14:53:34 +0200 Subject: [PATCH 056/128] =?UTF-8?q?fix(communication):=20=F0=9F=90=9E=20bu?= =?UTF-8?q?gfix:=20de-serialization=20is=20more=20tolerant=20if=20we=20rec?= =?UTF-8?q?eive=20more=20information=20than=20expected.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dummy channel type "unknown" removed from "mode" command - connection configuration update moved to "start task" (Note: might be problematic if the IP or port configuration changes - not sure if we support this ...) Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 3 +- .../connection/HueSyncConnection.java | 14 ++++--- .../connection/HueSyncDeviceConnection.java | 7 ++-- .../internal/handler/HueSyncHandler.java | 41 ++++++++++++------- .../resources/OH-INF/i18n/huesync.properties | 2 +- .../resources/OH-INF/thing/channel-types.xml | 8 +--- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 1eceec80676b3..1c70118427935 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -103,7 +103,7 @@ Information about a HDMI input connection. | Channel | Type | Read/Write | Description | | ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | String | R |
Hue Sync operation mode
  • powersave
  • passthrough
  • video
  • game
  • music
  • unknown (read only)
| +| name | String | R |
Hue Sync operation mode
  • powersave
  • passthrough
  • video
  • game
  • music
| #### mode @@ -112,7 +112,6 @@ Information about a HDMI input connection. - **game**
Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n - **passthrough** - **powersave** -- **unknown**
The device reports a mode not known to the binding (e.g. introduced by a new firmware). *NOTE: It is not possible to send this command to the device.* This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/). @@ -12,7 +13,6 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [device-firmware](#device-firmware) - [device-hdmi-connection-\[in|out\]](#device-hdmi-connection-inout) - [device-commands](#device-commands) - - [modes](#modes) - [Item Configuration](#item-configuration)
@@ -100,14 +100,18 @@ Information about a HDMI input connection. | ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | type | String | R |
Friendly Type
  • generic
  • video
  • game
  • music
  • xbox
  • playstation
  • nintendoswitch
  • phone
  • desktop
  • laptop
  • appletv
  • roku
  • shield
  • chromecast
  • firetv
  • diskplayer
  • settopbox
  • satellite
  • avreceiver
  • soundbar
  • hdmiswitch
| | status | String | R |
Device connection status
  • unplugged
  • plugged
  • linked
  • unknown
| +| name | String | R | Friendly Name | | mode | String | R |
Mode
  • video
  • game
  • music
  • powersave
  • passthrough
| #### device-commands -| Channel | Type | Read/Write | Description | -| ---------- | ------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| mode | String | R/W |
Hue Sync operation mode
  • video
  • game
  • music
  • powersave
  • passthrough
| -| syncActive | Switch | R/W |
Synchronization

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| +| Channel | Type | Read/Write | Description | +| ---------- | ------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| mode | String | R/W |
Hue Sync operation mode
  • video
  • game
  • music
  • powersave
  • passthrough
| +| hdmiSource | Switch | R/W |
Source
  • input1
  • input2
  • input3
  • input4
| +| syncActive | Switch | R/W |
Synchronization

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| + + + + String Installed firmware version + iconify:mdi:text @@ -15,6 +23,7 @@ String Latest available firmware version + iconify:mdi:text
@@ -22,6 +31,7 @@ String Friendly name of the HDMI connection + iconify:mdi:label @@ -29,6 +39,7 @@ String Status of the HDMI input + iconify:mdi:connection @@ -44,6 +55,7 @@ String Type of the connected HDMI device + iconify:mdi:multimedia @@ -76,6 +88,7 @@ String Last sync mode used for this channel + iconify:mdi:sync @@ -120,6 +133,7 @@

]]> + iconify:mdi:multimedia @@ -144,6 +158,7 @@

]]> + iconify:mdi:toggle-switch @@ -161,6 +176,7 @@

]]> + iconify:mdi:video-input-hdmi @@ -171,4 +187,33 @@
+ + + Dimmer + + + + 0 - 200 +
    +
  • 0 = max reduction
  • +
  • 100 = no brightness reduction/boost compared to input
  • +
  • 200 = max boost
  • +
+

+ ]]> +
+ iconify:mdi:brightness-percent + + Control + Light + + +
+ diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 4235d2d5afb89..a5fb90fc68bbe 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -12,7 +12,7 @@ smart light that responds to and reflects the content you watch or listen to. - NetworkAppliance + iconify:mdi:audio-video @@ -39,7 +39,7 @@ Information about the installed device firmaware and available updates. - Group + iconify:mdi:information @@ -48,7 +48,7 @@ HDMI connection - Receiver + iconify:mdi:hdmi-port @@ -59,7 +59,7 @@ HDMI connection - Screen + iconify:mdi:hdmi-port @@ -71,10 +71,12 @@ Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These commands allow you to influence how the lights react to your entertainment. + iconify:mdi:remote + From 61e88ecdd7ba83f2f42a85cc2c4cb3e39e94c31d Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 16 Aug 2024 21:19:35 +0200 Subject: [PATCH 068/128] =?UTF-8?q?feat(channel):=20=E2=9E=95=20Add=20supp?= =?UTF-8?q?ort=20to=20adjust=20brightness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📜 Refactoring: Simplification of command execution infrastructure. Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 22 ++++---- .../huesync/internal/HueSyncConstants.java | 1 + .../dto/execution/HueSyncExecutionDto.java | 6 ++ .../internal/connection/ExecutionPayload.java | 31 +++++++++++ .../connection/HueSyncDeviceConnection.java | 55 ++++++++++--------- .../internal/handler/HueSyncHandler.java | 3 + .../resources/OH-INF/i18n/huesync.properties | 2 +- .../resources/OH-INF/thing/channel-types.xml | 10 +--- 8 files changed, 87 insertions(+), 43 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/ExecutionPayload.java diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index d5c2d586d0854..4eac3e722d3da 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -105,11 +105,12 @@ Information about a HDMI input connection. #### device-commands -| Channel | Type | Read/Write | Description | -| ---------- | ------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| mode | String | R/W |
Hue Sync operation mode
  • video
  • game
  • music
  • powersave
  • passthrough
| -| hdmiSource | Switch | R/W |
Source
  • input1
  • input2
  • input3
  • input4
| -| syncActive | Switch | R/W |
Synchronization

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| +| Channel | Type | Read/Write | Description | +| ---------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| mode | String | R/W |
Hue Sync operation mode
  • video
  • game
  • music
  • powersave
  • passthrough
| +| hdmiSource | Switch | R/W |
Source
  • input1
  • input2
  • input3
  • input4
| +| syncActive | Switch | R/W |
Synchronization

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| +| brightness | Number:Dimensionless | R/W |
0 ... 200

  • 0 = max reduction
  • 100 = no brightness reduction/boost compared to input
  • 200 = max boost

| - Dimmer + Number:Dimensionless - 0 - 200 + 0 ... 200
  • 0 = max reduction
  • 100 = no brightness reduction/boost compared to input
  • @@ -209,10 +209,6 @@ ]]> iconify:mdi:brightness-percent - - Control - Light - From e69fd3fdaa45892fc7fda1fd3b5945900c358b82 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Mon, 26 Aug 2024 22:29:58 +0200 Subject: [PATCH 069/128] =?UTF-8?q?fix(channel):=20=F0=9F=90=9B=20default?= =?UTF-8?q?=20execution=20handler=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ➕ Add support for HDMI active channel Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 8 +++---- .../huesync/internal/HueSyncConstants.java | 1 + .../internal/connection/ExecutionPayload.java | 2 +- .../connection/HueSyncDeviceConnection.java | 3 ++- .../internal/handler/HueSyncHandler.java | 2 ++ .../resources/OH-INF/i18n/huesync.properties | 4 +++- .../resources/OH-INF/thing/channel-types.xml | 22 +++++++++++++++++-- .../resources/OH-INF/thing/thing-types.xml | 1 + 8 files changed, 34 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 4eac3e722d3da..f826c3f9c8ace 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -110,7 +110,7 @@ Information about a HDMI input connection. | mode | String | R/W |
    Hue Sync operation mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | | hdmiSource | Switch | R/W |
    Source
    • input1
    • input2
    • input3
    • input4
    | | syncActive | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| brightness | Number:Dimensionless | R/W |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +| brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | - -### Thing Configuration "huesyncthing" - | Name | Type | Description | Default | Required | Advanced | | -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | | host | text | IP address of the device | N/A | yes | no | @@ -79,11 +73,77 @@ _Note that it is planned to generate some part of this based on the XML files wi | apiAccessToken | text | API Access Token | N/A | no | yes | | statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | +Once the thing is connection, you can add the equipment to the model using the advanced configuration option of the UI. Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": + +![Add Equipment to Model](doc/add_equipment_to_model.png) + +You may use **Expert Mode** for configuration, or use the UI wizard. + +![Expert Mode](doc/expert_mode.png) + +You need to update the device UID "**..**" in the example with the UID of your device. +
    + Expert Mode - Configuration Example +
    +Group HueSyncBox "HueSyncBox" <iconify:mdi:tv> ["NetworkAppliance"]
    +
    +Group HueSyncBox_Inputs "Inputs" <receiver> (HueSyncBox) ["Receiver"]
    +
    +Group HueSyncBox_Input_1 "Input 1" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    +Group HueSyncBox_Input_2 "Input 2" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    +Group HueSyncBox_Input_3 "Input 3" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    +Group HueSyncBox_Input_4 "Input 4" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    +Group HueSyncBox_Output "Output" <iconify:mdi:tv> (HueSyncBox) ["Screen"]
    +Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox)
    +Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"]
    +
    +String HueSyncBox_Device_Mode  "Mode"  <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#mode" }
    +String HueSyncBox_Device_Input "Input" <iconify:mdi:input>      (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiSource" }
    +Switch HueSyncBox_Device_Hdmi  "HDMI"  <iconify:mdi:hdmi-port>  (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiActive" }
    +Switch HueSyncBox_Device_Sync  "Sync"  <iconify:mdi:sync>       (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#syncActive" }
    +Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#brightness" }
    +
    +String HueSyncBox_Firmware_Version        "Firmware Version"        <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" }           
    +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" } 
    +
    +String HueSyncBox_Device_hdmi_in1_Name      "Name - Input 1"           <iconify:mdi:text>       (HueSyncBox_Input_1)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" }              
    +String HueSyncBox_Device_hdmi_in1_Type      "Type - Input 1"           <iconify:mdi:devices>    (HueSyncBox_Input_1)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" }              
    +String HueSyncBox_Device_hdmi_in1_Status    "Status - Input 1"         <iconify:mdi:connection> (HueSyncBox_Input_1)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#status" }
    +String HueSyncBox_Device_hdmi_in1_Mode      "Mode - Input 1"           <iconify:mdi:multimedia> (HueSyncBox_Input_1)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#mode" }               
    +
    +String HueSyncBox_Device_hdmi_in2_Name      "Name - Input 2"           <iconify:mdi:text>       (HueSyncBox_Input_2)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" }              
    +String HueSyncBox_Device_hdmi_in2_Type      "Type - Input 2"           <iconify:mdi:devices>    (HueSyncBox_Input_2)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" }              
    +String HueSyncBox_Device_hdmi_in2_Status    "Status - Input 2"         <iconify:mdi:connection> (HueSyncBox_Input_2)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#status" } 
    +String HueSyncBox_Device_hdmi_in2_Mode      "Mode - Input 2"           <iconify:mdi:multimedia> (HueSyncBox_Input_2)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#mode" }              
    +
    +String HueSyncBox_Device_hdmi_in3_Name      "Name - Input 3"           <iconify:mdi:text>       (HueSyncBox_Input_3)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" }              
    +String HueSyncBox_Device_hdmi_in3_Type      "Type - Input 3"           <iconify:mdi:devices>    (HueSyncBox_Input_3)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" }              
    +String HueSyncBox_Device_hdmi_in3_Status    "Status - Input 3"         <iconify:mdi:connection> (HueSyncBox_Input_3)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#status" } 
    +String HueSyncBox_Device_hdmi_in3_Mode      "Mode - Input 3"           <iconify:mdi:multimedia> (HueSyncBox_Input_3)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#mode" }              
    +
    +String HueSyncBox_Device_hdmi_in4_Name      "Name - Input 4"           <iconify:mdi:text>       (HueSyncBox_Input_4)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" }              
    +String HueSyncBox_Device_hdmi_in4_Type      "Type - Input 4"           <iconify:mdi:devices>    (HueSyncBox_Input_4)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" }              
    +String HueSyncBox_Device_hdmi_in4_Status    "Status - Input 4"         <iconify:mdi:connection> (HueSyncBox_Input_4)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#status" } 
    +String HueSyncBox_Device_hdmi_in4_Mode      "Mode - Input 4"           <iconify:mdi:multimedia> (HueSyncBox_Input_4)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#mode" }              
    +
    +String HueSyncBox_Device_hdmi_out_Name      "Name - Output"            <iconify:mdi:text>       (HueSyncBox_Output)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" }               
    +String HueSyncBox_Device_hdmi_out_Type      "Type - Output"            <iconify:mdi:tv>         (HueSyncBox_Output)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" }               
    +String HueSyncBox_Device_hdmi_out_Status    "Status - Output"          <iconify:mdi:connection> (HueSyncBox_Output)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#status" }              
    +String HueSyncBox_Device_hdmi_out_Mode      "Mode - Output"            <iconify:mdi:multimedia> (HueSyncBox_Output)   { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#mode" }
    +  
    +
    + +This will give you a default model and item setup: + +![Semantic Model](doc/model.png) + +The following chapters provide more details for a tailored setup. + ## Channels -### Groups +### Channels - Groups -#### device-firmware +### Channels - device-firmware Information about the installed device firmware and available updates. @@ -92,7 +152,7 @@ Information about the installed device firmware and available updates. | firmware | String | R | Installed firmware version | | available-firmware | String | R | Latest available firmware version | -#### device-hdmi-connection-[in\|out] +### Channels - device-hdmi-connection-[in\|out] Information about a HDMI input connection. @@ -137,6 +197,8 @@ Example thing configuration goes here. ## Item Configuration +### Items - Groups +
    Groups @@ -153,7 +215,7 @@ Example thing configuration goes here. | Group | HueSyncBox_Output | "Output" | \ | (HueSyncBox) | ["Screen"] | |
    ---- +### Items - Remote Control
    Remote Control @@ -166,7 +228,8 @@ Example thing configuration goes here. | Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiActive" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | | Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    |
    ---- + +### Items - Firmware
    Firmware diff --git a/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png b/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png new file mode 100644 index 0000000000000000000000000000000000000000..b3303bdfe4b37c439154e4567184727993f036c5 GIT binary patch literal 8625 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 W0TKmus)rX{02F1_KsD0l5&s28NEt)` literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.huesync/doc/expert_mode.png b/bundles/org.openhab.binding.huesync/doc/expert_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..c35d0ba478229faffd5341204b23424ae929ab83 GIT binary patch literal 24022 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 openhab-runtime-base openhab-transport-mdns + mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index f9f699eb198d1..b62e4673936eb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -15,7 +15,7 @@ String Installed firmware version - iconify:mdi:text + text @@ -23,7 +23,7 @@ String Latest available firmware version - iconify:mdi:text + text @@ -31,7 +31,7 @@ String Friendly name of the HDMI connection - iconify:mdi:label + text @@ -39,7 +39,7 @@ String Status of the HDMI input - iconify:mdi:connection + status @@ -55,7 +55,7 @@ String Type of the connected HDMI device - iconify:mdi:multimedia + text @@ -88,7 +88,7 @@ String Last sync mode used for this channel - iconify:mdi:sync + text @@ -133,7 +133,7 @@

    ]]> - iconify:mdi:multimedia + text @@ -159,7 +159,7 @@

    ]]> - iconify:mdi:toggle-switch + switch @@ -176,7 +176,7 @@

    ]]> - iconify:mdi:toggle-switch + switch
    @@ -194,7 +194,7 @@

    ]]> - iconify:mdi:video-input-hdmi + receiver @@ -226,7 +226,7 @@

    ]]> - iconify:mdi:brightness-percent + slider
    diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index cde89af2e10a6..7a1b17594b572 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -12,7 +12,7 @@ smart light that responds to and reflects the content you watch or listen to. - iconify:mdi:audio-video + receiver @@ -39,7 +39,7 @@ Information about the installed device firmaware and available updates. - iconify:mdi:information + text @@ -48,7 +48,7 @@ HDMI connection - iconify:mdi:hdmi-port + settings @@ -59,7 +59,7 @@ HDMI connection - iconify:mdi:hdmi-port + settings @@ -71,7 +71,7 @@ Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These commands allow you to influence how the lights react to your entertainment. - iconify:mdi:remote + settings From 27a3e97d657fc3fdd142508e998d4c5286f62c81 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 13 Sep 2024 20:16:14 +0200 Subject: [PATCH 071/128] =?UTF-8?q?fix(channel):=20=F0=9F=90=9B=20PUT=20co?= =?UTF-8?q?mmand=20payload=20problem=20solved=20for=20`StringType`=20chann?= =?UTF-8?q?el(s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncConnection.java | 6 +++++- .../internal/connection/HueSyncDeviceConnection.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 10633b09a80ec..16a8068d90a15 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -225,8 +225,12 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint, Strin Request request = this.httpClient.newRequest(uri).method(method); + this.logger.trace("uri: {}", uri); + this.logger.trace("method: {}", method); + this.logger.trace("payload: {}", payload); + if (!payload.isBlank()) { - request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString()) + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.toString()) .content(new StringContentProvider(payload)); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index db0b3f2407bf2..8677fbff49dd9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -91,7 +91,7 @@ private void execute(String key, Command command) { } else if (command instanceof OnOffType) { value = ((OnOffType) command).name().equals("ON") ? "true" : "false"; } else if (command instanceof StringType) { - value = ((StringType) command).toString(); + value = "\"" + ((StringType) command).toString() + "\""; } else { this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); return; From 90476fb3981ec2aefb8712148cb8130bcbfebfd9 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 11:15:29 +0200 Subject: [PATCH 072/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed unnecessary files from project/unwanted files from project. Signed-off-by: Patrik Gfeller --- .../org.openhab.binding.huesync/.gitignore | 1 - .../.vscode/extensions.json | 7 -- .../.vscode/launch.json | 15 ---- .../.vscode/scripts/build.sh | 37 --------- .../.vscode/settings.json | 64 --------------- .../.vscode/tasks.json | 81 ------------------- bundles/org.openhab.binding.huesync/AUTHORS | 15 ---- 7 files changed, 220 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/.gitignore delete mode 100644 bundles/org.openhab.binding.huesync/.vscode/extensions.json delete mode 100644 bundles/org.openhab.binding.huesync/.vscode/launch.json delete mode 100755 bundles/org.openhab.binding.huesync/.vscode/scripts/build.sh delete mode 100644 bundles/org.openhab.binding.huesync/.vscode/settings.json delete mode 100644 bundles/org.openhab.binding.huesync/.vscode/tasks.json delete mode 100644 bundles/org.openhab.binding.huesync/AUTHORS diff --git a/bundles/org.openhab.binding.huesync/.gitignore b/bundles/org.openhab.binding.huesync/.gitignore deleted file mode 100644 index f0f1cccf7f156..0000000000000 --- a/bundles/org.openhab.binding.huesync/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!.vscode \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/.vscode/extensions.json b/bundles/org.openhab.binding.huesync/.vscode/extensions.json deleted file mode 100644 index 53053c8396780..0000000000000 --- a/bundles/org.openhab.binding.huesync/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "spmeesseman.vscode-taskexplorer", - "christian-kohler.path-intellisense", - "streetsidesoftware.code-spell-checker" - ] -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/.vscode/launch.json b/bundles/org.openhab.binding.huesync/.vscode/launch.json deleted file mode 100644 index eab5f44e66fc2..0000000000000 --- a/bundles/org.openhab.binding.huesync/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "preLaunchTask": "Start", - "postDebugTask": "Stop openHAB", - - "type": "java", - "request": "attach", - "name": "Debug (using Docker 📦)", - "hostName": "127.0.0.1", - "port": "5005" - }, - ] -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/.vscode/scripts/build.sh b/bundles/org.openhab.binding.huesync/.vscode/scripts/build.sh deleted file mode 100755 index 3b87ca4285052..0000000000000 --- a/bundles/org.openhab.binding.huesync/.vscode/scripts/build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/bash -set -e -PROJECT_ROOT=$1 -POM=$PROJECT_ROOT/pom.xml - -VERSION=`yq ".project.parent.version" $POM` -BINDING=`yq ".project.artifactId" $POM` - -MVN_OPT="--quiet -f ${POM}" - -echo "➡️ $BINDING-$VERSION" -echo "" -echo "🧹 clean" -mvn clean $MVN_OPT - -echo "📃 format" -mvn spotless:apply $MVN_OPT - -echo "🔎 verify" -mvn verify $MVN_OPT - -echo "💬 update translations" -mvn i18n:generate-default-translations $MVN_OPT - -echo "📦 package" -mvn package $MVN_OPT - -DOCKER_OPENHAB_VERSION=4.3.0-snapshot-debian -DOCKER_MOUNT=$HOME/Temp/openhab/$DOCKER_OPENHAB_VERSION - -LOGS=$DOCKER_MOUNT/userdata/logs/* -ARTIFACT=$PROJECT_ROOT/target/$BINDING-$VERSION.jar -ADDONS=$DOCKER_MOUNT/addons/ - -sudo truncate --size 0 $LOGS - -cp --force $ARTIFACT $ADDONS \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/.vscode/settings.json b/bundles/org.openhab.binding.huesync/.vscode/settings.json deleted file mode 100644 index b0f28dcb6c49f..0000000000000 --- a/bundles/org.openhab.binding.huesync/.vscode/settings.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#65c89b", - "activityBar.background": "#65c89b", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#945bc4", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#65c89b", - "statusBar.background": "#42b883", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#359268", - "statusBarItem.remoteBackground": "#42b883", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#42b883", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#42b88399", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#42b883", - "git.alwaysSignOff": true, - "git.enableCommitSigning": true, - "git-graph.repository.commits.showSignatureStatus": true, - "git-graph.repository.sign.commits": true, - "git-graph.repository.sign.tags": true, - "githubPullRequests.assignCreated": "${user}", - "maven.pomfile.autoUpdateEffectivePOM": true, - "maven.view": "hierarchical", - "java.debug.settings.hotCodeReplace": "never", - "java.jdt.ls.java.home": "/usr/lib/jvm/java-17-openjdk-amd64", - "java.compile.nullAnalysis.mode": "automatic", - "cSpell.words": [ - "avahi", - "devicetype", - "huesync", - "huesyncthing", - "iconify", - "mdnsdiscovery", - "powersave", - "UIDS", - "uniqueid" - ], - "debug.console.wordWrap": false, - "java.configuration.updateBuildConfiguration": "interactive", - "taskExplorer.showHiddenWsTasks": false, - "taskExplorer.enabledTasks": { - "ant": false, - "bash": false, - "batch": false, - "composer": false, - "gradle": false, - "grunt": false, - "gulp": false, - "make": false, - "npm": false, - "tsc": false, - "ruby": false, - "python": false, - "powershell": false, - "pipenv": false, - "perl": false - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/.vscode/tasks.json b/bundles/org.openhab.binding.huesync/.vscode/tasks.json deleted file mode 100644 index 09b23b4295955..0000000000000 --- a/bundles/org.openhab.binding.huesync/.vscode/tasks.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Start", - "dependsOn": [ - "Start openHAB" - ], - "presentation": { - "echo": false, - "reveal": "always", - "focus": true, - "panel": "shared", - "showReuseMessage": false, - "clear": false - }, - "type": "shell", - "hide": true, - "command": "sleep", - "args": [ - "20" - ] - }, - { - "label": "Build", - "group": "build", - "presentation": { - "echo": false, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": true - }, - "type": "shell", - "command": "sh ${workspaceFolder}/.vscode/scripts/build.sh ${workspaceFolder}", - }, - { - "label": "Start openHAB", - "hide": true, - "presentation": { - "echo": true, - "reveal": "silent", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": false - }, - "dependsOn": [ - "Build" - ], - "type": "shell", - "command": "docker", - "args": [ - "container", - "start", - "openhab-development-server" - ] - }, - { - "label": "Stop openHAB", - "hide": true, - "presentation": { - "echo": true, - "reveal": "silent", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": false, - "close": true - }, - "type": "shell", - "command": "docker", - "args": [ - "container", - "stop", - "openhab-development-server" - ] - }, - ] -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.huesync/AUTHORS b/bundles/org.openhab.binding.huesync/AUTHORS deleted file mode 100644 index 98958ae755d1a..0000000000000 --- a/bundles/org.openhab.binding.huesync/AUTHORS +++ /dev/null @@ -1,15 +0,0 @@ -# This is the list of huesync's significant contributors. -# -# This does not necessarily list everyone who has contributed code, -# To see the full list of contributors, see the revision history in -# source control. - -@pgfeller - Patrik Gfeller - -# The binding used work from the following developers as reference -# and base for the implementation: -@HHomey-GER - Marco Kawon (https://github.com/Homey-GER/openhab-addons) - -@andrewfg - Andrew Fiddian-Green (hue binding) -@cweitkamp - Christoph Weitkamp (hue binding) - From b815915415893968b914e81e3f697173a8f68a1b Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 11:30:58 +0200 Subject: [PATCH 073/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I do not use the root folder for development, but only the binding project folder (to speed up things on my machine). Without this change the .vscode folder used in`openhab-addons/bundles/org.openhab.binding.huesync` will not be ignored by git. Signed-off-by: Patrik Gfeller --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 93f8fcbb6200b..1fa39ea94ddff 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ xtend-gen/ **/.settings/org.eclipse.* bundles/**/src/main/history +bundles/**/.vscode features/**/src/main/history features/**/src/main/feature From f0b72acff1616350675529fa86bef0ba8fc1a6ea Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 11:46:24 +0200 Subject: [PATCH 074/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove 'dto' from file and class names in 'dto' namespace - as this information is redundant. Signed-off-by: Patrik Gfeller --- ...eSyncDeviceDto.java => HueSyncDevice.java} | 6 ++-- ...ava => HueSyncDeviceCapabilitiesInfo.java} | 4 +-- ...tailed.java => HueSyncDeviceDetailed.java} | 8 ++--- ...a => HueSyncDeviceDetailedUpdateInfo.java} | 4 +-- ...ava => HueSyncDeviceDetailedWifiInfo.java} | 4 +-- ...xecutionDto.java => HueSyncExecution.java} | 12 +++---- ...DtoGame.java => HueSyncExecutionGame.java} | 2 +- ...oMusic.java => HueSyncExecutionMusic.java} | 2 +- ...oVideo.java => HueSyncExecutionVideo.java} | 2 +- .../{HueSyncHdmiDto.java => HueSyncHdmi.java} | 14 ++++---- ...fo.java => HueSyncHdmiConnectionInfo.java} | 2 +- ...ationDto.java => HueSyncRegistration.java} | 2 +- ...o.java => HueSyncRegistrationRequest.java} | 2 +- .../connection/HueSyncDeviceConnection.java | 36 +++++++++---------- .../internal/handler/HueSyncHandler.java | 22 ++++++------ .../tasks/HueSyncRegistrationTask.java | 14 ++++---- .../handler/tasks/HueSyncUpdateTask.java | 6 ++-- .../tasks/HueSyncUpdateTaskResultDto.java | 12 +++---- 18 files changed, 77 insertions(+), 77 deletions(-) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceDto.java => HueSyncDevice.java} (93%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceDtoCapabilitiesInfo.java => HueSyncDeviceCapabilitiesInfo.java} (89%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceDtoDetailed.java => HueSyncDeviceDetailed.java} (85%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceDtoDetailedUpdateInfo.java => HueSyncDeviceDetailedUpdateInfo.java} (91%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/{HueSyncDeviceDtoDetailedWifiInfo.java => HueSyncDeviceDetailedWifiInfo.java} (89%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/{HueSyncExecutionDto.java => HueSyncExecution.java} (90%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/{HueSyncExecutionDtoGame.java => HueSyncExecutionGame.java} (94%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/{HueSyncExecutionDtoMusic.java => HueSyncExecutionMusic.java} (94%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/{HueSyncExecutionDtoVideo.java => HueSyncExecutionVideo.java} (94%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/{HueSyncHdmiDto.java => HueSyncHdmi.java} (69%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/{HueSyncHdmiDtoConnectionInfo.java => HueSyncHdmiConnectionInfo.java} (96%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/{HueSyncRegistrationDto.java => HueSyncRegistration.java} (94%) rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/{HueSyncRegistrationRequestDto.java => HueSyncRegistrationRequest.java} (94%) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java similarity index 93% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java index e450b637d96a8..57a5ce5b3ed63 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * HDMI Sync Box Device Information DTO + * HDMI Sync Box Device Information * * @author Patrik Gfeller - Initial Contribution * @@ -25,7 +25,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceDto { +public class HueSyncDevice { /** Friendly name of the device */ public @Nullable String name; /** Device Type identifier */ @@ -58,7 +58,7 @@ public class HueSyncDeviceDto { public @Nullable String wifiState; public @Nullable String ipAddress; - public @Nullable HueSyncDeviceDtoCapabilitiesInfo capabilities; + public @Nullable HueSyncDeviceCapabilitiesInfo capabilities; public boolean beta; public boolean overheating; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoCapabilitiesInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java similarity index 89% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoCapabilitiesInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java index b2cba279a7b61..8049f1557dbf4 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoCapabilitiesInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * HDMI Sync Box Device Information Capabilities DTO + * HDMI Sync Box Device Information Capabilities * * @author Patrik Gfeller - Initial Contribution * @@ -24,7 +24,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceDtoCapabilitiesInfo { +public class HueSyncDeviceCapabilitiesInfo { /** The total number of IR codes configurable */ public int maxIrCodes; /** The total number of Presets configurable */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailed.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java similarity index 85% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailed.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java index a173c03bd6132..12bb1ff358c6d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailed.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * HDMI Sync Box Device Information DTO - Extended information (only available + * HDMI Sync Box Device Information - Extended information (only available * to registered clients) * * @author Patrik Gfeller - Initial Contribution @@ -28,9 +28,9 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceDtoDetailed extends HueSyncDeviceDto { - public @Nullable HueSyncDeviceDtoDetailedWifiInfo wifi; - public @Nullable HueSyncDeviceDtoDetailedUpdateInfo update; +public class HueSyncDeviceDetailed extends HueSyncDevice { + public @Nullable HueSyncDeviceDetailedWifiInfo wifi; + public @Nullable HueSyncDeviceDetailedUpdateInfo update; /** UTC time when last check for update was performed. */ public @Nullable Date lastCheckedUpdate; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedUpdateInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java similarity index 91% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedUpdateInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java index ca59995572e81..73c99bb466b24 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedUpdateInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * HDMI Sync Box Device Information DTO - Automatic Firmware update + * HDMI Sync Box Device Information - Automatic Firmware update * * @author Patrik Gfeller - Initial Contribution * @@ -24,7 +24,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceDtoDetailedUpdateInfo { +public class HueSyncDeviceDetailedUpdateInfo { /** * Sync Box checks daily for a firmware update. If true, an available update * will automatically be installed. This will be postponed if Sync Box is diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedWifiInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java similarity index 89% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedWifiInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java index b8d771716bb33..0c99f86acb5eb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDtoDetailedWifiInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * HDMI Sync Box Device Information DTO - Wifi connection information + * HDMI Sync Box Device Information - Wifi connection information * * @author Patrik Gfeller - Initial Contribution * @@ -25,7 +25,7 @@ * HDMI Sync Box API */ @NonNullByDefault -public class HueSyncDeviceDtoDetailedWifiInfo { +public class HueSyncDeviceDetailedWifiInfo { /** Wifi SSID */ public @Nullable String ssid; /** diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java similarity index 90% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java index 360370b4763ae..5ca1063f553b4 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java @@ -31,7 +31,7 @@ * */ @NonNullByDefault -public class HueSyncExecutionDto { +public class HueSyncExecution { private static final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); public static final List KNOWN_MODES = Collections @@ -57,13 +57,13 @@ public class HueSyncExecutionDto { * @param mode powersave, passthrough, video, game, music */ public void setMode(String mode) { - if (!HueSyncExecutionDto.KNOWN_MODES.contains(mode)) { + if (!HueSyncExecution.KNOWN_MODES.contains(mode)) { logger.warn( "device mode [{}] is not known by this version of the binding ➡️ please open an issue to notify the maintainer(s). Fallback will be used. ", mode); } - this.mode = HueSyncExecutionDto.KNOWN_MODES.contains(mode) ? mode : "unknown"; + this.mode = HueSyncExecution.KNOWN_MODES.contains(mode) ? mode : "unknown"; } /** @@ -97,7 +97,7 @@ public void setMode(String mode) { */ public int brightness; - public @Nullable HueSyncExecutionDtoVideo video; - public @Nullable HueSyncExecutionDtoGame game; - public @Nullable HueSyncExecutionDtoMusic music; + public @Nullable HueSyncExecutionVideo video; + public @Nullable HueSyncExecutionGame game; + public @Nullable HueSyncExecutionMusic music; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoGame.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoGame.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java index 041116d541c9f..f806d2efa425c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoGame.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java @@ -21,7 +21,7 @@ * */ @NonNullByDefault -public class HueSyncExecutionDtoGame { +public class HueSyncExecutionGame { public @Nullable String intensity; public boolean backgroundLighting; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoMusic.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoMusic.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java index 218a81063fa0b..d56a92782d4d5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoMusic.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java @@ -21,7 +21,7 @@ * */ @NonNullByDefault -public class HueSyncExecutionDtoMusic { +public class HueSyncExecutionMusic { public @Nullable String intensity; public @Nullable String palette; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoVideo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoVideo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java index 573fb0ff49317..46cb01e574a91 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionDtoVideo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java @@ -21,7 +21,7 @@ * */ @NonNullByDefault -public class HueSyncExecutionDtoVideo { +public class HueSyncExecutionVideo { public @Nullable String intensity; public boolean backgroundLighting; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java similarity index 69% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java index 0716c98448cf9..d4457528b4339 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java @@ -21,15 +21,15 @@ * */ @NonNullByDefault -public class HueSyncHdmiDto { - public @Nullable HueSyncHdmiDtoConnectionInfo input1; - public @Nullable HueSyncHdmiDtoConnectionInfo input2; - public @Nullable HueSyncHdmiDtoConnectionInfo input3; - public @Nullable HueSyncHdmiDtoConnectionInfo input4; +public class HueSyncHdmi { + public @Nullable HueSyncHdmiConnectionInfo input1; + public @Nullable HueSyncHdmiConnectionInfo input2; + public @Nullable HueSyncHdmiConnectionInfo input3; + public @Nullable HueSyncHdmiConnectionInfo input4; - public @Nullable HueSyncHdmiDtoConnectionInfo output; + public @Nullable HueSyncHdmiConnectionInfo output; - /** x @ */ + /** x @ */ public @Nullable String contentSpecs; /** Current content specs supported for video sync (video/game mode) */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDtoConnectionInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java similarity index 96% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDtoConnectionInfo.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java index 244175b85ba57..1dac4c657e80c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiDtoConnectionInfo.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java @@ -21,7 +21,7 @@ * */ @NonNullByDefault -public class HueSyncHdmiDtoConnectionInfo { +public class HueSyncHdmiConnectionInfo { /** Friendly name, not empty */ public @Nullable String name; /** diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java index bd1d811d52b96..89b2343dab412 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java @@ -19,7 +19,7 @@ * @author Patrik Gfeller - Initial Contribution */ @NonNullByDefault -public class HueSyncRegistrationDto { +public class HueSyncRegistration { public String registrationId = ""; public String accessToken = ""; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequestDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java similarity index 94% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequestDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java index 149e131298a75..d7651a6872f89 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequestDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java @@ -20,7 +20,7 @@ * @author Patrik Gfeller - Initial Contribution */ @NonNullByDefault -public class HueSyncRegistrationRequestDto { +public class HueSyncRegistrationRequest { /** User recognizable name of registered application */ public @Nullable String appName; /** User recognizable name of application instance. */ diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 8677fbff49dd9..fa21035541979 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -26,12 +26,12 @@ import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.HueSyncConstants.CHANNELS.COMMANDS; import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDto; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDtoDetailed; -import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecutionDto; -import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiDto; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationDto; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequestDto; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.library.types.OnOffType; @@ -126,39 +126,39 @@ public void executeCommand(Channel channel, Command command) { } } - public @Nullable HueSyncDeviceDto getDeviceInfo() { - return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDeviceDto.class); + public @Nullable HueSyncDevice getDeviceInfo() { + return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDevice.class); } - public @Nullable HueSyncDeviceDtoDetailed getDetailedDeviceInfo() { + public @Nullable HueSyncDeviceDetailed getDetailedDeviceInfo() { return this.connection.isRegistered() - ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDtoDetailed.class) + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDetailed.class) : null; } - public @Nullable HueSyncHdmiDto getHdmiInfo() { + public @Nullable HueSyncHdmi getHdmiInfo() { return this.connection.isRegistered() - ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmiDto.class) + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmi.class) : null; } - public @Nullable HueSyncExecutionDto getExecutionInfo() { + public @Nullable HueSyncExecution getExecutionInfo() { return this.connection.isRegistered() - ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecutionDto.class) + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecution.class) : null; } - public @Nullable HueSyncRegistrationDto registerDevice(String id) { + public @Nullable HueSyncRegistration registerDevice(String id) { if (!id.isBlank()) { try { - HueSyncRegistrationRequestDto dto = new HueSyncRegistrationRequestDto(); + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); dto.appName = HueSyncConstants.APPLICATION_NAME; dto.instanceName = id; String payload = HueSyncConnection.ObjectMapper.writeValueAsString(dto); - HueSyncRegistrationDto registration = this.connection.executeRequest(HttpMethod.POST, - ENDPOINTS.REGISTRATIONS, payload, HueSyncRegistrationDto.class); + HueSyncRegistration registration = this.connection.executeRequest(HttpMethod.POST, + ENDPOINTS.REGISTRATIONS, payload, HueSyncRegistration.class); if (registration != null) { this.connection.updateAuthentication(id, registration.accessToken); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 3defc23bb4d68..ab47ede8e90bf 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -26,11 +26,11 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDto; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDtoDetailed; -import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecutionDto; -import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiDto; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationDto; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; @@ -70,7 +70,7 @@ public class HueSyncHandler extends BaseThingHandler { Map> tasks = new HashMap<>(); - private Optional deviceInfo = Optional.empty(); + private Optional deviceInfo = Optional.empty(); private HueSyncDeviceConnection connection; private HttpClient httpClient; @@ -203,7 +203,7 @@ private void logMissingUpdateInformation(String api) { } @SuppressWarnings("null") - private void updateHdmiInformation(HueSyncHdmiDto hdmiStatus) { + private void updateHdmiInformation(HueSyncHdmi hdmiStatus) { // TODO: Resolve warnings ➡️ consider to encapsulate hdmi status obj to avoid complex null // handling ... this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.NAME, new StringType(hdmiStatus.input1.name)); @@ -232,7 +232,7 @@ private void updateHdmiInformation(HueSyncHdmiDto hdmiStatus) { this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.MODE, new StringType(hdmiStatus.output.lastSyncMode)); } - private void updateFirmwareInformation(HueSyncDeviceDtoDetailed deviceStatus) { + private void updateFirmwareInformation(HueSyncDeviceDetailed deviceStatus) { State firmwareState = new StringType(deviceStatus.firmwareVersion); State firmwareAvailableState = new StringType( deviceStatus.updatableFirmwareVersion != null ? deviceStatus.updatableFirmwareVersion @@ -245,7 +245,7 @@ private void updateFirmwareInformation(HueSyncDeviceDtoDetailed deviceStatus) { this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); } - private void updateExecutionInformation(HueSyncExecutionDto executionStatus) { + private void updateExecutionInformation(HueSyncExecution executionStatus) { this.updateState(HueSyncConstants.CHANNELS.COMMANDS.MODE, new StringType(executionStatus.getMode())); this.updateState(HueSyncConstants.CHANNELS.COMMANDS.SYNC, executionStatus.syncActive ? OnOffType.ON : OnOffType.OFF); @@ -255,7 +255,7 @@ private void updateExecutionInformation(HueSyncExecutionDto executionStatus) { this.updateState(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, new DecimalType(executionStatus.brightness)); } - private void handleRegistration(HueSyncRegistrationDto registration) { + private void handleRegistration(HueSyncRegistration registration) { this.stopTasks(); setProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); @@ -272,7 +272,7 @@ private void handleRegistration(HueSyncRegistrationDto registration) { private void checkCompatibility() throws HueSyncApiException { try { - HueSyncDeviceDto deviceInformation = this.deviceInfo.orElseThrow(); + HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow(); if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { throw new HueSyncApiException("@text/api.minimal-version", this.logger); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index b706af58af7ae..2fbcf14d069b7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -15,8 +15,8 @@ import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDto; -import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationDto; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -31,12 +31,12 @@ public class HueSyncRegistrationTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); private HueSyncDeviceConnection connection; - private HueSyncDeviceDto deviceInfo; + private HueSyncDevice deviceInfo; - private Consumer action; + private Consumer action; - public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDeviceDto deviceInfo, - Consumer action) { + public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, + Consumer action) { this.connection = connection; this.deviceInfo = deviceInfo; @@ -55,7 +55,7 @@ public void run() { this.logger.info("Listening for device registration - {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, id); - HueSyncRegistrationDto registration = this.connection.registerDevice(id); + HueSyncRegistration registration = this.connection.registerDevice(id); if (registration != null) { this.logger.info("API token for {} received", this.deviceInfo.name); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 9fe140a490cc2..16b1d2782dba0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDto; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -32,11 +32,11 @@ public class HueSyncUpdateTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); private HueSyncDeviceConnection connection; - private HueSyncDeviceDto deviceInfo; + private HueSyncDevice deviceInfo; private Consumer<@Nullable HueSyncUpdateTaskResultDto> action; - public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDeviceDto deviceInfo, + public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, Consumer<@Nullable HueSyncUpdateTaskResultDto> action) { this.connection = connection; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java index 0b8f14c85cd9e..0eff9cc8317a3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java @@ -14,9 +14,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDtoDetailed; -import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecutionDto; -import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiDto; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; /** * @@ -24,7 +24,7 @@ */ @NonNullByDefault public class HueSyncUpdateTaskResultDto { - public @Nullable HueSyncDeviceDtoDetailed deviceStatus; - public @Nullable HueSyncHdmiDto hdmiStatus; - public @Nullable HueSyncExecutionDto execution; + public @Nullable HueSyncDeviceDetailed deviceStatus; + public @Nullable HueSyncHdmi hdmiStatus; + public @Nullable HueSyncExecution execution; } From 4cf94bd2c32843816888d6df826bbe51d09868ba Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 14:39:27 +0200 Subject: [PATCH 075/128] Update bundles/org.openhab.binding.huesync/src/main/feature/feature.xml Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../src/main/feature/feature.xml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml index b710c5c7140fd..0e7a2a09685ad 100644 --- a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml @@ -5,17 +5,6 @@ openhab-runtime-base openhab-transport-mdns - mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} From ea8c492bb24be25247510bc05ef1ba998e7fe6c2 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 14:40:07 +0200 Subject: [PATCH 076/128] Update bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../src/main/resources/OH-INF/thing/channel-types.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index b62e4673936eb..17021ccd0612b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -4,13 +4,6 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - String From 1b7dcf1db0e51fac1f57096b7553e265f3b2c80c Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 29 Sep 2024 14:40:49 +0200 Subject: [PATCH 077/128] Update bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../src/main/resources/OH-INF/thing/channel-types.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index 17021ccd0612b..f1c928b6434ac 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -117,8 +117,7 @@ "game":

    Reacts to the action on your screen, intensifying the in-game atmosphere - with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n -

    + with bursts of light that correspond to explosions, gunfire, and other gameplay events.

  • "passthrough"
  • "powersave"
  • From 2866bd35510a09858f598ff19d19a5dd5f501e93 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 5 Oct 2024 09:15:19 +0200 Subject: [PATCH 078/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed artifactId in pom.xml according to code review comment. Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml index 3c3b7290af7db..7ca10a423d798 100644 --- a/bundles/org.openhab.binding.huesync/pom.xml +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -12,6 +12,6 @@ org.openhab.binding.huesync - openHAB Add-ons :: Bundles :: Hue Play HDMI Sync Box Binding + openHAB Add-ons :: Bundles :: Hue Sync Box Binding From d9f1a9739126b860e72066044498b06a7009e72f Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 5 Oct 2024 09:21:51 +0200 Subject: [PATCH 079/128] Update bundles/org.openhab.binding.huesync/src/main/feature/feature.xml Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../org.openhab.binding.huesync/src/main/feature/feature.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml index 0e7a2a09685ad..3d292e7237ad3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml @@ -2,7 +2,7 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base openhab-transport-mdns mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} From 45babc6a887a804eb4b0a8c5a6d872fd9b30177d Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 5 Oct 2024 09:48:25 +0200 Subject: [PATCH 080/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THING_TYPE_ID updated according to review comment Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 122 +++++++++--------- .../huesync/internal/HueSyncConstants.java | 2 +- .../resources/OH-INF/i18n/huesync.properties | 10 +- .../resources/OH-INF/thing/thing-types.xml | 2 +- 4 files changed, 68 insertions(+), 68 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 8092020948023..c536ebac61072 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -7,7 +7,7 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [HueSync Binding](#huesync-binding) - [Discovery](#discovery) - [Thing Configuration](#thing-configuration) - - [Thing Configuration "huesyncthing"](#thing-configuration-huesyncthing) + - [Thing Configuration "huesynbox"](#thing-configuration-huesynbox) - [Channels](#channels) - [Channels - Groups](#channels---groups) - [Channels - device-firmware](#channels----device-firmware) @@ -97,39 +97,39 @@ Group HueSyncBox_Output "Output" <iconify:mdi:tv> (HueSyncBox) ["Screen"] Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox) Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"] -String HueSyncBox_Device_Mode "Mode" <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#mode" } -String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiSource" } -Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiActive" } -Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#syncActive" } -Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesyncthing:HueSyncBox:device-commands#brightness" } - -String HueSyncBox_Firmware_Version "Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" } -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" } - -String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" <iconify:mdi:text> (HueSyncBox_Input_1) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" } -String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" <iconify:mdi:devices> (HueSyncBox_Input_1) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" } -String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" <iconify:mdi:connection> (HueSyncBox_Input_1) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#status" } -String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" <iconify:mdi:multimedia> (HueSyncBox_Input_1) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#mode" } - -String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" <iconify:mdi:text> (HueSyncBox_Input_2) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" } -String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" <iconify:mdi:devices> (HueSyncBox_Input_2) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" } -String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" <iconify:mdi:connection> (HueSyncBox_Input_2) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#status" } -String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" <iconify:mdi:multimedia> (HueSyncBox_Input_2) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#mode" } - -String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" <iconify:mdi:text> (HueSyncBox_Input_3) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" } -String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" <iconify:mdi:devices> (HueSyncBox_Input_3) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" } -String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" <iconify:mdi:connection> (HueSyncBox_Input_3) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#status" } -String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" <iconify:mdi:multimedia> (HueSyncBox_Input_3) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#mode" } - -String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" <iconify:mdi:text> (HueSyncBox_Input_4) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" } -String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" <iconify:mdi:devices> (HueSyncBox_Input_4) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" } -String HueSyncBox_Device_hdmi_in4_Status "Status - Input 4" <iconify:mdi:connection> (HueSyncBox_Input_4) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#status" } -String HueSyncBox_Device_hdmi_in4_Mode "Mode - Input 4" <iconify:mdi:multimedia> (HueSyncBox_Input_4) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#mode" } - -String HueSyncBox_Device_hdmi_out_Name "Name - Output" <iconify:mdi:text> (HueSyncBox_Output) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" } -String HueSyncBox_Device_hdmi_out_Type "Type - Output" <iconify:mdi:tv> (HueSyncBox_Output) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" } -String HueSyncBox_Device_hdmi_out_Status "Status - Output" <iconify:mdi:connection> (HueSyncBox_Output) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#status" } -String HueSyncBox_Device_hdmi_out_Mode "Mode - Output" <iconify:mdi:multimedia> (HueSyncBox_Output) { channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#mode" } +String HueSyncBox_Device_Mode "Mode" <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#mode" } +String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiSource" } +Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiActive" } +Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#syncActive" } +Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#brightness" } + +String HueSyncBox_Firmware_Version "Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesynbox:HueSyncBox:device-firmware#firmware" } +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesynbox:HueSyncBox:device-firmware#available-firmware" } + +String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" <iconify:mdi:text> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#name" } +String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" <iconify:mdi:devices> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#type" } +String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" <iconify:mdi:connection> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#status" } +String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" <iconify:mdi:multimedia> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#mode" } + +String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" <iconify:mdi:text> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#name" } +String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" <iconify:mdi:devices> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#type" } +String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" <iconify:mdi:connection> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#status" } +String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" <iconify:mdi:multimedia> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#mode" } + +String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" <iconify:mdi:text> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#name" } +String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" <iconify:mdi:devices> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#type" } +String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" <iconify:mdi:connection> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#status" } +String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" <iconify:mdi:multimedia> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#mode" } + +String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" <iconify:mdi:text> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#name" } +String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" <iconify:mdi:devices> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#type" } +String HueSyncBox_Device_hdmi_in4_Status "Status - Input 4" <iconify:mdi:connection> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#status" } +String HueSyncBox_Device_hdmi_in4_Mode "Mode - Input 4" <iconify:mdi:multimedia> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#mode" } + +String HueSyncBox_Device_hdmi_out_Name "Name - Output" <iconify:mdi:text> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#name" } +String HueSyncBox_Device_hdmi_out_Type "Type - Output" <iconify:mdi:tv> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#type" } +String HueSyncBox_Device_hdmi_out_Status "Status - Output" <iconify:mdi:connection> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#status" } +String HueSyncBox_Device_hdmi_out_Mode "Mode - Output" <iconify:mdi:multimedia> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#mode" }
    @@ -222,11 +222,11 @@ Example thing configuration goes here. | | | | | | | | | -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#mode" } | | -| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiSource" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | -| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#syncActive" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#hdmiActive" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | -| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncthing:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#mode" } | | +| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiSource" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | +| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#syncActive" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | +| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiActive" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | +| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    |
### Items - Firmware @@ -236,8 +236,8 @@ Example thing configuration goes here. | | | | | | | | | ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ---------------------------------------------------------------------------------- | -| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-firmware#firmware" }` | -| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-firmware#available-firmware" }` | +| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-firmware#firmware" }` | +| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-firmware#available-firmware" }` | @@ -248,10 +248,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#name" }` | -| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#type" }` | -| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#status" }` | -| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-1#mode" }` | +| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#name" }` | +| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#type" }` | +| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#status" }` | +| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#mode" }` | @@ -260,10 +260,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#name" }` | -| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#type" }` | -| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#status" }` | -| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-2#mode" }` | +| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#name" }` | +| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#type" }` | +| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#status" }` | +| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#mode" }` |
@@ -271,10 +271,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#name" }` | -| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#type" }` | -| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#status" }` | -| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-3#mode" }` | +| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#name" }` | +| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#type" }` | +| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#status" }` | +| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#mode" }` |
@@ -283,10 +283,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#name" }` | -| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#type" }` | -| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#status" }` | -| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-in-4#mode" }` | +| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#name" }` | +| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#type" }` | +| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#status" }` | +| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#mode" }` | @@ -297,10 +297,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ---------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#name" }` | -| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#type" }` | -| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#status" }` | -| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncthing:HueSyncBox:device-hdmi-out#mode" }` | +| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#name" }` | +| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#type" }` | +| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#status" }` | +| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#mode" }` | diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 212a4cc8ac0e5..dda13b9f5abf8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -90,7 +90,7 @@ public static class OUT { public static final Integer MINIMAL_API_VERSION = 7; public static final String BINDING_ID = "huesync"; - public static final String THING_TYPE_ID = "huesyncthing"; + public static final String THING_TYPE_ID = "huesynbox"; public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID); public static final String PARAMETER_HOST = "host"; diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 79a143433e795..521371c848400 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -5,8 +5,8 @@ addon.huesync.description = Binding for the Hue HDMI Sync Box. # thing types -thing-type.huesync.huesyncthing.label = Hue HDMI Sync Box -thing-type.huesync.huesyncthing.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. +thing-type.huesync.huesynbox.label = Hue HDMI Sync Box +thing-type.huesync.huesynbox.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. # thing types config @@ -54,7 +54,7 @@ channel-type.huesync.execution-brightness.description =

0 ... 200

  • 0 channel-type.huesync.execution-hdmiSource.label = HDMI input channel-type.huesync.execution-hdmiSource.description =

    • input1
    • input2
    • input3
    • input4

    channel-type.huesync.execution-mode.label = Hue Sync operation mode -channel-type.huesync.execution-mode.description =

    • "video":

      Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience.

    • "music":

      Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.

    • "game":

      Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.\n

    • "passthrough"
    • "powersave"

    +channel-type.huesync.execution-mode.description =

    • "video":

      Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience.

    • "music":

      Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.

    • "game":

      Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.

    • "passthrough"
    • "powersave"

    channel-type.huesync.execution-syncActive.label = Synchronization Active channel-type.huesync.execution-syncActive.description =

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    @@ -80,8 +80,8 @@ channel-type.huesync.device-info.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:% # thing types -thing-type.huesync.huesyncthing.channel.test.label = test -thing-type.huesync.huesyncthing.channel.test.description = test +thing-type.huesync.huesynbox.channel.test.label = test +thing-type.huesync.huesynbox.channel.test.description = test # channel group types diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 7a1b17594b572..09d67aca3eb63 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,7 +4,7 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI From b64be6e93c5b19b1383efeff6a80438cf11df028 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 5 Oct 2024 09:57:13 +0200 Subject: [PATCH 081/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - THING_TYPE_ID updated according to review comment (fixed typo) - label suggestions applied to channel-types Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 122 +++++++++--------- .../huesync/internal/HueSyncConstants.java | 2 +- .../resources/OH-INF/i18n/huesync.properties | 18 ++- .../resources/OH-INF/thing/channel-types.xml | 14 +- .../resources/OH-INF/thing/thing-types.xml | 2 +- 5 files changed, 81 insertions(+), 77 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index c536ebac61072..53e1b99d4d726 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -7,7 +7,7 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [HueSync Binding](#huesync-binding) - [Discovery](#discovery) - [Thing Configuration](#thing-configuration) - - [Thing Configuration "huesynbox"](#thing-configuration-huesynbox) + - [Thing Configuration "huesyncbox"](#thing-configuration-huesyncbox) - [Channels](#channels) - [Channels - Groups](#channels---groups) - [Channels - device-firmware](#channels----device-firmware) @@ -97,39 +97,39 @@ Group HueSyncBox_Output "Output" <iconify:mdi:tv> (HueSyncBox) ["Screen"] Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox) Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"] -String HueSyncBox_Device_Mode "Mode" <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#mode" } -String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiSource" } -Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiActive" } -Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#syncActive" } -Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesynbox:HueSyncBox:device-commands#brightness" } - -String HueSyncBox_Firmware_Version "Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesynbox:HueSyncBox:device-firmware#firmware" } -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesynbox:HueSyncBox:device-firmware#available-firmware" } - -String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" <iconify:mdi:text> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#name" } -String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" <iconify:mdi:devices> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#type" } -String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" <iconify:mdi:connection> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#status" } -String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" <iconify:mdi:multimedia> (HueSyncBox_Input_1) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#mode" } - -String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" <iconify:mdi:text> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#name" } -String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" <iconify:mdi:devices> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#type" } -String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" <iconify:mdi:connection> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#status" } -String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" <iconify:mdi:multimedia> (HueSyncBox_Input_2) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#mode" } - -String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" <iconify:mdi:text> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#name" } -String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" <iconify:mdi:devices> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#type" } -String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" <iconify:mdi:connection> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#status" } -String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" <iconify:mdi:multimedia> (HueSyncBox_Input_3) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#mode" } - -String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" <iconify:mdi:text> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#name" } -String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" <iconify:mdi:devices> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#type" } -String HueSyncBox_Device_hdmi_in4_Status "Status - Input 4" <iconify:mdi:connection> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#status" } -String HueSyncBox_Device_hdmi_in4_Mode "Mode - Input 4" <iconify:mdi:multimedia> (HueSyncBox_Input_4) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#mode" } - -String HueSyncBox_Device_hdmi_out_Name "Name - Output" <iconify:mdi:text> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#name" } -String HueSyncBox_Device_hdmi_out_Type "Type - Output" <iconify:mdi:tv> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#type" } -String HueSyncBox_Device_hdmi_out_Status "Status - Output" <iconify:mdi:connection> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#status" } -String HueSyncBox_Device_hdmi_out_Mode "Mode - Output" <iconify:mdi:multimedia> (HueSyncBox_Output) { channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#mode" } +String HueSyncBox_Device_Mode "Mode" <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#mode" } +String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiSource" } +Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiActive" } +Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#syncActive" } +Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#brightness" } + +String HueSyncBox_Firmware_Version "Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncbox:HueSyncBox:device-firmware#firmware" } +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncbox:HueSyncBox:device-firmware#available-firmware" } + +String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" <iconify:mdi:text> (HueSyncBox_Input_1) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#name" } +String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" <iconify:mdi:devices> (HueSyncBox_Input_1) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#type" } +String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" <iconify:mdi:connection> (HueSyncBox_Input_1) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#status" } +String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" <iconify:mdi:multimedia> (HueSyncBox_Input_1) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#mode" } + +String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" <iconify:mdi:text> (HueSyncBox_Input_2) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#name" } +String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" <iconify:mdi:devices> (HueSyncBox_Input_2) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#type" } +String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" <iconify:mdi:connection> (HueSyncBox_Input_2) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#status" } +String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" <iconify:mdi:multimedia> (HueSyncBox_Input_2) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#mode" } + +String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" <iconify:mdi:text> (HueSyncBox_Input_3) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#name" } +String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" <iconify:mdi:devices> (HueSyncBox_Input_3) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#type" } +String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" <iconify:mdi:connection> (HueSyncBox_Input_3) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#status" } +String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" <iconify:mdi:multimedia> (HueSyncBox_Input_3) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#mode" } + +String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" <iconify:mdi:text> (HueSyncBox_Input_4) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#name" } +String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" <iconify:mdi:devices> (HueSyncBox_Input_4) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#type" } +String HueSyncBox_Device_hdmi_in4_Status "Status - Input 4" <iconify:mdi:connection> (HueSyncBox_Input_4) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#status" } +String HueSyncBox_Device_hdmi_in4_Mode "Mode - Input 4" <iconify:mdi:multimedia> (HueSyncBox_Input_4) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#mode" } + +String HueSyncBox_Device_hdmi_out_Name "Name - Output" <iconify:mdi:text> (HueSyncBox_Output) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#name" } +String HueSyncBox_Device_hdmi_out_Type "Type - Output" <iconify:mdi:tv> (HueSyncBox_Output) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#type" } +String HueSyncBox_Device_hdmi_out_Status "Status - Output" <iconify:mdi:connection> (HueSyncBox_Output) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#status" } +String HueSyncBox_Device_hdmi_out_Mode "Mode - Output" <iconify:mdi:multimedia> (HueSyncBox_Output) { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#mode" } @@ -222,11 +222,11 @@ Example thing configuration goes here. | | | | | | | | | -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#mode" } | | -| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiSource" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | -| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#syncActive" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#hdmiActive" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | -| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesynbox:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#mode" } | | +| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiSource" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | +| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#syncActive" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | +| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiActive" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | +| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | ### Items - Firmware @@ -236,8 +236,8 @@ Example thing configuration goes here. | | | | | | | | | ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ---------------------------------------------------------------------------------- | -| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-firmware#firmware" }` | -| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-firmware#available-firmware" }` | +| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-firmware#firmware" }` | +| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-firmware#available-firmware" }` | @@ -248,10 +248,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#name" }` | -| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#type" }` | -| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#status" }` | -| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-1#mode" }` | +| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#name" }` | +| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#type" }` | +| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#status" }` | +| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#mode" }` | @@ -260,10 +260,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#name" }` | -| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#type" }` | -| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#status" }` | -| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-2#mode" }` | +| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#name" }` | +| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#type" }` | +| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#status" }` | +| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#mode" }` |
    @@ -271,10 +271,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#name" }` | -| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#type" }` | -| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#status" }` | -| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-3#mode" }` | +| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#name" }` | +| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#type" }` | +| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#status" }` | +| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#mode" }` |
    @@ -283,10 +283,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#name" }` | -| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#type" }` | -| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#status" }` | -| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-in-4#mode" }` | +| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#name" }` | +| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#type" }` | +| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#status" }` | +| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#mode" }` | @@ -297,10 +297,10 @@ Example thing configuration goes here. | | | | | | | | | ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ---------------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#name" }` | -| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#type" }` | -| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#status" }` | -| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesynbox:HueSyncBox:device-hdmi-out#mode" }` | +| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#name" }` | +| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#type" }` | +| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#status" }` | +| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#mode" }` | diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index dda13b9f5abf8..f7eb9c8ba8ae3 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -90,7 +90,7 @@ public static class OUT { public static final Integer MINIMAL_API_VERSION = 7; public static final String BINDING_ID = "huesync"; - public static final String THING_TYPE_ID = "huesynbox"; + public static final String THING_TYPE_ID = "huesyncbox"; public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID); public static final String PARAMETER_HOST = "host"; diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 521371c848400..ab1260bf59aec 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -5,8 +5,8 @@ addon.huesync.description = Binding for the Hue HDMI Sync Box. # thing types -thing-type.huesync.huesynbox.label = Hue HDMI Sync Box -thing-type.huesync.huesynbox.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. +thing-type.huesync.huesyncbox.label = Hue HDMI Sync Box +thing-type.huesync.huesyncbox.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. # thing types config @@ -37,11 +37,15 @@ channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connect channel-type.huesync.connection-lastSyncMode.label = Last sync mode channel-type.huesync.connection-lastSyncMode.description = Last sync mode used for this channel -channel-type.huesync.connection-name.label = Friendly name +channel-type.huesync.connection-name.label = HDMI Name channel-type.huesync.connection-name.description = Friendly name of the HDMI connection -channel-type.huesync.connection-status.label = HDMI connection Status +channel-type.huesync.connection-status.label = HDMI Status channel-type.huesync.connection-status.description = Status of the HDMI input -channel-type.huesync.connection-type.label = Friendly type +channel-type.huesync.connection-status.command.option.unplugged = Unplugged +channel-type.huesync.connection-status.command.option.plugged = Plugged +channel-type.huesync.connection-status.command.option.linked = Linked +channel-type.huesync.connection-status.command.option.unknown = Unknown +channel-type.huesync.connection-type.label = HDMI Type channel-type.huesync.connection-type.description = Type of the connected HDMI device channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version channel-type.huesync.device-info-firmware-available.description = Latest available firmware version @@ -80,8 +84,8 @@ channel-type.huesync.device-info.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:% # thing types -thing-type.huesync.huesynbox.channel.test.label = test -thing-type.huesync.huesynbox.channel.test.description = test +thing-type.huesync.huesyncbox.channel.test.label = test +thing-type.huesync.huesyncbox.channel.test.description = test # channel group types diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index f1c928b6434ac..e675e99d64b58 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -22,7 +22,7 @@ String - + Friendly name of the HDMI connection text @@ -30,23 +30,23 @@ String - + Status of the HDMI input status - - - - + + + + String - + Type of the connected HDMI device text diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 09d67aca3eb63..d09066f058cf9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,7 +4,7 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI From 19b4efc42699c472a1f20c5be7b887000962833d Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 11 Oct 2024 08:58:12 +0200 Subject: [PATCH 082/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default values for channel-type **connection-type** added. Signed-off-by: Patrik Gfeller --- .../resources/OH-INF/thing/channel-types.xml | 42 +++++++++---------- .../resources/OH-INF/i18n/jellyfin.properties | 4 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index e675e99d64b58..69086785f7059 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -52,27 +52,27 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties index fe1ad084aef3f..2c7e447d6a8c2 100644 --- a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties +++ b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties @@ -16,10 +16,10 @@ thing-type.config.jellyfin.server.clientActiveWithInSeconds.label = Client Activ thing-type.config.jellyfin.server.clientActiveWithInSeconds.description = Amount off seconds allowed since the last client activity to assert it's online (0 disabled) thing-type.config.jellyfin.server.hostname.label = Hostname/IP thing-type.config.jellyfin.server.hostname.description = Hostname or IP address of the server -thing-type.config.jellyfin.server.port.label = Port -thing-type.config.jellyfin.server.port.description = Port of the server thing-type.config.jellyfin.server.path.label = Base Path thing-type.config.jellyfin.server.path.description = Base path of the server +thing-type.config.jellyfin.server.port.label = Port +thing-type.config.jellyfin.server.port.description = Port of the server thing-type.config.jellyfin.server.refreshSeconds.label = Refresh Seconds thing-type.config.jellyfin.server.refreshSeconds.description = Interval to pull devices state from the server thing-type.config.jellyfin.server.ssl.label = SSL From 9301a438fdc4d8c677580233f346e5344fcd9b22 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 11 Oct 2024 18:36:17 +0200 Subject: [PATCH 083/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default values for channel-type **connection-type** added. Signed-off-by: Patrik Gfeller --- .../resources/OH-INF/i18n/huesync.properties | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index ab1260bf59aec..afa4dd8d15e98 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -47,6 +47,27 @@ channel-type.huesync.connection-status.command.option.linked = Linked channel-type.huesync.connection-status.command.option.unknown = Unknown channel-type.huesync.connection-type.label = HDMI Type channel-type.huesync.connection-type.description = Type of the connected HDMI device +channel-type.huesync.connection-type.command.option.generic = Generic +channel-type.huesync.connection-type.command.option.video = Video +channel-type.huesync.connection-type.command.option.game = Game +channel-type.huesync.connection-type.command.option.music = Music +channel-type.huesync.connection-type.command.option.xbox = XBox +channel-type.huesync.connection-type.command.option.playstation = PlayStation +channel-type.huesync.connection-type.command.option.nintendoswitch = Nintendo Switch +channel-type.huesync.connection-type.command.option.phone = Phone +channel-type.huesync.connection-type.command.option.desktop = Desktop +channel-type.huesync.connection-type.command.option.laptop = Laptop +channel-type.huesync.connection-type.command.option.appletv = Apple TV +channel-type.huesync.connection-type.command.option.roku = Roku +channel-type.huesync.connection-type.command.option.shield = Nvidia Shield +channel-type.huesync.connection-type.command.option.chromecast = Chromecast +channel-type.huesync.connection-type.command.option.firetv = Amazon Fire TV +channel-type.huesync.connection-type.command.option.diskplayer = Disk Player +channel-type.huesync.connection-type.command.option.settopbox = Set-top box +channel-type.huesync.connection-type.command.option.satellite = Satellite +channel-type.huesync.connection-type.command.option.avreceiver = AV receiver +channel-type.huesync.connection-type.command.option.soundbar = Soundbar +channel-type.huesync.connection-type.command.option.hdmiswitch = HDMI switch channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version channel-type.huesync.device-info-firmware-available.description = Latest available firmware version channel-type.huesync.device-info-firmware.label = Firmware Version From 8bc8285c0e8431135b6d71f10dae47686c050880 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 16 Oct 2024 22:29:05 +0200 Subject: [PATCH 084/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 16 +++++----- .../huesync/internal/HueSyncConstants.java | 14 +++++++-- .../internal/connection/ExecutionPayload.java | 31 ------------------- .../connection/HueSyncDeviceConnection.java | 26 +++++++++------- .../resources/OH-INF/i18n/huesync.properties | 23 ++++++++++---- .../resources/OH-INF/thing/channel-types.xml | 8 ++--- .../resources/OH-INF/thing/thing-types.xml | 10 +++--- 7 files changed, 60 insertions(+), 68 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/ExecutionPayload.java diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 53e1b99d4d726..4eec27c8dbb9d 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -98,9 +98,9 @@ Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"] String HueSyncBox_Device_Mode "Mode" <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#mode" } -String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiSource" } -Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmiActive" } -Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#syncActive" } +String HueSyncBox_Device_Input "Input" <iconify:mdi:input> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-source" } +Switch HueSyncBox_Device_Hdmi "HDMI" <iconify:mdi:hdmi-port> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-active" } +Switch HueSyncBox_Device_Sync "Sync" <iconify:mdi:sync> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#sync-active" } Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#brightness" } String HueSyncBox_Firmware_Version "Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncbox:HueSyncBox:device-firmware#firmware" } @@ -168,8 +168,8 @@ Information about a HDMI input connection. | Channel | Type | Read/Write | Description | | ---------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | mode | String | R/W |
    Hue Sync operation mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | -| hdmiSource | Switch | R/W |
    Source
    • input1
    • input2
    • input3
    • input4
    | -| syncActive | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | +| hdmi-source | Switch | R/W |
    Source
    • input1
    • input2
    • input3
    • input4
    | +| sync-active | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | | brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | - ## Discovery The binding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devices in the local network. @@ -81,11 +69,11 @@ You may use **Expert Mode** for configuration, or use the UI wizard. ![Expert Mode](doc/expert_mode.png) -You need to update the device UID "**..**" in the example with the UID of your device.
    - Expert Mode - Configuration Example + Expert Mode - Configuration Example to create a model and item setup: +

    You need to update the device in the example with the UID of your device (huesync:box:HueSyncBox).

    -Group HueSyncBox "HueSyncBox" <iconify:mdi:tv> ["NetworkAppliance"]
    +Group HueSyncBox "HueSyncBox" <iconify:mdi:television-ambient-light> ["NetworkAppliance"]
     
     Group HueSyncBox_Inputs "Inputs" <receiver> (HueSyncBox) ["Receiver"]
     
    @@ -93,54 +81,54 @@ Group HueSyncBox_Input_1 "Input 1" <iconify:mdi:hdmi-port> (HueSyncBox_Inp
     Group HueSyncBox_Input_2 "Input 2" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
     Group HueSyncBox_Input_3 "Input 3" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
     Group HueSyncBox_Input_4 "Input 4" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    -Group HueSyncBox_Output "Output" <iconify:mdi:tv> (HueSyncBox) ["Screen"]
    -Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox)
    -Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"]
    -
    -String HueSyncBox_Device_Mode  "Mode"  <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#mode" }
    -String HueSyncBox_Device_Input "Input" <iconify:mdi:input>      (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" }
    -Switch HueSyncBox_Device_Hdmi  "HDMI"  <iconify:mdi:hdmi-port>  (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" }
    -Switch HueSyncBox_Device_Sync  "Sync"  <iconify:mdi:sync>       (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#sync-active" }
    -Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#brightness" }
    -
    -String HueSyncBox_Firmware_Version        "Firmware Version"        <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#firmware" }           
    -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#available-firmware" } 
    -
    -String HueSyncBox_Device_hdmi_in1_Name      "Name - Input 1"           <iconify:mdi:text>       (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" }              
    -String HueSyncBox_Device_hdmi_in1_Type      "Type - Input 1"           <iconify:mdi:devices>    (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" }              
    -String HueSyncBox_Device_hdmi_in1_Status    "Status - Input 1"         <iconify:mdi:connection> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" }
    -String HueSyncBox_Device_hdmi_in1_Mode      "Mode - Input 1"           <iconify:mdi:multimedia> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" }               
    -
    -String HueSyncBox_Device_hdmi_in2_Name      "Name - Input 2"           <iconify:mdi:text>       (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" }              
    -String HueSyncBox_Device_hdmi_in2_Type      "Type - Input 2"           <iconify:mdi:devices>    (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }              
    -String HueSyncBox_Device_hdmi_in2_Status    "Status - Input 2"         <iconify:mdi:connection> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" } 
    -String HueSyncBox_Device_hdmi_in2_Mode      "Mode - Input 2"           <iconify:mdi:multimedia> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" }              
    -
    -String HueSyncBox_Device_hdmi_in3_Name      "Name - Input 3"           <iconify:mdi:text>       (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" }              
    -String HueSyncBox_Device_hdmi_in3_Type      "Type - Input 3"           <iconify:mdi:devices>    (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" }              
    -String HueSyncBox_Device_hdmi_in3_Status    "Status - Input 3"         <iconify:mdi:connection> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" } 
    -String HueSyncBox_Device_hdmi_in3_Mode      "Mode - Input 3"           <iconify:mdi:multimedia> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" }              
    -
    -String HueSyncBox_Device_hdmi_in4_Name      "Name - Input 4"           <iconify:mdi:text>       (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" }              
    -String HueSyncBox_Device_hdmi_in4_Type      "Type - Input 4"           <iconify:mdi:devices>    (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" }              
    -String HueSyncBox_Device_hdmi_in4_Status    "Status - Input 4"         <iconify:mdi:connection> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" } 
    -String HueSyncBox_Device_hdmi_in4_Mode      "Mode - Input 4"           <iconify:mdi:multimedia> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#mode" }              
    -
    -String HueSyncBox_Device_hdmi_out_Name      "Name - Output"            <iconify:mdi:text>       (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#name" }               
    -String HueSyncBox_Device_hdmi_out_Type      "Type - Output"            <iconify:mdi:tv>         (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#type" }               
    -String HueSyncBox_Device_hdmi_out_Status    "Status - Output"          <iconify:mdi:connection> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#status" }              
    -String HueSyncBox_Device_hdmi_out_Mode      "Mode - Output"            <iconify:mdi:multimedia> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#mode" }
    +
    +Group HueSyncBox_Output    "Output"         <iconify:mdi:tv>          (HueSyncBox) ["Screen"]
    +Group HueSyncBox_Firmware  "Firmware"       <iconify:mdi:information> (HueSyncBox)
    +Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote>      (HueSyncBox) ["RemoteControl"]
    +
    +String HueSyncBox_Device_Mode       "Mode"       <iconify:mdi:multimedia>         (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#mode" }
    +String HueSyncBox_Device_Input      "Input"      <iconify:mdi:input>              (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" }
    +Switch HueSyncBox_Device_Hdmi       "HDMI"       <iconify:mdi:hdmi-port>          (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" }
    +Switch HueSyncBox_Device_Sync       "Sync"       <iconify:mdi:sync>               (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#sync-active" }
    +Number HueSyncBox_Device_Brightness "Brightness" <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#brightness" }
    +
    +String HueSyncBox_Firmware_Version        "Firmware Version"        <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#firmware" }
    +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#available-firmware" }
    +
    +String HueSyncBox_Device_hdmi_in1_Name      "Name - Input 1"    <iconify:mdi:text>       (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" }
    +String HueSyncBox_Device_hdmi_in1_Type      "Type - Input 1"    <iconify:mdi:devices>    (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" }
    +String HueSyncBox_Device_hdmi_in1_Status    "Status - Input 1"  <iconify:mdi:connection> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" }
    +String HueSyncBox_Device_hdmi_in1_Mode      "Mode - Input 1"    <iconify:mdi:multimedia> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" }
    +
    +String HueSyncBox_Device_hdmi_in2_Name      "Name - Input 2"    <iconify:mdi:text>       (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" }
    +String HueSyncBox_Device_hdmi_in2_Type      "Type - Input 2"    <iconify:mdi:devices>    (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }
    +String HueSyncBox_Device_hdmi_in2_Status    "Status - Input 2"  <iconify:mdi:connection> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" }
    +String HueSyncBox_Device_hdmi_in2_Mode      "Mode - Input 2"    <iconify:mdi:multimedia> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" }
    +
    +String HueSyncBox_Device_hdmi_in3_Name      "Name - Input 3"    <iconify:mdi:text>       (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" }
    +String HueSyncBox_Device_hdmi_in3_Type      "Type - Input 3"    <iconify:mdi:devices>    (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" }
    +String HueSyncBox_Device_hdmi_in3_Status    "Status - Input 3"  <iconify:mdi:connection> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" }
    +String HueSyncBox_Device_hdmi_in3_Mode      "Mode - Input 3"    <iconify:mdi:multimedia> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" }
    +
    +String HueSyncBox_Device_hdmi_in4_Name      "Name - Input 4"    <iconify:mdi:text>       (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" }
    +String HueSyncBox_Device_hdmi_in4_Type      "Type - Input 4"    <iconify:mdi:devices>    (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" }
    +String HueSyncBox_Device_hdmi_in4_Status    "Status - Input 4"  <iconify:mdi:connection> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" }
    +String HueSyncBox_Device_hdmi_in4_Mode      "Mode - Input 4"    <iconify:mdi:multimedia> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#mode" }
    +
    +String HueSyncBox_Device_hdmi_out_Name      "Name - Output"     <iconify:mdi:text>       (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#name" }
    +String HueSyncBox_Device_hdmi_out_Type      "Type - Output"     <iconify:mdi:tv>         (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#type" }
    +String HueSyncBox_Device_hdmi_out_Status    "Status - Output"   <iconify:mdi:connection> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#status" }
    +String HueSyncBox_Device_hdmi_out_Mode      "Mode - Output"     <iconify:mdi:multimedia> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#mode" }
       
    - -This will give you a default model and item setup: +
    ![Semantic Model](doc/model.png) -The following chapters provide more details for a tailored setup. - ## Channels +The following chapters provide more details for a tailored setup. + ### Channels - Groups ### Channels - device-firmware @@ -172,29 +160,6 @@ Information about a HDMI input connection. | sync-active | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | | brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | - - ## Item Configuration ### Items - Groups @@ -213,6 +178,7 @@ Example thing configuration goes here. | Group | HueSyncBox_Input_3 | "Input 3" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | | Group | HueSyncBox_Input_4 | "Input 4" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | | Group | HueSyncBox_Output | "Output" | \ | (HueSyncBox) | ["Screen"] | | + ### Items - Remote Control @@ -227,6 +193,7 @@ Example thing configuration goes here. | Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#sync-active" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | | Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | | Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | + ### Items - Firmware @@ -264,6 +231,7 @@ Example thing configuration goes here. | String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }` | | String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" }` | | String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" }` | +
    diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index c8ba46d1b7033..823c6406c1c87 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -37,6 +37,9 @@ channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connect channel-type.huesync.connection-last-sync-mode.label = Last sync mode channel-type.huesync.connection-last-sync-mode.description = Last sync mode used for this channel +channel-type.huesync.connection-last-sync-mode.command.option.video = Video +channel-type.huesync.connection-last-sync-mode.command.option.game = Game +channel-type.huesync.connection-last-sync-mode.command.option.music = Music channel-type.huesync.connection-name.label = HDMI Name channel-type.huesync.connection-name.description = Friendly name of the HDMI connection channel-type.huesync.connection-status.label = HDMI Status diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index 09d5f8119cd67..e536dc64bcb49 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -85,9 +85,9 @@ - - - + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index de8416ae8c17c..6ae058a9b53d6 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -57,7 +57,7 @@ - + HDMI connection settings From 4eab015c65da08caf2461a7daf657983288f218b Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 26 Oct 2024 22:57:31 +0200 Subject: [PATCH 089/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../main/resources/OH-INF/config/config.xml | 10 +++---- .../resources/OH-INF/i18n/huesync.properties | 28 +++++++++---------- .../resources/OH-INF/thing/channel-types.xml | 10 +++---- .../resources/OH-INF/thing/thing-types.xml | 9 +++--- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml index 6ab50a784a2a9..55c4017329927 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml @@ -6,12 +6,12 @@ - + network-address - + Network address of the HDMI Sync Box. true @@ -25,13 +25,13 @@ - + The id of the API registration. true password - + To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 @@ -39,7 +39,7 @@ true - + Seconds between fetching values from the Hue Sync Box. true true diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties index 823c6406c1c87..6daff9e4dcd71 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -5,37 +5,37 @@ addon.huesync.description = Binding for the Hue HDMI Sync Box. # thing types -thing-type.huesync.box.label = Hue HDMI Sync Box +thing-type.huesync.box.label = HDMI Sync Box thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. # thing types config -thing-type.config.box.thing.apiAccessToken.label = API Access Token +thing-type.config.box.thing.apiAccessToken.label = Access Token thing-type.config.box.thing.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. -thing-type.config.box.thing.group.connection.label = Connection Settings -thing-type.config.box.thing.host.label = Network Address +thing-type.config.box.thing.group.connection.label = Connection +thing-type.config.box.thing.host.label = Address thing-type.config.box.thing.host.description = Network address of the HDMI Sync Box. thing-type.config.box.thing.port.label = Port thing-type.config.box.thing.port.description = Port of the HDMI Sync Box. -thing-type.config.box.thing.registrationId.label = Application Registration Id +thing-type.config.box.thing.registrationId.label = Registration Id thing-type.config.box.thing.registrationId.description = The id of the API registration. -thing-type.config.box.thing.statusUpdateInterval.label = Status Update Interval +thing-type.config.box.thing.statusUpdateInterval.label = Update Interval thing-type.config.box.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. # channel group types -channel-group-type.huesync.device-commands.label = Hue Sync execution API commands -channel-group-type.huesync.device-commands.description = Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These commands allow you to influence how the lights react to your entertainment. +channel-group-type.huesync.device-commands.label = Commands +channel-group-type.huesync.device-commands.description = Commands are used to control the real-time behavior of the hue sync box. These commands allow you to influence how the lights react to your entertainment. channel-group-type.huesync.device-firmware.label = Firmware channel-group-type.huesync.device-firmware.description = Information about the installed device firmaware and available updates. channel-group-type.huesync.device-hdmi-connection-in.label = HDMI Input channel-group-type.huesync.device-hdmi-connection-in.description = HDMI connection -channel-group-type.huesync.device-hdmi-connection-out.label = HDMI output +channel-group-type.huesync.device-hdmi-connection-out.label = HDMI Output channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connection # channel types -channel-type.huesync.connection-last-sync-mode.label = Last sync mode +channel-type.huesync.connection-last-sync-mode.label = Last Mode channel-type.huesync.connection-last-sync-mode.description = Last sync mode used for this channel channel-type.huesync.connection-last-sync-mode.command.option.video = Video channel-type.huesync.connection-last-sync-mode.command.option.game = Game @@ -71,17 +71,17 @@ channel-type.huesync.connection-type.command.option.satellite = Satellite channel-type.huesync.connection-type.command.option.avreceiver = AV receiver channel-type.huesync.connection-type.command.option.soundbar = Soundbar channel-type.huesync.connection-type.command.option.hdmiswitch = HDMI switch -channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version +channel-type.huesync.device-info-firmware-available.label = Latest Firmware channel-type.huesync.device-info-firmware-available.description = Latest available firmware version -channel-type.huesync.device-info-firmware.label = Firmware Version +channel-type.huesync.device-info-firmware.label = Firmware channel-type.huesync.device-info-firmware.description = Installed firmware version channel-type.huesync.executioin-hdmi-active.label = HDMI Active channel-type.huesync.executioin-hdmi-active.description =

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    channel-type.huesync.execution-brightness.label = Brightness channel-type.huesync.execution-brightness.description =

    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    -channel-type.huesync.execution-hdmi-source.label = HDMI input +channel-type.huesync.execution-hdmi-source.label = HDMI Input channel-type.huesync.execution-hdmi-source.description =

    • input1
    • input2
    • input3
    • input4

    -channel-type.huesync.execution-mode.label = Hue Sync operation mode +channel-type.huesync.execution-mode.label = Mode channel-type.huesync.execution-mode.description =

    • "Video":

      Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience.

    • "Music":

      Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.

    • "Game":

      Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.

    • "Passthrough"
    • "Powersave"

    channel-type.huesync.execution-mode.command.option.powersave = Powersave channel-type.huesync.execution-mode.command.option.passthrough = Passthrough diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index e536dc64bcb49..ff56dacd4f4a1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -6,7 +6,7 @@ String - + Installed firmware version text @@ -14,7 +14,7 @@ String - + Latest available firmware version text @@ -79,7 +79,7 @@ String - + Last sync mode used for this channel text @@ -94,7 +94,7 @@ String - + @@ -173,7 +173,7 @@ String - + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 6ae058a9b53d6..03d20d3662a73 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful @@ -68,9 +68,10 @@
    - - Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These - commands allow you to influence how the lights react to your entertainment. + + Commands are used to control the real-time behavior of the hue sync box. These + commands allow you to + influence how the lights react to your entertainment. settings From 3baf75d021813dc6fde3ebc48d98ec81d69b9eff Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Mon, 28 Oct 2024 20:45:06 +0100 Subject: [PATCH 090/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1fa39ea94ddff..93f8fcbb6200b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ xtend-gen/ **/.settings/org.eclipse.* bundles/**/src/main/history -bundles/**/.vscode features/**/src/main/history features/**/src/main/feature From a7b461244696b450379dfe3b49bd3611038e6082 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Mon, 28 Oct 2024 21:38:53 +0100 Subject: [PATCH 091/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 20 +++++++++--------- .../doc/bridge1.png | Bin 62342 -> 0 bytes .../doc/bridge2.png | Bin 84447 -> 0 bytes .../doc/device_registration.png | Bin 0 -> 12774 bytes 4 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/doc/bridge1.png delete mode 100644 bundles/org.openhab.binding.huesync/doc/bridge2.png create mode 100644 bundles/org.openhab.binding.huesync/doc/device_registration.png diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 583e6aff39aeb..6d5d2d02a52d6 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -1,6 +1,6 @@ # HueSync Binding - + This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/). @@ -17,11 +17,6 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [Items - Remote Control](#items---remote-control) - [Items - Firmware](#items---firmware) -
    - -![Play HDMI Sync Box](doc/bridge1.png) -![Play HDMI Sync Box](doc/bridge2.png) - ## Discovery The binding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devices in the local network. @@ -48,10 +43,15 @@ $ avahi-browse --resolve _huesync._tcp ## Thing Configuration -To enable the binding to communicate with the device, a registration is required. -Once the registration process is completed, the acquired token will authorize the binding to communicate with the device. -After initial discovery and thing creation the device will stay offline. -To complete the authentication you need to pressed the registration button on the sync box for 3 seconds. +### Registration + +To communicate with the sync box, you need to couple the thing with the hardware (registration). The thing will start this process automatically. To complete the registration you just press the "coupling" button on the sync box for 3 seconds.: + +![Device Registration](doc/device_registration.png) + +For special use cases it is possible to configure the id and token manually in the **advanced configuration** settings section. + +### Parameters | Name | Type | Description | Default | Required | Advanced | | -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | diff --git a/bundles/org.openhab.binding.huesync/doc/bridge1.png b/bundles/org.openhab.binding.huesync/doc/bridge1.png deleted file mode 100644 index e7795d60d0c7ce95363b039ab6d8fd5a47d73f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62342 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 R2crRBsxP$^KPy;;{~zP(%sl`A diff --git a/bundles/org.openhab.binding.huesync/doc/bridge2.png b/bundles/org.openhab.binding.huesync/doc/bridge2.png deleted file mode 100644 index e4d24ed0e470127cc4593a80e567d96193e677f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84447 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 literal 0 HcmV?d00001 From 3b7978191f01371dcd04ce6b0a1092fc28a6b788 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 17:26:43 +0100 Subject: [PATCH 092/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../src/main/resources/OH-INF/i18n/jellyfin.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties index 2c7e447d6a8c2..fe1ad084aef3f 100644 --- a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties +++ b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties @@ -16,10 +16,10 @@ thing-type.config.jellyfin.server.clientActiveWithInSeconds.label = Client Activ thing-type.config.jellyfin.server.clientActiveWithInSeconds.description = Amount off seconds allowed since the last client activity to assert it's online (0 disabled) thing-type.config.jellyfin.server.hostname.label = Hostname/IP thing-type.config.jellyfin.server.hostname.description = Hostname or IP address of the server -thing-type.config.jellyfin.server.path.label = Base Path -thing-type.config.jellyfin.server.path.description = Base path of the server thing-type.config.jellyfin.server.port.label = Port thing-type.config.jellyfin.server.port.description = Port of the server +thing-type.config.jellyfin.server.path.label = Base Path +thing-type.config.jellyfin.server.path.description = Base path of the server thing-type.config.jellyfin.server.refreshSeconds.label = Refresh Seconds thing-type.config.jellyfin.server.refreshSeconds.description = Interval to pull devices state from the server thing-type.config.jellyfin.server.ssl.label = SSL From ce62b8a94c9ee17f40ecf758b51211486d97fdc5 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:49:08 +0100 Subject: [PATCH 093/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/handler/tasks/HueSyncRegistrationTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 2fbcf14d069b7..b153972f0e345 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -37,7 +37,6 @@ public class HueSyncRegistrationTask implements Runnable { public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, Consumer action) { - this.connection = connection; this.deviceInfo = deviceInfo; this.action = action; From 4b40f35206ba332254163ebb1f5ac6c2bffa93b4 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:50:52 +0100 Subject: [PATCH 094/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../openhab/binding/huesync/internal/handler/HueSyncHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index f9cb407c060cd..f48c6001a2c1d 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -80,7 +80,6 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) super(thing); this.httpClient = httpClientFactory.getCommonHttpClient(); - this.httpClient.setName(this.thing.getUID().getAsString()); this.connection = new HueSyncDeviceConnection(this.httpClient, this.getConfigAs(HueSyncConfiguration.class)); } From 3f95878e6c0791cf67464bf0bf5062324f407159 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:52:30 +0100 Subject: [PATCH 095/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/connection/HueSyncConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 534acb83789a4..19dd1b921a958 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -76,7 +76,6 @@ public class HueSyncConnection { public HueSyncConnection(HttpClient httpClient, String host, Integer port) throws CertificateException, IOException, URISyntaxException { - this.host = host; this.port = port; From e67bb3aebaaa086aa2a297e08b0700bfd8a223cf Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:53:46 +0100 Subject: [PATCH 096/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/connection/HueSyncConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 19dd1b921a958..9aa2f7ea0e9d9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -216,7 +216,6 @@ private ContentResponse executeGetRequest(String endpoint) private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) throws InterruptedException, TimeoutException, ExecutionException { - String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); Request request = this.httpClient.newRequest(uri).method(method); From 1f04cd6972a21aaf55b130ad9669375c4aa8eca5 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:54:45 +0100 Subject: [PATCH 097/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 8389c125fd1ff..1a8dee92dd43e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -59,7 +59,6 @@ public class HueSyncDeviceConnection { public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration) throws CertificateException, IOException, URISyntaxException { - this.connection = new HueSyncConnection(httpClient, configuration.host, configuration.port); registerCommandHandlers(); From ce69d026fef6eff99faa55832f9a9edde2af0b7d Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 20:56:36 +0100 Subject: [PATCH 098/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/handler/tasks/HueSyncUpdateTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 16b1d2782dba0..65b572e60c64e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -38,7 +38,6 @@ public class HueSyncUpdateTask implements Runnable { public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, Consumer<@Nullable HueSyncUpdateTaskResultDto> action) { - this.connection = connection; this.deviceInfo = deviceInfo; From 10c513cb1f72f7a7bdf56b725fad370c1eae22e3 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 21:19:13 +0100 Subject: [PATCH 099/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update names to follow coding convention. Signed-off-by: Patrik Gfeller --- .../internal/connection/HueSyncConnection.java | 4 ++-- .../connection/HueSyncDeviceConnection.java | 18 +++++++++--------- .../internal/i18n/HueSyncLocalizer.java | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 9aa2f7ea0e9d9..7eca9b374d722 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -52,7 +52,7 @@ */ @NonNullByDefault public class HueSyncConnection { - public static final ObjectMapper ObjectMapper = new ObjectMapper() + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); /** * Request format: The Sync Box API can be accessed locally via HTTPS on root level (port 443, @@ -194,7 +194,7 @@ protected void dispose() { private @Nullable T deserialize(String json, Class type) { try { - return ObjectMapper.readValue(json, type); + return OBJECT_MAPPER.readValue(json, type); } catch (JsonProcessingException | NoClassDefFoundError e) { this.logger.error("{}", e.getMessage()); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 1a8dee92dd43e..05e115c213894 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -55,7 +55,7 @@ public class HueSyncDeviceConnection { private HueSyncConnection connection; - private Map> DeviceCommandExecutors = new HashMap<>(); + private Map> deviceCommandExecutors = new HashMap<>(); public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration) throws CertificateException, IOException, URISyntaxException { @@ -67,15 +67,15 @@ public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration confi // #region private private void registerCommandHandlers() { - this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.MODE, + this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.MODE, defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.MODE)); - this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, + this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SOURCE)); - this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, + this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.BRIGHTNESS)); - this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC, + this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC, defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SYNC)); - this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI, + this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI, defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.HDMI)); } @@ -123,8 +123,8 @@ public void executeCommand(Channel channel, Command command) { return; } - if (this.DeviceCommandExecutors.containsKey(commandId)) { - this.DeviceCommandExecutors.get(commandId).accept(command); + if (this.deviceCommandExecutors.containsKey(commandId)) { + this.deviceCommandExecutors.get(commandId).accept(command); } else { this.logger.error("No executor registered for command {} - please report this as an issue", commandId); } @@ -159,7 +159,7 @@ public void executeCommand(Channel channel, Command command) { dto.appName = HueSyncConstants.APPLICATION_NAME; dto.instanceName = id; - String payload = HueSyncConnection.ObjectMapper.writeValueAsString(dto); + String payload = HueSyncConnection.OBJECT_MAPPER.writeValueAsString(dto); HueSyncRegistration registration = this.connection.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, payload, HueSyncRegistration.class); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 321324e737acb..17b7ea8a8647c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -28,22 +28,22 @@ */ @NonNullByDefault public class HueSyncLocalizer { - private static final Locale locale = Locale.ENGLISH; - private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) + private static final Locale LOCALE = Locale.ENGLISH; + private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); - private static final ServiceReference serviceReference = bundleContext + private static final ServiceReference SERVICE_REFERENCE = BUNDLE_CONTEXT .getServiceReference(TranslationProvider.class); - private static final Bundle bundle = bundleContext.getBundle(); + private static final Bundle BUNDLE = BUNDLE_CONTEXT.getBundle(); public static String getResourceString(String key) { String lookupKey = key.replace("@text/", ""); String missingKey = "⚠️ Missing Translation ⚠️: " + key; @Nullable - TranslationProvider translationProvider = bundleContext.getService(serviceReference); + TranslationProvider translationProvider = BUNDLE_CONTEXT.getService(SERVICE_REFERENCE); String result = translationProvider == null ? missingKey - : translationProvider.getText(bundle, lookupKey, missingKey, locale); + : translationProvider.getText(BUNDLE, lookupKey, missingKey, LOCALE); return result == null ? missingKey : result; } From 2be093c5a1fb930a1de02c0c679424d393c99876 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 30 Oct 2024 21:31:54 +0100 Subject: [PATCH 100/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obsolete image file removed. Signed-off-by: Patrik Gfeller --- .../org.openhab.binding.huesync/doc/logo.jpg | Bin 28099 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/doc/logo.jpg diff --git a/bundles/org.openhab.binding.huesync/doc/logo.jpg b/bundles/org.openhab.binding.huesync/doc/logo.jpg deleted file mode 100644 index 8a19642cd01a8d322222457e751f7a340d314720..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28099 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 F{{SdO&k6tl From 88ca7190a3cee44ca8be093c3a353183deb265ff Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 21:56:21 +0100 Subject: [PATCH 101/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/HdmiChannels.java | 31 +++++++++++ .../huesync/internal/HueSyncConstants.java | 45 ++++------------ .../api/dto/execution/HueSyncExecution.java | 2 +- .../HueSyncDiscoveryParticipant.java | 4 +- .../internal/handler/HueSyncHandler.java | 54 ++++++++----------- .../internal/i18n/HueSyncLocalizer.java | 2 +- 6 files changed, 67 insertions(+), 71 deletions(-) create mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java new file mode 100644 index 0000000000000..24bd099eadee1 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +public class HdmiChannels { + public String NAME; + public String TYPE; + public String MODE; + public String STATUS; + + public HdmiChannels(String name, String type, String mode, String status) { + NAME = name; + TYPE = type; + MODE = mode; + STATUS = status; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 090e211525edf..9009cc835d7a6 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -55,40 +55,17 @@ public static class COMMANDS { } public static class HDMI { - public static class IN_1 { - public static final String NAME = "device-hdmi-in-1#name"; - public static final String TYPE = "device-hdmi-in-1#type"; - public static final String STATUS = "device-hdmi-in-1#status"; - public static final String MODE = "device-hdmi-in-1#mode"; - } - - public static class IN_2 { - public static final String NAME = "device-hdmi-in-2#name"; - public static final String TYPE = "device-hdmi-in-2#type"; - public static final String STATUS = "device-hdmi-in-2#status"; - public static final String MODE = "device-hdmi-in-2#mode"; - } - - public static class IN_3 { - public static final String NAME = "device-hdmi-in-3#name"; - public static final String TYPE = "device-hdmi-in-3#type"; - public static final String STATUS = "device-hdmi-in-3#status"; - public static final String MODE = "device-hdmi-in-3#mode"; - } - - public static class IN_4 { - public static final String NAME = "device-hdmi-in-4#name"; - public static final String TYPE = "device-hdmi-in-4#type"; - public static final String STATUS = "device-hdmi-in-4#status"; - public static final String MODE = "device-hdmi-in-4#mode"; - } - - public static class OUT { - public static final String NAME = "device-hdmi-out#name"; - public static final String TYPE = "device-hdmi-out#type"; - public static final String STATUS = "device-hdmi-out#status"; - public static final String MODE = "device-hdmi-out#mode"; - } + public static final HdmiChannels IN_1 = new HdmiChannels("device-hdmi-in-1#name", "device-hdmi-in-1#type", + "device-hdmi-in-1#mode", "device-hdmi-in-1#status"); + public static final HdmiChannels IN_2 = new HdmiChannels("device-hdmi-in-2#name", "device-hdmi-in-2#type", + "device-hdmi-in-2#mode", "device-hdmi-in-2#status"); + public static final HdmiChannels IN_3 = new HdmiChannels("device-hdmi-in-3#name", "device-hdmi-in-3#type", + "device-hdmi-in-3#mode", "device-hdmi-in-3#status"); + public static final HdmiChannels IN_4 = new HdmiChannels("device-hdmi-in-4#name", "device-hdmi-in-4#type", + "device-hdmi-in-4#mode", "device-hdmi-in-4#status"); + + public static final HdmiChannels OUT = new HdmiChannels("device-hdmi-out#name", "device-hdmi-out#type", + "device-hdmi-out#mode", "device-hdmi-out#status"); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java index 5ca1063f553b4..1c3dbc200bbbb 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java @@ -59,7 +59,7 @@ public class HueSyncExecution { public void setMode(String mode) { if (!HueSyncExecution.KNOWN_MODES.contains(mode)) { logger.warn( - "device mode [{}] is not known by this version of the binding ➡️ please open an issue to notify the maintainer(s). Fallback will be used. ", + "device mode [{}] is not known by this version of the binding. Please open an issue to notify the maintainer(s). Fallback will be used. ", mode); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 16df0a87c6aac..3274e42ee6ae7 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -134,9 +134,9 @@ private void updateService(ComponentContext componentContext) { .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) { - boolean value = Boolean.valueOf(autoDiscoveryPropertyValue); + boolean value = Boolean.parseBoolean(autoDiscoveryPropertyValue); if (value != this.autoDiscoveryEnabled) { - logger.debug("{} update: {} ➡️ {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, + logger.debug("{} update: {} - {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, autoDiscoveryPropertyValue, value); this.autoDiscoveryEnabled = value; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index f48c6001a2c1d..e66dc151c5ea9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -25,11 +25,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.huesync.internal.HdmiChannels; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiConnectionInfo; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; @@ -71,9 +73,9 @@ public class HueSyncHandler extends BaseThingHandler { Map> tasks = new HashMap<>(); private Optional deviceInfo = Optional.empty(); - private HueSyncDeviceConnection connection; - private HttpClient httpClient; + private final HueSyncDeviceConnection connection; + private final HttpClient httpClient; public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) throws CertificateException, IOException, URISyntaxException { @@ -199,37 +201,25 @@ private void handleUpdate(@Nullable HueSyncUpdateTaskResultDto dto) { } private void logMissingUpdateInformation(String api) { - this.logger.warn("⚠️ Device information - {} status missing ⚠️", api); + this.logger.warn("Device information - {} status missing", api); } - @SuppressWarnings("null") private void updateHdmiInformation(HueSyncHdmi hdmiStatus) { - // TODO: Resolve warnings ➡️ consider to encapsulate hdmi status obj to avoid complex null - // handling ... - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.NAME, new StringType(hdmiStatus.input1.name)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.TYPE, new StringType(hdmiStatus.input1.type)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.STATUS, new StringType(hdmiStatus.input1.status)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.MODE, new StringType(hdmiStatus.input1.lastSyncMode)); - - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.NAME, new StringType(hdmiStatus.input2.name)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.TYPE, new StringType(hdmiStatus.input2.type)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.STATUS, new StringType(hdmiStatus.input2.status)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.MODE, new StringType(hdmiStatus.input2.lastSyncMode)); - - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.NAME, new StringType(hdmiStatus.input3.name)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.TYPE, new StringType(hdmiStatus.input3.type)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.STATUS, new StringType(hdmiStatus.input3.status)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.MODE, new StringType(hdmiStatus.input3.lastSyncMode)); - - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.NAME, new StringType(hdmiStatus.input4.name)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.TYPE, new StringType(hdmiStatus.input4.type)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.STATUS, new StringType(hdmiStatus.input4.status)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.MODE, new StringType(hdmiStatus.input4.lastSyncMode)); - - this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.NAME, new StringType(hdmiStatus.output.name)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.TYPE, new StringType(hdmiStatus.output.type)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.STATUS, new StringType(hdmiStatus.output.status)); - this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.MODE, new StringType(hdmiStatus.output.lastSyncMode)); + updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_1, hdmiStatus.input1); + updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_2, hdmiStatus.input2); + updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_3, hdmiStatus.input3); + updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_4, hdmiStatus.input4); + + updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.OUT, hdmiStatus.output); + } + + private void updateHdmiStatus(HdmiChannels channels, @Nullable HueSyncHdmiConnectionInfo hdmiStatusInfo) { + if (hdmiStatusInfo != null) { + this.updateState(channels.NAME, new StringType(hdmiStatusInfo.name)); + this.updateState(channels.TYPE, new StringType(hdmiStatusInfo.type)); + this.updateState(channels.MODE, new StringType(hdmiStatusInfo.lastSyncMode)); + this.updateState(channels.STATUS, new StringType(hdmiStatusInfo.status)); + } } private void updateFirmwareInformation(HueSyncDeviceDetailed deviceStatus) { @@ -306,8 +296,6 @@ private void saveProperty(String key, String value, Map properti // #endregion // #region Override - // TODO: Life cycle handling for connection should resolve complex null problem (➡️ mode - // constructor) @Override public void initialize() { try { @@ -326,7 +314,7 @@ public void initialize() { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (thing.getStatus() != ThingStatus.ONLINE) { - this.logger.warn("Device status: {} ➡️ Command {} for chanel {} will be ignored", + this.logger.warn("Device status: {} - Command {} for chanel {} will be ignored", thing.getStatus().toString(), command.toFullString(), channelUID.toString()); return; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 17b7ea8a8647c..cc47fa8715b15 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -37,7 +37,7 @@ public class HueSyncLocalizer { public static String getResourceString(String key) { String lookupKey = key.replace("@text/", ""); - String missingKey = "⚠️ Missing Translation ⚠️: " + key; + String missingKey = "Missing Translation: " + key; @Nullable TranslationProvider translationProvider = BUNDLE_CONTEXT.getService(SERVICE_REFERENCE); From 6108d4e4312b1013022d6f0de6d5d64766d4f229 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 21:58:33 +0100 Subject: [PATCH 102/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 05e115c213894..82fdde6124ac1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -94,7 +94,8 @@ private void execute(String key, Command command) { value = Integer.toString(((QuantityType) command).intValue()); } else if (command instanceof OnOffType) { value = ((OnOffType) command).name().equals("ON") ? "true" : "false"; - } else if (command instanceof StringType) { + } else if (command instanceof OnOffType onOffCommand) { + value = OnOffType.ON.equals(onOffCommand) ? "true" : "false"; value = "\"" + ((StringType) command).toString() + "\""; } else { this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); From 59c8c5c0c7331135d68f0796bd86ea6e055baf62 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 22:08:17 +0100 Subject: [PATCH 103/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 82fdde6124ac1..8c6c9ca364d16 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -189,7 +189,7 @@ public void dispose() { } public void updateConfiguration(HueSyncConfiguration config) { - this.logger.debug("🔧 Connection configuration update for device {}:{} - Registration Id [{}]", config.host, + this.logger.debug("Connection configuration update for device {}:{} - Registration Id [{}]", config.host, config.port, config.registrationId); this.connection.updateAuthentication(config.registrationId, config.apiAccessToken); From 356dad6c23203d26b3184aa879715db2cf24a582 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 22:12:56 +0100 Subject: [PATCH 104/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/connection/HueSyncConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 7eca9b374d722..48ab4b7dd7c86 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -120,7 +120,7 @@ public void updateAuthentication(String id, String token) { } catch (ExecutionException e) { this.handleExecutionException(e); } catch (InterruptedException | TimeoutException e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); } return null; From 34c114ac2b177eff2a439e0c9d05a6fcb5b90218 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 22:13:59 +0100 Subject: [PATCH 105/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 8c6c9ca364d16..ead9092e03542 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -92,7 +92,8 @@ private void execute(String key, Command command) { if (command instanceof QuantityType) { value = Integer.toString(((QuantityType) command).intValue()); - } else if (command instanceof OnOffType) { + if (command instanceof QuantityType quantityCommand) { + value = Integer.toString(quantityCommand.intValue()); value = ((OnOffType) command).name().equals("ON") ? "true" : "false"; } else if (command instanceof OnOffType onOffCommand) { value = OnOffType.ON.equals(onOffCommand) ? "true" : "false"; From ee9170a32c5e93162cde0a7e3277aefd7bba3f6b Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 22:14:39 +0100 Subject: [PATCH 106/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index ead9092e03542..a314ab72238a1 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -98,7 +98,8 @@ private void execute(String key, Command command) { } else if (command instanceof OnOffType onOffCommand) { value = OnOffType.ON.equals(onOffCommand) ? "true" : "false"; value = "\"" + ((StringType) command).toString() + "\""; - } else { + } else if (command instanceof StringType stringCommand) { + value = "\"" + stringCommand.toString() + "\""; this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); return; } From 5d2a7881e2e9090ed960513a96cf249c43e5f9ab Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 6 Nov 2024 22:15:51 +0100 Subject: [PATCH 107/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../internal/discovery/HueSyncDiscoveryParticipant.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 3274e42ee6ae7..645cee303cc00 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -93,9 +93,8 @@ public String getServiceType() { properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]); properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort()); - DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(service.getName()) + return DiscoveryResultBuilder.create(uid).withLabel(service.getName()) .withProperties(properties).build(); - return result; } catch (Exception e) { logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e.getMessage()); From 2ad011b2291a0deb50bfccc07bf40b4771ecd2a4 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 7 Nov 2024 17:53:09 +0100 Subject: [PATCH 108/128] =?UTF-8?q?=F0=9F=94=A7=20post=20merge=20clean=20u?= =?UTF-8?q?p=20(fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../connection/HueSyncDeviceConnection.java | 30 +++++++++---------- .../HueSyncDiscoveryParticipant.java | 4 +-- .../handler/tasks/HueSyncUpdateTask.java | 6 ++-- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index a314ab72238a1..7e5e68bf80af6 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -53,9 +53,9 @@ public class HueSyncDeviceConnection { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); - private HueSyncConnection connection; + private final HueSyncConnection connection; - private Map> deviceCommandExecutors = new HashMap<>(); + private final Map> deviceCommandExecutors = new HashMap<>(); public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration) throws CertificateException, IOException, URISyntaxException { @@ -88,28 +88,26 @@ private Consumer defaultHandler(String endpoint) { private void execute(String key, Command command) { this.logger.info("Command executor: {} - {}", key, command); - String value = ""; + if (!this.connection.isRegistered()) { + this.logger.warn("Device is not registered - ignoring command: {}", command); + return; + } + + String value; if (command instanceof QuantityType) { value = Integer.toString(((QuantityType) command).intValue()); - if (command instanceof QuantityType quantityCommand) { - value = Integer.toString(quantityCommand.intValue()); - value = ((OnOffType) command).name().equals("ON") ? "true" : "false"; - } else if (command instanceof OnOffType onOffCommand) { - value = OnOffType.ON.equals(onOffCommand) ? "true" : "false"; - value = "\"" + ((StringType) command).toString() + "\""; - } else if (command instanceof StringType stringCommand) { - value = "\"" + stringCommand.toString() + "\""; + } else if (command instanceof OnOffType) { + value = command == OnOffType.ON ? "true" : "false"; + } else if (command instanceof StringType) { + value = '"' + command.toString() + '"'; + } else { this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); return; } - if (!this.connection.isRegistered()) { - this.logger.warn("Device is not registered - ignoring command: {}", command); - return; - } - String json = String.format("{ \"%s\": %s }", key, value); + this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index 645cee303cc00..ac684bc67638f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -93,8 +93,8 @@ public String getServiceType() { properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]); properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort()); - return DiscoveryResultBuilder.create(uid).withLabel(service.getName()) - .withProperties(properties).build(); + return DiscoveryResultBuilder.create(uid).withLabel(service.getName()).withProperties(properties) + .build(); } catch (Exception e) { logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), e.getMessage()); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 65b572e60c64e..9f905a5dcdcde 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -31,10 +31,10 @@ public class HueSyncUpdateTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); - private HueSyncDeviceConnection connection; - private HueSyncDevice deviceInfo; + private final HueSyncDeviceConnection connection; + private final HueSyncDevice deviceInfo; - private Consumer<@Nullable HueSyncUpdateTaskResultDto> action; + private final Consumer<@Nullable HueSyncUpdateTaskResultDto> action; public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, Consumer<@Nullable HueSyncUpdateTaskResultDto> action) { From 6101c155d9c7b725b81150314b51e86858b85733 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 12 Nov 2024 15:33:31 +0100 Subject: [PATCH 109/128] =?UTF-8?q?chore(project):=20=F0=9F=94=8E=20Resolv?= =?UTF-8?q?e=20code=20review=20findings=20(README.md)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 6d5d2d02a52d6..29ba388d8ad4c 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -7,6 +7,8 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [HueSync Binding](#huesync-binding) - [Discovery](#discovery) - [Thing Configuration](#thing-configuration) + - [Registration](#registration) + - [Parameters](#parameters) - [Channels](#channels) - [Channels - Groups](#channels---groups) - [Channels - device-firmware](#channels---device-firmware) @@ -45,7 +47,9 @@ $ avahi-browse --resolve _huesync._tcp ### Registration -To communicate with the sync box, you need to couple the thing with the hardware (registration). The thing will start this process automatically. To complete the registration you just press the "coupling" button on the sync box for 3 seconds.: +To communicate with the sync box, you need to couple the thing with the hardware (registration). +The thing will start this process automatically. +To complete the registration you just press the "coupling" button on the sync box for 3 seconds.: ![Device Registration](doc/device_registration.png) @@ -61,7 +65,8 @@ For special use cases it is possible to configure the id and token manually in t | apiAccessToken | text | API Access Token | N/A | no | yes | | statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | -Once the thing is connection, you can add the equipment to the model using the advanced configuration option of the UI. Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": +Once the thing is connection, you can add the equipment to the model using the advanced configuration option of the UI. +Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": ![Add Equipment to Model](doc/add_equipment_to_model.png) @@ -153,12 +158,12 @@ Information about a HDMI input connection. #### device-commands -| Channel | Type | Read/Write | Description | -| ---------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| mode | String | R/W |
    Hue Sync operation mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | +| Channel | Type | Read/Write | Description | +| ----------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| mode | String | R/W |
    Hue Sync operation mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | | hdmi-source | Switch | R/W |
    Source
    • input1
    • input2
    • input3
    • input4
    | | sync-active | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +| brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | ## Item Configuration @@ -186,13 +191,13 @@ Information about a HDMI input connection.
    Remote Control -| | | | | | | | -| -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#mode" } | | -| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | -| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#sync-active" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | -| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +| | | | | | | | +| -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#mode" } | | +| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | +| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#sync-active" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | +| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | +| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    |
    @@ -201,8 +206,8 @@ Information about a HDMI input connection.
    Firmware -| | | | | | | | -| ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ---------------------------------------------------------------------------------- | +| | | | | | | | +| ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ------------------------------------------------------------------------- | | String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-firmware#firmware" }` | | String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-firmware#available-firmware" }` | @@ -213,8 +218,8 @@ Information about a HDMI input connection.
    Input 1 -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | | String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" }` | | String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" }` | | String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" }` | @@ -225,8 +230,8 @@ Information about a HDMI input connection.
    Input 2 -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | | String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" }` | | String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }` | | String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" }` | @@ -237,8 +242,8 @@ Information about a HDMI input connection.
    Input 3 -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | | String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" }` | | String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" }` | | String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" }` | @@ -249,8 +254,8 @@ Information about a HDMI input connection.
    Input 4 -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | | String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" }` | | String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" }` | | String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" }` | @@ -263,8 +268,8 @@ Information about a HDMI input connection.
    Output -| | | | | | | | -| ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ---------------------------------------------------------------------- | +| | | | | | | | +| ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ------------------------------------------------------------- | | String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#name" }` | | String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#type" }` | | String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#status" }` | From 87412db8747608043a42a48c476e279dd67c52d0 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 12 Nov 2024 16:21:16 +0100 Subject: [PATCH 110/128] =?UTF-8?q?refactor(lint):=20=F0=9F=94=8E=20Apply?= =?UTF-8?q?=20static=20code=20analysis=20improvements/cosmetics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../internal/connection/HueSyncConnection.java | 10 +++++----- .../internal/connection/HueSyncDeviceConnection.java | 3 ++- .../discovery/HueSyncDiscoveryParticipant.java | 2 +- .../internal/factory/HueSyncHandlerFactory.java | 5 ++++- .../huesync/internal/handler/HueSyncHandler.java | 11 +++++------ 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 48ab4b7dd7c86..1ea8644945cc0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -63,12 +63,12 @@ public class HueSyncConnection { private static final String API = "api/v1"; private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); - private Integer port; - private String host; + private final Integer port; + private final String host; - private ServiceRegistration tlsProviderService; - private HttpClient httpClient; - private URI uri; + private final ServiceRegistration tlsProviderService; + private final HttpClient httpClient; + private final URI uri; private Optional authentication = Optional.empty(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 7e5e68bf80af6..ad53eb33b2d07 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -102,7 +102,8 @@ private void execute(String key, Command command) { } else if (command instanceof StringType) { value = '"' + command.toString() + '"'; } else { - this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); + this.logger.error("Type [{}] not supported by this connection", + (command != null) ? command.getClass().getCanonicalName() : "null"); return; } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index ac684bc67638f..a2a924e950121 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -47,7 +47,7 @@ @NonNullByDefault @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { - private Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); /** * diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index eecaefa35bcba..bdc7ad078a5be 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.huesync.internal.factory; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; import java.util.Collections; import java.util.Set; @@ -62,7 +65,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) { try { return new HueSyncHandler(thing, this.httpClientFactory); - } catch (Exception e) { + } catch (IOException | URISyntaxException | CertificateException e) { // TODO: Implementation ... } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index e66dc151c5ea9..0aca16c3d8d4c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -136,7 +136,7 @@ private void startTasks() { long interval = 0; switch (id) { - case POLL: + case POLL -> { initialDelay = 0; interval = this.getConfigAs(HueSyncConfiguration.class).statusUpdateInterval; @@ -144,9 +144,8 @@ private void startTasks() { task = new HueSyncUpdateTask(this.connection, this.deviceInfo.get(), deviceStatus -> this.handleUpdate(deviceStatus)); - - break; - case REGISTER: + } + case REGISTER -> { initialDelay = HueSyncConstants.REGISTRATION_INITIAL_DELAY; interval = HueSyncConstants.REGISTRATION_INTERVAL; @@ -155,8 +154,7 @@ private void startTasks() { task = new HueSyncRegistrationTask(this.connection, this.deviceInfo.get(), registration -> this.handleRegistration(registration)); - - break; + } } if (task != null) { @@ -262,6 +260,7 @@ private void handleRegistration(HueSyncRegistration registration) { private void checkCompatibility() throws HueSyncApiException { try { + @SuppressWarnings("null") HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow(); if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { From 95cfce51ad9b7836ccac7b790fd64d5f39a06019 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 12 Nov 2024 21:29:56 +0100 Subject: [PATCH 111/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20and=20code=20analysis=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../connection/HueSyncConnection.java | 39 ++++++++----------- .../connection/HueSyncDeviceConnection.java | 11 +++--- .../HueSyncDiscoveryParticipant.java | 7 ++-- .../exceptions/HueSyncApiException.java | 5 +-- .../HueSyncConnectionException.java | 5 +-- .../internal/exceptions/HueSyncException.java | 13 +------ .../exceptions/HueSyncTaskException.java | 5 +-- .../factory/HueSyncHandlerFactory.java | 6 ++- .../internal/handler/HueSyncHandler.java | 14 +++---- .../tasks/HueSyncRegistrationTask.java | 14 +++---- .../handler/tasks/HueSyncUpdateTask.java | 2 +- 11 files changed, 53 insertions(+), 68 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 1ea8644945cc0..b1c6ed89621d5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -68,7 +68,7 @@ public class HueSyncConnection { private final ServiceRegistration tlsProviderService; private final HttpClient httpClient; - private final URI uri; + private final URI deviceUri; private Optional authentication = Optional.empty(); @@ -79,7 +79,7 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port) this.host = host; this.port = port; - this.uri = new URI(String.format("https://%s:%s", this.host, this.port)); + this.deviceUri = new URI(String.format("https://%s:%s", this.host, this.port)); HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); @@ -95,7 +95,7 @@ public void updateAuthentication(String id, String token) { if (!id.isBlank() && !token.isBlank()) { this.registrationId = id; - this.authentication = Optional.of(new HueSyncAuthenticationResult(this.uri, token)); + this.authentication = Optional.of(new HueSyncAuthenticationResult(this.deviceUri, token)); this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication.get()); } } @@ -108,7 +108,7 @@ public void updateAuthentication(String id, String token) { } catch (ExecutionException e) { this.handleExecutionException(e); } catch (InterruptedException | TimeoutException e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); } return null; @@ -140,7 +140,7 @@ protected void unregisterDevice() { this.removeAuthentication(); } } catch (InterruptedException | TimeoutException | ExecutionException e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); } } } @@ -166,28 +166,23 @@ protected void dispose() { * 500 Internal: Internal errors like out of memory */ switch (status) { - case HttpStatus.OK_200: + case HttpStatus.OK_200 -> { return (type != null && (response instanceof ContentResponse)) ? this.deserialize(((ContentResponse) response).getContentAsString(), type) : null; - case HttpStatus.BAD_REQUEST_400: - this.logger.trace("registration in progress: no token received yet"); - break; - case HttpStatus.UNAUTHORIZED_401: + } + case HttpStatus.BAD_REQUEST_400 -> this.logger.debug("registration in progress: no token received yet"); + case HttpStatus.UNAUTHORIZED_401 -> { this.authentication = Optional.empty(); - throw new HueSyncConnectionException("@text/connection.invalid-login", this.logger); - - case HttpStatus.NOT_FOUND_404: - this.logger.error("invalid device URI or API endpoint"); - break; - case HttpStatus.INTERNAL_SERVER_ERROR_500: - this.logger.error("hue sync box server problem"); - break; - default: - this.logger.warn("unexpected HTTP status: {}", status); + throw new HueSyncConnectionException("@text/connection.invalid-login"); + } + + case HttpStatus.NOT_FOUND_404 -> this.logger.warn("invalid device URI or API endpoint"); + case HttpStatus.INTERNAL_SERVER_ERROR_500 -> this.logger.warn("hue sync box server problem"); + default -> this.logger.warn("unexpected HTTP status: {}", status); } } catch (HueSyncConnectionException e) { - + this.logger.warn("{}", e.getMessage()); } return null; } @@ -233,7 +228,7 @@ private ContentResponse executeRequest(HttpMethod method, String endpoint, Strin } private void handleExecutionException(ExecutionException e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); Throwable cause = e.getCause(); if (cause != null && cause instanceof HttpResponseException) { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index ad53eb33b2d07..9847da706fd85 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -17,6 +17,7 @@ import java.security.cert.CertificateException; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -86,7 +87,7 @@ private Consumer defaultHandler(String endpoint) { } private void execute(String key, Command command) { - this.logger.info("Command executor: {} - {}", key, command); + this.logger.debug("Command executor: {} - {}", key, command); if (!this.connection.isRegistered()) { this.logger.warn("Device is not registered - ignoring command: {}", command); @@ -102,8 +103,7 @@ private void execute(String key, Command command) { } else if (command instanceof StringType) { value = '"' + command.toString() + '"'; } else { - this.logger.error("Type [{}] not supported by this connection", - (command != null) ? command.getClass().getCanonicalName() : "null"); + this.logger.warn("Type [{}] not supported by this connection", command.getClass().getCanonicalName()); return; } @@ -114,19 +114,18 @@ private void execute(String key, Command command) { // #endregion - @SuppressWarnings("null") public void executeCommand(Channel channel, Command command) { String uid = channel.getUID().getAsString(); String commandId = channel.getUID().getId(); - this.logger.trace("Channel UID: {} - Command: {}", uid, command.toFullString()); + this.logger.debug("Channel UID: {} - Command: {}", uid, command.toFullString()); if (RefreshType.REFRESH.equals(command)) { return; } if (this.deviceCommandExecutors.containsKey(commandId)) { - this.deviceCommandExecutors.get(commandId).accept(command); + Objects.requireNonNull(this.deviceCommandExecutors.get(commandId)).accept(command); } else { this.logger.error("No executor registered for command {} - please report this as an issue", commandId); } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index a2a924e950121..f089f85d24194 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -15,7 +15,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; import javax.jmdns.ServiceInfo; @@ -83,9 +82,9 @@ public String getServiceType() { public @Nullable DiscoveryResult createResult(ServiceInfo service) { if (this.autoDiscoveryEnabled) { ThingUID uid = getThingUID(service); - if (Objects.nonNull(uid)) { + if (uid != null) { try { - logger.trace("HDMI Sync Box {} discovered at {}:{}", service.getName(), + logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(), service.getHostAddresses()[0], service.getPort()); Map properties = new HashMap<>(); @@ -96,7 +95,7 @@ public String getServiceType() { return DiscoveryResultBuilder.create(uid).withLabel(service.getName()).withProperties(properties) .build(); } catch (Exception e) { - logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), + logger.debug("Unable to query device information for {}: {}", service.getQualifiedName(), e.getMessage()); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java index 29f300beda4f1..c4096dfec7dfe 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -13,7 +13,6 @@ package org.openhab.binding.huesync.internal.exceptions; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; /** * @@ -23,7 +22,7 @@ public class HueSyncApiException extends HueSyncException { private static final long serialVersionUID = 0L; - public HueSyncApiException(String message, Logger logger) { - super(message, logger); + public HueSyncApiException(String message) { + super(message); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java index d8bbdbe8e7cea..b42393814da1c 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java @@ -13,7 +13,6 @@ package org.openhab.binding.huesync.internal.exceptions; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; /** * @@ -23,7 +22,7 @@ public class HueSyncConnectionException extends HueSyncException { private static final long serialVersionUID = 0L; - public HueSyncConnectionException(String message, Logger logger) { - super(message, logger); + public HueSyncConnectionException(String message) { + super(message); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java index 988ca1dced4b0..583d169e8b814 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java @@ -14,7 +14,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; -import org.slf4j.Logger; /** * Base class for all HueSyncExceptions @@ -25,15 +24,7 @@ public abstract class HueSyncException extends Exception { private static final long serialVersionUID = 0L; - public HueSyncException(String message, Logger logger) { - super(message); - - String logMessage = message; - - if (message.startsWith("@text")) { - logMessage = HueSyncLocalizer.getResourceString(message); - } - - logger.error("{}", logMessage); + public HueSyncException(String message) { + super(message.startsWith("@text") ? HueSyncLocalizer.getResourceString(message) : message); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java index c0f5618735833..c3c288ff7bbd5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java @@ -13,7 +13,6 @@ package org.openhab.binding.huesync.internal.exceptions; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; /** * @@ -23,7 +22,7 @@ public class HueSyncTaskException extends HueSyncException { private static final long serialVersionUID = 0L; - public HueSyncTaskException(String message, Logger logger) { - super(message, logger); + public HueSyncTaskException(String message) { + super(message); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index bdc7ad078a5be..ded3e7ab53e4a 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.handler.HueSyncHandler; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -31,6 +32,7 @@ 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; /** * The {@link HueSyncHandlerFactory} is responsible for creating things and @@ -44,6 +46,7 @@ public class HueSyncHandlerFactory extends BaseThingHandlerFactory { private final HttpClientFactory httpClientFactory; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandlerFactory.class); @Activate public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception { @@ -66,7 +69,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { try { return new HueSyncHandler(thing, this.httpClientFactory); } catch (IOException | URISyntaxException | CertificateException e) { - // TODO: Implementation ... + this.logger.warn("It was not possible to create a handler for {}: {}", thingTypeUID.getId(), + e.getMessage()); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 0aca16c3d8d4c..ecd8b9a5d3044 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -158,13 +158,13 @@ private void startTasks() { } if (task != null) { - logger.trace("Starting task [{}]", id); + logger.debug("Starting task [{}]", id); this.tasks.put(id, this.executeTask(task, initialDelay, interval)); } } private void stopTasks() { - logger.trace("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet())); + logger.debug("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet())); this.tasks.values().forEach(task -> this.stopTask(task)); this.tasks.clear(); @@ -264,10 +264,10 @@ private void checkCompatibility() throws HueSyncApiException { HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow(); if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { - throw new HueSyncApiException("@text/api.minimal-version", this.logger); + throw new HueSyncApiException("@text/api.minimal-version"); } } catch (NoSuchElementException e) { - throw new HueSyncApiException("@text/api.communication-problem", logger); + throw new HueSyncApiException("@text/api.communication-problem"); } } @@ -304,7 +304,7 @@ public void initialize() { scheduler.execute(initializeConnection()); } catch (Exception e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); this.updateStatus(ThingStatus.OFFLINE); } @@ -336,9 +336,9 @@ public void dispose() { this.stopTasks(); this.connection.dispose(); } catch (Exception e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); } finally { - this.logger.info("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); + this.logger.debug("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index b153972f0e345..d97a095e4e879 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -18,6 +18,7 @@ import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; +import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException; import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; @@ -30,10 +31,9 @@ public class HueSyncRegistrationTask implements Runnable { private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); - private HueSyncDeviceConnection connection; - private HueSyncDevice deviceInfo; - - private Consumer action; + private final HueSyncDeviceConnection connection; + private final HueSyncDevice deviceInfo; + private final Consumer action; public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, Consumer action) { @@ -51,7 +51,7 @@ public void run() { return; } - this.logger.info("Listening for device registration - {} {}:{}", this.deviceInfo.name, + this.logger.debug("Listening for device registration - {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, id); HueSyncRegistration registration = this.connection.registerDevice(id); @@ -61,8 +61,8 @@ public void run() { this.action.accept(registration); } - } catch (Exception e) { - this.logger.debug("{}", e.getMessage()); + } catch (HueSyncConnectionException e) { + this.logger.warn("{}", e.getMessage()); } } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 9f905a5dcdcde..2ba6b713717d9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -47,7 +47,7 @@ public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice devic @Override public void run() { try { - this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.logger.debug("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, this.deviceInfo.uniqueId); if (!this.connection.isRegistered()) { From 1616f914005820f1afa90a71185888a83555018e Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 16 Nov 2024 22:31:29 +0100 Subject: [PATCH 112/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20and=20code=20analysis=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 303 ++++++------------ .../org.openhab.binding.huesync/doc/model.png | Bin 13136 -> 0 bytes .../HueSyncAuthenticationResult.java | 4 +- .../HueSyncTrustManagerProvider.java | 2 +- .../internal/handler/HueSyncHandler.java | 6 +- .../handler/tasks/HueSyncUpdateTask.java | 6 +- ...tDto.java => HueSyncUpdateTaskResult.java} | 2 +- 7 files changed, 104 insertions(+), 219 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/doc/model.png rename bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/{HueSyncUpdateTaskResultDto.java => HueSyncUpdateTaskResult.java} (95%) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 29ba388d8ad4c..59aa8cd56fb72 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -1,51 +1,56 @@ -# HueSync Binding +# hueSync Binding -This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. +This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-philips-hue-play-hdmi-sync-box/046677579753) into openHAB. The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/). -- [HueSync Binding](#huesync-binding) +- [hueSync Binding](#huesync-binding) + - [Supported Things](#supported-things) - [Discovery](#discovery) + - [Binding Configuration](#binding-configuration) - [Thing Configuration](#thing-configuration) - - [Registration](#registration) - - [Parameters](#parameters) - - [Channels](#channels) - - [Channels - Groups](#channels---groups) - - [Channels - device-firmware](#channels---device-firmware) - - [Channels - device-hdmi-connection-\[in|out\]](#channels---device-hdmi-connection-inout) - - [device-commands](#device-commands) + - [Channels](#channels) + - [firmware](#firmware) + - [hdmi-connection \[in|out\]](#hdmi-connection-inout) + - [commands](#commands) - [Item Configuration](#item-configuration) - - [Items - Groups](#items---groups) - - [Items - Remote Control](#items---remote-control) - - [Items - Firmware](#items---firmware) + - [Sitemap Configuration](#sitemap-configuration) + - [Additional Information](#additional-information) + - [Configuration using GUI](#configuration-using-gui) + +## Supported Things + +This binding provides only one thing type: **`box`**. +Each thing will represent a Hue Play HDMI sync box. ## Discovery -The binding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devices in the local network. -The LED on the Sync Box must be white or red. -This indicates that the device is connected to the Network. -If the LED is blinking blue, you need to setup the device using the official [Hue Sync App](https://www.philips-hue.com/en-in/explore-hue/propositions/entertainment/hue-sync). +The binding supports auto discovery using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to find devices in the local network. -If the device is not discovered you can check if it is properly configured and discoverable in the network: +Make sure the device is connected to the network (the LED on the Sync Box is white or red). +If the LED is blinking blue, you need to setup the device using the official [Hue Sync App](https://www.philips-hue.com/en-in/explore-hue/propositions/entertainment/hue-sync). -
    - Linux (Ubuntu based distributions) +If the device is not discovered you can check if it is properly configured and discoverable: -```bash -$ avahi-browse --resolve _huesync._tcp -+ wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local -= wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local - hostname = [XXXXXXXXXXX.local] - address = [192.168.0.12] - port = [443] - txt = ["name=Sync Box" "devicetype=HSB1" "uniqueid=XXXXXXXXXXX" "path=/api"] -``` +

    +

    + Linux (Ubuntu based distributions) -
    + ```bash + $ avahi-browse --resolve _huesync._tcp + + wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local + = wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local + hostname = [XXXXXXXXXXX.local] + address = [192.168.0.12] + port = [443] + txt = ["name=Sync Box" "devicetype=HSB1" "uniqueid=XXXXXXXXXXX" "path=/api"] + ``` -## Thing Configuration +
    +

    -### Registration +mDNS uses link-local multicast addresses; its scope is limited to a single physical or logical LAN ([Layer 3](https://en.wikipedia.org/wiki/OSI_model#Layer_3:_Network_layer")). +If your device is not automatically discovered, create a thing and manually configure the "host" parameter. To communicate with the sync box, you need to couple the thing with the hardware (registration). The thing will start this process automatically. @@ -55,7 +60,11 @@ To complete the registration you just press the "coupling" button on the sync bo For special use cases it is possible to configure the id and token manually in the **advanced configuration** settings section. -### Parameters +## Binding Configuration + +The binding does not require configuration. + +## Thing Configuration | Name | Type | Description | Default | Required | Advanced | | -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | @@ -65,78 +74,9 @@ For special use cases it is possible to configure the id and token manually in t | apiAccessToken | text | API Access Token | N/A | no | yes | | statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | -Once the thing is connection, you can add the equipment to the model using the advanced configuration option of the UI. -Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": - -![Add Equipment to Model](doc/add_equipment_to_model.png) - -You may use **Expert Mode** for configuration, or use the UI wizard. - -![Expert Mode](doc/expert_mode.png) - -
    - Expert Mode - Configuration Example to create a model and item setup: -

    You need to update the device in the example with the UID of your device (huesync:box:HueSyncBox).

    -
    -Group HueSyncBox "HueSyncBox" <iconify:mdi:television-ambient-light> ["NetworkAppliance"]
    -
    -Group HueSyncBox_Inputs "Inputs" <receiver> (HueSyncBox) ["Receiver"]
    -
    -Group HueSyncBox_Input_1 "Input 1" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    -Group HueSyncBox_Input_2 "Input 2" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    -Group HueSyncBox_Input_3 "Input 3" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    -Group HueSyncBox_Input_4 "Input 4" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
    -
    -Group HueSyncBox_Output    "Output"         <iconify:mdi:tv>          (HueSyncBox) ["Screen"]
    -Group HueSyncBox_Firmware  "Firmware"       <iconify:mdi:information> (HueSyncBox)
    -Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote>      (HueSyncBox) ["RemoteControl"]
    -
    -String HueSyncBox_Device_Mode       "Mode"       <iconify:mdi:multimedia>         (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#mode" }
    -String HueSyncBox_Device_Input      "Input"      <iconify:mdi:input>              (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" }
    -Switch HueSyncBox_Device_Hdmi       "HDMI"       <iconify:mdi:hdmi-port>          (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" }
    -Switch HueSyncBox_Device_Sync       "Sync"       <iconify:mdi:sync>               (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#sync-active" }
    -Number HueSyncBox_Device_Brightness "Brightness" <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#brightness" }
    -
    -String HueSyncBox_Firmware_Version        "Firmware Version"        <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#firmware" }
    -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#available-firmware" }
    -
    -String HueSyncBox_Device_hdmi_in1_Name      "Name - Input 1"    <iconify:mdi:text>       (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" }
    -String HueSyncBox_Device_hdmi_in1_Type      "Type - Input 1"    <iconify:mdi:devices>    (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" }
    -String HueSyncBox_Device_hdmi_in1_Status    "Status - Input 1"  <iconify:mdi:connection> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" }
    -String HueSyncBox_Device_hdmi_in1_Mode      "Mode - Input 1"    <iconify:mdi:multimedia> (HueSyncBox_Input_1)   { channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" }
    -
    -String HueSyncBox_Device_hdmi_in2_Name      "Name - Input 2"    <iconify:mdi:text>       (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" }
    -String HueSyncBox_Device_hdmi_in2_Type      "Type - Input 2"    <iconify:mdi:devices>    (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }
    -String HueSyncBox_Device_hdmi_in2_Status    "Status - Input 2"  <iconify:mdi:connection> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" }
    -String HueSyncBox_Device_hdmi_in2_Mode      "Mode - Input 2"    <iconify:mdi:multimedia> (HueSyncBox_Input_2)   { channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" }
    -
    -String HueSyncBox_Device_hdmi_in3_Name      "Name - Input 3"    <iconify:mdi:text>       (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" }
    -String HueSyncBox_Device_hdmi_in3_Type      "Type - Input 3"    <iconify:mdi:devices>    (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" }
    -String HueSyncBox_Device_hdmi_in3_Status    "Status - Input 3"  <iconify:mdi:connection> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" }
    -String HueSyncBox_Device_hdmi_in3_Mode      "Mode - Input 3"    <iconify:mdi:multimedia> (HueSyncBox_Input_3)   { channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" }
    +### Channels
     
    -String HueSyncBox_Device_hdmi_in4_Name      "Name - Input 4"    <iconify:mdi:text>       (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" }
    -String HueSyncBox_Device_hdmi_in4_Type      "Type - Input 4"    <iconify:mdi:devices>    (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" }
    -String HueSyncBox_Device_hdmi_in4_Status    "Status - Input 4"  <iconify:mdi:connection> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" }
    -String HueSyncBox_Device_hdmi_in4_Mode      "Mode - Input 4"    <iconify:mdi:multimedia> (HueSyncBox_Input_4)   { channel="huesync:box:HueSyncBox:device-hdmi-in-4#mode" }
    -
    -String HueSyncBox_Device_hdmi_out_Name      "Name - Output"     <iconify:mdi:text>       (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#name" }
    -String HueSyncBox_Device_hdmi_out_Type      "Type - Output"     <iconify:mdi:tv>         (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#type" }
    -String HueSyncBox_Device_hdmi_out_Status    "Status - Output"   <iconify:mdi:connection> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#status" }
    -String HueSyncBox_Device_hdmi_out_Mode      "Mode - Output"     <iconify:mdi:multimedia> (HueSyncBox_Output)   { channel="huesync:box:HueSyncBox:device-hdmi-out#mode" }
    -  
    -
    -
    - -![Semantic Model](doc/model.png) - -## Channels - -The following chapters provide more details for a tailored setup. - -### Channels - Groups - -### Channels - device-firmware +#### firmware Information about the installed device firmware and available updates. @@ -145,7 +85,7 @@ Information about the installed device firmware and available updates. | firmware | String | R | Installed firmware version | | available-firmware | String | R | Latest available firmware version | -### Channels - device-hdmi-connection-[in\|out] +#### hdmi-connection [in\|out] Information about a HDMI input connection. @@ -156,7 +96,7 @@ Information about a HDMI input connection. | name | String | R | Friendly Name | | mode | String | R |
    Mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | -#### device-commands +#### commands | Channel | Type | Read/Write | Description | | ----------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -167,127 +107,72 @@ Information about a HDMI input connection. ## Item Configuration -### Items - Groups - -
    - Groups - -| | | | | | | | -| ----- | -------------------- | ---------------- | --------------------------- | ------------------- | -------------------- | --- | -| Group | HueSyncBox | "HueSyncBox" | \ | | ["NetworkAppliance"] | | -| Group | HueSyncBox_Execution | "Remote Control" | \ | (HueSyncBox) | ["RemoteControl"] | | -| Group | HueSyncBox_Firmware | "Firmware" | \ | (HueSyncBox) | ["Point"] | | -| Group | HueSyncBox_Inputs | "Inputs" | \ | (HueSyncBox) | ["Receiver"] | | -| Group | HueSyncBox_Input_1 | "Input 1" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | -| Group | HueSyncBox_Input_2 | "Input 2" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | -| Group | HueSyncBox_Input_3 | "Input 3" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | -| Group | HueSyncBox_Input_4 | "Input 4" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | -| Group | HueSyncBox_Output | "Output" | \ | (HueSyncBox) | ["Screen"] | | - -
    - -### Items - Remote Control - -
    - Remote Control - -| | | | | | | | -| -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#mode" } | | -| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" } |
    HDMI Source
    • input1
    • input2
    • input3
    • input4
    | -| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#sync-active" } |
    HDMI Sync

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | -| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } |
    HDMI Active

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    | -| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:box:HueSyncBox:device-commands#brightness" } |
    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | +TODO -
    - -### Items - Firmware - -
    - Firmware - -| | | | | | | | -| ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ------------------------------------------------------------------------- | -| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-firmware#firmware" }` | -| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-firmware#available-firmware" }` | - -
    - ---- +### Sitemap Configuration -
    - Input 1 +TODO -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" }` | -| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" }` | -| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" }` | -| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" }` | +## Additional Information -
    +### Configuration using GUI -
    - Input 2 +It is also possible to add the equipment to the model using the advanced configuration option available in the UI. +Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" }` | -| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" }` | -| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" }` | -| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" }` | +![Add Equipment to Model](doc/add_equipment_to_model.png) -
    +Select "**Expert Mode**" to use the adapted example configuration. -
    - Input 3 +![Expert Mode](doc/expert_mode.png) -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" }` | -| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" }` | -| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" }` | -| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" }` | +You need to update the device in the example with the UID of your thing (huesync:box:HueSyncBox).

    -
    +```plaintext +Group HueSyncBox "HueSyncBox" ["NetworkAppliance"] -
    - Input 4 +Group HueSyncBox_Inputs "Inputs" (HueSyncBox) ["Receiver"] -| | | | | | | | -| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | -------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" }` | -| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" }` | -| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" }` | -| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-in-4#mode" }` | +Group HueSyncBox_Input_1 "Input 1" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_2 "Input 2" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_3 "Input 3" (HueSyncBox_Inputs) ["Receiver"] +Group HueSyncBox_Input_4 "Input 4" (HueSyncBox_Inputs) ["Receiver"] -
    +Group HueSyncBox_Output "Output" (HueSyncBox) ["Screen"] +Group HueSyncBox_Firmware "Firmware" (HueSyncBox) +Group HueSyncBox_Execution "Remote Control" (HueSyncBox) ["RemoteControl"] ---- +String HueSyncBox_Device_Mode "Mode" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#mode" } +String HueSyncBox_Device_Input "Input" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" } +Switch HueSyncBox_Device_Hdmi "HDMI" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } +Switch HueSyncBox_Device_Sync "Sync" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#sync-active" } +Number HueSyncBox_Device_Brightness "Brightness" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#brightness" } -
    - Output +String HueSyncBox_Firmware_Version "Firmware Version" (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#firmware" } +String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#available-firmware" } -| | | | | | | | -| ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ------------------------------------------------------------- | -| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#name" }` | -| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#type" }` | -| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#status" }` | -| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:box:HueSyncBox:device-hdmi-out#mode" }` | +String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" } +String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" } +String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" } +String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" } -
    +String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" } +String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" } +String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" } +String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" } - +String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" } +String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" } +String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" } +String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" } - diff --git a/bundles/org.openhab.binding.huesync/doc/model.png b/bundles/org.openhab.binding.huesync/doc/model.png deleted file mode 100644 index f054a9b9db550865df676e35ac178c6c113ed1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13136 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 action; + private final Consumer<@Nullable HueSyncUpdateTaskResult> action; public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, - Consumer<@Nullable HueSyncUpdateTaskResultDto> action) { + Consumer<@Nullable HueSyncUpdateTaskResult> action) { this.connection = connection; this.deviceInfo = deviceInfo; @@ -54,7 +54,7 @@ public void run() { this.action.accept(null); } - HueSyncUpdateTaskResultDto updateInfo = new HueSyncUpdateTaskResultDto(); + HueSyncUpdateTaskResult updateInfo = new HueSyncUpdateTaskResult(); updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo(); updateInfo.hdmiStatus = this.connection.getHdmiInfo(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java similarity index 95% rename from bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java rename to bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java index 0eff9cc8317a3..7f44a86312747 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java @@ -23,7 +23,7 @@ * @author Patrik Gfeller - Initial contribution */ @NonNullByDefault -public class HueSyncUpdateTaskResultDto { +public class HueSyncUpdateTaskResult { public @Nullable HueSyncDeviceDetailed deviceStatus; public @Nullable HueSyncHdmi hdmiStatus; public @Nullable HueSyncExecution execution; From f6bf2e5e7f977af689be0cfb8e5491bb38e618a7 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sun, 17 Nov 2024 18:32:36 +0100 Subject: [PATCH 113/128] =?UTF-8?q?refactor(logger):=20=F0=9F=A7=B9=20Obso?= =?UTF-8?q?lete=20custom=20log=20factory=20removed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../api/dto/execution/HueSyncExecution.java | 5 ++- .../connection/HueSyncConnection.java | 4 +-- .../connection/HueSyncDeviceConnection.java | 4 +-- .../HueSyncDiscoveryParticipant.java | 4 +-- .../factory/HueSyncHandlerFactory.java | 4 +-- .../internal/handler/HueSyncHandler.java | 4 +-- .../tasks/HueSyncRegistrationTask.java | 4 +-- .../handler/tasks/HueSyncUpdateTask.java | 4 +-- .../internal/log/HueSyncLogFactory.java | 31 ------------------- 9 files changed, 16 insertions(+), 48 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java index 1c3dbc200bbbb..74dd1579423ea 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java @@ -18,9 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.huesync.internal.handler.HueSyncHandler; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonProperty; @@ -32,7 +31,7 @@ */ @NonNullByDefault public class HueSyncExecution { - private static final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); + private static final Logger logger = LoggerFactory.getLogger(HueSyncExecution.class); public static final List KNOWN_MODES = Collections .unmodifiableList(Arrays.asList("powersave", "passthrough", "video", "game", "music")); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index b1c6ed89621d5..6a6a5874d2fe5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -35,12 +35,12 @@ import org.eclipse.jetty.http.MimeTypes; import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.io.net.http.TlsTrustManagerProvider; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -61,7 +61,7 @@ public class HueSyncConnection { */ private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; private static final String API = "api/v1"; - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncConnection.class); private final Integer port; private final String host; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 9847da706fd85..0438f7464fd9b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -34,7 +34,6 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -42,6 +41,7 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; @@ -52,7 +52,7 @@ */ @NonNullByDefault public class HueSyncDeviceConnection { - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncDeviceConnection.class); private final HueSyncConnection connection; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index f089f85d24194..d4d9f9d29ded2 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.HueSyncConstants; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -36,6 +35,7 @@ import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link HueSyncDiscoveryParticipant} is responsible for discovering @@ -46,7 +46,7 @@ @NonNullByDefault @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncDiscoveryParticipant.class); /** * diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java index ded3e7ab53e4a..5ce343649ba1f 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.HueSyncConstants; import org.openhab.binding.huesync.internal.handler.HueSyncHandler; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -33,6 +32,7 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link HueSyncHandlerFactory} is responsible for creating things and @@ -46,7 +46,7 @@ public class HueSyncHandlerFactory extends BaseThingHandlerFactory { private final HttpClientFactory httpClientFactory; - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandlerFactory.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncHandlerFactory.class); @Activate public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception { diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 7905669edb105..8692e116693d8 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -39,7 +39,6 @@ import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask; import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTaskResult; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.DecimalType; @@ -54,6 +53,7 @@ import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link HueSyncHandler} is responsible for handling commands, which are sent to one of the @@ -68,7 +68,7 @@ public class HueSyncHandler extends BaseThingHandler { private static final String PROPERTY_API_VERSION = "apiVersion"; - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class); Map> tasks = new HashMap<>(); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index d97a095e4e879..9967ff301f751 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -19,8 +19,8 @@ import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Task to handle device registration. @@ -29,7 +29,7 @@ */ @NonNullByDefault public class HueSyncRegistrationTask implements Runnable { - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncRegistrationTask.class); private final HueSyncDeviceConnection connection; private final HueSyncDevice deviceInfo; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 93ec0be4a2b88..0782c68c58591 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -18,8 +18,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; -import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Task to handle device information update. @@ -29,7 +29,7 @@ @NonNullByDefault public class HueSyncUpdateTask implements Runnable { - private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncUpdateTask.class); private final HueSyncDeviceConnection connection; private final HueSyncDevice deviceInfo; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java deleted file mode 100644 index 64c20e3222785..0000000000000 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2024 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.huesync.internal.log; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Helper class to use [en] resource files to log messages to be consistent - * with exception message displayed on screen. - * - * @author Patrik Gfeller - Initial contribution - */ -@NonNullByDefault -public class HueSyncLogFactory { - - public static Logger getLogger(Class clazz) { - return LoggerFactory.getLogger(clazz); - } -} From 8395105d8c2e7f20a29dc6bcf2cabd963d6f631b Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:30:02 +0100 Subject: [PATCH 114/128] =?UTF-8?q?docs(readme):=20=F0=9F=94=8E=20Address?= =?UTF-8?q?=20review=20findings=20and=20complete=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 225 +++++++++++------- .../doc/add_equipment_to_model.png | Bin 8625 -> 0 bytes .../doc/expert_mode.png | Bin 24022 -> 0 bytes .../huesync/internal/HdmiChannels.java | 3 + .../resources/OH-INF/i18n/huesync.properties | 98 +------- .../resources/OH-INF/thing/channel-types.xml | 2 +- .../resources/OH-INF/thing/thing-types.xml | 4 +- 7 files changed, 145 insertions(+), 187 deletions(-) delete mode 100644 bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png delete mode 100644 bundles/org.openhab.binding.huesync/doc/expert_mode.png diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 59aa8cd56fb72..d915d221a12b9 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -7,16 +7,17 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [hueSync Binding](#huesync-binding) - [Supported Things](#supported-things) - [Discovery](#discovery) - - [Binding Configuration](#binding-configuration) - - [Thing Configuration](#thing-configuration) - - [Channels](#channels) - - [firmware](#firmware) - - [hdmi-connection \[in|out\]](#hdmi-connection-inout) - - [commands](#commands) - - [Item Configuration](#item-configuration) - - [Sitemap Configuration](#sitemap-configuration) - - [Additional Information](#additional-information) - - [Configuration using GUI](#configuration-using-gui) + - [Channels](#channels) + - [Firmware Information](#firmware-information) + - [HDMI connections \[in|out\]](#hdmi-connections-inout) + - [Commands](#commands) + - [Configuration](#configuration) + - [Binding](#binding) + - [Thing(s)](#things) + - [Example Configuration](#example-configuration) + - [huesyncbox.things](#huesyncboxthings) + - [huesyncbox.items](#huesyncboxitems) + - [example.sitemap](#examplesitemap) ## Supported Things @@ -60,23 +61,9 @@ To complete the registration you just press the "coupling" button on the sync bo For special use cases it is possible to configure the id and token manually in the **advanced configuration** settings section. -## Binding Configuration +## Channels -The binding does not require configuration. - -## Thing Configuration - -| Name | Type | Description | Default | Required | Advanced | -| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | -| host | text | IP address of the device | N/A | yes | no | -| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | -| registrationId | text | Application Registration Id | N/A | no | yes | -| apiAccessToken | text | API Access Token | N/A | no | yes | -| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | - -### Channels - -#### firmware +### Firmware Information Information about the installed device firmware and available updates. @@ -85,7 +72,7 @@ Information about the installed device firmware and available updates. | firmware | String | R | Installed firmware version | | available-firmware | String | R | Latest available firmware version | -#### hdmi-connection [in\|out] +### HDMI connections [in\|out] Information about a HDMI input connection. @@ -96,7 +83,7 @@ Information about a HDMI input connection. | name | String | R | Friendly Name | | mode | String | R |
    Mode
    • video
    • game
    • music
    • powersave
    • passthrough
    | -#### commands +### Commands | Channel | Type | Read/Write | Description | | ----------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -105,74 +92,134 @@ Information about a HDMI input connection. | sync-active | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | | brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | -## Item Configuration - -TODO - -### Sitemap Configuration - -TODO - -## Additional Information - -### Configuration using GUI +## Configuration -It is also possible to add the equipment to the model using the advanced configuration option available in the UI. -Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": +### Binding -![Add Equipment to Model](doc/add_equipment_to_model.png) - -Select "**Expert Mode**" to use the adapted example configuration. - -![Expert Mode](doc/expert_mode.png) - -You need to update the device in the example with the UID of your thing (huesync:box:HueSyncBox).

    - -```plaintext -Group HueSyncBox "HueSyncBox" ["NetworkAppliance"] - -Group HueSyncBox_Inputs "Inputs" (HueSyncBox) ["Receiver"] - -Group HueSyncBox_Input_1 "Input 1" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_2 "Input 2" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_3 "Input 3" (HueSyncBox_Inputs) ["Receiver"] -Group HueSyncBox_Input_4 "Input 4" (HueSyncBox_Inputs) ["Receiver"] - -Group HueSyncBox_Output "Output" (HueSyncBox) ["Screen"] -Group HueSyncBox_Firmware "Firmware" (HueSyncBox) -Group HueSyncBox_Execution "Remote Control" (HueSyncBox) ["RemoteControl"] +The binding does not require configuration. -String HueSyncBox_Device_Mode "Mode" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#mode" } -String HueSyncBox_Device_Input "Input" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-source" } -Switch HueSyncBox_Device_Hdmi "HDMI" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#hdmi-active" } -Switch HueSyncBox_Device_Sync "Sync" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#sync-active" } -Number HueSyncBox_Device_Brightness "Brightness" (HueSyncBox_Execution) { channel="huesync:box:HueSyncBox:device-commands#brightness" } +### Thing(s) -String HueSyncBox_Firmware_Version "Firmware Version" (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#firmware" } -String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" (HueSyncBox_Firmware) { channel="huesync:box:HueSyncBox:device-firmware#available-firmware" } +| Name | Type | Description | Default | Required | Advanced | +| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | +| host | text | IP address of the device | N/A | yes | no | +| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | +| registrationId | text | Application Registration Id | N/A | no | yes | +| apiAccessToken | text | API Access Token | N/A | no | yes | +| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | -String HueSyncBox_Device_hdmi_in1_Name "Name - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#name" } -String HueSyncBox_Device_hdmi_in1_Type "Type - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#type" } -String HueSyncBox_Device_hdmi_in1_Status "Status - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#status" } -String HueSyncBox_Device_hdmi_in1_Mode "Mode - Input 1" (HueSyncBox_Input_1) { channel="huesync:box:HueSyncBox:device-hdmi-in-1#mode" } +### Example Configuration -String HueSyncBox_Device_hdmi_in2_Name "Name - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#name" } -String HueSyncBox_Device_hdmi_in2_Type "Type - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#type" } -String HueSyncBox_Device_hdmi_in2_Status "Status - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#status" } -String HueSyncBox_Device_hdmi_in2_Mode "Mode - Input 2" (HueSyncBox_Input_2) { channel="huesync:box:HueSyncBox:device-hdmi-in-2#mode" } +#### huesyncbox.things -String HueSyncBox_Device_hdmi_in3_Name "Name - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#name" } -String HueSyncBox_Device_hdmi_in3_Type "Type - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#type" } -String HueSyncBox_Device_hdmi_in3_Status "Status - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#status" } -String HueSyncBox_Device_hdmi_in3_Mode "Mode - Input 3" (HueSyncBox_Input_3) { channel="huesync:box:HueSyncBox:device-hdmi-in-3#mode" } +```java +Thing huesync:box:LivingRoom "Philips Hue HDMI Sync Box, LivingRoom" [ + host="192.168.2.115", + httpPollingInterval=60, + apiAccessToken="yourTokenGoesHere=", + registrationId="8", + port=443, + statusUpdateInterval=10 +] +``` -String HueSyncBox_Device_hdmi_in4_Name "Name - Input 4" (HueSyncBox_Input_4) { channel="huesync:box:HueSyncBox:device-hdmi-in-4#name" } -String HueSyncBox_Device_hdmi_in4_Type "Type - Input 4" (HueSyncBox_Input_4) { channel="huesync:box:HueSyncBox:device-hdmi-in-4#type" } -String HueSyncBox_Device_hdmi_in4_Status "Status - Input 4" (HueSyncBox_Input_4) { channel="huesync:box:HueSyncBox:device-hdmi-in-4#status" } -String HueSyncBox_Device_hdmi_in4_Mode "Mode - Input 4" (HueSyncBox_Input_4) { channel="huesync:box:HueSyncBox:device-hdmi-in-4#mode" } +#### huesyncbox.items + +Both item and sitemap configuration example use the `iconify` support for the `firmware` as well as `input1` and `input2`. +Those icons loaded if needed from the internet and not suited for a pure offline setup. +The other items use the `classic` icons. +Read the documentation about the offline provider for `iconify` icons, or use the `classic` icons bundled with openHAB. + +```java +// Firmware +Group firmware "Firmware Information" +String firmware_version "Current Firmware" (firmware) {channel="huesync:box:LivingRoom:device-firmware#firmware"} +String latest_firmware_version "Latest Firmware" (firmware) {channel="huesync:box:LivingRoom:device-firmware#available-firmware"} + +//HDMI Input 1 +Group hdmi_in1 "HDMI 1" +String friendly_name_input1 "Friendly Name" (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#name"} +String friendly_type_input1 "Friendly Type" (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#type"} +String hdmi_connection_status_input1 "Connection Status" (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#status"} +String last_sync_mode_input1 "Last Sync Mode " (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#mode"} + +//HDMI Input 2 +Group hdmi_in2 +String friendly_name_input2 "Friendly Name" {channel="huesync:box:LivingRoom:device-hdmi-in-2#name"} +String friendly_type_input2 "Friendly Type" {channel="huesync:box:LivingRoom:device-hdmi-in-2#type"} +String hdmi_connection_status_input2 "Connection Status" {channel="huesync:box:LivingRoom:device-hdmi-in-2#status"} +String last_sync_mode_input2 "Last Sync Mode" {channel="huesync:box:LivingRoom:device-hdmi-in-2#mode"} + +//HDMI Input 3 +String friendly_name_input3 "Friendly Name" {channel="huesync:box:LivingRoom:device-hdmi-in-3#name"} +String friendly_type_input3 "Friendly Type" {channel="huesync:box:LivingRoom:device-hdmi-in-3#type"} +String hdmi_connection_status_input3 "Connection Status" {channel="huesync:box:LivingRoom:device-hdmi-in-3#status"} +String last_sync_mode_input3 "Last Sync Mode" {channel="huesync:box:LivingRoom:device-hdmi-in-3#mode"} + +//HDMI Input 4 +String friendly_name_input4 "Friendly Name" {channel="huesync:box:LivingRoom:device-hdmi-in-4#name"} +String friendly_type_input4 "Friendly Type" {channel="huesync:box:LivingRoom:device-hdmi-in-4#type"} +String hdmi_connection_status_input4 "Connection Status" {channel="huesync:box:LivingRoom:device-hdmi-in-4#status"} +String last_sync_mode_input4 "Last Sync Mode" {channel="huesync:box:LivingRoom:device-hdmi-in-4#mode"} + + +//HDMI output +String friendly_name_output "Friendly Name" {channel="huesync:box:LivingRoom:device-hdmi-out#name"} +String friendly_type_output "Friendly Type" {channel="huesync:box:LivingRoom:device-hdmi-out#type"} +String hdmi_connection_status_output "Connection Status" {channel="huesync:box:LivingRoom:device-hdmi-out#status"} +String last_sync_mode_output "Last Sync Mode" {channel="huesync:box:LivingRoom:device-hdmi-out#mode"} + +//Commands +String huesync_mode "Mode" {channel="huesync:box:LivingRoom:device-commands#mode"} +Switch sync_active "Sync active" {channel="huesync:box:LivingRoom:device-commands#sync-active"} +Switch hdmi_active "HDMI active" {channel="huesync:box:LivingRoom:device-commands#hdmi-active"} +String hdmi_source "HDMI Source" {channel="huesync:box:LivingRoom:device-commands#hdmi-source"} +Dimmer huesync_brightness "Brightness" {channel="huesync:box:LivingRoom:device-commands#brightness"} -String HueSyncBox_Device_hdmi_out_Name "Name - Output" (HueSyncBox_Output) { channel="huesync:box:HueSyncBox:device-hdmi-out#name" } -String HueSyncBox_Device_hdmi_out_Type "Type - Output" (HueSyncBox_Output) { channel="huesync:box:HueSyncBox:device-hdmi-out#type" } -String HueSyncBox_Device_hdmi_out_Status "Status - Output" (HueSyncBox_Output) { channel="huesync:box:HueSyncBox:device-hdmi-out#status" } -String HueSyncBox_Device_hdmi_out_Mode "Mode - Output" (HueSyncBox_Output) { channel="huesync:box:HueSyncBox:device-hdmi-out#mode" } ``` + +#### example.sitemap + +```java +sitemap demo label="Hue Sync Box" { + Frame { + Group item=firmware + } + Frame label="Commands" icon=settings { + Text item=huesync_mode + Text item=hdmi_active + Switch item=sync_active + Text item=hdmi_source + Buttongrid label="HDMI Source" staticIcon=player { + Button row=1 column=1 item=hdmi_source label="Source 1" stateless click=input1 + Button row=2 column=1 item=hdmi_source label="Source 2" stateless click=input2 + Button row=3 column=1 item=hdmi_source label="Source 3" stateless click=input3 + Button row=4 column=1 item=hdmi_source label="Source 4" stateless click=input4 + } + Selection item=hdmi_source mappings=[input1="Source 1", input2="Source 2", input3="Source 3", input3="Source 4"] + + Slider item=huesync_brightness minValue=0 maxValue=200 step=10 + } + Frame label="HDMI Inputs 1 & 2" icon="iconify:mdi:hdmi-port" { + Default item=hdmi_in1 + + Group item=hdmi_in2 label="HDMI 2" icon="iconify:mdi:hdmi-port" { + Default item=friendly_name_input2 icon="iconify:mdi:text" + Default item=friendly_type_input2 icon="iconify:mdi:devices" + Default item=hdmi_connection_status_input2 icon="iconify:mdi:connection" + Default item=last_sync_mode_input2 icon="iconify:mdi:multimedia" + } + } + Frame label="HDMI 3" icon=player { + Default item=friendly_name_input3 + Default item=friendly_type_input3 + Default item=hdmi_connection_status_input3 + Default item=last_sync_mode_input3 + } + Frame label="HDMI 4" icon=player { + Text item=friendly_name_input4 + Text item=friendly_type_input4 + Text item=hdmi_connection_status_input4 + Text item=last_sync_mode_input4 + } +` diff --git a/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png b/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png deleted file mode 100644 index b3303bdfe4b37c439154e4567184727993f036c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8625 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 W0TKmus)rX{02F1_KsD0l5&s28NEt)` diff --git a/bundles/org.openhab.binding.huesync/doc/expert_mode.png b/bundles/org.openhab.binding.huesync/doc/expert_mode.png deleted file mode 100644 index c35d0ba478229faffd5341204b23424ae929ab83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24022 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 OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    channel-type.huesync.execution-brightness.label = Brightness channel-type.huesync.execution-brightness.description =

    0 ... 200

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    +channel-type.huesync.execution-hdmi-active.label = HDMI Active +channel-type.huesync.execution-hdmi-active.description =

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    channel-type.huesync.execution-hdmi-source.label = HDMI Input channel-type.huesync.execution-hdmi-source.description =

    • input1
    • input2
    • input3
    • input4

    channel-type.huesync.execution-mode.label = Mode @@ -91,98 +91,6 @@ channel-type.huesync.execution-mode.command.option.music = Music channel-type.huesync.execution-sync-active.label = Synchronization Active channel-type.huesync.execution-sync-active.description =

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    -# thing types - -thing-type.huesync.huesyncbox.label = Hue HDMI Sync Box -thing-type.huesync.huesyncbox.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. - -# thing types config - -thing-type.config.huesync.thing.apiAccessToken.label = API Access Token -thing-type.config.huesync.thing.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. -thing-type.config.huesync.thing.group.connection.label = Connection Settings -thing-type.config.huesync.thing.host.label = Network Address -thing-type.config.huesync.thing.host.description = Network address of the HDMI Sync Box. -thing-type.config.huesync.thing.port.label = Port -thing-type.config.huesync.thing.port.description = Port of the HDMI Sync Box. -thing-type.config.huesync.thing.registrationId.label = Application Registration Id -thing-type.config.huesync.thing.registrationId.description = The id of the API registration. -thing-type.config.huesync.thing.statusUpdateInterval.label = Status Update Interval -thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. - -# channel types - -channel-type.huesync.connection-lastSyncMode.label = Last sync mode -channel-type.huesync.connection-lastSyncMode.description = Last sync mode used for this channel -channel-type.huesync.executioin-hdmiActive.label = HDMI Active -channel-type.huesync.executioin-hdmiActive.description =

    OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

    When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

    -channel-type.huesync.execution-hdmiSource.label = HDMI input -channel-type.huesync.execution-hdmiSource.description =

    • input1
    • input2
    • input3
    • input4

    -channel-type.huesync.execution-syncActive.label = Synchronization Active -channel-type.huesync.execution-syncActive.description =

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

    When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    - -# channel types - -channel-type.huesync.connection-type-in.label = Friendly type -channel-type.huesync.connection-type-out.label = Friendly type - -# channel group types - -channel-group-type.huesync.device-hdmi-connection.label = hdmi -channel-group-type.huesync.device-hdmi-connection.description = HDMI connection - -# channel group types - -channel-group-type.huesync.device.label = Generic device information - -# channel types - -channel-type.huesync.device-info.label = Last Updated -channel-type.huesync.device-info.description = The date and time when the sensor was last updated. -channel-type.huesync.device-info.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS - -# thing types - -thing-type.huesync.huesyncbox.channel.test.label = test -thing-type.huesync.huesyncbox.channel.test.description = test - -# channel group types - -channel-group-type.huesync.deviceGroup.label = Device Information - -# channel group types - -channel-group-type.huesync.test.label = Test Group -channel-group-type.huesync.test.description = Culpa occaecat aliquip tempor ipsum exercitation incididunt culpa sit mollit officia labore commodo. - -# channel types - -channel-type.huesync.mode.label = Sync Mode -channel-type.huesync.mode.description = Select the sync mode -channel-type.huesync.mode.state.option.powersave = Powersave -channel-type.huesync.mode.state.option.video = Video -channel-type.huesync.mode.state.option.music = Music -channel-type.huesync.mode.state.option.game = Game - -# thing types config - -thing-type.config.huesync.box.apiAccessToken.label = API Access Token -thing-type.config.huesync.box.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. -thing-type.config.huesync.box.group.connection.label = Connection Settings -thing-type.config.huesync.box.host.label = Network Address -thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. -thing-type.config.huesync.box.port.label = Port -thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. -thing-type.config.huesync.box.registrationId.label = Application Registration Id -thing-type.config.huesync.box.registrationId.description = The id of the API registration. -thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval -thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. - -# thing types - -thing-type.huesync.huesync.label = Hue HDMI Sync Box -thing-type.huesync.huesync.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. - # *** exceptions *** exception.generic.connection = "Unable to connect to device." diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml index ff56dacd4f4a1..0f423c075b680 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -154,7 +154,7 @@ switch - + Switch diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml index 03d20d3662a73..043dc019c8f31 100644 --- a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -38,7 +38,7 @@ - Information about the installed device firmaware and available updates. + Information about the installed device firmware and available updates. text @@ -76,7 +76,7 @@ - + From 818f6f42221e1fb38193320608a4d147644e491a Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:57:55 +0100 Subject: [PATCH 115/128] Update bundles/org.openhab.binding.huesync/README.md Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index d915d221a12b9..33c2c1a6762a9 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -94,10 +94,6 @@ Information about a HDMI input connection. ## Configuration -### Binding - -The binding does not require configuration. - ### Thing(s) | Name | Type | Description | Default | Required | Advanced | From 4befab58fae5760eda49117dac06b86e1531fb7e Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:58:46 +0100 Subject: [PATCH 116/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/handler/tasks/HueSyncUpdateTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java index 0782c68c58591..18067fa39f6ce 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -61,7 +61,6 @@ public void run() { updateInfo.execution = this.connection.getExecutionInfo(); this.action.accept(updateInfo); - } catch (Exception e) { this.logger.debug("{}", e.getMessage()); this.action.accept(null); From 7a788af08f684403cdc35af7afaee526d1eb1c5f Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:59:09 +0100 Subject: [PATCH 117/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/handler/tasks/HueSyncRegistrationTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java index 9967ff301f751..5b6d2c27bb75b 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -57,7 +57,7 @@ public void run() { HueSyncRegistration registration = this.connection.registerDevice(id); if (registration != null) { - this.logger.info("API token for {} received", this.deviceInfo.name); + this.logger.debug("API token for {} received", this.deviceInfo.name); this.action.accept(registration); } From 1293f644d8c783d92e6b7d43a77fa8a73fe85fd6 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:59:28 +0100 Subject: [PATCH 118/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/discovery/HueSyncDiscoveryParticipant.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java index d4d9f9d29ded2..2365a98b173dc 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -138,7 +138,6 @@ private void updateService(ComponentContext componentContext) { autoDiscoveryPropertyValue, value); this.autoDiscoveryEnabled = value; } - } } } From dc699a1bd692b2bae61d0b71bd26f7667944c9d0 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Wed, 20 Nov 2024 22:59:49 +0100 Subject: [PATCH 119/128] Update bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java Co-authored-by: lsiepel Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 0438f7464fd9b..be2748d5ed499 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -170,7 +170,7 @@ public void executeCommand(Channel channel, Command command) { return registration; } } catch (JsonProcessingException e) { - this.logger.error("{}", e.getMessage()); + this.logger.warn("{}", e.getMessage()); } } return null; From d49584dce14ccce964b17fd81cd790ef5adfc249 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Thu, 21 Nov 2024 22:25:16 +0100 Subject: [PATCH 120/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/connection/HueSyncDeviceConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index be2748d5ed499..8d95010a51224 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -96,8 +96,8 @@ private void execute(String key, Command command) { String value; - if (command instanceof QuantityType) { - value = Integer.toString(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + value = Integer.toString(quantityCommand.intValue()); } else if (command instanceof OnOffType) { value = command == OnOffType.ON ? "true" : "false"; } else if (command instanceof StringType) { From 546c7033ecd9bfede2997432645b7ad8d7dfe362 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 22 Nov 2024 18:42:40 +0100 Subject: [PATCH 121/128] =?UTF-8?q?docs(readme):=20=F0=9F=94=8E=20Resolve?= =?UTF-8?q?=20code=20review=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- bundles/org.openhab.binding.huesync/README.md | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md index 33c2c1a6762a9..9e95c5136794f 100644 --- a/bundles/org.openhab.binding.huesync/README.md +++ b/bundles/org.openhab.binding.huesync/README.md @@ -7,17 +7,16 @@ The integration happens directly through the Hue [HDMI Sync Box API](https://dev - [hueSync Binding](#huesync-binding) - [Supported Things](#supported-things) - [Discovery](#discovery) + - [Configuration](#configuration) + - [Thing(s)](#things) - [Channels](#channels) - [Firmware Information](#firmware-information) - [HDMI connections \[in|out\]](#hdmi-connections-inout) - [Commands](#commands) - - [Configuration](#configuration) - - [Binding](#binding) - - [Thing(s)](#things) - - [Example Configuration](#example-configuration) - - [huesyncbox.things](#huesyncboxthings) - - [huesyncbox.items](#huesyncboxitems) - - [example.sitemap](#examplesitemap) + - [Example Configuration](#example-configuration) + - [huesyncbox.things](#huesyncboxthings) + - [huesyncbox.items](#huesyncboxitems) + - [example.sitemap](#examplesitemap) ## Supported Things @@ -61,6 +60,18 @@ To complete the registration you just press the "coupling" button on the sync bo For special use cases it is possible to configure the id and token manually in the **advanced configuration** settings section. +## Configuration + +### Thing(s) + +| Name | Type | Description | Default | Required | Advanced | +| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | +| host | text | IP address of the device | N/A | yes | no | +| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | +| registrationId | text | Application Registration Id | N/A | no | yes | +| apiAccessToken | text | API Access Token | N/A | no | yes | +| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | + ## Channels ### Firmware Information @@ -92,21 +103,9 @@ Information about a HDMI input connection. | sync-active | Switch | R/W |
    Synchronization

    OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

    | | brightness | Number:Dimensionless | R/W |
    Brightness

    • 0 = max reduction
    • 100 = no brightness reduction/boost compared to input
    • 200 = max boost

    | -## Configuration - -### Thing(s) - -| Name | Type | Description | Default | Required | Advanced | -| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | -| host | text | IP address of the device | N/A | yes | no | -| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | -| registrationId | text | Application Registration Id | N/A | no | yes | -| apiAccessToken | text | API Access Token | N/A | no | yes | -| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | - -### Example Configuration +## Example Configuration -#### huesyncbox.things +### huesyncbox.things ```java Thing huesync:box:LivingRoom "Philips Hue HDMI Sync Box, LivingRoom" [ @@ -119,7 +118,7 @@ Thing huesync:box:LivingRoom "Philips Hue HDMI Sync Box, LivingRoom" [ ] ``` -#### huesyncbox.items +### huesyncbox.items Both item and sitemap configuration example use the `iconify` support for the `firmware` as well as `input1` and `input2`. Those icons loaded if needed from the internet and not suited for a pure offline setup. @@ -174,7 +173,7 @@ Dimmer huesync_brightness "Brightness" ``` -#### example.sitemap +### example.sitemap ```java sitemap demo label="Hue Sync Box" { @@ -218,4 +217,4 @@ sitemap demo label="Hue Sync Box" { Text item=hdmi_connection_status_input4 Text item=last_sync_mode_input4 } -` +``` From aeb8f76a3e8fda91c9007f266f243ac0bdc71e90 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 22 Nov 2024 19:58:25 +0100 Subject: [PATCH 122/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems to be a false positive reported by the IDE - seems to build ok - let's check with CI ... Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/handler/HueSyncHandler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 8692e116693d8..8ef07aab3427e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -87,8 +87,6 @@ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) } // #region private - - @SuppressWarnings("null") private Runnable initializeConnection() { return () -> { this.deviceInfo = Optional.ofNullable(this.connection.getDeviceInfo()); From 649d76535a51e29ecaf2f8e68137c3aeabfa2377 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Fri, 22 Nov 2024 20:30:36 +0100 Subject: [PATCH 123/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../internal/i18n/HueSyncLocalizer.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index cc47fa8715b15..36e986c30c2d9 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -15,7 +15,6 @@ import java.util.Locale; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.TranslationProvider; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -28,22 +27,21 @@ */ @NonNullByDefault public class HueSyncLocalizer { - private static final Locale LOCALE = Locale.ENGLISH; - private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class) + private static final Locale locale = Locale.ENGLISH; + private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); - private static final ServiceReference SERVICE_REFERENCE = BUNDLE_CONTEXT + private static final ServiceReference serviceReference = bundleContext .getServiceReference(TranslationProvider.class); - private static final Bundle BUNDLE = BUNDLE_CONTEXT.getBundle(); + private static final Bundle bundle = bundleContext.getBundle(); public static String getResourceString(String key) { String lookupKey = key.replace("@text/", ""); - String missingKey = "Missing Translation: " + key; - @Nullable - TranslationProvider translationProvider = BUNDLE_CONTEXT.getService(SERVICE_REFERENCE); + String missingKey = "Missing Translation: " + key; - String result = translationProvider == null ? missingKey - : translationProvider.getText(BUNDLE, lookupKey, missingKey, LOCALE); + String result = (bundleContext.getService(serviceReference) instanceof TranslationProvider translationProvider) + ? translationProvider.getText(bundle, lookupKey, missingKey, locale) + : missingKey; return result == null ? missingKey : result; } From 5633ef2fc28d9da4a29ea696b1a6e9c0d7954979 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Nov 2024 17:32:17 +0100 Subject: [PATCH 124/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20code=20review=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove @SuppressWarnings("null") Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/handler/HueSyncHandler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 8ef07aab3427e..0197917feba7e 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -171,7 +171,6 @@ private void stopTasks() { "@text/thing.config.huesync.box.registration"); } - @SuppressWarnings("null") private void handleUpdate(@Nullable HueSyncUpdateTaskResult dto) { try { HueSyncUpdateTaskResult update = Optional.ofNullable(dto).get(); @@ -258,7 +257,6 @@ private void handleRegistration(HueSyncRegistration registration) { private void checkCompatibility() throws HueSyncApiException { try { - @SuppressWarnings("null") HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow(); if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { From dd30809a3365c09110481140d5c00bf47161aacc Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Nov 2024 19:08:05 +0100 Subject: [PATCH 125/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20static=20code=20analysis=20-=20HdmiChannels.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/HdmiChannels.java | 16 ++++++++-------- .../huesync/internal/handler/HueSyncHandler.java | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java index 4314447684bb8..cb3355b1293d5 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java @@ -20,15 +20,15 @@ */ @NonNullByDefault public class HdmiChannels { - public String NAME; - public String TYPE; - public String MODE; - public String STATUS; + public String name; + public String type; + public String mode; + public String status; public HdmiChannels(String name, String type, String mode, String status) { - NAME = name; - TYPE = type; - MODE = mode; - STATUS = status; + this.name = name; + this.type = type; + this.mode = mode; + this.status = status; } } diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java index 0197917feba7e..22ba24b596328 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -210,10 +210,10 @@ private void updateHdmiInformation(HueSyncHdmi hdmiStatus) { private void updateHdmiStatus(HdmiChannels channels, @Nullable HueSyncHdmiConnectionInfo hdmiStatusInfo) { if (hdmiStatusInfo != null) { - this.updateState(channels.NAME, new StringType(hdmiStatusInfo.name)); - this.updateState(channels.TYPE, new StringType(hdmiStatusInfo.type)); - this.updateState(channels.MODE, new StringType(hdmiStatusInfo.lastSyncMode)); - this.updateState(channels.STATUS, new StringType(hdmiStatusInfo.status)); + this.updateState(channels.name, new StringType(hdmiStatusInfo.name)); + this.updateState(channels.type, new StringType(hdmiStatusInfo.type)); + this.updateState(channels.mode, new StringType(hdmiStatusInfo.lastSyncMode)); + this.updateState(channels.status, new StringType(hdmiStatusInfo.status)); } } From b1781ed4fdcc5005331285b40bec565b49f2c5f0 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Sat, 23 Nov 2024 19:25:33 +0100 Subject: [PATCH 126/128] =?UTF-8?q?refactor(project):=20=F0=9F=94=8E=20Res?= =?UTF-8?q?olve=20static=20code=20analysis=20-=20HueSyncLocalizer.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../huesync/internal/i18n/HueSyncLocalizer.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java index 36e986c30c2d9..d6c2eebfd0680 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -27,21 +27,22 @@ */ @NonNullByDefault public class HueSyncLocalizer { - private static final Locale locale = Locale.ENGLISH; - private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) + private static final Locale LOCALE = Locale.ENGLISH; + private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class) .getBundleContext(); - private static final ServiceReference serviceReference = bundleContext + private static final ServiceReference SERVICE_REFERENCE = BUNDLE_CONTEXT .getServiceReference(TranslationProvider.class); - private static final Bundle bundle = bundleContext.getBundle(); + private static final Bundle BUNDLE = BUNDLE_CONTEXT.getBundle(); public static String getResourceString(String key) { String lookupKey = key.replace("@text/", ""); String missingKey = "Missing Translation: " + key; - String result = (bundleContext.getService(serviceReference) instanceof TranslationProvider translationProvider) - ? translationProvider.getText(bundle, lookupKey, missingKey, locale) - : missingKey; + String result = (BUNDLE_CONTEXT + .getService(SERVICE_REFERENCE) instanceof TranslationProvider translationProvider) + ? translationProvider.getText(BUNDLE, lookupKey, missingKey, LOCALE) + : missingKey; return result == null ? missingKey : result; } From e842959a41e2efe48342bb97d8d812748e904fe0 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 26 Nov 2024 18:21:41 +0100 Subject: [PATCH 127/128] refactor(lint): Static code analysis ... Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/HueSyncConstants.java | 2 +- .../internal/api/dto/execution/HueSyncExecution.java | 2 +- .../internal/connection/HueSyncDeviceConnection.java | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java index 9009cc835d7a6..2c59c18b75936 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -29,7 +29,7 @@ public static class ENDPOINTS { public static final String HDMI = "hdmi"; public static final String EXECUTION = "execution"; - public static class EXECUTION_ENDPOINTS { + public static class COMMANDS { public static final String MODE = "mode"; public static final String SYNC = "syncActive"; public static final String HDMI = "hdmiActive"; diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java index 74dd1579423ea..88a51b78b0052 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java @@ -31,7 +31,7 @@ */ @NonNullByDefault public class HueSyncExecution { - private static final Logger logger = LoggerFactory.getLogger(HueSyncExecution.class); + private final Logger logger = LoggerFactory.getLogger(HueSyncExecution.class); public static final List KNOWN_MODES = Collections .unmodifiableList(Arrays.asList("powersave", "passthrough", "video", "game", "music")); diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java index 8d95010a51224..c6e590dcc5db0 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -69,15 +69,15 @@ public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration confi private void registerCommandHandlers() { this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.MODE, - defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.MODE)); + defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.MODE)); this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, - defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SOURCE)); + defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SOURCE)); this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, - defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.BRIGHTNESS)); + defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.BRIGHTNESS)); this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC, - defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SYNC)); + defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SYNC)); this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI, - defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.HDMI)); + defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.HDMI)); } private Consumer defaultHandler(String endpoint) { From d8d519ba888dbfe9c8253eb42fde8cd6736f6de8 Mon Sep 17 00:00:00 2001 From: Patrik Gfeller Date: Tue, 26 Nov 2024 18:43:34 +0100 Subject: [PATCH 128/128] =?UTF-8?q?refactor(lint):=20=F0=9F=A7=B9=20static?= =?UTF-8?q?=20code=20analysis=20-=20HueSyncConnection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Gfeller --- .../binding/huesync/internal/connection/HueSyncConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java index 6a6a5874d2fe5..afc670b364420 100644 --- a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -176,7 +176,6 @@ protected void dispose() { this.authentication = Optional.empty(); throw new HueSyncConnectionException("@text/connection.invalid-login"); } - case HttpStatus.NOT_FOUND_404 -> this.logger.warn("invalid device URI or API endpoint"); case HttpStatus.INTERNAL_SERVER_ERROR_500 -> this.logger.warn("hue sync box server problem"); default -> this.logger.warn("unexpected HTTP status: {}", status);