mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mqtt.homeassistant] Implement JSON schema lights (#13808)
* [mqtt.homeassistant] implement JSON schema lights * [mqtt.homeassistant] use enum for current state of color mode * [mqtt.homeassistant] use implicit lambdas * [mqtt.homeassistant] remove string constants in favor of an enum * [mqtt.homeassistant] allow sending ON and brightness commands through bare * [mqtt.homeassistant] turn down debug logging --------- Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
2c710a2a70
commit
f98f820325
@ -39,6 +39,8 @@ import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* A HomeAssistant component is comparable to a channel group.
|
||||
* It has a name and consists of multiple channels.
|
||||
@ -243,4 +245,8 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
public boolean isEnabledByDefault() {
|
||||
return channelConfiguration.isEnabledByDefault();
|
||||
}
|
||||
|
||||
public Gson getGson() {
|
||||
return componentConfiguration.getGson();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* A base class for common elements between JSON schema and template schema lights.
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class AbstractRawSchemaLight extends Light {
|
||||
protected static final String RAW_CHANNEL_ID = "raw";
|
||||
|
||||
protected ComponentChannel rawChannel;
|
||||
|
||||
public AbstractRawSchemaLight(ComponentFactory.ComponentConfiguration builder) {
|
||||
super(builder);
|
||||
hiddenChannels.add(rawChannel = buildChannel(RAW_CHANNEL_ID, new TextValue(), "Raw state", this)
|
||||
.stateTopic(channelConfiguration.stateTopic).commandTopic(channelConfiguration.commandTopic,
|
||||
channelConfiguration.isRetain(), channelConfiguration.getQos())
|
||||
.build(false));
|
||||
}
|
||||
|
||||
protected boolean handleCommand(Command command) {
|
||||
HSBType newState;
|
||||
if (colorValue.getChannelState() instanceof HSBType) {
|
||||
newState = (HSBType) colorValue.getChannelState();
|
||||
} else {
|
||||
newState = HSBType.WHITE;
|
||||
}
|
||||
|
||||
if (command.equals(PercentType.ZERO) || command.equals(OnOffType.OFF)) {
|
||||
newState = HSBType.BLACK;
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
if (newState.getBrightness().equals(PercentType.ZERO)) {
|
||||
newState = new HSBType(newState.getHue(), newState.getSaturation(), PercentType.HUNDRED);
|
||||
}
|
||||
} else if (command instanceof HSBType) {
|
||||
newState = (HSBType) command;
|
||||
} else if (command instanceof PercentType) {
|
||||
newState = new HSBType(newState.getHue(), newState.getSaturation(), (PercentType) command);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
publishState(newState);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void publishState(HSBType state);
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* A MQTT light, following the https://www.home-assistant.io/components/light.mqtt/ specification.
|
||||
*
|
||||
* Specifically, the JSON schema. All channels are synthetic, and wrap the single internal raw
|
||||
* state.
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
private static final BigDecimal SCALE_FACTOR = new BigDecimal("2.55"); // string to not lose precision
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(JSONSchemaLight.class);
|
||||
|
||||
private static class JSONState {
|
||||
protected static class Color {
|
||||
protected @Nullable Integer r, g, b, c, w;
|
||||
protected @Nullable BigDecimal x, y, h, s;
|
||||
}
|
||||
|
||||
protected @Nullable String state;
|
||||
protected @Nullable Integer brightness;
|
||||
@SerializedName("color_mode")
|
||||
protected @Nullable LightColorMode colorMode;
|
||||
@SerializedName("color_temp")
|
||||
protected @Nullable Integer colorTemp;
|
||||
protected @Nullable Color color;
|
||||
protected @Nullable String effect;
|
||||
protected @Nullable Integer transition;
|
||||
}
|
||||
|
||||
public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildChannels() {
|
||||
if (channelConfiguration.colorMode) {
|
||||
List<LightColorMode> supportedColorModes = channelConfiguration.supportedColorModes;
|
||||
if (supportedColorModes == null || channelConfiguration.supportedColorModes.isEmpty()) {
|
||||
throw new UnsupportedComponentException("JSON schema light with color modes '" + getHaID()
|
||||
+ "' does not define supported_color_modes!");
|
||||
}
|
||||
|
||||
if (LightColorMode.hasColorChannel(supportedColorModes)) {
|
||||
hasColorChannel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasColorChannel) {
|
||||
buildChannel(COLOR_CHANNEL_ID, colorValue, "Color", this).commandTopic(DUMMY_TOPIC, true, 1)
|
||||
.commandFilter(this::handleCommand).build();
|
||||
} else if (channelConfiguration.brightness) {
|
||||
brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, brightnessValue, "Brightness", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
|
||||
} else {
|
||||
onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, onOffValue, "On/Off State", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishState(HSBType state) {
|
||||
JSONState json = new JSONState();
|
||||
|
||||
logger.trace("Publishing new state {} of light {} to MQTT.", state, getName());
|
||||
if (state.getBrightness().equals(PercentType.ZERO)) {
|
||||
json.state = "OFF";
|
||||
} else {
|
||||
json.state = "ON";
|
||||
if (channelConfiguration.brightness || (channelConfiguration.supportedColorModes != null
|
||||
&& (channelConfiguration.supportedColorModes.contains(LightColorMode.COLOR_MODE_HS)
|
||||
|| channelConfiguration.supportedColorModes.contains(LightColorMode.COLOR_MODE_XY)))) {
|
||||
json.brightness = state.getBrightness().toBigDecimal()
|
||||
.multiply(new BigDecimal(channelConfiguration.brightnessScale))
|
||||
.divide(new BigDecimal(100), MathContext.DECIMAL128).intValue();
|
||||
}
|
||||
|
||||
if (hasColorChannel) {
|
||||
json.color = new JSONState.Color();
|
||||
if (channelConfiguration.supportedColorModes.contains(LightColorMode.COLOR_MODE_HS)) {
|
||||
json.color.h = state.getHue().toBigDecimal();
|
||||
json.color.s = state.getSaturation().toBigDecimal();
|
||||
} else if (LightColorMode.hasRGB(Objects.requireNonNull(channelConfiguration.supportedColorModes))) {
|
||||
var rgb = state.toRGB();
|
||||
json.color.r = rgb[0].toBigDecimal().multiply(SCALE_FACTOR).intValue();
|
||||
json.color.g = rgb[1].toBigDecimal().multiply(SCALE_FACTOR).intValue();
|
||||
json.color.b = rgb[2].toBigDecimal().multiply(SCALE_FACTOR).intValue();
|
||||
} else { // if (channelConfiguration.supportedColorModes.contains(COLOR_MODE_XY))
|
||||
var xy = state.toXY();
|
||||
json.color.x = xy[0].toBigDecimal();
|
||||
json.color.y = xy[1].toBigDecimal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String command = getGson().toJson(json);
|
||||
logger.debug("Publishing new state '{}' of light {} to MQTT.", command, getName());
|
||||
rawChannel.getState().publishValue(new StringType(command));
|
||||
}
|
||||
|
||||
protected boolean handleCommand(Command command) {
|
||||
JSONState json = new JSONState();
|
||||
if (command.getClass().equals(OnOffType.class)) {
|
||||
json.state = command.toString();
|
||||
} else if (command.getClass().equals(PercentType.class)) {
|
||||
if (command.equals(PercentType.ZERO)) {
|
||||
json.state = "OFF";
|
||||
} else {
|
||||
json.state = "ON";
|
||||
if (channelConfiguration.brightness) {
|
||||
json.brightness = ((PercentType) command).toBigDecimal()
|
||||
.multiply(new BigDecimal(channelConfiguration.brightnessScale))
|
||||
.divide(new BigDecimal(100), MathContext.DECIMAL128).intValue();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super.handleCommand(command);
|
||||
}
|
||||
|
||||
String jsonCommand = getGson().toJson(json);
|
||||
logger.debug("Publishing new state '{}' of light {} to MQTT.", jsonCommand, getName());
|
||||
rawChannel.getState().publishValue(new StringType(jsonCommand));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChannelState(ChannelUID channel, State state) {
|
||||
ChannelStateUpdateListener listener = this.channelStateUpdateListener;
|
||||
|
||||
@Nullable
|
||||
JSONState jsonState;
|
||||
try {
|
||||
jsonState = getGson().fromJson(state.toString(), JSONState.class);
|
||||
|
||||
if (jsonState == null) {
|
||||
logger.warn("JSON light state for '{}' is empty.", getHaID());
|
||||
return;
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("Cannot parse JSON light state '{}' for '{}'.", state, getHaID());
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonState.state != null) {
|
||||
onOffValue.update(new StringType(jsonState.state));
|
||||
if (brightnessValue.getChannelState() instanceof UnDefType) {
|
||||
brightnessValue.update((OnOffType) onOffValue.getChannelState());
|
||||
}
|
||||
if (colorValue.getChannelState() instanceof UnDefType) {
|
||||
colorValue.update((OnOffType) onOffValue.getChannelState());
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonState.brightness != null) {
|
||||
brightnessValue.update(new DecimalType(Objects.requireNonNull(jsonState.brightness)));
|
||||
if (colorValue.getChannelState() instanceof HSBType) {
|
||||
HSBType color = (HSBType) colorValue.getChannelState();
|
||||
colorValue.update(new HSBType(color.getHue(), color.getSaturation(),
|
||||
(PercentType) brightnessValue.getChannelState()));
|
||||
} else {
|
||||
colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO,
|
||||
(PercentType) brightnessValue.getChannelState()));
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonState.color != null) {
|
||||
PercentType brightness = brightnessValue.getChannelState() instanceof PercentType
|
||||
? (PercentType) brightnessValue.getChannelState()
|
||||
: PercentType.HUNDRED;
|
||||
// This corresponds to "deprecated" color mode handling, since we're not checking which color
|
||||
// mode is currently active.
|
||||
// HS is highest priority, then XY, then RGB
|
||||
// See
|
||||
// https://github.com/home-assistant/core/blob/4f965f0eca09f0d12ae1c98c6786054063a36b44/homeassistant/components/mqtt/light/schema_json.py#L258
|
||||
if (jsonState.color.h != null && jsonState.color.s != null) {
|
||||
colorValue.update(new HSBType(new DecimalType(Objects.requireNonNull(jsonState.color.h)),
|
||||
new PercentType(Objects.requireNonNull(jsonState.color.s)), brightness));
|
||||
} else if (jsonState.color.x != null && jsonState.color.y != null) {
|
||||
HSBType newColor = HSBType.fromXY(jsonState.color.x.floatValue(), jsonState.color.y.floatValue());
|
||||
colorValue.update(new HSBType(newColor.getHue(), newColor.getSaturation(), brightness));
|
||||
} else if (jsonState.color.r != null && jsonState.color.g != null && jsonState.color.b != null) {
|
||||
colorValue.update(HSBType.fromRGB(jsonState.color.r, jsonState.color.g, jsonState.color.b));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasColorChannel) {
|
||||
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), colorValue.getChannelState());
|
||||
} else if (brightnessChannel != null) {
|
||||
listener.updateChannelState(new ChannelUID(getGroupUID(), BRIGHTNESS_CHANNEL_ID),
|
||||
brightnessValue.getChannelState());
|
||||
} else {
|
||||
listener.updateChannelState(new ChannelUID(getGroupUID(), ON_OFF_CHANNEL_ID), onOffValue.getChannelState());
|
||||
}
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
|
||||
@SerializedName("color_mode")
|
||||
protected boolean colorMode = false; // JSON schema only
|
||||
@SerializedName("supported_color_modes")
|
||||
protected @Nullable List<String> supportedColorModes; // JSON schema only
|
||||
protected @Nullable List<LightColorMode> supportedColorModes; // JSON schema only
|
||||
// Defines when on the payload_on is sent. Using last (the default) will send
|
||||
// any style (brightness, color, etc)
|
||||
// topics first and then a payload_on to the command_topic. Using first will
|
||||
@ -257,6 +257,8 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
|
||||
switch (schema) {
|
||||
case DEFAULT_SCHEMA:
|
||||
return new DefaultSchemaLight(builder);
|
||||
case JSON_SCHEMA:
|
||||
return new JSONSchemaLight(builder);
|
||||
default:
|
||||
throw new UnsupportedComponentException(
|
||||
"Component '" + builder.getHaID() + "' of schema '" + schema + "' is not supported!");
|
||||
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The types of color modes a JSONSchemaLight can support.
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum LightColorMode {
|
||||
@SerializedName("onoff")
|
||||
COLOR_MODE_ONOFF,
|
||||
@SerializedName("brightness")
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
@SerializedName("color_temp")
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
@SerializedName("hs")
|
||||
COLOR_MODE_HS,
|
||||
@SerializedName("xy")
|
||||
COLOR_MODE_XY,
|
||||
@SerializedName("rgb")
|
||||
COLOR_MODE_RGB,
|
||||
@SerializedName("rgbw")
|
||||
COLOR_MODE_RGBW,
|
||||
@SerializedName("rgbww")
|
||||
COLOR_MODE_RGBWW,
|
||||
@SerializedName("white")
|
||||
COLOR_MODE_WHITE;
|
||||
|
||||
public static final List<LightColorMode> WITH_RGB = List.of(COLOR_MODE_RGB, COLOR_MODE_RGBW, COLOR_MODE_RGBWW);
|
||||
public static final List<LightColorMode> WITH_COLOR_CHANNEL = List.of(COLOR_MODE_HS, COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW, COLOR_MODE_RGBWW, COLOR_MODE_XY);
|
||||
|
||||
/**
|
||||
* Determines if the list of supported modes includes any that should generate an openHAB Color channel
|
||||
*/
|
||||
public static boolean hasColorChannel(List<LightColorMode> supportedColorModes) {
|
||||
return WITH_COLOR_CHANNEL.stream().anyMatch(cm -> supportedColorModes.contains(cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determins if the list of supported modes includes any that have RGB components
|
||||
*/
|
||||
public static boolean hasRGB(List<LightColorMode> supportedColorModes) {
|
||||
return WITH_RGB.stream().anyMatch(cm -> supportedColorModes.contains(cm));
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mqtt.generic.values.ColorValue;
|
||||
import org.openhab.binding.mqtt.generic.values.OnOffValue;
|
||||
import org.openhab.binding.mqtt.generic.values.PercentageValue;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* Tests for {@link Light} conforming to the JSON schema
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JSONSchemaLightTests extends AbstractComponentTests {
|
||||
public static final String CONFIG_TOPIC = "light/0x0000000000000000_light_zigbee2mqtt";
|
||||
|
||||
@Test
|
||||
public void testRgb() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"{ " +
|
||||
" \"availability\": [ " +
|
||||
" { " +
|
||||
" \"topic\": \"zigbee2mqtt/bridge/state\" " +
|
||||
" } " +
|
||||
" ], " +
|
||||
" \"device\": { " +
|
||||
" \"identifiers\": [ " +
|
||||
" \"zigbee2mqtt_0x0000000000000000\" " +
|
||||
" ], " +
|
||||
" \"manufacturer\": \"Lights inc\", " +
|
||||
" \"model\": \"light v1\", " +
|
||||
" \"name\": \"Light\", " +
|
||||
" \"sw_version\": \"Zigbee2MQTT 1.18.2\" " +
|
||||
" }, " +
|
||||
" \"name\": \"light\", " +
|
||||
" \"schema\": \"json\", " +
|
||||
" \"state_topic\": \"zigbee2mqtt/light/state\", " +
|
||||
" \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
|
||||
" \"brightness\": true, " +
|
||||
" \"color_mode\": true, " +
|
||||
" \"supported_color_modes\": [\"onoff\", \"brightness\", \"rgb\"]" +
|
||||
"}");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("light"));
|
||||
|
||||
assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
|
||||
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"ON\" }");
|
||||
assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"color\": {\"r\": 10, \"g\": 20, \"b\": 30 } }");
|
||||
assertState(component, Light.COLOR_CHANNEL_ID, HSBType.fromRGB(10, 20, 30));
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"brightness\": 255 }");
|
||||
assertState(component, Light.COLOR_CHANNEL_ID, new HSBType("210,66,100"));
|
||||
|
||||
sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
|
||||
assertPublished("zigbee2mqtt/light/set/state",
|
||||
"{\"state\":\"ON\",\"brightness\":255,\"color\":{\"r\":0,\"g\":0,\"b\":255}}");
|
||||
|
||||
// OnOff commands should route to the correct topic
|
||||
sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"OFF\"}");
|
||||
|
||||
sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.ON);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"ON\"}");
|
||||
|
||||
sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"ON\",\"brightness\":127}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrightnessAndOnOff() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"{ " +
|
||||
" \"name\": \"light\", " +
|
||||
" \"schema\": \"json\", " +
|
||||
" \"state_topic\": \"zigbee2mqtt/light/state\", " +
|
||||
" \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
|
||||
" \"brightness\": true" +
|
||||
"}");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("light"));
|
||||
|
||||
assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "", "dummy", "Brightness", PercentageValue.class);
|
||||
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"ON\", \"brightness\": 128 }");
|
||||
assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
|
||||
new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
|
||||
|
||||
sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.HUNDRED);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"ON\",\"brightness\":255}");
|
||||
|
||||
sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"OFF\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnOffOnly() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"{ " +
|
||||
" \"name\": \"light\", " +
|
||||
" \"schema\": \"json\", " +
|
||||
" \"state_topic\": \"zigbee2mqtt/light/state\", " +
|
||||
" \"command_topic\": \"zigbee2mqtt/light/set/state\"" +
|
||||
"}");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("light"));
|
||||
|
||||
assertChannel(component, Light.ON_OFF_CHANNEL_ID, "", "dummy", "On/Off State", OnOffValue.class);
|
||||
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"ON\" }");
|
||||
assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
|
||||
publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"OFF\" }");
|
||||
assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
|
||||
|
||||
sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"OFF\"}");
|
||||
sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
|
||||
assertPublished("zigbee2mqtt/light/set/state", "{\"state\":\"ON\"}");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getConfigTopics() {
|
||||
return Set.of(CONFIG_TOPIC);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user