[deconz] Add Pairing/Scene actions, new devices and improve code (#14622)

* port changes
* update instructions
* Incorporate review comments from #14134
* new improvements (mostly Java 17 changes)
* further improvements

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K
2023-03-18 16:06:55 +01:00
committed by GitHub
parent 23f3374ea9
commit ee1de11864
48 changed files with 1814 additions and 824 deletions

View File

@@ -60,7 +60,7 @@ import com.google.gson.GsonBuilder;
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@MockitoSettings(strictness = Strictness.WARN)
@NonNullByDefault
public class DeconzTest {
private @NonNullByDefault({}) Gson gson;

View File

@@ -0,0 +1,89 @@
/**
* 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.CHANNEL_ALL_ON;
import static org.openhab.binding.deconz.internal.BindingConstants.CHANNEL_ANY_ON;
import static org.openhab.binding.deconz.internal.BindingConstants.THING_TYPE_LIGHTGROUP;
import static org.openhab.core.thing.internal.ThingManagerImpl.PROPERTY_THING_TYPE_VERSION;
import java.io.IOException;
import java.util.Map;
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.DeconzDynamicCommandDescriptionProvider;
import org.openhab.binding.deconz.internal.dto.GroupMessage;
import org.openhab.binding.deconz.internal.handler.GroupThingHandler;
import org.openhab.binding.deconz.internal.types.GroupType;
import org.openhab.binding.deconz.internal.types.GroupTypeDeserializer;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.library.types.OnOffType;
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 light groups
*
* @author Christoph Weitkamp - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class LightGroupTest {
private @NonNullByDefault({}) Gson gson;
private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
private @Mock @NonNullByDefault({}) DeconzDynamicCommandDescriptionProvider commandDescriptionProvider;
@BeforeEach
public void initialize() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(GroupType.class, new GroupTypeDeserializer());
gson = gsonBuilder.create();
}
@Test
public void lightGroupUpdateTest() throws IOException {
GroupMessage lightMessage = DeconzTest.getObjectFromJson("group.json", GroupMessage.class, gson);
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "lightgroup");
ChannelUID channelUIDAllOn = new ChannelUID(thingUID, CHANNEL_ALL_ON);
ChannelUID channelUIDAnyOn = new ChannelUID(thingUID, CHANNEL_ANY_ON);
Thing group = ThingBuilder.create(THING_TYPE_LIGHTGROUP, thingUID)
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDAllOn, CoreItemFactory.SWITCH).build())
.withChannel(ChannelBuilder.create(channelUIDAnyOn, CoreItemFactory.SWITCH).build()).build();
GroupThingHandler groupThingHandler = new GroupThingHandler(group, gson, commandDescriptionProvider);
groupThingHandler.setCallback(thingHandlerCallback);
groupThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDAllOn), eq(OnOffType.OFF));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDAnyOn), eq(OnOffType.OFF));
}
}

View File

@@ -15,6 +15,7 @@ package org.openhab.binding.deconz;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.*;
import static org.openhab.binding.deconz.internal.BindingConstants.*;
import static org.openhab.core.thing.internal.ThingManagerImpl.PROPERTY_THING_TYPE_VERSION;
import java.io.IOException;
import java.util.HashMap;
@@ -77,34 +78,36 @@ public class LightsTest {
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_bri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUID_ct = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE);
ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE);
Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID)
.withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build())
.withChannel(ChannelBuilder.create(channelUID_ct, "Number").build()).build();
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build())
.withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider);
lightThingHandler.setCallback(thingHandlerCallback);
lightThingHandler.messageReceived("", lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_bri), eq(new PercentType("21")));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_ct), eq(new DecimalType("2500")));
lightThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("21")));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDCt), eq(new DecimalType("2500")));
}
@Test
public void colorTemperatureLightStateDescriptionProviderTest() {
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_bri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUID_ct = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE);
ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUIDCt = new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE);
Map<String, String> properties = new HashMap<>();
properties.put(PROPERTY_CT_MAX, "500");
properties.put(PROPERTY_CT_MIN, "200");
properties.put(PROPERTY_THING_TYPE_VERSION, "1");
Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID).withProperties(properties)
.withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build())
.withChannel(ChannelBuilder.create(channelUID_ct, "Number").build()).build();
.withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build())
.withChannel(ChannelBuilder.create(channelUIDCt, "Number").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider) {
// avoid warning when initializing
@@ -116,7 +119,7 @@ public class LightsTest {
lightThingHandler.initialize();
Mockito.verify(stateDescriptionProvider).setDescriptionFragment(eq(channelUID_ct), any());
Mockito.verify(stateDescriptionProvider).setDescriptionFragment(eq(channelUIDCt), any());
}
@Test
@@ -125,16 +128,17 @@ public class LightsTest {
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_bri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
.withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider);
lightThingHandler.setCallback(thingHandlerCallback);
lightThingHandler.messageReceived("", lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_bri), eq(new PercentType("38")));
lightThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("38")));
}
@Test
@@ -143,16 +147,17 @@ public class LightsTest {
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_bri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
.withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider);
lightThingHandler.setCallback(thingHandlerCallback);
lightThingHandler.messageReceived("", lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_bri), eq(new PercentType("100")));
lightThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("100")));
}
@Test
@@ -161,16 +166,17 @@ public class LightsTest {
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_bri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
ChannelUID channelUIDBri = new ChannelUID(thingUID, CHANNEL_BRIGHTNESS);
Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
.withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDBri, "Dimmer").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider);
lightThingHandler.setCallback(thingHandlerCallback);
lightThingHandler.messageReceived("", lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_bri), eq(new PercentType("0")));
lightThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDBri), eq(new PercentType("0")));
}
@Test
@@ -179,15 +185,16 @@ public class LightsTest {
assertNotNull(lightMessage);
ThingUID thingUID = new ThingUID("deconz", "light");
ChannelUID channelUID_pos = new ChannelUID(thingUID, CHANNEL_POSITION);
ChannelUID channelUIDPos = new ChannelUID(thingUID, CHANNEL_POSITION);
Thing light = ThingBuilder.create(THING_TYPE_WINDOW_COVERING, thingUID)
.withChannel(ChannelBuilder.create(channelUID_pos, "Rollershutter").build()).build();
.withProperties(Map.of(PROPERTY_THING_TYPE_VERSION, "1"))
.withChannel(ChannelBuilder.create(channelUIDPos, "Rollershutter").build()).build();
LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
commandDescriptionProvider);
lightThingHandler.setCallback(thingHandlerCallback);
lightThingHandler.messageReceived("", lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID_pos), eq(new PercentType("41")));
lightThingHandler.messageReceived(lightMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUIDPos), eq(new PercentType("41")));
}
}

View File

@@ -44,6 +44,7 @@ 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.types.UnDefType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -82,7 +83,7 @@ public class SensorsTest {
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
sensorThingHandler.setCallback(thingHandlerCallback);
sensorThingHandler.messageReceived("", sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(OnOffType.ON));
}
@@ -100,7 +101,7 @@ public class SensorsTest {
sensorThingHandler.setCallback(thingHandlerCallback);
// ACT
sensorThingHandler.messageReceived("", sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
// ASSERT
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(StringType.valueOf("good")));
@@ -120,10 +121,10 @@ public class SensorsTest {
sensorThingHandler.setCallback(thingHandlerCallback);
// ACT
sensorThingHandler.messageReceived("", sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
// ASSERT
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(new DecimalType(129)));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(new QuantityType<>("129 ppb")));
}
@Test
@@ -144,15 +145,23 @@ public class SensorsTest {
SensorThermostatThingHandler sensorThingHandler = new SensorThermostatThingHandler(sensor, gson);
sensorThingHandler.setCallback(thingHandlerCallback);
sensorThingHandler.messageReceived("", sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelValveUID),
eq(new QuantityType<>(100.0, Units.PERCENT)));
sensorMessage = DeconzTest.getObjectFromJson("thermostat-undef.json", SensorMessage.class, gson);
assertNotNull(sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelValveUID), eq(UnDefType.UNDEF));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelHeatSetPointUID),
eq(new QuantityType<>(25, SIUnits.CELSIUS)));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelModeUID),
eq(new StringType(ThermostatMode.AUTO.name())));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelTemperatureUID),
eq(new QuantityType<>(16.5, SIUnits.CELSIUS)));
sensorMessage = DeconzTest.getObjectFromJson("thermostat.json", SensorMessage.class, gson);
assertNotNull(sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelValveUID),
eq(new QuantityType<>(99, Units.PERCENT)));
}
@Test
@@ -174,7 +183,7 @@ public class SensorsTest {
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
sensorThingHandler.setCallback(thingHandlerCallback);
sensorThingHandler.messageReceived("", sensorMessage);
sensorThingHandler.messageReceived(sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelFireUID), eq(OnOffType.OFF));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelBatteryLevelUID), eq(new DecimalType(98)));

View File

@@ -3,7 +3,7 @@
"battery": 98,
"on": true,
"pending" : [],
"reachable": false
"reachable": true
},
"ep": 1,
"etag": "717549a99371f3ea1a5f0b40f1537094",

View File

@@ -0,0 +1,30 @@
{
"action": {
"alert": "none",
"bri": 127,
"colormode": "hs",
"ct": 0,
"effect": "none",
"hue": 0,
"on": false,
"sat": 127,
"scene": null,
"xy": [
0,
0
]
},
"devicemembership": [
"3"
],
"etag": "586d2448a818aa7f6f3baa4907f43468",
"id": "1",
"lights": [],
"name": "RM01",
"scenes": [],
"state": {
"all_on": false,
"any_on": false
},
"type": "LightGroup"
}

View File

@@ -0,0 +1,27 @@
{
"config": {
"battery": 85,
"displayflipped": null,
"heatsetpoint": 2500,
"locked": null,
"mode": "auto",
"offset": 0,
"on": true,
"reachable": true
},
"ep": 1,
"etag": "717549a99371f3ea1a5f0b40f1537094",
"lastseen": "2020-05-31T20:24:55.819",
"manufacturername": "Eurotronic",
"modelid": "SPZB0001",
"name": "Test Thermostat",
"state": {
"lastupdated": "2020-05-31T20:24:55.819",
"on": true,
"temperature": 1650,
"valve": 255
},
"swversion": "20191014",
"type": "ZHAThermostat",
"uniqueid": "00:15:8d:00:01:ff:8a:00-01-0201"
}

View File

@@ -19,7 +19,7 @@
"lastupdated": "2020-05-31T20:24:55.819",
"on": true,
"temperature": 1650,
"valve": 255
"valve": 99
},
"swversion": "20191014",
"type": "ZHAThermostat",