diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java index 0f9b0bf2678..3cd2a1aab7e 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java @@ -54,6 +54,7 @@ import org.openhab.core.types.CommandOption; import org.openhab.core.types.RefreshType; import org.openhab.core.types.StateDescriptionFragment; import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.types.UnDefType; import org.openhab.core.util.ColorUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -255,6 +256,11 @@ public class LightThingHandler extends DeconzBaseThingHandler { if (miredQuantity != null) { newLightState.ct = constrainToRange(miredQuantity.intValue(), ctMin, ctMax); newLightState.on = true; + Double transitiontime = config.transitiontime; + if (transitiontime != null) { + // value is in 1/10 seconds + newLightState.transitiontime = (int) Math.round(10 * transitiontime); + } } } case CHANNEL_POSITION -> { @@ -396,18 +402,8 @@ public class LightThingHandler extends DeconzBaseThingHandler { case CHANNEL_SWITCH, CHANNEL_LOCK -> updateSwitchChannel(channelUID, on); case CHANNEL_COLOR -> updateColorChannel(channelUID, newState); case CHANNEL_BRIGHTNESS -> updatePercentTypeChannel(channelUID, newState.bri, newState.on); - case CHANNEL_COLOR_TEMPERATURE -> { - Integer ct = newState.ct; - if (ct != null && ct >= ctMin && ct <= ctMax) { - updateState(channelUID, new DecimalType(miredToKelvin(ct))); - } - } - case CHANNEL_POSITION -> { - Integer lift = newState.lift; - if (lift != null) { - updateState(channelUID, new PercentType(lift)); - } - } + case CHANNEL_COLOR_TEMPERATURE -> updateColorTemperatureChannel(channelUID, newState); + case CHANNEL_POSITION -> updatePosition(channelUID, newState); case CHANNEL_EFFECT -> updateStringChannel(channelUID, newState.effect); case CHANNEL_EFFECT_SPEED -> updateDecimalTypeChannel(channelUID, newState.effectSpeed); } @@ -443,26 +439,85 @@ public class LightThingHandler extends DeconzBaseThingHandler { } } + /** + * Update the given {@link ChannelUID} depending on the given {@link LightState}. If the 'colorMode' is "xy" + * then update the channel with an {@link HSBType} built from the given CIE XY co-ordinates and brightness, + * otherwise if the 'colorMode' is not "ct" then update the channel with an {@link HSBType} from the given hue, + * saturation and brightness. In either case if the 'on' field is false then the brightness is set to zero. + * Furthermore if the color channel has been updated then cross-update the color temperature channel (if any) + * to {@link UnDefType.UNDEF} as well. + * + * @param channelUID the UID of the channel being updated. + * @param newState the new {@link LightState} + */ private void updateColorChannel(ChannelUID channelUID, LightState newState) { Boolean on = newState.on; Integer bri = newState.bri; Integer hue = newState.hue; Integer sat = newState.sat; + boolean ctChannelUpdate = false; if (on != null && !on) { - updateState(channelUID, OnOffType.OFF); - } else if (bri != null && "xy".equals(newState.colormode)) { + bri = 0; + } + + if (bri != null && "xy".equals(newState.colormode)) { final double @Nullable [] xy = newState.xy; if (xy != null && xy.length == 2) { - double[] xyY = new double[3]; - xyY[0] = xy[0]; - xyY[1] = xy[1]; - xyY[2] = ((double) bri) / BRIGHTNESS_MAX; - updateState(channelUID, ColorUtil.xyToHsb(xyY)); + HSBType hsX = ColorUtil.xyToHsb(xy); + HSBType hsb = new HSBType(hsX.getHue(), hsX.getSaturation(), toPercentType(bri)); + logger.trace("updateColorChannel(xy) channelUID:{}, hsb:{}", channelUID, hsb); + updateState(channelUID, hsb); + ctChannelUpdate = true; } - } else if (bri != null && hue != null && sat != null) { - updateState(channelUID, - new HSBType(new DecimalType(hue / HUE_FACTOR), toPercentType(sat), toPercentType(bri))); + } else if (bri != null && !"ct".equals(newState.colormode) && hue != null && sat != null) { + HSBType hsb = new HSBType(new DecimalType(hue / HUE_FACTOR), toPercentType(sat), toPercentType(bri)); + logger.trace("updateColorChannel(hsb) channelUID:{}, hsb:{}", channelUID, hsb); + updateState(channelUID, hsb); + ctChannelUpdate = true; + } + + // cross-update the color temperature channel (if any) + if (ctChannelUpdate && thing.getChannel(CHANNEL_COLOR_TEMPERATURE) instanceof Channel ctChannel) { + logger.trace("updateColorTemperatureChannel() channelUID:{}, ct:UNDEF", ctChannel.getUID()); + updateState(ctChannel.getUID(), UnDefType.UNDEF); + } + } + + /** + * Update the given {@link ChannelUID} depending on the given {@link LightState}. If the 'colorMode' is "ct" and + * there is a 'ct' value (in mired) then convert it to Kelvin and update the channel. If the color temperature + * channel state has been updated then cross-update the color channel (if any) state to an {@link HSBType} that + * matches the given Kelvin value on the "Planckian Locus" on the CIE color chart as well. + * + * @param channelUID the UID of the channel being updated. + * @param newState the new {@link LightState} + */ + private void updateColorTemperatureChannel(ChannelUID channelUID, LightState newState) { + Integer ct = newState.ct; + String colorMode = newState.colormode; + if ((colorMode == null || "ct".equals(colorMode)) && ct != null && ct >= ctMin && ct <= ctMax) { + int kelvin = miredToKelvin(ct); + logger.trace("updateColorTemperatureChannel() channelUID:{}, kelvin:{}", channelUID, kelvin); + updateState(channelUID, QuantityType.valueOf(kelvin, Units.KELVIN)); + + // cross-update the color channel (if any) + if (thing.getChannel(CHANNEL_COLOR) instanceof Channel colChannel) { + int brightness = !Boolean.TRUE.equals(newState.on) ? 0 : newState.bri instanceof Integer bri ? bri : -1; + if (brightness >= 0) { + HSBType hsX = ColorUtil.xyToHsb(ColorUtil.kelvinToXY(kelvin)); + HSBType hsb = new HSBType(hsX.getHue(), hsX.getSaturation(), toPercentType(brightness)); + logger.trace("updateColorChannel() channelUID:{}, hsb:{}", colChannel.getUID(), hsb); + updateState(colChannel.getUID(), hsb); + } + } + } + } + + private void updatePosition(ChannelUID channelUID, LightState newState) { + Integer lift = newState.lift; + if (lift != null) { + updateState(channelUID, new PercentType(lift)); } } } diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/group-thing-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/group-thing-types.xml index 87adc835222..06dba8331ac 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/group-thing-types.xml +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/group-thing-types.xml @@ -10,6 +10,7 @@ Lightbulb + Zone diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/light-thing-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/light-thing-types.xml index 4e4a0442ecf..0f408dd948d 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/light-thing-types.xml +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/light-thing-types.xml @@ -69,9 +69,15 @@ LightSource - - - + + veto + + + veto + + + veto + @@ -93,10 +99,18 @@ LightSource - - - - + + veto + + + veto + + + veto + + + veto + @@ -118,9 +132,15 @@ LightSource - - - + + veto + + + veto + + + veto + @@ -142,10 +162,18 @@ LightSource - - - - + + veto + + + veto + + + veto + + + veto + @@ -181,7 +209,7 @@ Rollershutter Control - OpenState + OpenLevel @@ -197,7 +225,7 @@ String - Status + Control Mode @@ -216,6 +244,10 @@ String Alarm + + Control + Mode + @@ -228,6 +260,10 @@ Switch + + Switch + OpenState + diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml index 035187402ab..c95f8d404c4 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml @@ -9,6 +9,10 @@ Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor, ... + + Status + VOC + @@ -17,6 +21,10 @@ Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is specified in ppb (parts per billion). + + Status + VOC + @@ -24,6 +32,9 @@ Switch Alarm was triggered. + + Alarm + @@ -46,6 +57,10 @@ Switch Carbon-monoxide was detected. + + Alarm + CO + @@ -54,6 +69,10 @@ Current consumption Energy + + Measurement + Energy + @@ -62,6 +81,10 @@ Current current Energy + + Measurement + Current + @@ -82,12 +105,20 @@ Contact + + Status + OpenState + Switch A fire was detected. + + Alarm + Smoke + @@ -122,6 +153,10 @@ Target temperature Heating + + Setpoint + Temperature + @@ -130,6 +165,10 @@ Current humidity Humidity + + Measurement + Humidity + @@ -152,6 +191,10 @@ String + + Status + Illuminance + @@ -165,6 +208,10 @@ Number:Illuminance Current light illuminance + + Measurement + Illuminance + @@ -172,6 +219,10 @@ Number Current light level. + + Measurement + Illuminance + @@ -180,6 +231,10 @@ Status of this thermostat's child lock. Lock + + Status + OpenState + @@ -187,6 +242,10 @@ Current mode Heating + + Control + Mode + @@ -200,6 +259,10 @@ Number:Dimensionless Current moisture + + Measurement + Moisture + @@ -213,6 +276,10 @@ Switch + + Switch + Heating + @@ -220,6 +287,10 @@ Contact Open/Close detected + + Status + OpenState + @@ -246,6 +317,10 @@ Current power usage Energy + + Measurement + Power + @@ -254,6 +329,10 @@ Current pressure Pressure + + Measurement + Pressure + @@ -261,6 +340,10 @@ Switch A zone is being tampered. + + Alarm + Tampered + @@ -269,6 +352,10 @@ Current temperature Temperature + + Measurement + Temperature + @@ -289,6 +376,10 @@ Number:Dimensionless Current valve position + + Status + OpenLevel + @@ -296,6 +387,10 @@ Switch Vibration was detected. + + Status + Vibration + @@ -311,6 +406,10 @@ Current voltage Energy + + Measurement + Voltage + @@ -318,12 +417,20 @@ Switch Water leakage detected + + Alarm + Water + Contact + + Status + OpenState + diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/thing-types.xml index 09292a8a7e8..5e87dab2d82 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/thing-types.xml @@ -7,7 +7,7 @@ A running deCONZ software instance. - WebService + Application UDN diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java index 5a914f8b758..f8abdda4eb7 100644 --- a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java @@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -37,8 +38,10 @@ import org.openhab.binding.deconz.internal.types.LightType; import org.openhab.binding.deconz.internal.types.LightTypeDeserializer; import org.openhab.binding.deconz.internal.types.ThermostatMode; import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter; -import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -46,6 +49,7 @@ import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.ThingHandlerCallback; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.types.UnDefType; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -64,6 +68,22 @@ public class LightsTest { private @Mock @NonNullByDefault({}) DeconzDynamicStateDescriptionProvider stateDescriptionProvider; private @Mock @NonNullByDefault({}) DeconzDynamicCommandDescriptionProvider commandDescriptionProvider; + /** + * Custom Mockito {@link ArgumentMatcher} to compare the closeness of two {@link HSBType} values. + */ + private static class CloseToHSBType implements ArgumentMatcher { + private HSBType target; + + public CloseToHSBType(HSBType target) { + this.target = target; + } + + @Override + public boolean matches(HSBType source) { + return source.closeTo(target, 0.02); + } + } + @BeforeEach public void initialize() { GsonBuilder gsonBuilder = new GsonBuilder(); @@ -72,6 +92,75 @@ public class LightsTest { gson = gsonBuilder.create(); } + @Test + public void extColorTemperatureLightUpdateHSBTest() throws IOException { + LightMessage lightMessage = DeconzTest.getObjectFromJson("extended_hsb.json", LightMessage.class, gson); + assertNotNull(lightMessage); + + ThingUID thingUID = new ThingUID("deconz", "light"); + ChannelUID channelUIDColor = new ChannelUID(thingUID, CHANNEL_COLOR); + ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE); + + Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID) + .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1")) + .withChannel(ChannelBuilder.create(channelUIDColor, "Color").build()) + .withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build(); + LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider, + commandDescriptionProvider); + lightThingHandler.setCallback(thingHandlerCallback); + + lightThingHandler.messageReceived(lightMessage); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDColor), eq(new HSBType("0,50,100"))); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), eq(UnDefType.UNDEF)); + } + + @Test + public void extColorTemperatureLightUpdateXYTest() throws IOException { + LightMessage lightMessage = DeconzTest.getObjectFromJson("extended_xy.json", LightMessage.class, gson); + assertNotNull(lightMessage); + + ThingUID thingUID = new ThingUID("deconz", "light"); + ChannelUID channelUIDColor = new ChannelUID(thingUID, CHANNEL_COLOR); + ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE); + + Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID) + .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1")) + .withChannel(ChannelBuilder.create(channelUIDColor, "Color").build()) + .withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build(); + LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider, + commandDescriptionProvider); + lightThingHandler.setCallback(thingHandlerCallback); + + lightThingHandler.messageReceived(lightMessage); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDColor), + argThat(new CloseToHSBType(new HSBType("357,100,50")))); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), eq(UnDefType.UNDEF)); + } + + @Test + public void extColorTemperatureLightUpdateCTTest() throws IOException { + LightMessage lightMessage = DeconzTest.getObjectFromJson("extended_ct.json", LightMessage.class, gson); + assertNotNull(lightMessage); + + ThingUID thingUID = new ThingUID("deconz", "light"); + ChannelUID channelUIDColor = new ChannelUID(thingUID, CHANNEL_COLOR); + ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE); + + Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID) + .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1")) + .withChannel(ChannelBuilder.create(channelUIDColor, "Color").build()) + .withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build(); + LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider, + commandDescriptionProvider); + lightThingHandler.setCallback(thingHandlerCallback); + + lightThingHandler.messageReceived(lightMessage); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDColor), + argThat(new CloseToHSBType(new HSBType("43,26,50")))); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), + eq(QuantityType.valueOf(4000, Units.KELVIN))); + } + @Test public void colorTemperatureLightUpdateTest() throws IOException { LightMessage lightMessage = DeconzTest.getObjectFromJson("colortemperature.json", LightMessage.class, gson); @@ -91,7 +180,32 @@ public class LightsTest { lightThingHandler.messageReceived(lightMessage); Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("21"))); - Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), eq(new DecimalType("2500"))); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), + eq(QuantityType.valueOf(2500, Units.KELVIN))); + } + + @Test + public void colorTemperatureSparseLightUpdateTest() throws IOException { + LightMessage lightMessage = DeconzTest.getObjectFromJson("colortemperature-sparse.json", LightMessage.class, + gson); + assertNotNull(lightMessage); + + ThingUID thingUID = new ThingUID("deconz", "light"); + ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS); + ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE); + + Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID) + .withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1")) + .withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build()) + .withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build(); + LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider, + commandDescriptionProvider); + lightThingHandler.setCallback(thingHandlerCallback); + + lightThingHandler.messageReceived(lightMessage); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("21"))); + Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), + eq(QuantityType.valueOf(2500, Units.KELVIN))); } @Test diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/colortemperature-sparse.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/colortemperature-sparse.json new file mode 100644 index 00000000000..86bc43b5f6e --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/colortemperature-sparse.json @@ -0,0 +1,14 @@ +{ + "e": "changed", + "id": "3", + "r": "lights", + "state": { + "alert": null, + "bri": 51, + "ct": 400, + "on": true, + "reachable": true + }, + "t": "event", + "uniqueid": "00:0b:57:ff:fe:eb:2f:84-01" +} diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_ct.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_ct.json new file mode 100644 index 00000000000..a21b66632f5 --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_ct.json @@ -0,0 +1,22 @@ +{ + "e": "changed", + "id": "11", + "r": "lights", + "state": { + "alert": "none", + "bri": 127, + "colormode": "ct", + "ct": 250, + "effect": "none", + "hue": 0, + "on": true, + "reachable": true, + "sat": 125, + "xy": [ + 0.6981, + 0.298 + ] + }, + "t": "event", + "uniqueid": "a4:c1:38:89:fb:f3:c5:a3-0b" +} diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_hsb.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_hsb.json new file mode 100644 index 00000000000..af58e6ad6b2 --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_hsb.json @@ -0,0 +1,22 @@ +{ + "e": "changed", + "id": "11", + "r": "lights", + "state": { + "alert": "none", + "bri": 254, + "colormode": "hs", + "ct": 250, + "effect": "none", + "hue": 0, + "on": true, + "reachable": true, + "sat": 125, + "xy": [ + 0.6981, + 0.298 + ] + }, + "t": "event", + "uniqueid": "a4:c1:38:89:fb:f3:c5:a3-0b" +} diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_xy.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_xy.json new file mode 100644 index 00000000000..e8bc069b2e8 --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/extended_xy.json @@ -0,0 +1,22 @@ +{ + "e": "changed", + "id": "11", + "r": "lights", + "state": { + "alert": "none", + "bri": 127, + "colormode": "xy", + "ct": 250, + "effect": "none", + "hue": 0, + "on": true, + "reachable": true, + "sat": 125, + "xy": [ + 0.6981, + 0.298 + ] + }, + "t": "event", + "uniqueid": "a4:c1:38:89:fb:f3:c5:a3-0b" +}