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.util.Objects;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.junit.jupiter.api.Test;
24 import org.openhab.binding.mqtt.generic.values.OnOffValue;
25 import org.openhab.binding.mqtt.generic.values.PercentageValue;
26 import org.openhab.binding.mqtt.generic.values.TextValue;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.PercentType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.types.UnDefType;
33 * Tests for {@link Fan}
35 * @author Anton Kharuzhy - Initial contribution
38 public class FanTests extends AbstractComponentTests {
39 public static final String CONFIG_TOPIC = "fan/0x0000000000000000_fan_zigbee2mqtt";
41 @SuppressWarnings("null")
43 public void test() throws InterruptedException {
45 var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
50 "topic": "zigbee2mqtt/bridge/state" \
55 "zigbee2mqtt_0x0000000000000000" \
57 "manufacturer": "Fans inc", \
59 "name": "FanBlower", \
60 "sw_version": "Zigbee2MQTT 1.18.2" \
63 "payload_off": "OFF_", \
64 "payload_on": "ON_", \
65 "state_topic": "zigbee2mqtt/fan/state", \
66 "command_topic": "zigbee2mqtt/fan/set/state" \
71 assertThat(component.channels.size(), is(1));
72 assertThat(component.getName(), is("fan"));
74 assertChannel(component, Fan.SWITCH_CHANNEL_ID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
75 "On/Off State", OnOffValue.class);
77 publishMessage("zigbee2mqtt/fan/state", "ON_");
78 assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
79 publishMessage("zigbee2mqtt/fan/state", "ON_");
80 assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
81 publishMessage("zigbee2mqtt/fan/state", "OFF_");
82 assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.OFF);
83 publishMessage("zigbee2mqtt/fan/state", "ON_");
84 assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
86 component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
87 assertPublished("zigbee2mqtt/fan/set/state", "OFF_");
88 component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.ON);
89 assertPublished("zigbee2mqtt/fan/set/state", "ON_");
92 @SuppressWarnings("null")
94 public void testCommandTemplate() throws InterruptedException {
95 var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
99 "topic": "zigbee2mqtt/bridge/state"
104 "zigbee2mqtt_0x0000000000000000"
106 "manufacturer": "Fans inc",
109 "sw_version": "Zigbee2MQTT 1.18.2"
112 "payload_off": "OFF_",
114 "state_topic": "zigbee2mqtt/fan/state",
115 "command_topic": "zigbee2mqtt/fan/set/state",
116 "command_template": "set to {{ value }}"
120 assertThat(component.channels.size(), is(1));
122 component.getChannel(Fan.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
123 assertPublished("zigbee2mqtt/fan/set/state", "set to OFF_");
126 @SuppressWarnings("null")
128 public void testComplex() throws InterruptedException {
129 var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
133 "topic": "zigbee2mqtt/bridge/state"
138 "zigbee2mqtt_0x0000000000000000"
140 "manufacturer": "Fans inc",
143 "sw_version": "Zigbee2MQTT 1.18.2"
145 "name": "Bedroom Fan",
146 "payload_off": "false",
147 "payload_on": "true",
148 "state_topic": "bedroom_fan/on/state",
149 "command_topic": "bedroom_fan/on/set",
150 "direction_state_topic": "bedroom_fan/direction/state",
151 "direction_command_topic": "bedroom_fan/direction/set",
152 "oscillation_state_topic": "bedroom_fan/oscillation/state",
153 "oscillation_command_topic": "bedroom_fan/oscillation/set",
154 "percentage_state_topic": "bedroom_fan/speed/percentage_state",
155 "percentage_command_topic": "bedroom_fan/speed/percentage",
156 "preset_mode_state_topic": "bedroom_fan/preset/preset_mode_state",
157 "preset_mode_command_topic": "bedroom_fan/preset/preset_mode",
165 "payload_oscillation_on": "true",
166 "payload_oscillation_off": "false",
167 "speed_range_min": 1,
168 "speed_range_max": 10
172 assertThat(component.channels.size(), is(4));
173 assertThat(component.getName(), is("Bedroom Fan"));
175 assertChannel(component, Fan.SPEED_CHANNEL_ID, "bedroom_fan/speed/percentage_state",
176 "bedroom_fan/speed/percentage", "Speed", PercentageValue.class);
177 var channel = Objects.requireNonNull(component.getChannel(Fan.SPEED_CHANNEL_ID));
178 assertThat(channel.getStateDescription().getStep(), is(BigDecimal.valueOf(10.0d)));
179 assertChannel(component, Fan.OSCILLATION_CHANNEL_ID, "bedroom_fan/oscillation/state",
180 "bedroom_fan/oscillation/set", "Oscillation", OnOffValue.class);
181 assertChannel(component, Fan.DIRECTION_CHANNEL_ID, "bedroom_fan/direction/state", "bedroom_fan/direction/set",
182 "Direction", TextValue.class);
183 assertChannel(component, Fan.PRESET_MODE_CHANNEL_ID, "bedroom_fan/preset/preset_mode_state",
184 "bedroom_fan/preset/preset_mode", "Preset Mode", TextValue.class);
186 publishMessage("bedroom_fan/on/state", "true");
187 assertState(component, Fan.SPEED_CHANNEL_ID, PercentType.HUNDRED);
188 publishMessage("bedroom_fan/on/state", "false");
189 assertState(component, Fan.SPEED_CHANNEL_ID, PercentType.ZERO);
190 publishMessage("bedroom_fan/on/state", "true");
191 publishMessage("bedroom_fan/speed/percentage_state", "50");
192 assertState(component, Fan.SPEED_CHANNEL_ID, new PercentType(50));
193 publishMessage("bedroom_fan/on/state", "false");
194 // Off, even though we got an updated speed
195 assertState(component, Fan.SPEED_CHANNEL_ID, PercentType.ZERO);
196 publishMessage("bedroom_fan/speed/percentage_state", "25");
197 assertState(component, Fan.SPEED_CHANNEL_ID, PercentType.ZERO);
198 publishMessage("bedroom_fan/on/state", "true");
199 // Now that it's on, the channel reflects the proper speed
200 assertState(component, Fan.SPEED_CHANNEL_ID, new PercentType(25));
202 publishMessage("bedroom_fan/oscillation/state", "true");
203 assertState(component, Fan.OSCILLATION_CHANNEL_ID, OnOffType.ON);
204 publishMessage("bedroom_fan/oscillation/state", "false");
205 assertState(component, Fan.OSCILLATION_CHANNEL_ID, OnOffType.OFF);
207 publishMessage("bedroom_fan/direction/state", "forward");
208 assertState(component, Fan.DIRECTION_CHANNEL_ID, new StringType("forward"));
209 publishMessage("bedroom_fan/direction/state", "backward");
210 assertState(component, Fan.DIRECTION_CHANNEL_ID, new StringType("backward"));
212 publishMessage("bedroom_fan/preset/preset_mode_state", "auto");
213 assertState(component, Fan.PRESET_MODE_CHANNEL_ID, new StringType("auto"));
214 publishMessage("bedroom_fan/preset/preset_mode_state", "None");
215 assertState(component, Fan.PRESET_MODE_CHANNEL_ID, UnDefType.NULL);
217 component.getChannel(Fan.SPEED_CHANNEL_ID).getState().publishValue(OnOffType.OFF);
218 assertPublished("bedroom_fan/on/set", "false");
219 component.getChannel(Fan.SPEED_CHANNEL_ID).getState().publishValue(OnOffType.ON);
220 assertPublished("bedroom_fan/on/set", "true");
221 // Setting to a specific speed turns it on first
222 component.getChannel(Fan.SPEED_CHANNEL_ID).getState().publishValue(PercentType.HUNDRED);
223 assertPublished("bedroom_fan/on/set", "true");
224 assertPublished("bedroom_fan/speed/percentage", "100");
226 component.getChannel(Fan.OSCILLATION_CHANNEL_ID).getState().publishValue(OnOffType.ON);
227 assertPublished("bedroom_fan/oscillation/set", "true");
229 component.getChannel(Fan.DIRECTION_CHANNEL_ID).getState().publishValue(new StringType("forward"));
230 assertPublished("bedroom_fan/direction/set", "forward");
232 component.getChannel(Fan.PRESET_MODE_CHANNEL_ID).getState().publishValue(new StringType("eco"));
233 assertPublished("bedroom_fan/preset/preset_mode", "eco");
237 protected Set<String> getConfigTopics() {
238 return Set.of(CONFIG_TOPIC);