[deconz] Cross update color & color temperature channels (#18482)

* [deconz] improve color / color temperature handling

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
pull/18674/head
Andrew Fiddian-Green 2025-05-12 19:21:40 +01:00 committed by GitHub
parent fcec2e1720
commit 7749bbbbed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 434 additions and 41 deletions

View File

@ -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));
}
}
}

View File

@ -10,6 +10,7 @@
</supported-bridge-type-refs>
<label>Light Group</label>
<category>Lightbulb</category>
<semantic-equipment-tag>Zone</semantic-equipment-tag>
<channels>
<channel typeId="all_on" id="all_on"/>
<channel typeId="any_on" id="any_on"/>

View File

@ -69,9 +69,15 @@
<semantic-equipment-tag>LightSource</semantic-equipment-tag>
<channels>
<channel typeId="system.brightness" id="brightness"/>
<channel typeId="ontime" id="ontime"/>
<channel typeId="alert" id="alert"/>
<channel typeId="system.brightness" id="brightness">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="ontime" id="ontime">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="alert" id="alert">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
</channels>
<properties>
@ -93,10 +99,18 @@
<semantic-equipment-tag>LightSource</semantic-equipment-tag>
<channels>
<channel typeId="system.brightness" id="brightness"/>
<channel typeId="system.color-temperature-abs" id="color_temperature"/>
<channel typeId="ontime" id="ontime"/>
<channel typeId="alert" id="alert"/>
<channel typeId="system.brightness" id="brightness">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="system.color-temperature-abs" id="color_temperature">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="ontime" id="ontime">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="alert" id="alert">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
</channels>
<properties>
@ -118,9 +132,15 @@
<semantic-equipment-tag>LightSource</semantic-equipment-tag>
<channels>
<channel typeId="system.color" id="color"/>
<channel typeId="ontime" id="ontime"/>
<channel typeId="alert" id="alert"/>
<channel typeId="system.color" id="color">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="ontime" id="ontime">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="alert" id="alert">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
</channels>
<properties>
@ -142,10 +162,18 @@
<semantic-equipment-tag>LightSource</semantic-equipment-tag>
<channels>
<channel typeId="system.color" id="color"/>
<channel typeId="system.color-temperature-abs" id="color_temperature"/>
<channel typeId="ontime" id="ontime"/>
<channel typeId="alert" id="alert"/>
<channel typeId="system.color" id="color">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="system.color-temperature-abs" id="color_temperature">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="ontime" id="ontime">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
<channel typeId="alert" id="alert">
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel>
</channels>
<properties>
@ -181,7 +209,7 @@
<category>Rollershutter</category>
<tags>
<tag>Control</tag>
<tag>OpenState</tag>
<tag>OpenLevel</tag>
</tags>
<state pattern="%.1f %%"/>
</channel-type>
@ -197,7 +225,7 @@
<item-type>String</item-type>
<label>Effect Channel</label>
<tags>
<tag>Status</tag>
<tag>Control</tag>
<tag>Mode</tag>
</tags>
</channel-type>
@ -216,6 +244,10 @@
<item-type>String</item-type>
<label>Alert</label>
<category>Alarm</category>
<tags>
<tag>Control</tag>
<tag>Mode</tag>
</tags>
<command>
<options>
<option value="none">No Alarm</option>
@ -228,6 +260,10 @@
<channel-type id="lock">
<item-type>Switch</item-type>
<label>Lock</label>
<tags>
<tag>Switch</tag>
<tag>OpenState</tag>
</tags>
</channel-type>
</thing:thing-descriptions>

View File

@ -9,6 +9,10 @@
<label>Air Quality</label>
<description>Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor,
...</description>
<tags>
<tag>Status</tag>
<tag>VOC</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -17,6 +21,10 @@
<label>Air Quality (ppb)</label>
<description>Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is
specified in ppb (parts per billion).</description>
<tags>
<tag>Status</tag>
<tag>VOC</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -24,6 +32,9 @@
<item-type>Switch</item-type>
<label>Alarm</label>
<description>Alarm was triggered.</description>
<tags>
<tag>Alarm</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -46,6 +57,10 @@
<item-type>Switch</item-type>
<label>Carbon-monoxide</label>
<description>Carbon-monoxide was detected.</description>
<tags>
<tag>Alarm</tag>
<tag>CO</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -54,6 +69,10 @@
<label>Consumption</label>
<description>Current consumption</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -62,6 +81,10 @@
<label>Current</label>
<description>Current current</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -82,12 +105,20 @@
<channel-type id="externalwindowopen">
<item-type>Contact</item-type>
<label>External Window Open</label>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
</tags>
</channel-type>
<channel-type id="fire">
<item-type>Switch</item-type>
<label>Fire</label>
<description>A fire was detected.</description>
<tags>
<tag>Alarm</tag>
<tag>Smoke</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -122,6 +153,10 @@
<label>Target Temperature</label>
<description>Target temperature</description>
<category>Heating</category>
<tags>
<tag>Setpoint</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.1f %unit%" step="0.5" max="28" min="6"/>
</channel-type>
@ -130,6 +165,10 @@
<label>Humidity</label>
<description>Current humidity</description>
<category>Humidity</category>
<tags>
<tag>Measurement</tag>
<tag>Humidity</tag>
</tags>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
@ -152,6 +191,10 @@
<channel-type id="light">
<item-type>String</item-type>
<label>Lightlevel</label>
<tags>
<tag>Status</tag>
<tag>Illuminance</tag>
</tags>
<state readOnly="true">
<options>
<option value="daylight">Daylight</option>
@ -165,6 +208,10 @@
<item-type>Number:Illuminance</item-type>
<label>Illuminance</label>
<description>Current light illuminance</description>
<tags>
<tag>Measurement</tag>
<tag>Illuminance</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -172,6 +219,10 @@
<item-type>Number</item-type>
<label>Light Level</label>
<description>Current light level.</description>
<tags>
<tag>Measurement</tag>
<tag>Illuminance</tag>
</tags>
<state readOnly="true" pattern="%d"/>
</channel-type>
@ -180,6 +231,10 @@
<label>Locked</label>
<description>Status of this thermostat's child lock.</description>
<category>Lock</category>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
</tags>
</channel-type>
<channel-type id="mode">
@ -187,6 +242,10 @@
<label>Mode</label>
<description>Current mode</description>
<category>Heating</category>
<tags>
<tag>Control</tag>
<tag>Mode</tag>
</tags>
<state>
<options>
<option value="AUTO">auto</option>
@ -200,6 +259,10 @@
<item-type>Number:Dimensionless</item-type>
<label>Moisture</label>
<description>Current moisture</description>
<tags>
<tag>Measurement</tag>
<tag>Moisture</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -213,6 +276,10 @@
<channel-type id="on">
<item-type>Switch</item-type>
<label>Heater State</label>
<tags>
<tag>Switch</tag>
<tag>Heating</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -220,6 +287,10 @@
<item-type>Contact</item-type>
<label>Open/Close</label>
<description>Open/Close detected</description>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -246,6 +317,10 @@
<label>Power</label>
<description>Current power usage</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -254,6 +329,10 @@
<label>Pressure</label>
<description>Current pressure</description>
<category>Pressure</category>
<tags>
<tag>Measurement</tag>
<tag>Pressure</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -261,6 +340,10 @@
<item-type>Switch</item-type>
<label>Tampered</label>
<description>A zone is being tampered.</description>
<tags>
<tag>Alarm</tag>
<tag>Tampered</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -269,6 +352,10 @@
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
@ -289,6 +376,10 @@
<item-type>Number:Dimensionless</item-type>
<label>Valve position</label>
<description>Current valve position</description>
<tags>
<tag>Status</tag>
<tag>OpenLevel</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -296,6 +387,10 @@
<item-type>Switch</item-type>
<label>Vibration</label>
<description>Vibration was detected.</description>
<tags>
<tag>Status</tag>
<tag>Vibration</tag>
</tags>
<state readOnly="true"/>
</channel-type>
@ -311,6 +406,10 @@
<label>Voltage</label>
<description>Current voltage</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
@ -318,12 +417,20 @@
<item-type>Switch</item-type>
<label>Water Leakage</label>
<description>Water leakage detected</description>
<tags>
<tag>Alarm</tag>
<tag>Water</tag>
</tags>
<state readOnly="true"/>
</channel-type>
<channel-type id="windowopen">
<item-type>Contact</item-type>
<label>Window Open</label>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
</tags>
</channel-type>
</thing:thing-descriptions>

View File

@ -7,7 +7,7 @@
<bridge-type id="deconz">
<label>deCONZ</label>
<description>A running deCONZ software instance.</description>
<semantic-equipment-tag>WebService</semantic-equipment-tag>
<semantic-equipment-tag>Application</semantic-equipment-tag>
<representation-property>UDN</representation-property>
<config-description-ref uri="thing-type:deconz:bridge"/>

View File

@ -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<HSBType> {
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

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}