mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
9cc3cd0cf9
commit
43d01ad49c
@ -6,7 +6,7 @@ This binding enables acting as a receiver of updates from devices that post meas
|
||||
If the hostname is configurable - as on weather stations based on the Fine Offset Electronics WH2600-IP - this is simple, otherwise you have to set up dns such that it resolves the above hostname to your server, without preventing the server from resolving the proper ip if you want to forward the request.
|
||||
|
||||
The server thus listens at http(s)://<your-openHAB-server>:<openHAB-port>/weatherstation/updateweatherstation.php and the device needs to be pointed at this address.
|
||||
If you can't configure the device itself to submit to an alternate hostname you would need to set up a dns server that resolves rtupdate.wunderground.com to the IP-address of your server and provide as dns to the device does DHCP.
|
||||
If you can't configure the device itself to submit to an alternate hostname you would need to set up a dns server that resolves rtupdate.wunderground.com to the IP-address of your server and provide it as the DHCP dns-server to the device.
|
||||
Make sure not to use this dns server instance for any other DHCP clients.
|
||||
|
||||
The request is in itself simple to parse, so by redirecting it to your openHAB server you can intercept the values and use them to control items in your home.
|
||||
@ -19,6 +19,7 @@ It can also be used to submit the same measurements to multiple weather services
|
||||
## Supported Things
|
||||
|
||||
Any device that sends weather measurement updates to the wunderground.com update URLs is supported.
|
||||
Multiple devices submitting to the same wunderground account ID can be aggregated.
|
||||
It is easiest to use with devices that have a configurable target address, but can be made to work with any internet-connected device, that gets its dns server via DHCP or where the DNS server can be set.
|
||||
|
||||
## Discovery
|
||||
@ -42,7 +43,9 @@ If you don't plan on submitting measurements to wunderground.com, it can be any
|
||||
Each measurement type the wunderground.com update service accepts has a channel.
|
||||
The channels must be named exactly as the request parameter they receive.
|
||||
I.e. the wind speed channel must be named `windspeedmph` as that is the request parameter name defined by Wunderground in their API.
|
||||
The channel name set up in the binding should be considered an id with no semantic content other than pointing to the wounderground API.
|
||||
Illegal channel id characters are converted to -.
|
||||
For example, AqPM2.5 has a channel named `AqPM2-5`.
|
||||
The channel name set up in the binding should be considered an id with no semantic content other than pointing to the wunderground API.
|
||||
Additionally there is a receipt timestamp and a trigger channel.
|
||||
|
||||
### Request parameters are mapped to one of the following channel-types:
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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("-");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user