2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mqtt.homeassistant.internal.component;
15 import static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.MatcherAssert.assertThat;
18 import java.math.BigDecimal;
19 import java.math.MathContext;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.junit.jupiter.api.Test;
24 import org.openhab.binding.mqtt.generic.values.ColorValue;
25 import org.openhab.binding.mqtt.generic.values.NumberValue;
26 import org.openhab.binding.mqtt.generic.values.OnOffValue;
27 import org.openhab.binding.mqtt.generic.values.PercentageValue;
28 import org.openhab.core.library.types.HSBType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.unit.Units;
33 import org.openhab.core.types.UnDefType;
36 * Tests for {@link Light} conforming to the template schema
38 * @author Cody Cutrer - Initial contribution
41 public class TemplateSchemaLightTests extends AbstractComponentTests {
42 public static final String CONFIG_TOPIC = "light/0x0000000000000000_light_zigbee2mqtt";
45 public void testRgb() throws InterruptedException {
46 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
50 "topic": "zigbee2mqtt/bridge/state"
55 "zigbee2mqtt_0x0000000000000000"
57 "manufacturer": "Lights inc",
60 "sw_version": "Zigbee2MQTT 1.18.2"
64 "state_topic": "zigbee2mqtt/light/state",
65 "command_topic": "zigbee2mqtt/light/set/state",
66 "command_on_template": "{{state}},{{red}},{{green}},{{blue}}",
67 "command_off_template": "off",
68 "state_template": "{{value_json.state}}",
69 "red_template": "{{value_json.r}}",
70 "green_template": "{{value_json.g}}",
71 "blue_template": "{{value_json.b}}",
72 "brightness_template": "{{value_json.brightness}}"
76 assertThat(component.channels.size(), is(1));
77 assertThat(component.getName(), is("light"));
79 assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
81 publishMessage("zigbee2mqtt/light/state", """
82 { "state": "on", "r": 255, "g": 255, "b": 255, "brightness": 255 }
84 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
86 sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
87 assertPublished("zigbee2mqtt/light/set/state", "on,0,0,255");
89 // OnOff commands should route to the correct topic
90 sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
91 assertPublished("zigbee2mqtt/light/set/state", "off");
95 public void testBrightnessAndOnOff() throws InterruptedException {
96 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
100 "state_topic": "zigbee2mqtt/light/state",
101 "command_topic": "zigbee2mqtt/light/set/state",
102 "command_on_template": "{{state}},{{brightness}}",
103 "command_off_template": "off",
104 "state_template": "{{value_json.state}}",
105 "brightness_template": "{{value_json.brightness}}"
109 assertThat(component.channels.size(), is(1));
110 assertThat(component.getName(), is("light"));
112 assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "", "dummy", "Brightness", PercentageValue.class);
114 publishMessage("zigbee2mqtt/light/state", "{ \"state\": \"on\", \"brightness\": 128 }");
115 assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
116 new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
118 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.HUNDRED);
119 assertPublished("zigbee2mqtt/light/set/state", "on,255");
121 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
122 assertPublished("zigbee2mqtt/light/set/state", "off");
126 public void testBrightnessAndCCT() throws InterruptedException {
127 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
130 "schema": "template",
131 "name": "Bulb-white",
132 "command_topic": "shellies/bulb/color/0/set",
133 "state_topic": "shellies/bulb/color/0/status",
134 "availability_topic": "shellies/bulb/online",
135 "command_on_template": "{\\"turn\\": \\"on\\", \\"mode\\": \\"white\\"{%- if brightness is defined -%}, \\"brightness\\": {{brightness | float | multiply(0.39215686) | round(0)}}{%- endif -%}{%- if color_temp is defined -%}, \\"temp\\": {{ (1000000 / color_temp | float) | round(0) }}{%- endif -%}}",
136 "command_off_template": "{\\"turn\\":\\"off\\", \\"mode\\": \\"white\\"}",
137 "state_template": "{% if value_json.ison and value_json.mode == 'white' %}on{% else %}off{% endif %}",
138 "brightness_template": "{{ value_json.brightness | float | multiply(2.55) | round(0) }}",
139 "color_temp_template": "{{ (1000000 / value_json.temp | float) | round(0) }}",
140 "payload_available": "true",
141 "payload_not_available": "false",
150 assertThat(component.channels.size(), is(2));
151 assertThat(component.getName(), is("Bulb-white"));
153 assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "", "dummy", "Brightness", PercentageValue.class);
154 assertChannel(component, Light.COLOR_TEMP_CHANNEL_ID, "", "dummy", "Color Temperature", NumberValue.class);
156 publishMessage("shellies/bulb/color/0/status", "{ \"state\": \"on\", \"brightness\": 100 }");
157 assertState(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.HUNDRED);
158 assertState(component, Light.COLOR_TEMP_CHANNEL_ID, UnDefType.NULL);
160 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, PercentType.HUNDRED);
161 assertPublished("shellies/bulb/color/0/set", "{\"turn\": \"on\", \"mode\": \"white\", \"brightness\": 100}");
163 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
164 assertPublished("shellies/bulb/color/0/set", "{\"turn\":\"off\", \"mode\": \"white\"}");
166 sendCommand(component, Light.COLOR_TEMP_CHANNEL_ID, new QuantityType(200, Units.MIRED));
167 assertPublished("shellies/bulb/color/0/set", "{\"turn\": \"on\", \"mode\": \"white\", \"temp\": 5000}");
171 public void testOnOffOnly() throws InterruptedException {
172 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
175 "schema": "template",
176 "state_topic": "zigbee2mqtt/light/state",
177 "command_topic": "zigbee2mqtt/light/set/state",
178 "state_template": "{{ value_json.power }}",
179 "command_on_template": "{{state}}",
180 "command_off_template": "off"
184 assertThat(component.channels.size(), is(1));
185 assertThat(component.getName(), is("light"));
187 assertChannel(component, Light.ON_OFF_CHANNEL_ID, "", "dummy", "On/Off State", OnOffValue.class);
189 publishMessage("zigbee2mqtt/light/state", "{\"power\": \"on\"}");
190 assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
191 publishMessage("zigbee2mqtt/light/state", "{\"power\": \"off\"}");
192 assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
194 sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
195 assertPublished("zigbee2mqtt/light/set/state", "off");
196 sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
197 assertPublished("zigbee2mqtt/light/set/state", "on");
201 protected Set<String> getConfigTopics() {
202 return Set.of(CONFIG_TOPIC);