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;
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);
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());
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;
}
}
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());
}
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;
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;
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 {
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();
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();
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();
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()))
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()))
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());
+ }
+ }
}