[nanoleaf] CODEOWNERs, add Shapes Support, beta-firmware support (#10029)

Signed-off-by: Stefan Höhn <stefan@andreaundstefanhoehn.de>
This commit is contained in:
stefan-hoehn 2021-02-03 21:26:13 +01:00 committed by GitHub
parent f9a982e548
commit 570f86d043
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 115 additions and 42 deletions

View File

@ -170,7 +170,7 @@
/bundles/org.openhab.binding.mqtt.homeassistant/ @davidgraeff
/bundles/org.openhab.binding.mqtt.homie/ @davidgraeff
/bundles/org.openhab.binding.mystrom/ @pail23
/bundles/org.openhab.binding.nanoleaf/ @raepple
/bundles/org.openhab.binding.nanoleaf/ @raepple @stefan-hoehn
/bundles/org.openhab.binding.neato/ @jjlauterbach
/bundles/org.openhab.binding.neeo/ @tmrobert8
/bundles/org.openhab.binding.neohub/ @andrewfg

View File

@ -1,6 +1,6 @@
# Nanoleaf Binding
This binding integrates the [Nanoleaf Light Panels](https://nanoleaf.me/en/consumer-led-lighting/products/smarter-series/nanoleaf-light-panels-smarter-kit/).
This binding integrates the [Nanoleaf Light Panels](https://nanoleaf.me/en/consumer-led-lighting/products/smarter-series/nanoleaf-light-panels-smarter-kit/).
![Image](doc/Nanoleaf.jpg)
@ -11,8 +11,10 @@ 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.
Currently Nanoleaf's "Light Panels" and "Canvas" devices are supported.
Note that only the canvas type does support the touch functionality.
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.
@ -21,7 +23,18 @@ With the controller thing you can control channels which affect all panels, e.g.
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 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.
| 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 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.
@ -49,7 +62,7 @@ Tip: if you press (2) just before adding the item from the inbox it usually catc
**Adding the invidual light panels as a thing**
After you have added the controller as a thing and it has been successfully paired as described as above, the individual panels connected to it can be discovered by **starting another scan** for the Nanoleaf binding.
After you have added the controller as a thing and it has been successfully paired as described as above, the individual panels connected to it can be discovered by **starting another scan** for the Nanoleaf binding.
All connected panels will be added as separate things to the inbox.
Troubleshooting: In seldom cases (in particular together with updating the binding) things or items do not work as expected, are offline or may not be detected.
@ -62,7 +75,7 @@ In this case:
**Knowing which panel has which id**
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)
- Set up a switch item with the channel panelLayout on the controller (see NanoRetrieveLayout below) and set the switch to true
@ -73,14 +86,14 @@ Compare the following output with the right picture at the beginning of the arti
```
31413 9162 13276
55836 56093 48111 38724 17870 5164 64279
55836 56093 48111 38724 17870 5164 64279
58086 8134 39755
41451
```
Disclaimer: this works best with square devices and not necessarily well with triangles due to the more geometrically flexible layout.
## Thing Configuration
@ -131,7 +144,7 @@ A lightpanel thing has the following channels:
**color and panelColor**
The color and panelColor channels support full color control with hue, saturation and brightness values.
The color and panelColor 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.
@ -140,7 +153,7 @@ What might not be obvious and even maybe confusing is the fact that brightness a
**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
- 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)
@ -158,7 +171,7 @@ If a panel is tapped the switch is set to ON and automatically reset to OFF afte
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.
- 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.
@ -179,16 +192,16 @@ 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
* the [address="..." ] of the controller
* the [address="..." ] of the controller
* and the [id=123] of the lightpanel
in the bracket to identify the uniqueness of the discovered device. Therefore it is recommended to the give the controller a fixed ip address.
Note: To generate the `authToken`:
* On the Nanoleaf controller, hold the on-off button for 5-7 seconds until the LED starts flashing.
* Send a POST request to the authorization endpoint within 30 seconds of activating pairing, like this:
`http://<address>:16021/api/v1/new`
e.g. via command line `curl --location --request POST 'http://<address>:16021/api/v1/new'`
@ -232,10 +245,10 @@ sitemap nanoleaf label="Nanoleaf"
{
Frame label="Controller" {
Switch item=NanoleafPower
Slider item=NanoleafBrightness
Slider item=NanoleafBrightness
Colorpicker item=NanoleafColor
Text item=NanoleafHue
Text item=NanoleafSaturation
Text item=NanoleafSaturation
Slider item=NanoleafColorTemp
Setpoint item=NanoleafColorTempAbs step=100 minValue=1200 maxValue=6500
Text item=NanoleafColorMode
@ -245,20 +258,20 @@ sitemap nanoleaf label="Nanoleaf"
Selection item=NanoleafRhythmSource mappings=[0="Microphone", 1="Aux"]
Switch item=NanoRetrieveLayout
}
Frame label="Panels" {
Colorpicker item=Panel1Color
Slider item=Panel1Brightness
Colorpicker item=Panel2Color
}
Frame label="Scenes" {
Switch item=NanoleafRainbowScene
}
}
```
Note: The mappings to effects in the selection item are specific for each Nanoleaf installation and should be adapted accordingly.
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
@ -281,7 +294,7 @@ then
var hue = 0
var direction = 1
while(NanoleafRainbowScene.state == ON) {
Thread::sleep(pause)
hue = hue + (5 * direction)

View File

@ -12,6 +12,9 @@
*/
package org.openhab.binding.nanoleaf.internal;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
@ -71,9 +74,12 @@ public class NanoleafBindingConstants {
public static final String API_MIN_FW_VER_LIGHTPANELS = "1.5.0";
public static final String API_MIN_FW_VER_CANVAS = "1.1.0";
public static final String MODEL_ID_LIGHTPANELS = "NL22";
public static final String MODEL_ID_CANVAS = "NL29";
public static final List<String> MODELS_WITH_TOUCHSUPPORT = Arrays.asList("NL29", "NL42");
public static final String DEVICE_TYPE_LIGHTPANELS = "lightPanels";
public static final String DEVICE_TYPE_CANVAS = "canvas";
public static final String DEVICE_TYPE_TOUCHSUPPORT = "canvas"; // we need to keep this enum for backward
// compatibility even though not only canvas type
// support touch
// mDNS discovery service type
// see http://forum.nanoleaf.me/docs/openapi#_gf9l5guxt8r0

View File

@ -49,6 +49,7 @@ public class OpenAPIUtils {
// Regular expression for firmware version
private static final Pattern FIRMWARE_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)");
private static final Pattern FIRMWARE_VERSION_PATTERN_BETA = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)-(\\d+)");
public static Request requestBuilder(HttpClient httpClient, NanoleafControllerConfig controllerConfig,
String apiOperation, HttpMethod method) throws NanoleafException {
@ -162,11 +163,21 @@ public class OpenAPIUtils {
return true;
}
private static int[] getFirmwareVersionNumbers(String firmwareVersion) throws IllegalArgumentException {
public static int[] getFirmwareVersionNumbers(String firmwareVersion) throws IllegalArgumentException {
LOGGER.debug("firmwareVersion: {}", firmwareVersion);
Matcher m = FIRMWARE_VERSION_PATTERN.matcher(firmwareVersion);
if (!m.matches()) {
throw new IllegalArgumentException("Malformed controller firmware version");
if (m.matches()) {
return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
Integer.parseInt(m.group(3)) };
} else {
m = FIRMWARE_VERSION_PATTERN_BETA.matcher(firmwareVersion);
if (m.matches()) {
return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)) };
} else {
throw new IllegalArgumentException("Malformed controller firmware version " + firmwareVersion);
}
}
return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)) };
}
}

View File

@ -75,7 +75,7 @@ public class NanoleafMDNSDiscoveryParticipant implements MDNSDiscoveryParticipan
properties.put(Thing.PROPERTY_MODEL_ID, modelId);
properties.put(Thing.PROPERTY_VENDOR, "Nanoleaf");
String qualifiedName = service.getQualifiedName();
logger.debug("AVR found: {}", qualifiedName);
logger.debug("Device found: {}", qualifiedName);
logger.trace("Discovered nanoleaf host: {} port: {} firmWare: {} modelId: {} qualifiedName: {}", host, port,
firmwareVersion, modelId, qualifiedName);

View File

@ -17,10 +17,7 @@ import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
@ -132,8 +129,8 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
Map<String, String> properties = getThing().getProperties();
String propertyModelId = properties.get(Thing.PROPERTY_MODEL_ID);
if (MODEL_ID_CANVAS.equals(propertyModelId)) {
config.deviceType = DEVICE_TYPE_CANVAS;
if (hasTouchSupport(propertyModelId)) {
config.deviceType = DEVICE_TYPE_TOUCHSUPPORT;
} else {
config.deviceType = DEVICE_TYPE_LIGHTPANELS;
}
@ -334,9 +331,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
private synchronized void startTouchJob() {
NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class);
if (!config.deviceType.equals(DEVICE_TYPE_CANVAS)) {
if (!config.deviceType.equals(DEVICE_TYPE_TOUCHSUPPORT)) {
logger.debug("NOT starting TouchJob for Panel {} because it has wrong device type '{}' vs required '{}'",
this.getThing().getUID(), config.deviceType, DEVICE_TYPE_CANVAS);
this.getThing().getUID(), config.deviceType, DEVICE_TYPE_TOUCHSUPPORT);
return;
} else {
logger.debug("Starting TouchJob for Panel {}", this.getThing().getUID());
@ -353,6 +350,10 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
}
}
private boolean hasTouchSupport(@Nullable String deviceType) {
return (MODELS_WITH_TOUCHSUPPORT.contains(deviceType));
}
private synchronized void stopTouchJob() {
if (touchJob != null && !touchJob.isCancelled()) {
logger.debug("Stop touch job");
@ -636,9 +637,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
Configuration config = editConfiguration();
if (MODEL_ID_CANVAS.equals(controllerInfo.getModel())) {
config.put(NanoleafControllerConfig.DEVICE_TYPE, DEVICE_TYPE_CANVAS);
logger.debug("Set to device type {}", DEVICE_TYPE_CANVAS);
if (hasTouchSupport(controllerInfo.getModel())) {
config.put(NanoleafControllerConfig.DEVICE_TYPE, DEVICE_TYPE_TOUCHSUPPORT);
logger.debug("Set to device type {}", DEVICE_TYPE_TOUCHSUPPORT);
} else {
config.put(NanoleafControllerConfig.DEVICE_TYPE, DEVICE_TYPE_LIGHTPANELS);
logger.debug("Set to device type {}", DEVICE_TYPE_LIGHTPANELS);

View File

@ -318,7 +318,7 @@ public class NanoleafPanelHandler extends BaseThingHandler {
}
}
} catch (NanoleafNotFoundException nfe) {
logger.warn("Panel data could not be retrieved as no data was returned (static type missing?) : {}",
logger.debug("Panel data could not be retrieved as no data was returned (static type missing?) : {}",
nfe.getMessage());
} catch (NanoleafBadRequestException nfe) {
logger.debug(

View File

@ -1,5 +1,5 @@
binding.nanoleaf.name = Nanoleaf Binding
binding.nanoleaf.description = Integrates the Nanoleaf Light Panels (v150320)
binding.nanoleaf.description = Integrates the Nanoleaf Light Panels (v010221)
# thing types
thing-type.nanoleaf.controller.name = Nanoleaf Controller

View File

@ -1,5 +1,5 @@
binding.nanoleaf.name = Nanoleaf Binding
binding.nanoleaf.description = Binding für die Integration des Nanoleaf Light Panels (v150320)
binding.nanoleaf.description = Binding für die Integration des Nanoleaf Light Panels (Ein mit dem Controller verbundenes Paneel)
# thing types
thing-type.nanoleaf.controller.name = Nanoleaf Controller

View File

@ -0,0 +1,42 @@
/**
* 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;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* Test Firmware check
*
* @author Stefan Höhn - Initial contribution
*/
@NonNullByDefault
public class OpenAPUUtilsTest {
@Test
public void testStateOn() {
int[] versions = OpenAPIUtils.getFirmwareVersionNumbers("5.1.2");
assertThat(versions[0], is(5));
assertThat(versions[1], is(1));
assertThat(versions[2], is(2));
int[] versions2 = OpenAPIUtils.getFirmwareVersionNumbers("5.1.2-4");
assertThat(versions2[0], is(5));
assertThat(versions2[1], is(1));
assertThat(versions2[2], is(2));
assertThat(versions2[3], is(4));
}
}