[wundergroundupdatereceiver] Bugfixes: Regenerate trigger channel with proper type and more metadata. Normalize channel names. It might be easiest to delete and allow recreation of channels. (#13327)

* [wundergroundupdatereceiver] LAST_QUERY parameter should not be mapped automatically
* [wundergroundupdatereceiver] All channeltype props need to be applied
Especially the channel kind
* [wundergroundupdatereceiver] Remove illegal characters from channel name

Additionally expand the channel naming test to assert the generated channelUID and test that _ in names isn't inadvertently replaced

* [wundergroundupdatereceiver] Don't default AutoUpdatePolicy on creation
* [wundergroundupdatereceiver] Migrate changed channel to trigger type

Signed-off-by: Daniel Demus <daniel-github@demus.dk>
This commit is contained in:
Daniel Demus
2022-12-01 22:47:11 +01:00
committed by GitHub
parent 9cc3cd0cf9
commit 43d01ad49c
9 changed files with 241 additions and 37 deletions

View File

@@ -49,6 +49,8 @@ public class WundergroundUpdateReceiverBindingConstants {
public static final String NOW = "now";
public static final String UNCATEGORIZED = "Uncategorized";
// Excluded technical paramter names
public static final String REALTIME_MARKER = "realtime";
public static final String PASSWORD = "PASSWORD";
@@ -56,8 +58,8 @@ public class WundergroundUpdateReceiverBindingConstants {
// List of default synthetic channeltypes added to a new thing
public static final String DATEUTC_DATETIME = "dateutc-datetime";
public static final String LAST_RECEIVED_DATETIME = "last-received-datetime";
public static final String LAST_RECEIVED = "last-received";
public static final String LAST_RECEIVED_DATETIME = LAST_RECEIVED + "-datetime";
public static final String LAST_QUERY = "last-query";
public static final String LAST_QUERY_STATE = LAST_QUERY + "-state";
public static final String LAST_QUERY_TRIGGER = LAST_QUERY + "-trigger";
@@ -72,7 +74,7 @@ public class WundergroundUpdateReceiverBindingConstants {
public static final String PRESSURE_GROUP = "pressure";
public static final String POLLUTION_GROUP = "pollution";
// Known or observed request paramters received from devices submitting to wunderground.com
// Known or observed request parameters received from devices submitting to wunderground.com
public static final String DATEUTC = "dateutc";
public static final String SOFTWARE_TYPE = "softwaretype";
public static final String LOW_BATTERY = "lowbatt";
@@ -125,7 +127,7 @@ public class WundergroundUpdateReceiverBindingConstants {
public static final String AQ_OC = "AqOC";
public static final String AQ_BC = "AqBC";
public static final String AQ_UV_AETH = "AqUV-AETH";
public static final String AQ_PM2_5 = "AqPM2.5";
public static final String AQ_PM2_5 = "AqPM2-5";
public static final String AQ_PM10 = "AqPM10";
public static final String AQ_OZONE = "AqOZONE";

View File

@@ -38,7 +38,7 @@ public class WundergroundUpdateReceiverDiscoveryService extends AbstractDiscover
WundergroundUpdateReceiverServletControls servletControls;
private static final int TIMEOUT_SEC = 1;
private final HashMap<String, Map<String, String[]>> thinglessStationIds = new HashMap<>();
private final HashMap<String, Map<String, String>> thinglessStationIds = new HashMap<>();
private boolean servletWasInactive = false;
private boolean scanning = false;
@@ -57,7 +57,7 @@ public class WundergroundUpdateReceiverDiscoveryService extends AbstractDiscover
thinglessStationIds.remove(stationId);
}
public void addUnhandledStationId(@Nullable String stationId, Map<String, String[]> request) {
public void addUnhandledStationId(@Nullable String stationId, Map<String, String> request) {
if (stationId == null || stationId.isEmpty()) {
return;
}
@@ -73,7 +73,7 @@ public class WundergroundUpdateReceiverDiscoveryService extends AbstractDiscover
return isBackgroundDiscoveryEnabled() || isScanning();
}
public @Nullable Map<String, String[]> getUnhandledStationRequest(@Nullable String stationId) {
public @Nullable Map<String, String> getUnhandledStationRequest(@Nullable String stationId) {
return this.thinglessStationIds.get(stationId);
}

View File

@@ -40,6 +40,7 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.types.Command;
@@ -110,16 +111,15 @@ public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
this.config = getConfigAs(WundergroundUpdateReceiverConfiguration.class);
wundergroundUpdateReceiverServlet.addHandler(this);
@Nullable
Map<String, String[]> requestParameters = discoveryService.getUnhandledStationRequest(config.stationId);
Map<String, String> requestParameters = discoveryService.getUnhandledStationRequest(config.stationId);
if (requestParameters != null && thing.getChannels().isEmpty()) {
final String[] noValues = new String[0];
ThingBuilder thingBuilder = editThing();
List.of(LAST_RECEIVED, LAST_QUERY_TRIGGER, DATEUTC_DATETIME, LAST_QUERY_STATE)
.forEach((String channelId) -> buildChannel(thingBuilder, channelId, noValues));
requestParameters
.forEach((String parameter, String[] query) -> buildChannel(thingBuilder, parameter, query));
.forEach((String channelId) -> buildChannel(thingBuilder, channelId, ""));
requestParameters.forEach((String parameter, String query) -> buildChannel(thingBuilder, parameter, query));
updateThing(thingBuilder.build());
}
migrateChannels();
discoveryService.removeUnhandledStationId(config.stationId);
if (wundergroundUpdateReceiverServlet.isActive()) {
updateStatus(ThingStatus.ONLINE);
@@ -130,6 +130,17 @@ public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
wundergroundUpdateReceiverServlet.getErrorDetail());
}
private void migrateChannels() {
Optional.ofNullable(getThing().getChannel(queryTriggerChannel)).ifPresent(c -> {
if (c.getKind() != ChannelKind.TRIGGER) {
ThingBuilder builder = editThing();
builder.withoutChannel(c.getUID());
buildChannel(builder, LAST_QUERY_TRIGGER, "");
updateThing(builder.build());
}
});
}
@Override
public void dispose() {
wundergroundUpdateReceiverServlet.removeHandler(this.getStationId());
@@ -149,10 +160,10 @@ public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
triggerChannel(queryTriggerChannel, lastQuery);
}
private void buildChannel(ThingBuilder thingBuilder, String parameter, String... query) {
private void buildChannel(ThingBuilder thingBuilder, String parameter, String value) {
@Nullable
WundergroundUpdateReceiverParameterMapping channelTypeMapping = WundergroundUpdateReceiverParameterMapping
.getOrCreateMapping(parameter, String.join("", query), channelTypeProvider);
.getOrCreateMapping(parameter, value, channelTypeProvider);
if (channelTypeMapping == null) {
return;
}
@@ -162,7 +173,10 @@ public class WundergroundUpdateReceiverHandler extends BaseThingHandler {
}
ChannelBuilder channelBuilder = ChannelBuilder
.create(new ChannelUID(thing.getUID(), channelTypeMapping.channelGroup, parameter))
.withType(channelTypeMapping.channelTypeId).withAcceptedItemType(channelType.getItemType());
.withKind(channelType.getKind()).withAutoUpdatePolicy(channelType.getAutoUpdatePolicy())
.withDefaultTags(channelType.getTags()).withType(channelTypeMapping.channelTypeId)
.withAcceptedItemType(channelType.getItemType()).withLabel(channelType.getLabel());
Optional.ofNullable(channelType.getDescription()).ifPresent(channelBuilder::withDescription);
thingBuilder.withChannel(channelBuilder.build());
}

View File

@@ -54,7 +54,7 @@ public class WundergroundUpdateReceiverParameterMapping {
}
private static final List<String> UNMAPPED_PARAMETERS = List.of(STATION_ID_PARAMETER, PASSWORD, ACTION,
REALTIME_MARKER);
REALTIME_MARKER, LAST_QUERY);
private static final WundergroundUpdateReceiverParameterMapping[] KNOWN_MAPPINGS = {
new WundergroundUpdateReceiverParameterMapping(LAST_RECEIVED, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
@@ -173,7 +173,7 @@ public class WundergroundUpdateReceiverParameterMapping {
}
Optional<WundergroundUpdateReceiverParameterMapping> knownMapping = lookupMapping(parameterName);
return knownMapping.orElseGet(() -> new WundergroundUpdateReceiverParameterMapping(parameterName,
channelTypeProvider.getOrCreateChannelType(parameterName, value).getUID(), "Uncategorized", null, false,
channelTypeProvider.getOrCreateChannelType(parameterName, value).getUID(), UNCATEGORIZED, null, false,
null));
}

View File

@@ -27,6 +27,7 @@ import java.util.Hashtable;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -53,6 +54,7 @@ public class WundergroundUpdateReceiverServlet extends BaseOpenHABServlet
public static final String SERVLET_URL = "/weatherstation/updateweatherstation.php";
private static final long serialVersionUID = -5296703727081438023L;
private static final Pattern CLEANER = Pattern.compile("[^\\w-]");
private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverServlet.class);
private final Map<String, WundergroundUpdateReceiverHandler> handlers = new HashMap<>();
@@ -173,6 +175,11 @@ public class WundergroundUpdateReceiverServlet extends BaseOpenHABServlet
}
}
protected Map<String, String> normalizeParameterMap(Map<String, String[]> parameterMap) {
return parameterMap.entrySet().stream()
.collect(toMap(e -> makeUidSafeString(e.getKey()), e -> String.join("", e.getValue())));
}
@Override
protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
if (!active) {
@@ -190,16 +197,15 @@ public class WundergroundUpdateReceiverServlet extends BaseOpenHABServlet
logger.trace("doGet {}", req.getQueryString());
String stationId = req.getParameter(STATION_ID_PARAMETER);
Map<String, String> states = normalizeParameterMap(req.getParameterMap());
Optional.ofNullable(this.handlers.get(stationId)).ifPresentOrElse(handler -> {
Map<String, String> states = req.getParameterMap().entrySet().stream()
.collect(toMap(Map.Entry::getKey, e -> String.join("", e.getValue())));
String queryString = req.getQueryString();
if (queryString != null && queryString.length() > 0) {
states.put(LAST_QUERY, queryString);
}
handler.updateChannelStates(states);
}, () -> {
this.discoveryService.addUnhandledStationId(stationId, req.getParameterMap());
this.discoveryService.addUnhandledStationId(stationId, states);
});
resp.setStatus(HttpServletResponse.SC_OK);
@@ -216,4 +222,8 @@ public class WundergroundUpdateReceiverServlet extends BaseOpenHABServlet
protected Map<String, WundergroundUpdateReceiverHandler> getHandlers() {
return Collections.unmodifiableMap(this.handlers);
}
private String makeUidSafeString(String key) {
return CLEANER.matcher(key).replaceAll("-");
}
}

View File

@@ -13,6 +13,7 @@
package org.openhab.binding.wundergroundupdatereceiver.internal;
import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER;
import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.UNCATEGORIZED;
import java.util.Collection;
import java.util.List;
@@ -22,6 +23,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.thing.type.AutoUpdatePolicy;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeBuilder;
import org.openhab.core.thing.type.ChannelTypeProvider;
@@ -37,7 +40,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class WundergroundUpdateReceiverUnknownChannelTypeProvider implements ChannelTypeProvider {
private static final List<String> BOOLEAN_STRINGS = List.of("1", "0", "true", "false");
private static final List<String> BOOLEAN_STRINGS = List.of("1", "0", "true", "false", "yes", "no", "on", "off");
private final Map<ChannelTypeUID, ChannelType> channelTypes = new ConcurrentHashMap<>();
private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverUnknownChannelTypeProvider.class);
@@ -57,7 +60,8 @@ public class WundergroundUpdateReceiverUnknownChannelTypeProvider implements Cha
ChannelType type = getChannelType(typeUid, null);
if (type == null) {
String itemType = guessItemType(value);
type = ChannelTypeBuilder.state(typeUid, parameterName + " channel type", itemType).build();
type = ChannelTypeBuilder.state(typeUid, parameterName + " channel type", itemType).isAdvanced(true)
.withCategory(UNCATEGORIZED).withAutoUpdatePolicy(AutoUpdatePolicy.DEFAULT).build();
return addChannelType(typeUid, type);
}
return type;
@@ -65,18 +69,18 @@ public class WundergroundUpdateReceiverUnknownChannelTypeProvider implements Cha
private static String guessItemType(String value) {
if (BOOLEAN_STRINGS.contains(value.toLowerCase())) {
return "Switch";
return CoreItemFactory.SWITCH;
}
try {
Float.valueOf(value);
return "Number";
return CoreItemFactory.NUMBER;
} catch (NumberFormatException ignored) {
}
return "String";
return CoreItemFactory.STRING;
}
private ChannelType addChannelType(ChannelTypeUID channelTypeUID, ChannelType channelType) {
logger.warn("Adding channelType {} for unknown parameter", channelTypeUID.getAsString());
logger.warn("Adding new synthetic channelType {} for unrecognised parameter", channelTypeUID.getAsString());
this.channelTypes.put(channelTypeUID, channelType);
return channelType;
}

View File

@@ -59,9 +59,9 @@ channel-type.wundergroundupdatereceiver.indoor-humidity.label = Indoor Humidity
channel-type.wundergroundupdatereceiver.indoor-humidity.description = Indoor humidity in %.
channel-type.wundergroundupdatereceiver.indoor-temperature.label = Indoor Temperature
channel-type.wundergroundupdatereceiver.indoor-temperature.description = Indoor temperature.
channel-type.wundergroundupdatereceiver.last-query-state.label = The last query
channel-type.wundergroundupdatereceiver.last-query-state.label = Last query
channel-type.wundergroundupdatereceiver.last-query-state.description = The query part of the last request from the device
channel-type.wundergroundupdatereceiver.last-query-trigger.label = The last query
channel-type.wundergroundupdatereceiver.last-query-trigger.label = Last query
channel-type.wundergroundupdatereceiver.last-query-trigger.description = The query part of the last request from the device
channel-type.wundergroundupdatereceiver.last-received-datetime.label = Last Received
channel-type.wundergroundupdatereceiver.last-received-datetime.description = The date and time of the last update.

View File

@@ -35,11 +35,13 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
import org.openhab.core.thing.ManagedThingProvider;
import org.openhab.core.thing.Thing;
@@ -48,6 +50,7 @@ import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.internal.type.StateChannelTypeBuilderImpl;
import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelTypeProvider;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeUID;
@@ -69,6 +72,60 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
openMocks(this);
}
@Test
void programmaticChannelsAreAddedCorrectlyOnce() {
// Given
final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "humidity=74&" + "AqPM2.5=30&"
+ "windspdmph_avg2m=10&" + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&"
+ "action=updateraw&" + "realtime=1&" + "rtfreq=5";
MetaData.Request request = new MetaData.Request("GET",
new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
HttpVersion.HTTP_1_1, new HttpFields());
HttpChannel httpChannel = mock(HttpChannel.class);
Request req = new Request(httpChannel, null);
req.setMetaData(request);
TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
true);
HttpService httpService = mock(HttpService.class);
WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req.getParameterMap()));
Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
.withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
.withLabel("test thing").withLocation("location").build();
ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
when(managedThingProvider.get(any())).thenReturn(thing);
WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
handler.setCallback(mock(ThingHandlerCallback.class));
// When
handler.initialize();
var actual = handler.getThing().getChannels();
// Then
assertThat(actual.size(), is(9));
assertChannel(actual.get(0), METADATA_GROUP, LAST_RECEIVED, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
ChannelKind.STATE, is("DateTime"));
assertChannel(actual.get(1), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
ChannelKind.TRIGGER, nullValue());
assertChannel(actual.get(2), METADATA_GROUP, LAST_QUERY_STATE, LAST_QUERY_STATE_CHANNELTYPEUID,
ChannelKind.STATE, is("String"));
assertChannel(actual.get(3), METADATA_GROUP, DATEUTC, DATEUTC_CHANNELTYPEUID, ChannelKind.STATE, is("String"));
assertChannel(actual.get(4), METADATA_GROUP, REALTIME_FREQUENCY, REALTIME_FREQUENCY_CHANNELTYPEUID,
ChannelKind.STATE, is("Number"));
assertChannel(actual.get(5), METADATA_GROUP, SOFTWARE_TYPE, SOFTWARETYPE_CHANNELTYPEUID, ChannelKind.STATE,
is("String"));
assertChannel(actual.get(6), HUMIDITY_GROUP, HUMIDITY, HUMIDITY_CHANNELTYPEUID, ChannelKind.STATE,
is("Number:Dimensionless"));
assertChannel(actual.get(7), WIND_GROUP, WIND_SPEED_AVG_2MIN, WIND_SPEED_AVG_2MIN_CHANNELTYPEUID,
ChannelKind.STATE, is("Number:Speed"));
assertChannel(actual.get(8), POLLUTION_GROUP, AQ_PM2_5, PM2_5_MASS_CHANNELTYPEUID, ChannelKind.STATE,
is("Number:Density"));
}
@Test
void aRequestWithAnUnregisteredStationidIsAddedToTheQueueOnce()
throws ServletException, NamespaceException, IOException {
@@ -124,9 +181,9 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
false);
discoveryService.addUnhandledStationId(REQ_STATION_ID, req.getParameterMap());
HttpService httpService = mock(HttpService.class);
WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req.getParameterMap()));
Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
.withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
.withLabel("test thing").withLocation("location").build();
@@ -170,9 +227,9 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
true);
discoveryService.addUnhandledStationId(REQ_STATION_ID, req1.getParameterMap());
HttpService httpService = mock(HttpService.class);
WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
.withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
.withLabel("test thing").withLocation("location").build();
@@ -237,9 +294,9 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
true);
discoveryService.addUnhandledStationId(REQ_STATION_ID, req1.getParameterMap());
HttpService httpService = mock(HttpService.class);
WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
.withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
.withLabel("test thing").withLocation("location").build();
@@ -285,11 +342,99 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
assertThat(actual, equalTo(before));
}
class TestChannelTypeRegistry extends ChannelTypeRegistry {
@Test
void lastQueryTriggerIsMigratedSuccessfully() throws IOException {
// Given
final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
+ "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
+ "realtime=1&" + "rtfreq=5";
MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
"http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
HttpVersion.HTTP_1_1, new HttpFields());
HttpChannel httpChannel = mock(HttpChannel.class);
Request req1 = new Request(httpChannel, null);
req1.setMetaData(request1);
TestChannelTypeRegistry() {
UpdatingChannelTypeRegistry channelTypeRegistry = new UpdatingChannelTypeRegistry();
WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
true);
HttpService httpService = mock(HttpService.class);
WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
.withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
.withLabel("test thing").withLocation("location").build();
ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
when(managedThingProvider.get(any())).thenReturn(null);
WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
handler.setCallback(mock(ThingHandlerCallback.class));
// When
handler.initialize();
sut.addHandler(handler);
// Then
ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
LAST_QUERY_TRIGGER_CHANNELTYPEUID };
List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
.collect(Collectors.toList());
assertThat(before, hasItems(expectedBefore));
// When
var actual = handler.getThing().getChannels();
// Then
assertThat(actual.size(), is(8));
assertChannel(actual.get(7), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
ChannelKind.STATE, is("DateTime"));
// When
handler.dispose();
handler.initialize();
final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
+ "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
+ "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
MetaData.Request request = new MetaData.Request("GET", new HttpURI(
"http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
HttpVersion.HTTP_1_1, new HttpFields());
Request req2 = new Request(httpChannel, null);
req2.setMetaData(request);
sut.activate();
// Then
assertThat(sut.isActive(), is(true));
// When
sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
actual = handler.getThing().getChannels();
// Then
assertThat(actual.size(), is(8));
assertChannel(actual.get(7), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
ChannelKind.TRIGGER, nullValue());
}
private void assertChannel(Channel actual, String expectedGroup, String expectedName, ChannelTypeUID expectedUid,
ChannelKind expectedKind, Matcher<Object> expectedItemType) {
assertThat(actual, is(notNullValue()));
assertThat(actual.getLabel() + " UID", actual.getUID(),
is(new ChannelUID(TEST_THING_UID, expectedGroup, expectedName)));
assertThat(actual.getLabel() + " ChannelTypeUID", actual.getChannelTypeUID(), is(expectedUid));
assertThat(actual.getLabel() + " Kind", actual.getKind(), is(expectedKind));
assertThat(actual.getLabel() + " AcceptedItemType", actual.getAcceptedItemType(), expectedItemType);
}
abstract class AbstractTestChannelTypeRegistry extends ChannelTypeRegistry {
protected final ChannelTypeProvider provider;
AbstractTestChannelTypeRegistry(ChannelTypeProvider mock) {
super();
ChannelTypeProvider provider = mock(ChannelTypeProvider.class);
this.provider = mock;
when(provider.getChannelType(eq(SOFTWARETYPE_CHANNELTYPEUID), any())).thenReturn(
new StateChannelTypeBuilderImpl(SOFTWARETYPE_CHANNELTYPEUID, "Software type", "String").build());
when(provider.getChannelType(eq(TEMPERATURE_CHANNELTYPEUID), any()))
@@ -303,6 +448,11 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
when(provider.getChannelType(eq(HUMIDITY_CHANNELTYPEUID), any())).thenReturn(
new StateChannelTypeBuilderImpl(HUMIDITY_CHANNELTYPEUID, "Humidity", "Number:Dimensionless")
.build());
when(provider.getChannelType(eq(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID), any()))
.thenReturn(new StateChannelTypeBuilderImpl(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID,
"Wind Speed 2min Average", "Number:Speed").build());
when(provider.getChannelType(eq(PM2_5_MASS_CHANNELTYPEUID), any())).thenReturn(
new StateChannelTypeBuilderImpl(PM2_5_MASS_CHANNELTYPEUID, "PM2.5 Mass", "Number:Density").build());
when(provider.getChannelType(eq(DATEUTC_CHANNELTYPEUID), any())).thenReturn(
new StateChannelTypeBuilderImpl(DATEUTC_CHANNELTYPEUID, "Last Updated", "String").build());
when(provider.getChannelType(eq(LOW_BATTERY_CHANNELTYPEUID), any()))
@@ -316,10 +466,31 @@ class WundergroundUpdateReceiverDiscoveryServiceTest {
when(provider.getChannelType(eq(LAST_RECEIVED_DATETIME_CHANNELTYPEUID), any())).thenReturn(
new StateChannelTypeBuilderImpl(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Last Received", "DateTime")
.build());
when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
this.addChannelTypeProvider(provider);
this.addChannelTypeProvider(new WundergroundUpdateReceiverUnknownChannelTypeProvider());
}
}
class TestChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
TestChannelTypeRegistry() {
super(mock(ChannelTypeProvider.class));
when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
}
}
class UpdatingChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
UpdatingChannelTypeRegistry() {
super(mock(ChannelTypeProvider.class));
when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any()))
.thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
"DateTime").build())
.thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
"DateTime").build())
.thenReturn(new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query")
.build());
}
}
}