[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.