2 * Copyright (c) 2010-2020 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.nest.handler;
15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.core.Is.is;
17 import static org.openhab.binding.nest.internal.NestBindingConstants.*;
18 import static org.openhab.binding.nest.internal.data.NestDataUtil.*;
19 import static org.openhab.core.library.types.OnOffType.*;
20 import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
21 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
23 import java.io.IOException;
24 import java.util.HashMap;
27 import org.junit.jupiter.api.Test;
28 import org.openhab.binding.nest.internal.config.NestDeviceConfiguration;
29 import org.openhab.binding.nest.internal.handler.NestThermostatHandler;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.unit.Units;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.builder.ThingBuilder;
42 * Tests for {@link NestThermostatHandler}.
44 * @author Wouter Born - Increase test coverage
46 public class NestThermostatHandlerTest extends NestThingHandlerOSGiTest {
48 private static final ThingUID THERMOSTAT_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thermostat1");
49 private static final int CHANNEL_COUNT = 25;
51 public NestThermostatHandlerTest() {
52 super(NestThermostatHandler.class);
56 protected Thing buildThing(Bridge bridge) {
57 Map<String, Object> properties = new HashMap<>();
58 properties.put(NestDeviceConfiguration.DEVICE_ID, THERMOSTAT1_DEVICE_ID);
60 return ThingBuilder.create(THING_TYPE_THERMOSTAT, THERMOSTAT_UID).withLabel("Test Thermostat")
61 .withBridge(bridge.getUID()).withChannels(buildChannels(THING_TYPE_THERMOSTAT, THERMOSTAT_UID))
62 .withConfiguration(new Configuration(properties)).build();
66 public void completeThermostatCelsiusUpdate() throws IOException {
67 assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
68 assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
70 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
71 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
72 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
74 assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
75 assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
76 assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
77 assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(12.5, CELSIUS));
78 assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
79 assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
80 assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
81 assertThatItemHasState(CHANNEL_HAS_FAN, ON);
82 assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
83 assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
84 assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
85 assertThatItemHasState(CHANNEL_LOCKED, OFF);
86 assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(22, CELSIUS));
87 assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
88 assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(24, CELSIUS));
89 assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(20, CELSIUS));
90 assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
91 assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
92 assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(15.5, CELSIUS));
93 assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
94 assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
95 assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
96 assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(19, CELSIUS));
97 assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
98 assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
100 assertThatAllItemStatesAreNotNull();
104 public void completeThermostatFahrenheitUpdate() throws IOException {
105 assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
106 assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
108 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
109 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
110 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
112 assertThatItemHasState(CHANNEL_CAN_COOL, OFF);
113 assertThatItemHasState(CHANNEL_CAN_HEAT, ON);
114 assertThatItemHasState(CHANNEL_ECO_MAX_SET_POINT, new QuantityType<>(76, FAHRENHEIT));
115 assertThatItemHasState(CHANNEL_ECO_MIN_SET_POINT, new QuantityType<>(55, FAHRENHEIT));
116 assertThatItemHasState(CHANNEL_FAN_TIMER_ACTIVE, OFF);
117 assertThatItemHasState(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(15, Units.MINUTE));
118 assertThatItemHasState(CHANNEL_FAN_TIMER_TIMEOUT, parseDateTimeType("1970-01-01T00:00:00.000Z"));
119 assertThatItemHasState(CHANNEL_HAS_FAN, ON);
120 assertThatItemHasState(CHANNEL_HAS_LEAF, ON);
121 assertThatItemHasState(CHANNEL_HUMIDITY, new QuantityType<>(25, Units.PERCENT));
122 assertThatItemHasState(CHANNEL_LAST_CONNECTION, parseDateTimeType("2017-02-02T21:00:06.000Z"));
123 assertThatItemHasState(CHANNEL_LOCKED, OFF);
124 assertThatItemHasState(CHANNEL_LOCKED_MAX_SET_POINT, new QuantityType<>(72, FAHRENHEIT));
125 assertThatItemHasState(CHANNEL_LOCKED_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
126 assertThatItemHasState(CHANNEL_MAX_SET_POINT, new QuantityType<>(75, FAHRENHEIT));
127 assertThatItemHasState(CHANNEL_MIN_SET_POINT, new QuantityType<>(68, FAHRENHEIT));
128 assertThatItemHasState(CHANNEL_MODE, new StringType("HEAT"));
129 assertThatItemHasState(CHANNEL_PREVIOUS_MODE, new StringType("HEAT"));
130 assertThatItemHasState(CHANNEL_SET_POINT, new QuantityType<>(60, FAHRENHEIT));
131 assertThatItemHasState(CHANNEL_STATE, new StringType("OFF"));
132 assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ACTIVE, OFF);
133 assertThatItemHasState(CHANNEL_SUNLIGHT_CORRECTION_ENABLED, ON);
134 assertThatItemHasState(CHANNEL_TEMPERATURE, new QuantityType<>(66, FAHRENHEIT));
135 assertThatItemHasState(CHANNEL_TIME_TO_TARGET, new QuantityType<>(0, Units.MINUTE));
136 assertThatItemHasState(CHANNEL_USING_EMERGENCY_HEAT, OFF);
138 assertThatAllItemStatesAreNotNull();
142 public void incompleteThermostatUpdate() throws IOException {
143 assertThat(thing.getChannels().size(), is(CHANNEL_COUNT));
144 assertThat(thing.getStatus(), is(ThingStatus.OFFLINE));
146 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
147 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
148 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
149 assertThatAllItemStatesAreNotNull();
151 putStreamingEventData(fromFile(INCOMPLETE_DATA_FILE_NAME));
152 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.UNKNOWN)));
153 assertThatAllItemStatesAreNull();
157 public void thermostatGone() throws IOException {
158 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
159 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
160 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
162 putStreamingEventData(fromFile(EMPTY_DATA_FILE_NAME));
163 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.OFFLINE)));
164 assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.GONE));
168 public void channelRefresh() throws IOException {
169 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
170 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME));
171 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
172 assertThatAllItemStatesAreNotNull();
174 updateAllItemStatesToNull();
175 assertThatAllItemStatesAreNull();
177 refreshAllChannels();
178 assertThatAllItemStatesAreNotNull();
182 public void handleFanTimerActiveCommands() throws IOException {
183 handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
184 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
186 handleCommand(CHANNEL_FAN_TIMER_ACTIVE, OFF);
187 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "false");
189 handleCommand(CHANNEL_FAN_TIMER_ACTIVE, ON);
190 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_active", "true");
194 public void handleFanTimerDurationCommands() throws IOException {
195 int[] durations = { 15, 30, 45, 60, 120, 240, 480, 960, 15 };
196 for (int duration : durations) {
197 handleCommand(CHANNEL_FAN_TIMER_DURATION, new QuantityType<>(duration, Units.MINUTE));
198 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "fan_timer_duration", String.valueOf(duration));
203 public void handleMaxSetPointCelsiusCommands() throws IOException {
204 celsiusCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_c");
208 public void handleMaxSetPointFahrenheitCommands() throws IOException {
209 fahrenheitCommandsTest(CHANNEL_MAX_SET_POINT, "target_temperature_high_f");
213 public void handleMinSetPointCelsiusCommands() throws IOException {
214 celsiusCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_c");
218 public void handleMinSetPointFahrenheitCommands() throws IOException {
219 fahrenheitCommandsTest(CHANNEL_MIN_SET_POINT, "target_temperature_low_f");
223 public void handleChannelModeCommands() throws IOException {
224 handleCommand(CHANNEL_MODE, new StringType("HEAT"));
225 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
227 handleCommand(CHANNEL_MODE, new StringType("COOL"));
228 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "cool");
230 handleCommand(CHANNEL_MODE, new StringType("HEAT_COOL"));
231 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat-cool");
233 handleCommand(CHANNEL_MODE, new StringType("ECO"));
234 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "eco");
236 handleCommand(CHANNEL_MODE, new StringType("OFF"));
237 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "off");
239 handleCommand(CHANNEL_MODE, new StringType("HEAT"));
240 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, "hvac_mode", "heat");
244 public void handleSetPointCelsiusCommands() throws IOException {
245 celsiusCommandsTest(CHANNEL_SET_POINT, "target_temperature_c");
249 public void handleSetPointFahrenheitCommands() throws IOException {
250 fahrenheitCommandsTest(CHANNEL_SET_POINT, "target_temperature_f");
253 private void celsiusCommandsTest(String channelId, String apiPropertyName) throws IOException {
254 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
255 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, CELSIUS));
256 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
258 handleCommand(channelId, new QuantityType<>(20, CELSIUS));
259 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "20.0");
261 handleCommand(channelId, new QuantityType<>(21.123, CELSIUS));
262 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
264 handleCommand(channelId, new QuantityType<>(22.541, CELSIUS));
265 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "22.5");
267 handleCommand(channelId, new QuantityType<>(23.74, CELSIUS));
268 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "23.5");
270 handleCommand(channelId, new QuantityType<>(23.75, CELSIUS));
271 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "24.0");
273 handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
274 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "21.0");
277 private void fahrenheitCommandsTest(String channelId, String apiPropertyName) throws IOException {
278 waitForAssert(() -> assertThat(bridge.getStatus(), is(ThingStatus.ONLINE)));
279 putStreamingEventData(fromFile(COMPLETE_DATA_FILE_NAME, FAHRENHEIT));
280 waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
282 handleCommand(channelId, new QuantityType<>(70, FAHRENHEIT));
283 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");
285 handleCommand(channelId, new QuantityType<>(71.123, FAHRENHEIT));
286 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "71");
288 handleCommand(channelId, new QuantityType<>(71.541, FAHRENHEIT));
289 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "72");
291 handleCommand(channelId, new QuantityType<>(72.74, FAHRENHEIT));
292 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "73");
294 handleCommand(channelId, new QuantityType<>(73.75, FAHRENHEIT));
295 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "74");
297 handleCommand(channelId, new QuantityType<>(21, CELSIUS));
298 assertNestApiPropertyState(THERMOSTAT1_DEVICE_ID, apiPropertyName, "70");