From 009208adee028febbc1b52c7b84b2bbc2001366d Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Wed, 17 Mar 2021 09:12:04 +0100 Subject: [PATCH] [nanoleaf] Refactored code to use core features and more (#10101) This is a bigger refactoring bringing these (breaking) changes: - System channel types are used where applicable - Obsolete channels (such as power) were removed - Some channel types were marked "advanced" - "Tap" channels were converted to a trigger channel type providing a "system button" behavior - Layout can now be requested by a console command - Command options for effect channel are dynamically provided - Log level has been reduced where appropriate - HTTP request timeouts were reduced - handleRemoval now returns quickly as expected - Fixed hanging thread / infinite loop when requesting layouts for non-square panels - Various other smaller enhancements and fixes - Documentation has been adapted accordingly Signed-off-by: Kai Kreuzer --- .../org.openhab.binding.nanoleaf/README.md | 98 ++++------ .../internal/NanoleafBindingConstants.java | 9 +- .../internal/NanoleafHandlerFactory.java | 36 +--- .../nanoleaf/internal/OpenAPIUtils.java | 3 +- .../command/NanoleafCommandExtension.java | 103 ++++++++++ .../NanoleafCommandDescriptionProvider.java | 80 ++++++++ .../NanoleafMDNSDiscoveryParticipant.java | 4 +- .../NanoleafPanelsDiscoveryService.java | 107 +++++++---- .../handler/NanoleafControllerHandler.java | 181 ++++++------------ .../handler/NanoleafPanelHandler.java | 46 ++--- .../nanoleaf/internal/model/Layout.java | 18 +- .../main/resources/OH-INF/config/config.xml | 2 +- .../resources/OH-INF/i18n/nanoleaf.properties | 20 +- .../OH-INF/i18n/nanoleaf_de.properties | 18 +- .../resources/OH-INF/thing/lightpanels.xml | 85 ++------ 15 files changed, 407 insertions(+), 403 deletions(-) create mode 100644 bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/command/NanoleafCommandExtension.java create mode 100644 bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/commanddescription/NanoleafCommandDescriptionProvider.java diff --git a/bundles/org.openhab.binding.nanoleaf/README.md b/bundles/org.openhab.binding.nanoleaf/README.md index ec2435cb03..47ba4db643 100644 --- a/bundles/org.openhab.binding.nanoleaf/README.md +++ b/bundles/org.openhab.binding.nanoleaf/README.md @@ -11,35 +11,30 @@ The binding uses the [Nanoleaf OpenAPI](https://forum.nanoleaf.me/docs/openapi), ## Supported Things -Nanoleaf provides a bunch of devices of which some are connected to Wifi whereas other use the new Thread Technology. This binding only supports devices that are connected to Wifi. +Nanoleaf provides a bunch of devices of which some are connected to Wifi whereas other use the new Thread Technology. This binding only supports devices that are connected through Wifi. Currently Nanoleaf's "Light Panels" and "Canvas" devices are supported. -Note that only specific types do support the touch functionality, so the binding needs to check these types. The binding supports two thing types: controller and lightpanel. -The controller thing is the bridge for the individually attached panels/canvas and can be perceived as the nanoleaf device at the wall as a whole (either called "light panels" or "canvas" by Nanoleaf). +The controller thing is the bridge for the individually attached panels/canvas and can be perceived as the Nanoleaf device at the wall as a whole (either called "light panels" or "canvas" by Nanoleaf). With the controller thing you can control channels which affect all panels, e.g. selecting effects or setting the brightness. The lightpanel (singular) thing controls one of the individual panels/canvas that are connected to each other. Each individual panel has therefore its own id assigned to it. -You can set the **color** for each panel or turn it on (white) or off (black) and in the case of a nanoleaf canvas you can even detect single and double **touch events** related to an individual panel which opens a whole new world of controlling any other device within your openHAB environment. +You can set the **color** for each panel and in the case of a Nanoleaf canvas you can even detect single and double **touch events** related to an individual panel which opens a whole new world of controlling any other device within your openHAB environment. | Nanoleaf Name | Type | Description | supported | touch support | | ---------------------- | ---- | ---------------------------------------------------------- | --------- | ------------- | | Light Panels | NL22 | Triangles 1st Generation | X | (-) | | Shapes Triangle | NL42 | Triangles 2nd Generation (rounded edges) | X | X | -| Shapes Hexagon | NL42 | Triangles 2nd Generation (rounded edges) | (X) | (X) | +| Shapes Hexagon | NL42 | Hexagons | X | X | | Shapes Mini Triangles | ?? | Mini Triangles | ? | ? | | Canvas | NL29 | Squares | X | X | x = Supported (x) = Supported but only tested by community (-) = unknown (no device available to test) -Note: In case of major changes of a binding (like adding more features to a thing) it becomes necessary to delete your things due to the things not being compatible anymore. -Don't worry too much though as they will be easily redetected and nothing really is lost. -Just make sure that you delete them and rediscover as described below. - ## Discovery **Adding the Controller as a Thing** @@ -73,13 +68,16 @@ In this case: - stop and then start openHAB - Rediscover like described above -**Knowing which panel has which id** +### Panel Layout + +Unfortunately it is not easy to find out which panel gets which id, and this becomes pretty important if you have lots of them and want to assign rules. -Unfortunately it is not easy to find out which panel gets which id, and this becomes pretty important if you have lots of them and want to assign rules. -Don't worry as the binding comes with some helpful support in the background the canvas type (this is only provided for the canvas device because triangles can have weird layouts that are hard to express in a log output) +For canvas that use square panels, you can request the layout through a console command: -- Set up a switch item with the channel panelLayout on the controller (see NanoRetrieveLayout below) and set the switch to true -- look out for something like "Panel layout and ids" in the openHAB logs. Below that you will see a panel layout similar to +``` +openhab:nanoleaf layout [] +``` +The `thingUID` is an optional parameter. If it is not provided, the command loops through all Nanoleaf controller things it can find and prints the layout for each of them. Compare the following output with the right picture at the beginning of the article @@ -93,9 +91,7 @@ Compare the following output with the right picture at the beginning of the arti 41451 ``` - -Disclaimer: this works best with square devices and not necessarily well with triangles due to the more geometrically flexible layout. - + ## Thing Configuration The controller thing has the following parameters: @@ -106,7 +102,7 @@ The controller thing has the following parameters: | port | Port number of the light panels contoller. Default is 16021 | | authToken | The authentication token received from the controller after successful pairing. | | refreshInterval | Interval in seconds to refresh the state of the light panels settings. Default is 60. | -| deviceType | (readOnly) defines the type: lightpanels (triangle) or canvas (square) | +| deviceType | Defines the type `lightpanels` (triangle) or `canvas` (square or hexagon) | The lightpanel thing has the following parameters: @@ -115,7 +111,7 @@ The lightpanel thing has the following parameters: | id | ID assigned by the controller to the individual panel (e.g. 158) | The IDs of the individual panels can be determined by starting another scan once the controller is configured and online. -This will add all connected panels with their IDs to the inbox. +This discovers all connected panels with their IDs. ## Channels @@ -123,8 +119,7 @@ The controller bridge has the following channels: | Channel | Item Type | Description | Read Only | |---------------------|-----------|------------------------------------------------------------------------|-----------| -| power | Switch | Power state of the light panels | No | -| color | Color | Color of all light panels | No | +| color | Color | Color, power and brightness of all light panels | No | | colorTemperature | Dimmer | Color temperature (in percent) of all light panels | No | | colorTemperatureAbs | Number | Color temperature (in Kelvin, 1200 to 6500) of all light panels | No | | colorMode | String | Color mode of the light panels | Yes | @@ -132,48 +127,39 @@ The controller bridge has the following channels: | rhythmState | Switch | Connection state of the rhythm module | Yes | | rhythmActive | Switch | Activity state of the rhythm module | Yes | | rhythmMode | Number | Sound source for the rhythm module. 0=Microphone, 1=Aux cable | No | -| panelLayout | Switch | Set to true will log out panel layout (returns to off automatically | No | A lightpanel thing has the following channels: -| Channel | Item Type | Description | Read Only | +| Channel | Type | Description | Read Only | |---------------------|-----------|------------------------------------------------------------------------|-----------| -| panelColor | Color | Color of the individual light panel | No | -| singleTap | Switch | [Canvas Only] Is set when the user taps that panel once (1 second pulse) | Yes | -| doubleTap | Switch | [Canvas Only] Is set when the user taps that panel twice (1 second pulse) | Yes | - -**color and panelColor** +| color | Color | Color of the individual light panel | No | +| tap | Trigger | [Canvas Only] Sends events of gestures. Currently, these are SHORT_PRESSED and DOUBLE_PRESSED events. | Yes | -The color and panelColor channels support full color control with hue, saturation and brightness values. +The color channels support full color control with hue, saturation and brightness values. For example, brightness of *all* panels at once can be controlled by defining a dimmer item for the color channel of the *controller thing*. -The same applies to the panelColor channel of an individual lightpanel thing. - -What might not be obvious and even maybe confusing is the fact that brightness and color use the *same* channel but two different *itemTypes*. While the Color-itemtype controls the color, the Dimmer-itemtype controls the brightness on the same channel. +The same applies to the color channel of an individual lightpanel. **Limitations assigning specific colors on individual panels:** - Due to the way the API of the nanoleaf is designed, each time a color is assigned to a panel, it will be directly sent to that panel. The result is that if you send colors to several panels more or less at the same time, they will not be set at the same time but one after the other and rather appear like a sequence but as a one shot. -- Another important limitation is that individual panels cannot be set while a dynamic effect is running on the panel which means that the following happens - - As soon as you set an individual panel a so called "static effect" is created which replaces the chosen dynamic effect. You can even see that in the nanoleaf app that shows that a static effect is now running. - - Unfortunately, at least at the moment, the colors of the current state cannot be retrieved due to the high frequency of color changes that cannot be read quickly enough from the canvas, so all panels go to OFF - - The the first panelColor command is applied to that panel (and of course then all subsequent commands) - - The fact that it is called a static effect does not mean that you cannot create animations. The Rainbow rule below shows a good example for the whole canvas. Just replace the controller item with a panel item and you will get the rainbow effect with an individual panel. +- Another important limitation is that individual panels cannot be set while a dynamic effect is running on the panel which means that as soon as you set an individual panel the "static effect" is set, which disables the chosen dynamic effect. The nanoleaf app shows that a static effect is now running, too. +- The colors of the current state cannot be retrieved due to the high frequency of color changes that cannot be read quickly enough from the canvas, so all panels go to OFF +- The first panelColor command is applied to that panel (and of course then all subsequent commands) +- The fact that it is called a static effect does not mean that you cannot create animations. The Rainbow rule below shows a good example for the whole canvas. Just replace the controller item with a panel item and you will get the rainbow effect with an individual panel. **Touch Support** -Nanoleaf's Canvas introduces a whole new experience by adding touch support to it. This allows single and double taps on individual panels to be detected and then processed via rules to further control any other device! +Nanoleaf's Canvas introduces a whole new experience by supporting touch. This allows single and double taps on individual panels to be detected and processed via rules. Note that even gestures like up, down, left, right are sent but can only be detected on the whole set of panels and not on an individual panel. These four gestures are not yet supported by the binding but may be added in a later release. -To detect single and double taps the panel's have been extended to have two additional channels named singleTap and doubleTap which act like switches that are turned on as soon as a tap type is detected. +To detect single and double taps the panels have been extended to have two additional channels named singleTap and doubleTap which act like switches that are turned on as soon as a tap type is detected. These switches then act as a pulse to further control anything else via rules. -If a panel is tapped the switch is set to ON and automatically reset to OFF after 1 second (this may be configured in the future) to simulate a pulse. A rule can easily detect the transition from OFF to ON and later detect another tap as it is automatically reset by the binding. See the example below on Panel 2. - Keep in mind that the double tap is used as an already built-in functionality by default when you buy the nanoleaf: it switches all panels (hence the controller) to on or off like a light switch for all the panels at once. To circumvent that - Within the nanoleaf app go to the dashboard and choose your device. Enter the settings for that device by clicking the cog icon in the upper right corner. - Enable "Touch Gesture" and assign the gestures you want to happen but set the double tap to unassigned. -- To still have the possibility to switch on the whole canvas device with all its panels by double tapping a specific panel, you can easily write a rule that triggers on the double tap channel of that panel and then toggles the Power Channel of the controller. See the example below on Panel 1. +- To still have the possibility to switch on the whole canvas device with all its panels by double tapping a specific panel, you can easily write a rule that triggers on the tap channel of that panel and then sends an ON to the color channel of the controller. See the example below on Panel 1. More details can be found in the full example below. @@ -190,7 +176,7 @@ Bridge nanoleaf:controller:MyLightPanels @ "mylocation" [ address="192.168.1.100 } ``` -If you define your device statically in the thing file, autodiscovery of the same thing is suppressed by using +If you define your device statically in the thing file, auto-discovery of the same thing is suppressed by using * the [address="..." ] of the controller * and the [id=123] of the lightpanel @@ -208,13 +194,13 @@ e.g. via command line `curl --location --request POST 'http://
:16021/ap ### nanoleaf.items -Note: If you did autodiscover your things and items: +Note: If you auto-discovered your things and items: - A controller item looks like nanoleaf:controller:F0ED4F9351AF:power where F0ED4F9351AF is the id of the controller that has been automatically assigned by the binding. - A panel item looks like nanoleaf:lightpanel:F0ED4F9351AF:39755:singleTap where 39755 is the id of the panel that has been automatically assigned by the binding. ``` -Switch NanoleafPower "Nanoleaf" { channel="nanoleaf:controller:MyLightPanels:power" } +Switch NanoleafPower "Nanoleaf" { channel="nanoleaf:controller:MyLightPanels:color" } Color NanoleafColor "Color" { channel="nanoleaf:controller:MyLightPanels:color" } Dimmer NanoleafBrightness "Brightness [%.0f]" { channel="nanoleaf:controller:MyLightPanels:color" } String NanoleafHue "Hue [%s]" @@ -226,15 +212,11 @@ String NanoleafEffect "Effect" { channel="nanoleaf:controller:MyLightPanels:effe Switch NanoleafRhythmState "Rhythm connected [MAP(nanoleaf.map):%s]" { channel="nanoleaf:controller:MyLightPanels:rhythmState" } Switch NanoleafRhythmActive "Rhythm active [MAP(nanoleaf.map):%s]" { channel="nanoleaf:controller:MyLightPanels:rhythmActive" } Number NanoleafRhythmSource "Rhythm source [%s]" { channel="nanoleaf:controller:MyLightPanels:rhythmMode" } -Switch NanoRetrieveLayout "Nano Layout" { channel="nanoleaf:controller:D81E7A7E424E:panelLayout" } // note that the next to items use the exact same channel but the two different types Color and Dimmer to control different parameters -Color Panel1Color "Panel 1" { channel="nanoleaf:lightpanel:MyLightPanels:135:panelColor" } -Dimmer Panel1Brightness "Panel 1" { channel="nanoleaf:lightpanel:MyLightPanels:135:panelColor" } -Switch Panel1DoubleTap "Toggle device on and off" { channel="nanoleaf:lightpanel:MyLightPanels:135:doubleTap" } -Switch Panel2Color "Panel 2" { channel="nanoleaf:lightpanel:MyLightPanels:158:panelColor" } -Switch Panel2SingleTap "Panel 2 Single Tap" { channel="nanoleaf:lightpanel:MyLightPanels:158:singleTap" } -Switch Panel2DoubleTap "Panel 2 Double Tap" { channel="nanoleaf:lightpanel:MyLightPanels:158:doubleTap" } +Color PanelColor "Panel 1" { channel="nanoleaf:lightpanel:MyLightPanels:135:color" } +Dimmer Panel1Brightness "Panel 1" { channel="nanoleaf:lightpanel:MyLightPanels:135:color" } +Switch Panel2Color "Panel 2" { channel="nanoleaf:lightpanel:MyLightPanels:158:color" } Switch NanoleafRainbowScene "Show Rainbow Scene" ``` @@ -252,11 +234,10 @@ sitemap nanoleaf label="Nanoleaf" Slider item=NanoleafColorTemp Setpoint item=NanoleafColorTempAbs step=100 minValue=1200 maxValue=6500 Text item=NanoleafColorMode - Selection item=NanoleafEffect mappings=["Color Burst"="Color Burst", "Fireworks" = "Fireworks", "Flames" = "Flames", "Forest" = "Forest", "Inner Peace" = "Inner Peace", "Meteor Shower" = "Meteor Shower", "Nemo" = "Nemo", "Northern Lights" = "Northern Lights", "Paint Splatter" = "Paint Splatter", "Pulse Pop Beats" = "Pulse Pop Beats", "Rhythmic Northern Lights" = "Rhythmic Northern Lights", "Ripple" = "Ripple", "Romantic" = "Romantic", "Snowfall" = "Snowfall", "Sound Bar" = "Sound Bar", "Streaking Notes" = "Streaking Notes", "moonlight" = "Moonlight", "*Static*" = "Color (single panels)", "*Dynamic*" = "Color (all panels)" ] + Selection item=NanoleafEffect Text item=NanoleafRhythmState Text item=NanoleafRhythmActive Selection item=NanoleafRhythmSource mappings=[0="Microphone", 1="Aux"] - Switch item=NanoRetrieveLayout } Frame label="Panels" { @@ -271,9 +252,6 @@ sitemap nanoleaf label="Nanoleaf" } ``` -Note: The mappings to effects in the selection item are specific for each Nanoleaf installation and should be adapted accordingly. -Only the effects "\*Static\*" and "\*Dynamic\*" are predefined by the controller and should always be present in the mappings. - ### nanoleaf.rules ``` @@ -313,8 +291,7 @@ end rule "Nanoleaf canvas touch detection Panel 2" when - Item Panel2SingleTap changed from NULL to ON or - Item Panel2SingleTap changed from OFF to ON + Channel "nanoleaf:lightpanel:MyLightPanels:158:tap" triggered SHORT_PRESS then logInfo("CanvasTouch", "Nanoleaf Canvas Panel 2 was touched once") @@ -327,8 +304,7 @@ end rule "Nanoleaf double tap toggles power of device" when - Item Panel1DoubleTap changed from NULL to ON or - Item Panel1DoubleTap changed from OFF to ON + Channel "nanoleaf:lightpanel:MyLightPanels:135:tap" triggered DOUBLE_PRESS then logInfo("CanvasTouch", "Nanoleaf Canvas Panel 1 was touched twice. Toggle Power of whole canvas.") diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafBindingConstants.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafBindingConstants.java index 57933199d4..6a4b08f7da 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafBindingConstants.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafBindingConstants.java @@ -27,7 +27,7 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class NanoleafBindingConstants { - private static final String BINDING_ID = "nanoleaf"; + public static final String BINDING_ID = "nanoleaf"; // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller"); @@ -44,7 +44,6 @@ public class NanoleafBindingConstants { public static final String CONFIG_PANEL_ID = "id"; // List of controller channels - public static final String CHANNEL_POWER = "power"; public static final String CHANNEL_COLOR = "color"; public static final String CHANNEL_COLOR_TEMPERATURE = "colorTemperature"; public static final String CHANNEL_COLOR_TEMPERATURE_ABS = "colorTemperatureAbs"; @@ -53,12 +52,10 @@ public class NanoleafBindingConstants { public static final String CHANNEL_RHYTHM_STATE = "rhythmState"; public static final String CHANNEL_RHYTHM_ACTIVE = "rhythmActive"; public static final String CHANNEL_RHYTHM_MODE = "rhythmMode"; - public static final String CHANNEL_PANEL_LAYOUT = "panelLayout"; // List of light panel channels - public static final String CHANNEL_PANEL_COLOR = "panelColor"; - public static final String CHANNEL_PANEL_SINGLE_TAP = "singleTap"; - public static final String CHANNEL_PANEL_DOUBLE_TAP = "doubleTap"; + public static final String CHANNEL_PANEL_COLOR = "color"; + public static final String CHANNEL_PANEL_TAP = "tap"; // Nanoleaf OpenAPI URLs public static final String API_V1_BASE_URL = "/api/v1"; diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafHandlerFactory.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafHandlerFactory.java index 8c7f2a32e5..f942a10f0d 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafHandlerFactory.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafHandlerFactory.java @@ -15,9 +15,6 @@ package org.openhab.binding.nanoleaf.internal; import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*; import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -25,19 +22,15 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService; import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler; import org.openhab.binding.nanoleaf.internal.handler.NanoleafPanelHandler; -import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -49,6 +42,7 @@ import org.slf4j.LoggerFactory; * and panel (thing) handlers. * * @author Martin Raepple - Initial contribution + * @author Kai Kreuzer - made discovery a handler service */ @NonNullByDefault @Component(configurationPid = "binding.nanoleaf", service = ThingHandlerFactory.class) @@ -58,7 +52,6 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory { .unmodifiableSet(Stream.of(THING_TYPE_LIGHT_PANEL, THING_TYPE_CONTROLLER).collect(Collectors.toSet())); private final Logger logger = LoggerFactory.getLogger(NanoleafHandlerFactory.class); - private final Map> discoveryServiceRegs = new HashMap<>(); private final HttpClient httpClient; @Activate @@ -77,7 +70,6 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory { if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) { NanoleafControllerHandler handler = new NanoleafControllerHandler((Bridge) thing, httpClient); - registerDiscoveryService(handler); logger.debug("Nanoleaf controller handler created."); return handler; } else if (THING_TYPE_LIGHT_PANEL.equals(thingTypeUID)) { @@ -87,30 +79,4 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory { } return null; } - - @Override - protected void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof NanoleafControllerHandler) { - unregisterDiscoveryService(thingHandler.getThing()); - logger.debug("Nanoleaf controller handler removed."); - } - } - - private synchronized void registerDiscoveryService(NanoleafControllerHandler bridgeHandler) { - NanoleafPanelsDiscoveryService discoveryService = new NanoleafPanelsDiscoveryService(bridgeHandler); - discoveryServiceRegs.put(bridgeHandler.getThing().getUID(), - bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); - logger.debug("Discovery service for panels registered."); - } - - @SuppressWarnings("null") - private synchronized void unregisterDiscoveryService(Thing thing) { - @Nullable - ServiceRegistration serviceReg = discoveryServiceRegs.remove(thing.getUID()); - // would require null check but "if (response!=null)" throws warning on comoile time :´-( - if (serviceReg != null) { - serviceReg.unregister(); - } - logger.debug("Discovery service for panels removed."); - } } diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/OpenAPIUtils.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/OpenAPIUtils.java index 1875d474f7..af7658cb52 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/OpenAPIUtils.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/OpenAPIUtils.java @@ -20,6 +20,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,7 +58,7 @@ public class OpenAPIUtils { LOGGER.trace("RequestBuilder: Sending Request {}:{} {} ", requestURI.getHost(), requestURI.getPort(), requestURI.getPath()); - return httpClient.newRequest(requestURI).method(method); + return httpClient.newRequest(requestURI).method(method).timeout(10, TimeUnit.SECONDS); } public static URI getUri(NanoleafControllerConfig controllerConfig, String apiOperation, @Nullable String query) diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/command/NanoleafCommandExtension.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/command/NanoleafCommandExtension.java new file mode 100644 index 0000000000..d47e100729 --- /dev/null +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/command/NanoleafCommandExtension.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2010-2021 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.nanoleaf.internal.command; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants; +import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler; +import org.openhab.core.io.console.Console; +import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension; +import org.openhab.core.io.console.extensions.ConsoleCommandExtension; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Console commands for interacting with Nanoleaf integration + * + * @author Kai Kreuzer - Initial contribution + */ +@NonNullByDefault +@Component(service = ConsoleCommandExtension.class) +public class NanoleafCommandExtension extends AbstractConsoleCommandExtension { + + private static final String CMD_LAYOUT = "layout"; + private final ThingRegistry thingRegistry; + + @Activate + public NanoleafCommandExtension(@Reference ThingRegistry thingRegistry) { + super("nanoleaf", "Interact with the Nanoleaf integration."); + this.thingRegistry = thingRegistry; + } + + @Override + public void execute(String[] args, Console console) { + if (args.length > 0) { + String subCommand = args[0]; + switch (subCommand) { + case CMD_LAYOUT: + if (args.length == 1) { + thingRegistry.getAll().forEach(thing -> { + if (thing.getUID().getBindingId().equals(NanoleafBindingConstants.BINDING_ID)) { + ThingHandler handler = thing.getHandler(); + if (handler instanceof NanoleafControllerHandler) { + NanoleafControllerHandler nanoleafControllerHandler = (NanoleafControllerHandler) handler; + String layout = nanoleafControllerHandler.getLayout(); + console.println("Layout of Nanoleaf controller '" + thing.getUID().getAsString() + + "' with label '" + thing.getLabel() + "':" + System.lineSeparator()); + console.println(layout); + console.println(System.lineSeparator()); + } + } + }); + } else if (args.length == 2) { + String uid = args[1]; + Thing thing = thingRegistry.get(new ThingUID(uid)); + if (thing != null) { + ThingHandler handler = thing.getHandler(); + if (handler instanceof NanoleafControllerHandler) { + NanoleafControllerHandler nanoleafControllerHandler = (NanoleafControllerHandler) handler; + String layout = nanoleafControllerHandler.getLayout(); + console.println(layout); + } else { + console.println("Thing with UID '" + uid.toString() + + "' is not an initialized Nanoleaf controller."); + } + } else { + console.println("Thing with UID '" + uid.toString() + "' does not exist."); + } + } else { + printUsage(console); + } + break; + + default: + console.println("Unknown command '" + subCommand + "'"); + printUsage(console); + break; + } + } + } + + @Override + public List getUsages() { + return Arrays.asList(buildCommandUsage(CMD_LAYOUT + " ", "Prints the panel layout on the console.")); + } +} diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/commanddescription/NanoleafCommandDescriptionProvider.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/commanddescription/NanoleafCommandDescriptionProvider.java new file mode 100644 index 0000000000..3d59ba3f53 --- /dev/null +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/commanddescription/NanoleafCommandDescriptionProvider.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2021 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.nanoleaf.internal.commanddescription; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants; +import org.openhab.binding.nanoleaf.internal.NanoleafControllerListener; +import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler; +import org.openhab.binding.nanoleaf.internal.model.ControllerInfo; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.thing.type.DynamicCommandDescriptionProvider; +import org.openhab.core.types.CommandOption; +import org.osgi.service.component.annotations.Component; + +/** + * This class provides the available effects as dynamic options as they are read from the Nanoleaf controller. + * + * @author Kai Kreuzer - Initial contribution + * + */ +@NonNullByDefault +@Component(service = { DynamicCommandDescriptionProvider.class }) +public class NanoleafCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider + implements NanoleafControllerListener, ThingHandlerService { + + private @Nullable ChannelUID effectChannelUID; + + private @Nullable NanoleafControllerHandler bridgeHandler; + + @Override + public void setThingHandler(ThingHandler handler) { + this.bridgeHandler = (NanoleafControllerHandler) handler; + bridgeHandler.registerControllerListener(this); + effectChannelUID = new ChannelUID(handler.getThing().getUID(), NanoleafBindingConstants.CHANNEL_EFFECT); + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + public void deactivate() { + if (bridgeHandler != null) { + bridgeHandler.unregisterControllerListener(this); + } + super.deactivate(); + } + + @Override + public void onControllerInfoFetched(@NonNull ThingUID bridge, @NonNull ControllerInfo controllerInfo) { + List<@NonNull String> effects = controllerInfo.getEffects().getEffectsList(); + ChannelUID uid = effectChannelUID; + if (effects != null && uid != null && uid.getThingUID().equals(bridge)) { + List<@NonNull CommandOption> commandOptions = effects.stream() // + .map(effect -> new CommandOption(effect, effect)) // + .collect(Collectors.toList()); + setCommandOptions(uid, commandOptions); + } + } +} diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafMDNSDiscoveryParticipant.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafMDNSDiscoveryParticipant.java index 751f90712e..aa18f063f8 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafMDNSDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafMDNSDiscoveryParticipant.java @@ -79,10 +79,10 @@ public class NanoleafMDNSDiscoveryParticipant implements MDNSDiscoveryParticipan logger.trace("Discovered nanoleaf host: {} port: {} firmWare: {} modelId: {} qualifiedName: {}", host, port, firmwareVersion, modelId, qualifiedName); - logger.debug("Adding Nanoleaf controller {} with FW version {} found at {} {} to inbox", qualifiedName, + logger.debug("Adding Nanoleaf controller {} with FW version {} found at {}:{} to inbox", qualifiedName, firmwareVersion, host, port); if (!OpenAPIUtils.checkRequiredFirmware(service.getPropertyString("md"), firmwareVersion)) { - logger.warn("Nanoleaf controller firmware is too old. Must be {} or higher", + logger.debug("Nanoleaf controller firmware is too old. Must be {} or higher", MODEL_ID_LIGHTPANELS.equals(modelId) ? API_MIN_FW_VER_LIGHTPANELS : API_MIN_FW_VER_CANVAS); } diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafPanelsDiscoveryService.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafPanelsDiscoveryService.java index 391d36f63b..73e4404718 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafPanelsDiscoveryService.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/discovery/NanoleafPanelsDiscoveryService.java @@ -33,6 +33,8 @@ import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,36 +43,37 @@ import org.slf4j.LoggerFactory; * panels connected to the controller. * * @author Martin Raepple - Initial contribution + * @author Kai Kreuzer - Made it a ThingHandlerService */ @NonNullByDefault -public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService implements NanoleafControllerListener { +public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService + implements NanoleafControllerListener, ThingHandlerService { private static final int SEARCH_TIMEOUT_SECONDS = 60; private final Logger logger = LoggerFactory.getLogger(NanoleafPanelsDiscoveryService.class); - private final NanoleafControllerHandler bridgeHandler; + private @Nullable NanoleafControllerHandler bridgeHandler; + private @Nullable ControllerInfo controllerInfo; /** - * Constructs a new {@link NanoleafPanelsDiscoveryService} attached to the given bridge handler. - * - * @param nanoleafControllerHandler The bridge handler this discovery service is attached to + * Constructs a new {@link NanoleafPanelsDiscoveryService}. */ - public NanoleafPanelsDiscoveryService(NanoleafControllerHandler nanoleafControllerHandler) { + public NanoleafPanelsDiscoveryService() { super(NanoleafHandlerFactory.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIMEOUT_SECONDS, false); - this.bridgeHandler = nanoleafControllerHandler; } @Override - protected void startScan() { - logger.debug("Starting Nanoleaf panel discovery"); - bridgeHandler.registerControllerListener(this); + public void deactivate() { + if (bridgeHandler != null) { + bridgeHandler.unregisterControllerListener(this); + } + super.deactivate(); } @Override - protected synchronized void stopScan() { - logger.debug("Stopping Nanoleaf panel discovery"); - super.stopScan(); - bridgeHandler.unregisterControllerListener(this); + protected void startScan() { + logger.debug("Starting Nanoleaf panel discovery"); + createResultsFromControllerInfo(); } /** @@ -81,38 +84,60 @@ public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService imp */ @Override public void onControllerInfoFetched(ThingUID bridge, ControllerInfo controllerInfo) { - logger.debug("Discover panels connected to controller with id {}", bridge.getAsString()); - final PanelLayout panelLayout = controllerInfo.getPanelLayout(); - @Nullable - Layout layout = panelLayout.getLayout(); + this.controllerInfo = controllerInfo; + } - if (layout != null && layout.getNumPanels() > 0) { + private void createResultsFromControllerInfo() { + ThingUID bridgeUID; + if (bridgeHandler != null) { + bridgeUID = bridgeHandler.getThing().getUID(); + } else { + return; + } + if (controllerInfo != null) { + final PanelLayout panelLayout = controllerInfo.getPanelLayout(); @Nullable - final List positionData = layout.getPositionData(); - if (positionData != null) { - Iterator iterator = positionData.iterator(); - while (iterator.hasNext()) { - @Nullable - PositionDatum panel = iterator.next(); - ThingUID newPanelThingUID = new ThingUID(NanoleafBindingConstants.THING_TYPE_LIGHT_PANEL, bridge, - Integer.toString(panel.getPanelId())); - - final Map properties = new HashMap<>(1); - properties.put(CONFIG_PANEL_ID, panel.getPanelId()); - - DiscoveryResult newPanel = DiscoveryResultBuilder.create(newPanelThingUID).withBridge(bridge) - .withProperties(properties).withLabel("Light Panel " + panel.getPanelId()) - .withRepresentationProperty(CONFIG_PANEL_ID).build(); - - logger.debug("Adding panel with id {} to inbox", panel.getPanelId()); - thingDiscovered(newPanel); + Layout layout = panelLayout.getLayout(); + + if (layout != null && layout.getNumPanels() > 0) { + @Nullable + final List positionData = layout.getPositionData(); + if (positionData != null) { + Iterator iterator = positionData.iterator(); + while (iterator.hasNext()) { + @Nullable + PositionDatum panel = iterator.next(); + ThingUID newPanelThingUID = new ThingUID(NanoleafBindingConstants.THING_TYPE_LIGHT_PANEL, + bridgeUID, Integer.toString(panel.getPanelId())); + + final Map properties = new HashMap<>(1); + properties.put(CONFIG_PANEL_ID, panel.getPanelId()); + + DiscoveryResult newPanel = DiscoveryResultBuilder.create(newPanelThingUID).withBridge(bridgeUID) + .withProperties(properties).withLabel("Light Panel " + panel.getPanelId()) + .withRepresentationProperty(CONFIG_PANEL_ID).build(); + + logger.debug("Adding panel with id {} to inbox", panel.getPanelId()); + thingDiscovered(newPanel); + } + } else { + logger.debug("Couldn't add panels to inbox as layout position data was null"); } + } else { - logger.debug("Couldn't add panels to inbox as layout position data was null"); + logger.debug("No panels found or connected to controller"); } - - } else { - logger.info("No panels found or connected to controller"); } } + + @Override + public void setThingHandler(ThingHandler handler) { + this.bridgeHandler = (NanoleafControllerHandler) handler; + this.bridgeHandler.registerControllerListener(this); + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } } diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafControllerHandler.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafControllerHandler.java index ce5d0f0677..c36ef3bcf1 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafControllerHandler.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafControllerHandler.java @@ -17,7 +17,11 @@ import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Scanner; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; @@ -36,10 +40,11 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.nanoleaf.internal.NanoleafControllerListener; import org.openhab.binding.nanoleaf.internal.NanoleafException; -import org.openhab.binding.nanoleaf.internal.NanoleafInterruptedException; import org.openhab.binding.nanoleaf.internal.NanoleafUnauthorizedException; import org.openhab.binding.nanoleaf.internal.OpenAPIUtils; +import org.openhab.binding.nanoleaf.internal.commanddescription.NanoleafCommandDescriptionProvider; import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig; +import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService; import org.openhab.binding.nanoleaf.internal.model.AuthToken; import org.openhab.binding.nanoleaf.internal.model.BooleanState; import org.openhab.binding.nanoleaf.internal.model.Brightness; @@ -67,6 +72,7 @@ import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -81,15 +87,13 @@ import com.google.gson.JsonSyntaxException; * * @author Martin Raepple - Initial contribution * @author Stefan Höhn - Canvas Touch Support + * @author Kai Kreuzer - refactoring, bug fixing and code clean up */ @NonNullByDefault public class NanoleafControllerHandler extends BaseBridgeHandler { // Pairing interval in seconds - private static final int PAIRING_INTERVAL = 25; - - // Panel discovery interval in seconds - private static final int PANEL_DISCOVERY_INTERVAL = 30; + private static final int PAIRING_INTERVAL = 10; private final Logger logger = LoggerFactory.getLogger(NanoleafControllerHandler.class); private HttpClient httpClient; @@ -98,7 +102,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { // Pairing, update and panel discovery jobs and touch event job private @NonNullByDefault({}) ScheduledFuture pairingJob; private @NonNullByDefault({}) ScheduledFuture updateJob; - private @NonNullByDefault({}) ScheduledFuture panelDiscoveryJob; private @NonNullByDefault({}) ScheduledFuture touchJob; // JSON parser for API responses @@ -120,7 +123,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { @Override public void initialize() { logger.debug("Initializing the controller (bridge)"); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED); + updateStatus(ThingStatus.UNKNOWN); NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class); setAddress(config.address); setPort(config.port); @@ -157,13 +160,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { "@text/error.nanoleaf.controller.noToken"); startPairingJob(); stopUpdateJob(); - stopPanelDiscoveryJob(); } else { - logger.debug("Controller is online. Stop pairing job, start update & panel discovery jobs"); - updateStatus(ThingStatus.ONLINE); stopPairingJob(); startUpdateJob(); - startPanelDiscoveryJob(); startTouchJob(); } } catch (IllegalArgumentException iae) { @@ -186,11 +185,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { updateFromControllerInfo(); } else { switch (channelUID.getId()) { - case CHANNEL_POWER: case CHANNEL_COLOR: case CHANNEL_COLOR_TEMPERATURE: case CHANNEL_COLOR_TEMPERATURE_ABS: - case CHANNEL_PANEL_LAYOUT: sendStateCommand(channelUID.getId(), command); break; case CHANNEL_EFFECT: @@ -218,25 +215,28 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { @Override public void handleRemoval() { - // delete token for openHAB - ContentResponse deleteTokenResponse; - try { - Request deleteTokenRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), API_DELETE_USER, - HttpMethod.DELETE); - deleteTokenResponse = OpenAPIUtils.sendOpenAPIRequest(deleteTokenRequest); - if (deleteTokenResponse.getStatus() != HttpStatus.NO_CONTENT_204) { - logger.warn("Failed to delete token for openHAB. Response code is {}", deleteTokenResponse.getStatus()); - return; + scheduler.execute(() -> { + // delete token for openHAB + ContentResponse deleteTokenResponse; + try { + Request deleteTokenRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), + API_DELETE_USER, HttpMethod.DELETE); + deleteTokenResponse = OpenAPIUtils.sendOpenAPIRequest(deleteTokenRequest); + if (deleteTokenResponse.getStatus() != HttpStatus.NO_CONTENT_204) { + logger.warn("Failed to delete token for openHAB. Response code is {}", + deleteTokenResponse.getStatus()); + return; + } + logger.debug("Successfully deleted token for openHAB from controller"); + } catch (NanoleafUnauthorizedException e) { + logger.warn("Attempt to delete token for openHAB failed. Token unauthorized."); + } catch (NanoleafException ne) { + logger.warn("Attempt to delete token for openHAB failed : {}", ne.getMessage()); } - logger.debug("Successfully deleted token for openHAB from controller"); - } catch (NanoleafUnauthorizedException e) { - logger.warn("Attempt to delete token for openHAB failed. Token unauthorized."); - } catch (NanoleafException ne) { - logger.warn("Attempt to delete token for openHAB failed : {}", ne.getMessage()); - } - stopAllJobs(); - super.handleRemoval(); - logger.debug("Nanoleaf controller removed"); + stopAllJobs(); + super.handleRemoval(); + logger.debug("Nanoleaf controller removed"); + }); } @Override @@ -246,34 +246,37 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { logger.debug("Disposing handler for Nanoleaf controller {}", getThing().getUID()); } + @Override + public Collection> getServices() { + return List.of(NanoleafPanelsDiscoveryService.class, NanoleafCommandDescriptionProvider.class); + } + public boolean registerControllerListener(NanoleafControllerListener controllerListener) { logger.debug("Register new listener for controller {}", getThing().getUID()); - boolean result = controllerListeners.add(controllerListener); - if (result) { - startPanelDiscoveryJob(); - } - return result; + return controllerListeners.add(controllerListener); } public boolean unregisterControllerListener(NanoleafControllerListener controllerListener) { logger.debug("Unregister listener for controller {}", getThing().getUID()); - boolean result = controllerListeners.remove(controllerListener); - if (result) { - stopPanelDiscoveryJob(); - } - return result; + return controllerListeners.remove(controllerListener); } public NanoleafControllerConfig getControllerConfig() { NanoleafControllerConfig config = new NanoleafControllerConfig(); config.address = Objects.requireNonNullElse(getAddress(), ""); config.port = getPort(); - config.refreshInterval = getRefreshIntervall(); + config.refreshInterval = getRefreshInterval(); config.authToken = getAuthToken(); config.deviceType = Objects.requireNonNullElse(getDeviceType(), ""); return config; } + public String getLayout() { + Layout layout = controllerInfo.getPanelLayout().getLayout(); + String layoutView = (layout != null) ? layout.getLayoutView() : ""; + return layoutView; + } + public synchronized void startPairingJob() { if (pairingJob == null || pairingJob.isCancelled()) { logger.debug("Start pairing job, interval={} sec", PAIRING_INTERVAL); @@ -293,8 +296,8 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { String localAuthToken = getAuthToken(); if (localAuthToken != null && !localAuthToken.isEmpty()) { if (updateJob == null || updateJob.isCancelled()) { - logger.debug("Start controller status job, repeat every {} sec", getRefreshIntervall()); - updateJob = scheduler.scheduleWithFixedDelay(this::runUpdate, 0, getRefreshIntervall(), + logger.debug("Start controller status job, repeat every {} sec", getRefreshInterval()); + updateJob = scheduler.scheduleWithFixedDelay(this::runUpdate, 0, getRefreshInterval(), TimeUnit.SECONDS); } } else { @@ -311,24 +314,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { } } - public synchronized void startPanelDiscoveryJob() { - logger.debug("Starting panel discovery job. Has Controller-Listeners: {} panelDiscoveryJob: {}", - !controllerListeners.isEmpty(), panelDiscoveryJob); - if (!controllerListeners.isEmpty() && (panelDiscoveryJob == null || panelDiscoveryJob.isCancelled())) { - logger.debug("Start panel discovery job, interval={} sec", PANEL_DISCOVERY_INTERVAL); - panelDiscoveryJob = scheduler.scheduleWithFixedDelay(this::runPanelDiscovery, 0, PANEL_DISCOVERY_INTERVAL, - TimeUnit.SECONDS); - } - } - - private synchronized void stopPanelDiscoveryJob() { - if (controllerListeners.isEmpty() && panelDiscoveryJob != null && !panelDiscoveryJob.isCancelled()) { - logger.debug("Stop panel discovery job"); - panelDiscoveryJob.cancel(true); - this.panelDiscoveryJob = null; - } - } - private synchronized void startTouchJob() { NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class); if (!config.deviceType.equals(DEVICE_TYPE_TOUCHSUPPORT)) { @@ -367,11 +352,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { try { updateFromControllerInfo(); startTouchJob(); // if device type has changed, start touch detection. - // controller might have been offline, e.g. for firmware update. In this case, return to online state - if (ThingStatus.OFFLINE.equals(getThing().getStatus())) { - logger.debug("Controller {} is back online", thing.getUID()); - updateStatus(ThingStatus.ONLINE); - } + updateStatus(ThingStatus.ONLINE); } catch (NanoleafUnauthorizedException nae) { logger.warn("Status update unauthorized: {}", nae.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, @@ -403,7 +384,8 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { return; } ContentResponse authTokenResponse = OpenAPIUtils - .requestBuilder(httpClient, getControllerConfig(), API_ADD_USER, HttpMethod.POST).send(); + .requestBuilder(httpClient, getControllerConfig(), API_ADD_USER, HttpMethod.POST) + .timeout(20, TimeUnit.SECONDS).send(); if (logger.isTraceEnabled()) { logger.trace("Auth token response: {}", authTokenResponse.getContentAsString()); } @@ -429,7 +411,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { stopPairingJob(); startUpdateJob(); - startPanelDiscoveryJob(); startTouchJob(); } else { logger.debug("No auth token found in response: {}", authTokenResponse.getContentAsString()); @@ -446,7 +427,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.nanoleaf.controller.noTokenReceived"); } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.warn("Cannot send authorization request to controller: ", e); + logger.debug("Cannot send authorization request to controller: ", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.nanoleaf.controller.authRequest"); } catch (RuntimeException e) { @@ -459,34 +440,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { } } - private void runPanelDiscovery() { - logger.debug("Run panel discovery job"); - // Trigger a new discovery of connected panels - for (NanoleafControllerListener controllerListener : controllerListeners) { - try { - controllerListener.onControllerInfoFetched(getThing().getUID(), receiveControllerInfo()); - } catch (NanoleafUnauthorizedException nue) { - logger.warn("Panel discovery unauthorized: {}", nue.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.nanoleaf.controller.invalidToken"); - String localAuthToken = getAuthToken(); - if (localAuthToken == null || localAuthToken.isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "@text/error.nanoleaf.controller.noToken"); - } - } catch (NanoleafInterruptedException nie) { - logger.info("Panel discovery has been stopped."); - } catch (NanoleafException ne) { - logger.warn("Failed to discover panels: ", ne); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.nanoleaf.controller.communication"); - } catch (RuntimeException e) { - logger.warn("Panel discovery job failed", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.nanoleaf.controller.runtime"); - } - } - } - /** * This is based on the touch event detection described in https://forum.nanoleaf.me/docs/openapi#_842h3097vbgq */ @@ -579,14 +532,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { private void updateFromControllerInfo() throws NanoleafException { logger.debug("Update channels for controller {}", thing.getUID()); this.controllerInfo = receiveControllerInfo(); - if (controllerInfo == null) { - logger.debug("No Controller Info has been provided"); - return; - } final State state = controllerInfo.getState(); OnOffType powerState = state.getOnOff(); - updateState(CHANNEL_POWER, powerState); @Nullable Ct colorTemperature = state.getColorTemperature(); @@ -662,6 +610,10 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { panelHandler.updatePanelColorChannel(); } }); + + for (NanoleafControllerListener controllerListener : controllerListeners) { + controllerListener.onControllerInfoFetched(getThing().getUID(), controllerInfo); + } } private ControllerInfo receiveControllerInfo() throws NanoleafException, NanoleafUnauthorizedException { @@ -674,17 +626,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { private void sendStateCommand(String channel, Command command) throws NanoleafException { State stateObject = new State(); switch (channel) { - case CHANNEL_POWER: - if (command instanceof OnOffType) { - // On/Off command - turns controller on/off - BooleanState state = new On(); - state.setValue(OnOffType.ON.equals(command)); - stateObject.setState(state); - } else { - logger.warn("Unhandled command type: {}", command.getClass().getName()); - return; - } - break; case CHANNEL_COLOR: if (command instanceof OnOffType) { // On/Off command - turns controller on/off @@ -780,13 +721,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { return; } break; - case CHANNEL_PANEL_LAYOUT: - @Nullable - Layout layout = controllerInfo.getPanelLayout().getLayout(); - String layoutView = (layout != null) ? layout.getLayoutView() : ""; - logger.info("Panel layout and ids for controller {} \n{}", thing.getUID(), layoutView); - updateState(CHANNEL_PANEL_LAYOUT, OnOffType.OFF); - break; default: logger.warn("Unhandled command type: {}", command.getClass().getName()); return; @@ -844,7 +778,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { this.port = port; } - private int getRefreshIntervall() { + private int getRefreshInterval() { return refreshIntervall; } @@ -871,7 +805,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler { private void stopAllJobs() { stopPairingJob(); stopUpdateJob(); - stopPanelDiscoveryJob(); stopTouchJob(); } } diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafPanelHandler.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafPanelHandler.java index 628f119606..f25c54822e 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafPanelHandler.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafPanelHandler.java @@ -15,11 +15,11 @@ package org.openhab.binding.nanoleaf.internal.handler; import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Arrays; import java.util.HashMap; 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; @@ -28,13 +28,21 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpMethod; -import org.openhab.binding.nanoleaf.internal.*; +import org.openhab.binding.nanoleaf.internal.NanoleafBadRequestException; +import org.openhab.binding.nanoleaf.internal.NanoleafException; +import org.openhab.binding.nanoleaf.internal.NanoleafNotFoundException; +import org.openhab.binding.nanoleaf.internal.NanoleafUnauthorizedException; +import org.openhab.binding.nanoleaf.internal.OpenAPIUtils; import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig; import org.openhab.binding.nanoleaf.internal.model.Effects; import org.openhab.binding.nanoleaf.internal.model.Write; -import org.openhab.core.library.types.*; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.CommonTriggerEvents; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; @@ -165,8 +173,9 @@ public class NanoleafPanelHandler extends BaseThingHandler { private void sendRenderedEffectCommand(Command command) throws NanoleafException { logger.debug("Command Type: {}", command.getClass()); HSBType currentPanelColor = getPanelColor(); - if (currentPanelColor != null) + if (currentPanelColor != null) { logger.debug("currentPanelColor: {}", currentPanelColor.toString()); + } HSBType newPanelColor = new HSBType(); if (command instanceof HSBType) { @@ -205,11 +214,11 @@ public class NanoleafPanelHandler extends BaseThingHandler { // transform to RGB PercentType[] rgbPercent = newPanelColor.toRGB(); logger.trace("Setting new rgbpercent {} {} {}", rgbPercent[0], rgbPercent[1], rgbPercent[2]); - int red = rgbPercent[0].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP) + int red = rgbPercent[0].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP) .multiply(new BigDecimal(255)).intValue(); - int green = rgbPercent[1].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP) + int green = rgbPercent[1].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP) .multiply(new BigDecimal(255)).intValue(); - int blue = rgbPercent[2].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP) + int blue = rgbPercent[2].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP) .multiply(new BigDecimal(255)).intValue(); logger.trace("Setting new rgb {} {} {}", red, green, blue); Bridge bridge = getBridge(); @@ -253,8 +262,9 @@ public class NanoleafPanelHandler extends BaseThingHandler { @Nullable HSBType panelColor = getPanelColor(); logger.trace("updatePanelColorChannel: panelColor: {}", panelColor); - if (panelColor != null) + if (panelColor != null) { updateState(CHANNEL_PANEL_COLOR, panelColor); + } } /** @@ -265,28 +275,14 @@ public class NanoleafPanelHandler extends BaseThingHandler { public void updatePanelGesture(int gesture) { switch (gesture) { case 0: - updateState(CHANNEL_PANEL_SINGLE_TAP, OnOffType.ON); - singleTapJob = scheduler.schedule(this::resetSingleTap, 1, TimeUnit.SECONDS); - logger.debug("Asserting single tap of panel {} to ON", getPanelID()); + triggerChannel(CHANNEL_PANEL_TAP, CommonTriggerEvents.SHORT_PRESSED); break; case 1: - updateState(CHANNEL_PANEL_DOUBLE_TAP, OnOffType.ON); - doubleTapJob = scheduler.schedule(this::resetDoubleTap, 1, TimeUnit.SECONDS); - logger.debug("Asserting double tap of panel {} to ON", getPanelID()); + triggerChannel(CHANNEL_PANEL_TAP, CommonTriggerEvents.DOUBLE_PRESSED); break; } } - private void resetSingleTap() { - updateState(CHANNEL_PANEL_SINGLE_TAP, OnOffType.OFF); - logger.debug("Resetting single tap of panel {} to OFF", getPanelID()); - } - - private void resetDoubleTap() { - updateState(CHANNEL_PANEL_DOUBLE_TAP, OnOffType.OFF); - logger.debug("Resetting double tap of panel {} to OFF", getPanelID()); - } - public String getPanelID() { String panelID = getThing().getConfiguration().get(CONFIG_PANEL_ID).toString(); return panelID; @@ -327,7 +323,7 @@ public class NanoleafPanelHandler extends BaseThingHandler { } catch (NanoleafException nue) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.nanoleaf.panel.communication"); - logger.warn("Panel data could not be retrieved: {}", nue.getMessage()); + logger.debug("Panel data could not be retrieved: {}", nue.getMessage()); } return panelInfo.get(panelID); diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/model/Layout.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/model/Layout.java index dd8a323016..2c3db6be1e 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/model/Layout.java +++ b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/model/Layout.java @@ -60,7 +60,7 @@ public class Layout { * Returns an text representation for a canvas layout. * * Note only canvas supported currently due to its easy geometry - * + * * @return a String containing the layout */ public String getLayoutView() { @@ -97,6 +97,11 @@ public class Layout { int shiftWidth = getSideLength() / 2; + if (shiftWidth == 0) { + // seems we do not have squares here + return "Cannot render layout. Please note that layout views are only supported for square panels."; + } + int lineY = maxy; Map map; @@ -108,8 +113,9 @@ public class Layout { @Nullable PositionDatum panel = positionData.get(index); - if (panel != null && panel.getPosY() == lineY) + if (panel != null && panel.getPosY() == lineY) { map.put(panel.getPosX(), panel); + } } } lineY -= shiftWidth; @@ -118,14 +124,16 @@ public class Layout { @Nullable PositionDatum panel = map.get(x); view += String.format("%5s ", panel.getPanelId()); - } else + } else { view += " "; + } } - view += "\n"; + view += System.lineSeparator(); } return view; - } else + } else { return ""; + } } } diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/config/config.xml index 22348de623..76ce175865 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/config/config.xml @@ -25,7 +25,7 @@ @text/thing-type.config.nanoleaf.controller.refreshInterval.description 60 - + @text/thing-type.config.nanoleaf.controller.deviceType.description lightPanels diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf.properties b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf.properties index 39aefb6623..caf3424be8 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf.properties +++ b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf.properties @@ -1,5 +1,5 @@ binding.nanoleaf.name = Nanoleaf Binding -binding.nanoleaf.description = Integrates the Nanoleaf Light Panels (v010221) +binding.nanoleaf.description = Integrates the Nanoleaf light panels # thing types thing-type.nanoleaf.controller.name = Nanoleaf Controller @@ -22,14 +22,6 @@ thing-type.config.nanoleaf.lightpanel.id.label = ID Of The Panel thing-type.config.nanoleaf.lightpanel.id.description = The ID of the panel assigned by the controller # channel types -channel-type.nanoleaf.power.label = Power -channel-type.nanoleaf.power.description = Power state of the controller -channel-type.nanoleaf.color.label = Color -channel-type.nanoleaf.color.description = Color setting for an individual or all panels -channel-type.nanoleaf.colorTemperature.label = Color Temperature -channel-type.nanoleaf.colorTemperature.description = Color temperature in percent -channel-type.nanoleaf.colorTemperatureAbs.label = Color Temperature (K) -channel-type.nanoleaf.colorTemperatureAbs.description = Color temperature in Kelvin (K) channel-type.nanoleaf.colorMode.label = Color Mode channel-type.nanoleaf.colorMode.description = Current color mode: Effect, hue/saturation or color temperature channel-type.nanoleaf.effect.label = Effect @@ -40,21 +32,17 @@ channel-type.nanoleaf.rhythmActive.label = Rhythm Active channel-type.nanoleaf.rhythmActive.description = Activity state of the rhythm module channel-type.nanoleaf.rhythmMode.label = Rhythm Mode channel-type.nanoleaf.rhythmMode.description = Sound source for the rhythm module (microphone or aux cable) -channel-type.nanoleaf.panelLayout.label = Panel Layout -channel-type.nanoleaf.panelLayout.description = Creates a panel layout upon request channel-type.nanoleaf.panelColor.label = Panel Color channel-type.nanoleaf.panelColor.description = Color of the individual panel -channel-type.nanoleaf.singleTap.label = SingleTap -channel-type.nanoleaf.singleTap.description = Single tap on the panel -channel-type.nanoleaf.doubleTap.label = DoubleTap -channel-type.nanoleaf.doubleTap.description = Double tap on the panel +channel-type.nanoleaf.tap.label = Button +channel-type.nanoleaf.tap.description = Button events of the panel # error messages error.nanoleaf.controller.noIp = IP/host address and/or port are not configured for the controller. error.nanoleaf.controller.incompatibleFirmware = Incompatible controller firmware. Remove the device, update the firmware, and add it again. error.nanoleaf.controller.noToken = No authorization token found. To start pairing, press the on-off button of the controller for 5-7 seconds until the LED starts flashing in a pattern. error.nanoleaf.controller.invalidToken = Invalid token. Replace with valid token or start pairing again by removing the invalid token from the configuration. -error.nanoleaf.controller.communication = Communication failed. Please check configuration. +error.nanoleaf.controller.communication = Communication failed. Please check your network and configuration. error.nanoleaf.controller.pairingFailed = Pairing failed. Press the on-off button for 5-7 seconds until the LED starts flashing in a pattern. error.nanoleaf.controller.invalidData = Pairing failed. Received invalid data error.nanoleaf.controller.noTokenReceived = Pairing failed. No authorization token received from controller. diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf_de.properties b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf_de.properties index b21023e07c..d945315758 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf_de.properties +++ b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/i18n/nanoleaf_de.properties @@ -22,14 +22,6 @@ thing-type.config.nanoleaf.lightpanel.id.label = Paneel ID thing-type.config.nanoleaf.lightpanel.id.description = Vom Controller vergebene ID eines Paneels # channel types -channel-type.nanoleaf.power.label = Power -channel-type.nanoleaf.power.description = Ermöglicht das An- und Ausschalten des Nanoleaf Light Panels -channel-type.nanoleaf.color.label = Farbe -channel-type.nanoleaf.color.description = Farbe aller oder eines einzelnen Paneels -channel-type.nanoleaf.colorTemperature.label = Farbtemperatur -channel-type.nanoleaf.colorTemperature.description = Farbtemperatur in Prozent -channel-type.nanoleaf.colorTemperatureAbs.label = Farbtemperatur (K) -channel-type.nanoleaf.colorTemperatureAbs.description = Farbtemperatur in Kelvin (K) channel-type.nanoleaf.colorMode.label = Farbmodus channel-type.nanoleaf.colorMode.description = Effekt, Hue/Sat oder Farbtemperatur für alle Paneele. channel-type.nanoleaf.effect.label = Effekt @@ -40,21 +32,17 @@ channel-type.nanoleaf.rhythmActive.label = Rhythm Aktiv channel-type.nanoleaf.rhythmActive.description = Zeigt an ob das Mikrofon des Rhythm Modules ativ ist. channel-type.nanoleaf.rhythmMode.label = Rhythm Modus channel-type.nanoleaf.rhythmMode.description = Erlaubt den Wechsel zwischen eingebautem Mikrofon und AUX-Kabel. -channel-type.nanoleaf.panelLayout.label = PanelLayout -channel-type.nanoleaf.panelLayout.description = Erzeugt auf Anfrage ein Panel-Layout channel-type.nanoleaf.panelColor.label = Paneelfarbe channel-type.nanoleaf.panelColor.description = Farbe des einzelnen Paneels -channel-type.nanoleaf.singleTap.label = Einzel-Tap -channel-type.nanoleaf.singleTap.description = Panel wurde einmal angetippt -channel-type.nanoleaf.doubleTap.label = Doppel-Tap -channel-type.nanoleaf.doubleTap.description = Panel wurde zweimal hintereinander angetippt +channel-type.nanoleaf.tap.label = Taster +channel-type.nanoleaf.tap.description = Tastevents des Panels # error messages error.nanoleaf.controller.noIp = IP/Host-Adresse und/oder Port sind für den Controller nicht konfiguriert. error.nanoleaf.controller.incompatibleFirmware = Inkompatible Controller-Firmware. Firmware aktualisieren und das Gerät neu hinzufügen. error.nanoleaf.controller.noToken = Kein Authentifizierungstoken gefunden. Um das Pairing zu starten, den An/Aus-Knopf am Controller für 5-7 Sekunden lang gedrückt halten, bis die LED anfängt zu blinken. error.nanoleaf.controller.invalidToken = Ungültiges Authentifizierungstoken. Token ändern oder das Pairing neu starten durch Löschen des ungültigen Tokens. -error.nanoleaf.controller.communication = Kommunikationsfehler. Konfiguration des Controllers prüfen. +error.nanoleaf.controller.communication = Kommunikationsfehler. Netzwerk und Konfiguration des Controllers prüfen. error.nanoleaf.controller.pairingFailed = Pairing fehlgeschlagen. Um das Pairing zu starten, den An/Aus-Knopf am Controller für 5-7 Sekunden lang gedrückt halten, bis die LED anfängt zu blinken. error.nanoleaf.controller.invalidData = Pairing fehlgeschlagen. Ungültige Daten vom Controller empfangen. error.nanoleaf.controller.noTokenReceived = Pairing fehlgeschlagen. Kein Authentifizierungstoken empfangen. diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/thing/lightpanels.xml b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/thing/lightpanels.xml index a8a7f33d80..5aa026613d 100644 --- a/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/thing/lightpanels.xml +++ b/bundles/org.openhab.binding.nanoleaf/src/main/resources/OH-INF/thing/lightpanels.xml @@ -9,16 +9,14 @@ @text/thing-type.nanoleaf.controller.description - - - - + + + - @@ -40,9 +38,8 @@ @text/thing-type.nanoleaf.lightpanel.description - - - + + id @@ -50,35 +47,13 @@ - - Switch - - @text/channel-type.nanoleaf.power.description - Switch - - - - - - - - - - Dimmer - - @text/channel-type.nanoleaf.colorTemperature.description - - - - - Number - - @text/channel-type.nanoleaf.colorTemperatureAbs.description - ColorLight - + + String + + @text/channel-type.nanoleaf.effect.description - + String @text/channel-type.nanoleaf.colorMode.description @@ -91,62 +66,30 @@ - - Color - - @text/Color of the Light Panels - - - - String - - @text/channel-type.nanoleaf.effect.description - - - + Switch @text/channel-type.nanoleaf.rhythmState.description - + Switch @text/channel-type.nanoleaf.rhythmActive.description - + Number @text/channel-type.nanoleaf.rhythmMode.description - + - - Switch - - @text/channel-type.nanoleaf.singleTap.description - - - - - Switch - - @text/channel-type.nanoleaf.doubleTap.description - - - - - Switch - - @text/channel-type.nanoleaf.panelLayout.description - - -- 2.47.3