[deconz] Cleanup code and improve tests, edit channels to vibration sensor (#14641)
* [deconz] Cleanup code and improve tests Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* 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.deconz;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
||||
import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
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 com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* This class provides tests for deconz sensors
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
* @author Lukas Agethen - Added Thermostat
|
||||
* @author Philipp Schneider - Added air quality sensor
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@NonNullByDefault
|
||||
public class SensorsTest {
|
||||
private @NonNullByDefault({}) Gson gson;
|
||||
|
||||
private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
|
||||
|
||||
@BeforeEach
|
||||
public void initialize() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||
gson = gsonBuilder.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carbonmonoxideSensorUpdateTest() throws IOException {
|
||||
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("carbonmonoxide.json", SensorMessage.class, gson);
|
||||
assertNotNull(sensorMessage);
|
||||
|
||||
ThingUID thingUID = new ThingUID("deconz", "sensor");
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, "carbonmonoxide");
|
||||
Thing sensor = ThingBuilder.create(THING_TYPE_CARBONMONOXIDE_SENSOR, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID, "Switch").build()).build();
|
||||
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
|
||||
sensorThingHandler.setCallback(thingHandlerCallback);
|
||||
|
||||
sensorThingHandler.messageReceived(sensorMessage);
|
||||
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(OnOffType.ON));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void airQualitySensorUpdateTest() throws IOException {
|
||||
// ARRANGE
|
||||
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("airquality.json", SensorMessage.class, gson);
|
||||
assertNotNull(sensorMessage);
|
||||
|
||||
ThingUID thingUID = new ThingUID("deconz", "sensor");
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, "airquality");
|
||||
Thing sensor = ThingBuilder.create(THING_TYPE_AIRQUALITY_SENSOR, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID, "String").build()).build();
|
||||
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
|
||||
sensorThingHandler.setCallback(thingHandlerCallback);
|
||||
|
||||
// ACT
|
||||
sensorThingHandler.messageReceived(sensorMessage);
|
||||
|
||||
// ASSERT
|
||||
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(StringType.valueOf("good")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void airQualityPpbSensorUpdateTest() throws IOException {
|
||||
// ARRANGE
|
||||
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("airquality.json", SensorMessage.class, gson);
|
||||
assertNotNull(sensorMessage);
|
||||
|
||||
ThingUID thingUID = new ThingUID("deconz", "sensor");
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, "airqualityppb");
|
||||
Thing sensor = ThingBuilder.create(THING_TYPE_AIRQUALITY_SENSOR, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID, "Number").build()).build();
|
||||
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
|
||||
sensorThingHandler.setCallback(thingHandlerCallback);
|
||||
|
||||
// ACT
|
||||
sensorThingHandler.messageReceived(sensorMessage);
|
||||
|
||||
// ASSERT
|
||||
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(new QuantityType<>("129 ppb")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fireSensorUpdateTest() throws IOException {
|
||||
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("fire.json", SensorMessage.class, gson);
|
||||
assertNotNull(sensorMessage);
|
||||
|
||||
ThingUID thingUID = new ThingUID("deconz", "sensor");
|
||||
ChannelUID channelBatteryLevelUID = new ChannelUID(thingUID, CHANNEL_BATTERY_LEVEL);
|
||||
ChannelUID channelFireUID = new ChannelUID(thingUID, CHANNEL_FIRE);
|
||||
ChannelUID channelTamperedUID = new ChannelUID(thingUID, CHANNEL_TAMPERED);
|
||||
ChannelUID channelLastSeenUID = new ChannelUID(thingUID, CHANNEL_LAST_SEEN);
|
||||
|
||||
Thing sensor = ThingBuilder.create(THING_TYPE_FIRE_SENSOR, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelBatteryLevelUID, "Number").build())
|
||||
.withChannel(ChannelBuilder.create(channelFireUID, "Switch").build())
|
||||
.withChannel(ChannelBuilder.create(channelTamperedUID, "Switch").build())
|
||||
.withChannel(ChannelBuilder.create(channelLastSeenUID, "DateTime").build()).build();
|
||||
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
|
||||
sensorThingHandler.setCallback(thingHandlerCallback);
|
||||
|
||||
sensorThingHandler.messageReceived(sensorMessage);
|
||||
|
||||
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelFireUID), eq(OnOffType.OFF));
|
||||
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelBatteryLevelUID), eq(new DecimalType(98)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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.deconz.internal.handler;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.BINDING_ID;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.BRIDGE_TYPE;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.CONFIG_ID;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.THING_TYPE_THERMOSTAT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.openhab.binding.deconz.DeconzTest;
|
||||
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
||||
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
||||
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
|
||||
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatMode;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.test.java.JavaTest;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
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.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link BaseDeconzThingHandlerTest} is the base class for test classes that are used to test subclasses of
|
||||
* {@link DeconzBaseThingHandler}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BaseDeconzThingHandlerTest extends JavaTest {
|
||||
private static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE, "bridge");
|
||||
private static final ThingUID THING_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thing");
|
||||
protected @NonNullByDefault({}) DeconzBaseMessage deconzMessage;
|
||||
private @Mock @NonNullByDefault({}) Bridge bridge;
|
||||
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
|
||||
private @Mock @NonNullByDefault({}) DeconzBridgeHandler bridgeHandler;
|
||||
private @Mock @NonNullByDefault({}) WebSocketConnection webSocketConnection;
|
||||
private @Mock @NonNullByDefault({}) BridgeFullState bridgeFullState;
|
||||
private @NonNullByDefault({}) Gson gson;
|
||||
private @NonNullByDefault({}) Thing thing;
|
||||
private @NonNullByDefault({}) DeconzBaseThingHandler thingHandler;
|
||||
|
||||
@BeforeEach
|
||||
public void setupMocks() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
|
||||
when(callback.getBridge(BRIDGE_UID)).thenReturn(bridge);
|
||||
when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
|
||||
.thenAnswer(i -> ChannelBuilder.create((ChannelUID) i.getArgument(0)).withType(i.getArgument(1)));
|
||||
doAnswer(i -> {
|
||||
thing = i.getArgument(0);
|
||||
thingHandler.thingUpdated(thing);
|
||||
return null;
|
||||
}).when(callback).thingUpdated(any(Thing.class));
|
||||
|
||||
when(bridge.getStatusInfo()).thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
|
||||
when(bridge.getHandler()).thenReturn(bridgeHandler);
|
||||
|
||||
when(bridgeHandler.getWebSocketConnection()).thenReturn(webSocketConnection);
|
||||
when(bridgeHandler.getBridgeFullState())
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(bridgeFullState)));
|
||||
|
||||
when(bridgeFullState.getMessage(ResourceType.SENSORS, "1")).thenAnswer(i -> deconzMessage);
|
||||
}
|
||||
|
||||
protected void createThing(ThingTypeUID thingTypeUID, List<String> channels,
|
||||
BiFunction<Thing, Gson, DeconzBaseThingHandler> handlerSupplier) {
|
||||
ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, THING_UID);
|
||||
thingBuilder.withBridge(BRIDGE_UID);
|
||||
for (String channelId : channels) {
|
||||
Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, channelId))
|
||||
.withType(new ChannelTypeUID(BINDING_ID, channelId)).build();
|
||||
thingBuilder.withChannel(channel);
|
||||
}
|
||||
thingBuilder.withConfiguration(new Configuration(Map.of(CONFIG_ID, "1")));
|
||||
thing = thingBuilder.build();
|
||||
|
||||
thingHandler = handlerSupplier.apply(thing, gson);
|
||||
thingHandler.setCallback(callback);
|
||||
}
|
||||
|
||||
protected void assertThing(String fileName, Set<TestParam> expected) throws IOException {
|
||||
deconzMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson);
|
||||
|
||||
thingHandler.initialize();
|
||||
|
||||
ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
|
||||
verify(callback, atLeast(2).description("assertQuantityOfStatusUpdates")).statusUpdated(eq(thing),
|
||||
captor.capture());
|
||||
|
||||
List<ThingStatusInfo> statusInfoList = captor.getAllValues();
|
||||
assertThat("assertFirstThingStatus", statusInfoList.get(0).getStatus(), is(ThingStatus.UNKNOWN));
|
||||
assertThat("assertLastThingStatus", statusInfoList.get(statusInfoList.size() - 1).getStatus(),
|
||||
is(ThingStatus.ONLINE));
|
||||
|
||||
assertThat("assertChannelCount:" + getAllChannels(thing), thing.getChannels().size(), is(expected.size()));
|
||||
for (TestParam testParam : expected) {
|
||||
Channel channel = thing.getChannel(testParam.channelId());
|
||||
assertThat("assertNonNullChannel:" + testParam.channelId, channel, is(notNullValue()));
|
||||
|
||||
State state = testParam.state;
|
||||
if (channel != null && state != null) {
|
||||
verify(callback, times(3).description(channel + " did not receive an update"))
|
||||
.stateUpdated(eq(channel.getUID()), eq(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getAllChannels(Thing thing) {
|
||||
return thing.getChannels().stream().map(Channel::getUID).map(ChannelUID::getId)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
protected record TestParam(String channelId, @Nullable State state) {
|
||||
}
|
||||
}
|
||||
@@ -12,69 +12,26 @@
|
||||
*/
|
||||
package org.openhab.binding.deconz.internal.handler;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.binding.deconz.DeconzTest;
|
||||
import org.openhab.binding.deconz.internal.Util;
|
||||
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
|
||||
import org.openhab.binding.deconz.internal.dto.SensorMessage;
|
||||
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
|
||||
import org.openhab.binding.deconz.internal.types.LightType;
|
||||
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
|
||||
import org.openhab.binding.deconz.internal.types.ResourceType;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatMode;
|
||||
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.test.java.JavaTest;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
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.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link SensorThermostatThingHandlerTest} contains test classes for the {@link SensorThermostatThingHandler}
|
||||
*
|
||||
@@ -83,72 +40,20 @@ import com.google.gson.GsonBuilder;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class SensorThermostatThingHandlerTest extends JavaTest {
|
||||
|
||||
private static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE, "bridge");
|
||||
private static final ThingUID THING_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thing");
|
||||
|
||||
private @Mock @NonNullByDefault({}) Bridge bridge;
|
||||
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
|
||||
|
||||
private @Mock @NonNullByDefault({}) DeconzBridgeHandler bridgeHandler;
|
||||
private @Mock @NonNullByDefault({}) WebSocketConnection webSocketConnection;
|
||||
private @Mock @NonNullByDefault({}) BridgeFullState bridgeFullState;
|
||||
|
||||
private @NonNullByDefault({}) Gson gson;
|
||||
private @NonNullByDefault({}) Thing thing;
|
||||
private @NonNullByDefault({}) SensorThermostatThingHandler thingHandler;
|
||||
private @NonNullByDefault({}) SensorMessage sensorMessage;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
|
||||
ThingBuilder thingBuilder = ThingBuilder.create(THING_TYPE_THERMOSTAT, THING_UID);
|
||||
thingBuilder.withBridge(BRIDGE_UID);
|
||||
for (String channelId : List.of(CHANNEL_TEMPERATURE, CHANNEL_HEATSETPOINT, CHANNEL_THERMOSTAT_MODE,
|
||||
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_LAST_UPDATED)) {
|
||||
Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, channelId))
|
||||
.withType(new ChannelTypeUID(BINDING_ID, channelId)).build();
|
||||
thingBuilder.withChannel(channel);
|
||||
}
|
||||
thingBuilder.withConfiguration(new Configuration(Map.of(CONFIG_ID, "1")));
|
||||
thing = thingBuilder.build();
|
||||
|
||||
thingHandler = new SensorThermostatThingHandler(thing, gson);
|
||||
thingHandler.setCallback(callback);
|
||||
|
||||
when(callback.getBridge(BRIDGE_UID)).thenReturn(bridge);
|
||||
when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
|
||||
.thenAnswer(i -> ChannelBuilder.create((ChannelUID) i.getArgument(0)).withType(i.getArgument(1)));
|
||||
doAnswer(i -> {
|
||||
thing = i.getArgument(0);
|
||||
thingHandler.thingUpdated(thing);
|
||||
return null;
|
||||
}).when(callback).thingUpdated(any(Thing.class));
|
||||
|
||||
when(bridge.getStatusInfo()).thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
|
||||
when(bridge.getHandler()).thenReturn(bridgeHandler);
|
||||
|
||||
when(bridgeHandler.getWebSocketConnection()).thenReturn(webSocketConnection);
|
||||
when(bridgeHandler.getBridgeFullState())
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(bridgeFullState)));
|
||||
|
||||
when(bridgeFullState.getMessage(ResourceType.SENSORS, "1")).thenAnswer(i -> sensorMessage);
|
||||
}
|
||||
public class SensorThermostatThingHandlerTest extends BaseDeconzThingHandlerTest {
|
||||
|
||||
@Test
|
||||
public void testDanfoss() throws IOException {
|
||||
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
|
||||
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("21.45 °C")),
|
||||
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("21.00 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("HEAT")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2023-03-18T05:52:29.506")),
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("21.45 °C")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("HEAT")),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(41)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
|
||||
@@ -156,99 +61,80 @@ public class SensorThermostatThingHandlerTest extends JavaTest {
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2023-03-18T05:58Z")),
|
||||
// dynamic channels
|
||||
new TestParam(CHANNEL_EXTERNAL_WINDOW_OPEN, OpenClosedType.CLOSED),
|
||||
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("1 %")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_LOCKED, OnOffType.OFF),
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.OFF),
|
||||
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("1 %")),
|
||||
new TestParam(CHANNEL_WINDOW_OPEN, OpenClosedType.CLOSED));
|
||||
|
||||
assertThermostat("json/thermostat/danfoss.json", expected);
|
||||
assertThing("json/thermostat/danfoss.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamron() throws IOException {
|
||||
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_TEMPERATURE, CHANNEL_HEATSETPOINT, CHANNEL_THERMOSTAT_MODE,
|
||||
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_LAST_UPDATED), SensorThermostatThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("20.39 °C")),
|
||||
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("22.00 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("OFF")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2023-03-18T18:10:39.296")),
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("20.39 °C")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("OFF")),
|
||||
// last seen
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2023-03-18T18:10Z")),
|
||||
// dynamic channels
|
||||
new TestParam(CHANNEL_THERMOSTAT_LOCKED, OnOffType.OFF),
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.OFF));
|
||||
|
||||
assertThermostat("json/thermostat/namron_ZB_E1.json", expected);
|
||||
assertThing("json/thermostat/namron_ZB_E1.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEurotronicValid() throws IOException {
|
||||
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
|
||||
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
|
||||
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("25.00 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(85)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
|
||||
// last seen
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
|
||||
// dynamic channels
|
||||
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("99 %")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON));
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON),
|
||||
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("99 %")));
|
||||
|
||||
assertThermostat("json/thermostat/eurotronic.json", expected);
|
||||
assertThing("json/thermostat/eurotronic.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEurotronicInvalid() throws IOException {
|
||||
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
|
||||
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
|
||||
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("25.00 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
|
||||
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
|
||||
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(85)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
|
||||
// last seen
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
|
||||
// dynamic channels
|
||||
new TestParam(CHANNEL_VALVE_POSITION, UnDefType.UNDEF),
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON));
|
||||
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON),
|
||||
new TestParam(CHANNEL_VALVE_POSITION, UnDefType.UNDEF));
|
||||
|
||||
assertThermostat("json/thermostat/eurotronic-invalid.json", expected);
|
||||
}
|
||||
|
||||
private void assertThermostat(String fileName, Set<TestParam> expected) throws IOException {
|
||||
sensorMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson);
|
||||
|
||||
thingHandler.initialize();
|
||||
|
||||
ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
|
||||
verify(callback, times(6)).statusUpdated(eq(thing), captor.capture());
|
||||
|
||||
List<ThingStatusInfo> statusInfoList = captor.getAllValues();
|
||||
assertThat(statusInfoList.get(0).getStatus(), is(ThingStatus.UNKNOWN));
|
||||
assertThat(statusInfoList.get(5).getStatus(), is(ThingStatus.ONLINE));
|
||||
|
||||
assertThat(thing.getChannels().size(), is(expected.size()));
|
||||
for (TestParam testParam : expected) {
|
||||
Channel channel = thing.getChannel(testParam.channelId());
|
||||
assertThat(channel + "expected but missing", channel, is(notNullValue()));
|
||||
|
||||
State state = testParam.state;
|
||||
if (state != null) {
|
||||
verify(callback, times(3).description(channel + " did not receive an update"))
|
||||
.stateUpdated(eq(channel.getUID()), eq(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record TestParam(String channelId, @Nullable State state) {
|
||||
assertThing("json/thermostat/eurotronic-invalid.json", expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.deconz.internal.handler;
|
||||
|
||||
import static org.openhab.binding.deconz.internal.BindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.binding.deconz.internal.Util;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link SensorThingHandlerTest} contains test classes for the {@link SensorThingHandler}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class SensorThingHandlerTest extends BaseDeconzThingHandlerTest {
|
||||
|
||||
@Test
|
||||
public void testAirQuality() throws IOException {
|
||||
createThing(THING_TYPE_AIRQUALITY_SENSOR,
|
||||
List.of(CHANNEL_AIRQUALITY, CHANNEL_AIRQUALITYPPB, CHANNEL_LAST_UPDATED), SensorThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_AIRQUALITY, new StringType("good")),
|
||||
new TestParam(CHANNEL_AIRQUALITYPPB, new QuantityType<>("129 ppb")),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2021-12-29T01:18:41.184")),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
|
||||
// last seen
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2021-12-29T01:18Z")));
|
||||
|
||||
assertThing("json/sensors/airquality.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCarbonMonoxide() throws IOException {
|
||||
createThing(THING_TYPE_CARBONMONOXIDE_SENSOR, List.of(CHANNEL_CARBONMONOXIDE, CHANNEL_LAST_UPDATED),
|
||||
SensorThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_CARBONMONOXIDE, OnOffType.ON),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, UnDefType.UNDEF),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF));
|
||||
|
||||
assertThing("json/sensors/carbonmonoxide.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFire() throws IOException {
|
||||
createThing(THING_TYPE_FIRE_SENSOR, List.of(CHANNEL_FIRE, CHANNEL_LAST_UPDATED), SensorThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_FIRE, OnOffType.OFF), new TestParam(CHANNEL_LAST_UPDATED, UnDefType.UNDEF),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(98)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF));
|
||||
|
||||
assertThing("json/sensors/fire.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitch() throws IOException {
|
||||
createThing(THING_TYPE_SWITCH, List.of(CHANNEL_BUTTON, CHANNEL_BUTTONEVENT, CHANNEL_LAST_UPDATED),
|
||||
SensorThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_BUTTON, new DecimalType(1002)), new TestParam(CHANNEL_BUTTONEVENT, null),
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2022-02-15T13:36:16.271")),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(7)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.ON));
|
||||
|
||||
assertThing("json/sensors/switch.json", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVibration() throws IOException {
|
||||
createThing(THING_TYPE_VIBRATION_SENSOR, List.of(CHANNEL_LAST_UPDATED, CHANNEL_VIBRATION),
|
||||
SensorThingHandler::new);
|
||||
|
||||
Set<TestParam> expected = Set.of(
|
||||
// standard channels
|
||||
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2022-09-09T18:13:44.653")),
|
||||
new TestParam(CHANNEL_VIBRATION, OnOffType.ON),
|
||||
// battery
|
||||
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
|
||||
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
|
||||
// last seen
|
||||
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2022-09-09T18:13Z")),
|
||||
// dynamic channels
|
||||
new TestParam(CHANNEL_ORIENTATION_X, new DecimalType(3)),
|
||||
new TestParam(CHANNEL_ORIENTATION_Y, new DecimalType(-1)),
|
||||
new TestParam(CHANNEL_ORIENTATION_Z, new DecimalType(-87)),
|
||||
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("26.00 °C")),
|
||||
new TestParam(CHANNEL_TILTANGLE, new QuantityType<>("176 °")),
|
||||
new TestParam(CHANNEL_VIBRATION_STRENGTH, new DecimalType(110)));
|
||||
|
||||
assertThing("json/sensors/vibration.json", expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"alert": "none",
|
||||
"battery": 7,
|
||||
"group": "8",
|
||||
"on": true,
|
||||
"reachable": true
|
||||
},
|
||||
"ep": 1,
|
||||
"etag": "43c4c267ebf1239c94791be02d3927c8",
|
||||
"lastannounced": null,
|
||||
"lastseen": null,
|
||||
"manufacturername": "IKEA of Sweden",
|
||||
"mode": 3,
|
||||
"modelid": "TRADFRI remote control",
|
||||
"name": "TRÅDFR Laura",
|
||||
"state": {
|
||||
"buttonevent": 1002,
|
||||
"lastupdated": "2022-02-15T13:36:16.271"
|
||||
},
|
||||
"swversion": "2.3.014",
|
||||
"type": "ZHASwitch",
|
||||
"uniqueid": "90:fd:9f:ff:fe:17:75:a3-01-1000"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"config": {
|
||||
"battery": 100,
|
||||
"on": true,
|
||||
"pending": [],
|
||||
"reachable": true,
|
||||
"sensitivity": null,
|
||||
"sensitivitymax": 21,
|
||||
"temperature": 2600
|
||||
},
|
||||
"ep": 1,
|
||||
"etag": "6c0452972eb8183b5604899c66a3e32f",
|
||||
"lastannounced": null,
|
||||
"lastseen": "2022-09-09T18:13Z",
|
||||
"manufacturername": "LUMI",
|
||||
"modelid": "lumi.vibration.aq1",
|
||||
"name": "Vibration Sensor",
|
||||
"state": {
|
||||
"lastupdated": "2022-09-09T18:13:44.653",
|
||||
"orientation": [
|
||||
3,
|
||||
-1,
|
||||
-87
|
||||
],
|
||||
"tiltangle": 176,
|
||||
"vibration": true,
|
||||
"vibrationstrength": 110
|
||||
},
|
||||
"swversion": "20180130",
|
||||
"type": "ZHAVibration",
|
||||
"uniqueid": "00:15:8d:00:08:52:a6:2c-01-0101"
|
||||
}
|
||||
Reference in New Issue
Block a user