From 38fd7f7bf34e6e2dc017e152b50fdd217143a1ed Mon Sep 17 00:00:00 2001 From: Holger Reichert Date: Fri, 15 Sep 2017 03:18:43 +0200 Subject: [PATCH] [WIP] [Tradfri] Support for new RGB bulbs #4251 Signed-off-by: Holger Reichert --- .../TradfriDiscoveryServiceTest.java | 20 +- .../tradfri/internal/TradfriColorTest.java | 80 ++++++++ .../ESH-INF/i18n/tradfri_de.properties | 6 + .../ESH-INF/thing/thing-types.xml | 29 +++ .../README.md | 15 +- .../tradfri/TradfriBindingConstants.java | 7 +- .../tradfri/handler/TradfriLightHandler.java | 146 +++++++++++--- .../tradfri/internal/TradfriColor.java | 178 ++++++++++++++++++ .../discovery/TradfriDiscoveryService.java | 33 +++- 9 files changed, 473 insertions(+), 41 deletions(-) create mode 100644 extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColorTest.java create mode 100644 extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColor.java diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/discovery/TradfriDiscoveryServiceTest.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/discovery/TradfriDiscoveryServiceTest.java index 71f657f0784..f9cdccae93b 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/discovery/TradfriDiscoveryServiceTest.java +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/discovery/TradfriDiscoveryServiceTest.java @@ -81,9 +81,10 @@ public void cleanUp() { @Test public void correctSupportedTypes() { - assertThat(discovery.getSupportedThingTypes().size(), is(2)); + assertThat(discovery.getSupportedThingTypes().size(), is(3)); assertTrue(discovery.getSupportedThingTypes().contains(TradfriBindingConstants.THING_TYPE_DIMMABLE_LIGHT)); assertTrue(discovery.getSupportedThingTypes().contains(TradfriBindingConstants.THING_TYPE_COLOR_TEMP_LIGHT)); + assertTrue(discovery.getSupportedThingTypes().contains(TradfriBindingConstants.THING_TYPE_COLOR_LIGHT)); } @Test @@ -101,4 +102,21 @@ public void validDiscoveryResult() { assertThat(discoveryResult.getProperties().get(DeviceConfig.ID), is(65537)); assertThat(discoveryResult.getRepresentationProperty(), is(DeviceConfig.ID)); } + + @Test + public void validDiscoveryResultColorLightCWS() { + String json = "{\"9001\":\"TRADFRI bulb E27 CWS opal 600lm\",\"9002\":1505151864,\"9020\":1505433527,\"9003\":65550,\"9019\":1,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 CWS opal 600lm\",\"2\":\"\",\"3\":\"1.3.002\",\"6\":1},\"3311\":[{\"5850\":1,\"5708\":0,\"5851\":254,\"5707\":0,\"5709\":33137,\"5710\":27211,\"5711\":0,\"5706\":\"efd275\",\"9003\":0}]}"; + JsonObject data = new JsonParser().parse(json).getAsJsonObject(); + + discovery.onUpdate("65550", data); + + assertNotNull(discoveryResult); + assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW)); + assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0200:1:65550"))); + assertThat(discoveryResult.getThingTypeUID(), is(TradfriBindingConstants.THING_TYPE_COLOR_LIGHT)); + assertThat(discoveryResult.getBridgeUID(), is(new ThingUID("tradfri:gateway:1"))); + assertThat(discoveryResult.getProperties().get(DeviceConfig.ID), is(65550)); + assertThat(discoveryResult.getRepresentationProperty(), is(DeviceConfig.ID)); + } + } diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColorTest.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColorTest.java new file mode 100644 index 00000000000..027a48ba426 --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri.test/src/test/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColorTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.binding.tradfri.internal; + +import static org.junit.Assert.*; + +import org.eclipse.smarthome.core.library.types.HSBType; +import org.junit.Test; + +/** + * Tests for {@link TradfriColor}. + * + * @author Holger Reichert - Initial contribution + */ +public class TradfriColorTest { + + @Test + public void testFromCieKnownGood1() { + TradfriColor color = TradfriColor.fromCie(29577, 12294, 254); + assertNotNull(color); + assertEquals(255, color.rgbR); + assertEquals(21, color.rgbG); + assertEquals(207, color.rgbB); + assertEquals(29577, color.xyX); + assertEquals(12294, color.xyY); + assertEquals(254, color.brightness); + } + + @Test + public void testFromCieKnownGood2() { + TradfriColor color = TradfriColor.fromCie(19983, 37417, 84); + assertNotNull(color); + assertEquals(110, color.rgbR); + assertEquals(174, color.rgbG); + assertEquals(58, color.rgbB); + assertEquals(19983, color.xyX); + assertEquals(37417, color.xyY); + assertEquals(84, color.brightness); + } + + @Test + public void testFromHSBTypeKnownGood1() { + TradfriColor color = TradfriColor.fromHSBType(HSBType.RED); + assertNotNull(color); + assertEquals(254, color.rgbR); + assertEquals(0, color.rgbG); + assertEquals(0, color.rgbB); + assertEquals(45914, color.xyX); + assertEquals(19615, color.xyY); + assertEquals(254, color.brightness); + } + + @Test + public void testConversionReverse() { + // convert from HSBType + TradfriColor color = TradfriColor.fromHSBType(HSBType.GREEN); + assertNotNull(color); + assertEquals(0, color.rgbR); + assertEquals(254, color.rgbG); // 254 instead of 255 - only approximated calculation + assertEquals(0, color.rgbB); + assertEquals(11299, color.xyX); + assertEquals(48941, color.xyY); + assertEquals(254, color.brightness); + // convert the result again based on the XY values + TradfriColor reverse = TradfriColor.fromCie(color.xyX, color.xyY, color.brightness); + assertNotNull(reverse); + assertEquals(0, reverse.rgbR); + assertEquals(255, reverse.rgbG); // 255 instead of 254 - only approximated calculation + assertEquals(0, reverse.rgbB); + assertEquals(11299, reverse.xyX); + assertEquals(48941, reverse.xyY); + assertEquals(254, reverse.brightness); + } + +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/i18n/tradfri_de.properties b/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/i18n/tradfri_de.properties index 2eea38a6c40..f35291cc405 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/i18n/tradfri_de.properties +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/i18n/tradfri_de.properties @@ -9,6 +9,8 @@ thing-type.tradfri.0100.label = Dimmbare Lampe (wei thing-type.tradfri.0100.description = Dimmbare Lampe mit fester Farbtemperatur. thing-type.tradfri.0220.label = Farbtemperatur Lampe (weiß) thing-type.tradfri.0220.description = Dimmbare Lampe mit einstellbarer Farbtemperatur. +thing-type.tradfri.0200.label = Farbspektrum Lampe +thing-type.tradfri.0200.description = Dimmbare Lampe mit einstellbarer Farbe und Farbtemperatur. # thing type configuration thing-type.config.tradfri.gateway.host.label = IP-Adresse @@ -21,9 +23,13 @@ thing-type.config.tradfri.0100.id.label = ID der Lampe thing-type.config.tradfri.0100.id.description = ID zur Identifikation der Lampe. thing-type.config.tradfri.0220.id.label = ID der Lampe thing-type.config.tradfri.0220.id.description = ID zur Identifikation der Lampe. +thing-type.config.tradfri.0200.id.label = ID der Lampe +thing-type.config.tradfri.0200.id.description = ID zur Identifikation der Lampe. # channel types channel-type.tradfri.brightness.label = Helligkeit channel-type.tradfri.brightness.description = Ermöglicht die Steuerung der Helligkeit. Ermöglicht ebenfalls die Lampe ein- und auszuschalten. channel-type.tradfri.color_temperature.label = Farbtemperatur channel-type.tradfri.color_temperature.description = Ermöglicht die Steuerung der Farbtemperatur. Von Tageslichtweiß (0) bis Warmweiß (100). +channel-type.tradfri.color.label = Farbe +channel-type.tradfri.color.description = Ermöglicht die Steuerung der Farbe. diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/thing/thing-types.xml b/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/thing/thing-types.xml index 4292bf656b9..a55975b9a70 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/thing/thing-types.xml +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/ESH-INF/thing/thing-types.xml @@ -71,6 +71,28 @@ + + + + + + + A dimmable light that supports full colors and color temperature settings. + + + + + + + + + + + The identifier of the light on the gateway + + + + @@ -108,4 +130,11 @@ ColorLight + + Color + + Control the color of the light. + ColorLight + + diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/README.md b/extensions/binding/org.eclipse.smarthome.binding.tradfri/README.md index f091eb0d95c..ebc0dffccc0 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/README.md +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/README.md @@ -12,7 +12,7 @@ The thing type ids are defined according to the lighting devices defined for Zig |--------------------------|------------------|------------| | Dimmable Light | 0x0100 | 0100 | | Colour Temperature Light | 0x0220 | 0220 | - +| Colour Light | 0x0200 | 0200 | The following matrix lists the capabilities (channels) for each of the supported lighting device types: @@ -20,6 +20,7 @@ The following matrix lists the capabilities (channels) for each of the supported |-------------|:------:|:----------:|:-----:|:-----------------:| | 0100 | X | X | | | | 0220 | X | X | | X | +| 0200 | X | X | X | X | ## Thing Configuration @@ -29,12 +30,15 @@ The devices require only a single (integer) parameter, which is their instance i ## Channels -All devices support the `brightness` channel, while the white spectrum bulbs additionally also support the `color_temperature` channel (refer to the matrix above). +All devices support the `brightness` channel. +The white spectrum bulbs additionally also support the `color_temperature` channel. Full color bulbs additionally also support the `color` channel. +Refer to the matrix above. | Channel Type ID | Item Type | Description | |-------------------|-----------|---------------------------------------------| | brightness | Dimmer | The brightness of the bulb in percent | | color_temperature | Dimmer | color temperature from 0%=cold to 100%=warm | +| color | Color | full color | ## Full Example @@ -43,14 +47,16 @@ demo.things: ``` Bridge tradfri:gateway:mygateway [ host="192.168.0.177", code="EHPW5rIJKyXFgjH3" ] { 0100 myDimmableBulb [ id=65537 ] - 0220 myColorTempBulb [ id=65538 ] + 0220 myColorTempBulb [ id=65538 ] + 0200 myColorBulb [ id=65539 ] } ``` demo.items: ``` -Dimmer Light { channel="tradfri:0100:mygateway:myDimmableBulb:brightness" } +Dimmer Light { channel="tradfri:0100:mygateway:myDimmableBulb:brightness" } +Color ColorLight { channel="tradfri:0200:mygateway:myColorBulb:color" } ``` demo.sitemap: @@ -60,6 +66,7 @@ sitemap demo label="Main Menu" { Frame { Slider item=Light label="Brightness [%.1f %%]" + Colorpicker item=ColorLight } } ``` diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/TradfriBindingConstants.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/TradfriBindingConstants.java index 6692a6ab272..a46260eb5e8 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/TradfriBindingConstants.java +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/TradfriBindingConstants.java @@ -29,10 +29,12 @@ public class TradfriBindingConstants { public final static ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "0100"); public final static ThingTypeUID THING_TYPE_COLOR_TEMP_LIGHT = new ThingTypeUID(BINDING_ID, "0220"); + public final static ThingTypeUID THING_TYPE_COLOR_LIGHT = new ThingTypeUID(BINDING_ID, "0200"); public final static ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "0820"); - public static final Set SUPPORTED_LIGHT_TYPES_UIDS = Collections.unmodifiableSet( - Stream.of(THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_TEMP_LIGHT).collect(Collectors.toSet())); + public static final Set SUPPORTED_LIGHT_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_TEMP_LIGHT, THING_TYPE_COLOR_LIGHT) + .collect(Collectors.toSet())); // Not yet used - included for future support public static final Set SUPPORTED_CONTROLLER_TYPES_UIDS = Collections.singleton(THING_TYPE_DIMMER); @@ -40,6 +42,7 @@ public class TradfriBindingConstants { // List of all Channel IDs public static final String CHANNEL_BRIGHTNESS = "brightness"; public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature"; + public static final String CHANNEL_COLOR = "color"; // IPSO Objects public static final String DEVICES = "15001"; diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/handler/TradfriLightHandler.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/handler/TradfriLightHandler.java index 1056387a274..4fd0e291920 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/handler/TradfriLightHandler.java +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/handler/TradfriLightHandler.java @@ -16,6 +16,8 @@ import org.eclipse.smarthome.binding.tradfri.DeviceConfig; import org.eclipse.smarthome.binding.tradfri.internal.CoapCallback; import org.eclipse.smarthome.binding.tradfri.internal.TradfriCoapClient; +import org.eclipse.smarthome.binding.tradfri.internal.TradfriColor; +import org.eclipse.smarthome.core.library.types.HSBType; import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.PercentType; @@ -41,6 +43,7 @@ * individual lights. * * @author Kai Kreuzer - Initial contribution + * @author Holger Reichert - Support for color bulbs */ public class TradfriLightHandler extends BaseThingHandler implements CoapCallback { @@ -115,6 +118,9 @@ public void onUpdate(JsonElement data) { if (!state.getOnOffState()) { logger.debug("Setting state to OFF"); updateState(CHANNEL_BRIGHTNESS, PercentType.ZERO); + if (lightHasColorSupport()) { + updateState(CHANNEL_COLOR, HSBType.BLACK); + } // if we are turned off, we do not set any brightness value return; } @@ -128,27 +134,36 @@ public void onUpdate(JsonElement data) { if (colorTemp != null) { updateState(CHANNEL_COLOR_TEMPERATURE, colorTemp); } - + + HSBType color = null; + if (lightHasColorSupport()) { + color = state.getColor(); + if (color != null) { + updateState(CHANNEL_COLOR, color); + } + } + String devicefirmware = state.getFirmwareVersion(); if (devicefirmware != null) { getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, devicefirmware); } - + String modelId = state.getModelId(); if (modelId != null) { getThing().setProperty(Thing.PROPERTY_MODEL_ID, modelId); } - + String vendor = state.getVendor(); if (vendor != null) { getThing().setProperty(Thing.PROPERTY_VENDOR, vendor); } - - logger.debug("Updating thing for lightId {} to state {dimmer: {}, colorTemp: {}, devicefirmware: {}, modelId: {}, vendor: {}}" - , state.getLightId(), dimmer, colorTemp, devicefirmware, modelId, vendor); + + logger.debug( + "Updating thing for lightId {} to state {dimmer: {}, colorTemp: {}, color: {}, devicefirmware: {}, modelId: {}, vendor: {}}", + state.getLightId(), dimmer, colorTemp, color, devicefirmware, modelId, vendor); } } - + @Override public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { super.bridgeStatusChanged(bridgeStatusInfo); @@ -183,6 +198,12 @@ private void setColorTemperature(PercentType percent) { set(data.getJsonString()); } + private void setColor(HSBType hsb) { + LightData data = new LightData(); + data.setColor(hsb).setTransitionTime(DEFAULT_DIMMER_TRANSITION_TIME); + set(data.getJsonString()); + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { @@ -198,6 +219,9 @@ public void handleCommand(ChannelUID channelUID, Command command) { case CHANNEL_COLOR_TEMPERATURE: handleColorTemperatureCommand(command); break; + case CHANNEL_COLOR: + handleColorCommand(command); + break; default: logger.error("Unknown channel UID {}", channelUID); } @@ -243,6 +267,46 @@ private void handleColorTemperatureCommand(Command command) { } } + private void handleColorCommand(Command command) { + if (command instanceof HSBType) { + // tradfri can not handle color (xy) and brightness at once, + // so send color first and schedule the brightess update. + // 1500ms wait time because brightness setting would interrupt color fading + setColor((HSBType) command); + scheduler.schedule(() -> { + setBrightness(((HSBType) command).getBrightness()); + }, 1500, TimeUnit.MILLISECONDS); + } else if (command instanceof OnOffType) { + setState(((OnOffType) command)); + } else if (command instanceof PercentType) { + // PaperUI sends PercentType on color channel when changing Brightness + setBrightness((PercentType) command); + } else if (command instanceof IncreaseDecreaseType) { + // increase or decrease only the brightness, but keep color + if (state != null && state.getBrightness() != null) { + int current = state.getBrightness().intValue(); + if (IncreaseDecreaseType.INCREASE.equals(command)) { + setBrightness(new PercentType(Math.min(current + STEP, PercentType.HUNDRED.intValue()))); + } else { + setBrightness(new PercentType(Math.max(current - STEP, PercentType.ZERO.intValue()))); + } + } else { + logger.debug("Cannot handle inc/dec for color as current brightness is not known."); + } + } else { + logger.debug("Can't handle command {} on channel {}", command, CHANNEL_COLOR); + } + } + + /** + * Checks if this light supports full color. + * + * @return true if the light supports full color + */ + private boolean lightHasColorSupport() { + return thing.getThingTypeUID() == THING_TYPE_COLOR_LIGHT; + } + /** * This class is a Java wrapper for the raw JSON data about the light state. */ @@ -267,7 +331,7 @@ public LightData() { attributes = new JsonObject(); array.add(attributes); root.add(LIGHT, array); - + generalInfo = new JsonObject(); root.add(DEVICE, generalInfo); } @@ -347,24 +411,58 @@ public LightData setColorTemperature(PercentType c) { } PercentType getColorTemperature() { - // we only need to check one of the coordinates and figure out where between the presets we are - JsonElement colorX = attributes.get(COLOR_X); - if (colorX != null) { - double x = colorX.getAsInt(); - double value = 0.0; - if (x > X[1]) { - // is it between preset 1 and 2? - value = (x - X[1]) / (X[2] - X[1]) / 2.0 + 0.5; + // XXX TODO the current color temperature calculation does not work with color lights, will be fixed in + // final PR + try { + // we only need to check one of the coordinates and figure out where between the presets we are + JsonElement colorX = attributes.get(COLOR_X); + /// Y mit auswerten XXX + if (colorX != null) { + double x = colorX.getAsInt(); + double value = 0.0; + if (x > X[1]) { + // is it between preset 1 and 2? + value = (x - X[1]) / (X[2] - X[1]) / 2.0 + 0.5; + } else { + // it is between preset 0 and 1 + value = (x - X[0]) / (X[1] - X[0]) / 2.0; + } + // fallback: xyz to kelvin XXX + return new PercentType((int) Math.round(value * 100.0)); } else { - // it is between preset 0 and 1 - value = (x - X[0]) / (X[1] - X[0]) / 2.0; + return null; } - return new PercentType((int) Math.round(value * 100.0)); - } else { + } catch (Exception e) { + // XXX currently only stupid workaround, will be fixed in final PR return null; } } + public LightData setColor(HSBType hsb) { + // construct new HSBType with full brightness and extract XY color values from it + HSBType hsbFullBright = new HSBType(hsb.getHue(), hsb.getSaturation(), PercentType.HUNDRED); + TradfriColor color = TradfriColor.fromHSBType(hsbFullBright); + attributes.add(COLOR_X, new JsonPrimitive(color.xyX)); + attributes.add(COLOR_Y, new JsonPrimitive(color.xyY)); + return this; + } + + public HSBType getColor() { + // XY color coordinates plus brightness is needed for color calculation + JsonElement colorX = attributes.get(COLOR_X); + JsonElement colorY = attributes.get(COLOR_Y); + JsonElement dimmer = attributes.get(DIMMER); + if (colorX != null && colorY != null && dimmer != null) { + int x = colorX.getAsInt(); + int y = colorY.getAsInt(); + int brightness = dimmer.getAsInt(); + // extract HSBType from converted xy/brightness + TradfriColor color = TradfriColor.fromCie(x, y, brightness); + return HSBType.fromRGB(color.rgbR, color.rgbG, color.rgbB); + } + return null; + } + LightData setOnOffState(boolean on) { attributes.add(ONOFF, new JsonPrimitive(on ? 1 : 0)); return this; @@ -378,11 +476,11 @@ boolean getOnOffState() { return false; } } - + Integer getLightId() { return root.get(INSTANCE_ID).getAsInt(); } - + String getFirmwareVersion() { if (generalInfo.get(DEVICE_FIRMWARE) != null) { return generalInfo.get(DEVICE_FIRMWARE).getAsString(); @@ -390,7 +488,7 @@ String getFirmwareVersion() { return null; } } - + String getModelId() { if (generalInfo.get(DEVICE_MODEL) != null) { return generalInfo.get(DEVICE_MODEL).getAsString(); @@ -398,7 +496,7 @@ String getModelId() { return null; } } - + String getVendor() { if (generalInfo.get(DEVICE_VENDOR) != null) { return generalInfo.get(DEVICE_VENDOR).getAsString(); diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColor.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColor.java new file mode 100644 index 00000000000..25e013ae8bb --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/TradfriColor.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.binding.tradfri.internal; + +import org.eclipse.smarthome.core.library.types.HSBType; + +/** + * The {@link TradfriColor} is used for conversion between color formats. + * Use the static methods {@link TradfriColor#fromCie(int, int, int)} and {@link TradfriColor#fromHSBType(HSBType)} for + * construction. + * + * @author Holger Reichert - Initial contribution + * + */ +public class TradfriColor { + + /** + * RGB color values in the range 0 to 255. + */ + public int rgbR, rgbG, rgbB; + + /** + * CIE XY color values in the tradfri range 0 to 65535. + */ + public int xyX, xyY; + + /** + * Brightness level in the tradfri range 0 to 254. + */ + public int brightness; + + /** + * Private constructor based on all fields. + * + * @param rgbR RGB red value 0 to 255 + * @param rgbG RGB green value 0 to 255 + * @param rgbB RGB blue value 0 to 255 + * @param xyX CIE x value 0 to 65535 + * @param xyY CIE y value 0 to 65535 + * @param brightness brightness level 0 to 254 + */ + private TradfriColor(int rgbR, int rgbG, int rgbB, int xyX, int xyY, int brightness) { + super(); + this.rgbR = rgbR; + this.rgbG = rgbG; + this.rgbB = rgbB; + this.xyX = xyX; + this.xyY = xyY; + this.brightness = brightness; + } + + /** + * Construct from CIE XY values in the tradfri range. + * + * @param xyX x value 0 to 65535 + * @param xyY y value 0 to 65535 + * @param xyBrightness brightness from 0 to 254 + * @return {@link TradfriColor} object with converted color spaces + */ + public static TradfriColor fromCie(int xyX, int xyY, int xyBrightness) { + + // maximum brightness limited to 254 + int brightness = xyBrightness; + if (brightness > 254) { + brightness = 254; + } + + double x = unnormalize(xyX); + double y = unnormalize(xyY); + + // calculate XYZ using xy and brightness + double z = 1.0 - x - y; + double Y = (brightness / 254.0); + double X = (Y / y) * x; + double Z = (Y / y) * z; + + // Wide RGB D65 conversion + // math inspiration: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + double red = X * 1.656492 - Y * 0.354851 - Z * 0.255038; + double green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; + double blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530; + + // cap all values to 1.0 maximum + if (red > blue && red > green && red > 1.0) { + green = green / red; + blue = blue / red; + red = 1.0; + } else if (green > blue && green > red && green > 1.0) { + red = red / green; + blue = blue / green; + green = 1.0; + } else if (blue > red && blue > green && blue > 1.0) { + red = red / blue; + green = green / blue; + blue = 1.0; + } + + // gamma correction + red = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055; + green = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055; + blue = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055; + + // target range 0 to 255 + int rgbR = (int) Math.round(red * 255.0); + int rgbG = (int) Math.round(green * 255.0); + int rgbB = (int) Math.round(blue * 255.0); + + return new TradfriColor(rgbR, rgbG, rgbB, xyX, xyY, brightness); + + } + + /** + * Construct from {@link HSBType}. + * + * @param hsbType {@link HSBType} + * @return {@link TradfriColor} object with converted color spaces + */ + public static TradfriColor fromHSBType(HSBType hsbType) { + + // hsbType gives 0 to 100, we need 0.0 to 255.0 + double red = hsbType.getRed().intValue() * 2.55; + double green = hsbType.getGreen().intValue() * 2.55; + double blue = hsbType.getBlue().intValue() * 2.55; + + // saved for later use in constructor call + int rgbR = (int) red; + int rgbG = (int) green; + int rgbB = (int) blue; + + // gamma correction + red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); + green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92); + blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92); + + // Wide RGB D65 conversion + // math inspiration: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + double X = red * 0.664511 + green * 0.154324 + blue * 0.162028; + double Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; + double Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; + + // calculate the xy values from XYZ + double x = (X / (X + Y + Z)); + double y = (Y / (X + Y + Z)); + + int xyX = normalize(x); + int xyY = normalize(y); + int brightness = (int) (hsbType.getBrightness().intValue() * 2.54); + + return new TradfriColor(rgbR, rgbG, rgbB, xyX, xyY, brightness); + + } + + /** + * Normalize value to the tradfri range. + * + * @param value double in the range 0.0 to 1.0 + * @return normalized value in the range 0 to 65535 + */ + public static int normalize(double value) { + return (int) (value * 65535 + 0.5); + } + + /** + * Reverse-normalize value from the tradfri range. + * + * @param value integer in the range 0 to 65535 + * @return unnormalized value in the range 0.0 to 1.0 + */ + public static double unnormalize(int value) { + return (value / 65535.0); + } + +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/discovery/TradfriDiscoveryService.java b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/discovery/TradfriDiscoveryService.java index cd08746fdd9..78bfeecf54a 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/discovery/TradfriDiscoveryService.java +++ b/extensions/binding/org.eclipse.smarthome.binding.tradfri/src/main/java/org/eclipse/smarthome/binding/tradfri/internal/discovery/TradfriDiscoveryService.java @@ -20,6 +20,7 @@ import org.eclipse.smarthome.config.discovery.DiscoveryResult; import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,13 +35,15 @@ */ public class TradfriDiscoveryService extends AbstractDiscoveryService implements DeviceUpdateListener { - private Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class); + private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class); - private TradfriGatewayHandler handler; + private final TradfriGatewayHandler handler; - private String[] COLOR_TEMP_MODELS = new String[] { "TRADFRI bulb E27 WS opal 980lm", + private final String[] COLOR_TEMP_MODELS = new String[] { "TRADFRI bulb E27 WS opal 980lm", "TRADFRI bulb GU10 WS 400lm" }; + private final String COLOR_MODELS_IDENTIFIER = "CWS"; + public TradfriDiscoveryService(TradfriGatewayHandler bridgeHandler) { super(TradfriBindingConstants.SUPPORTED_LIGHT_TYPES_UIDS, 10, true); this.handler = bridgeHandler; @@ -75,15 +78,25 @@ public void onUpdate(String instanceId, JsonObject data) { if (type.equals(TYPE_LIGHT) && data.has(LIGHT)) { JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject(); - // Color temperature light + // Color temperature light: // We do not always receive a COLOR attribute, even the light supports it - but the gateway does not // seem to have this information, if the bulb is unreachable. We therefore also check against // concrete model names. - if (state.has(COLOR) || (model != null && Arrays.asList(COLOR_TEMP_MODELS).contains(model))) { - thingId = new ThingUID(THING_TYPE_COLOR_TEMP_LIGHT, bridge, Integer.toString(id)); - } else { - thingId = new ThingUID(THING_TYPE_DIMMABLE_LIGHT, bridge, Integer.toString(id)); + // Color light: + // As the protocol does not distinguishes between color and full-color lights, + // we check if the "CWS" identifier is given in the model name + ThingTypeUID thingType = null; + if (model != null && model.contains(COLOR_MODELS_IDENTIFIER)) { + thingType = THING_TYPE_COLOR_LIGHT; + } + if (thingType == null && // + (state.has(COLOR) || (model != null && Arrays.asList(COLOR_TEMP_MODELS).contains(model)))) { + thingType = THING_TYPE_COLOR_TEMP_LIGHT; + } + if (thingType == null) { + thingType = THING_TYPE_DIMMABLE_LIGHT; } + thingId = new ThingUID(thingType, bridge, Integer.toString(id)); } if (thingId == null) { @@ -96,14 +109,14 @@ public void onUpdate(String instanceId, JsonObject data) { Map properties = new HashMap<>(1); properties.put("id", id); properties.put(Thing.PROPERTY_MODEL_ID, model); - + if (deviceInfo.get(DEVICE_VENDOR) != null) { properties.put(Thing.PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString()); } if (deviceInfo.get(DEVICE_FIRMWARE) != null) { properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString()); } - + logger.debug("Adding device {} to inbox", thingId); DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingId).withBridge(bridge) .withLabel(label).withProperties(properties).withRepresentationProperty("id").build();