From: Christoph Weitkamp Date: Wed, 31 Mar 2021 19:42:33 +0000 (+0200) Subject: Set channel states to UNDEF instead of NULL; Fixed SAT warnings (#10421) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=12b0908806d9f482960a0fd6e3d7106900101d2a;p=openhab-addons.git Set channel states to UNDEF instead of NULL; Fixed SAT warnings (#10421) Signed-off-by: Christoph Weitkamp --- diff --git a/bundles/org.openhab.binding.dwdunwetter/README.md b/bundles/org.openhab.binding.dwdunwetter/README.md index 4d57b40948..b8ee201031 100644 --- a/bundles/org.openhab.binding.dwdunwetter/README.md +++ b/bundles/org.openhab.binding.dwdunwetter/README.md @@ -132,7 +132,9 @@ then end ``` + dwdunwetter_de.map + ``` ON=aktiv OFF=inaktiv @@ -151,8 +153,6 @@ NULL=undefiniert UNDEF=undefiniert ``` - - dwdunwetter_urgency_de.map ``` diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCache.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCache.java deleted file mode 100644 index decccf123c..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCache.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -/** - * Cache of Warnings to update the {@link DwdUnwetterBindingConstants#CHANNEL_UPDATED} if a new warning is sent to a - * channel. - * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningCache { - - // Remove Entries 30 Minutes after they expired - private static final long WAIT_TIME_IN_MINUTES = 30; - - private final Map idExpiresMap; - - public DwdWarningCache() { - idExpiresMap = new HashMap<>(); - } - - private boolean isExpired(Entry entry) { - Instant expireTime = entry.getValue().plus(WAIT_TIME_IN_MINUTES, ChronoUnit.MINUTES); - return Instant.now().isAfter(expireTime); - } - - /** - * Adds a Warning - * - * @param data The warning data - * @return true if it is a new warning, false if the warning is not new. - */ - public boolean addEntry(DwdWarningData data) { - return idExpiresMap.put(data.getId(), data.getExpires()) == null; - } - - /** - * Removes the expired Entries - */ - public void deleteOldEntries() { - List oldEntries = idExpiresMap.entrySet().stream().filter(this::isExpired).map(Entry::getKey) - .collect(Collectors.toList()); - oldEntries.forEach(idExpiresMap::remove); - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningData.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningData.java deleted file mode 100644 index 389fe9e384..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningData.java +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.math.BigDecimal; -import java.time.Instant; - -/** - * Data for one warning. - * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningData { - - private String id; - - private Severity severity; - private String description; - private Instant effective; - private Instant expires; - private Instant onset; - private String event; - private String status; - private String msgType; - private String headline; - private BigDecimal altitude; - private BigDecimal ceiling; - private String instruction; - private Urgency urgency; - - public void setId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - public void setSeverity(Severity severity) { - this.severity = severity; - } - - public Severity getSeverity() { - return severity == null ? Severity.UNKNOWN : severity; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - - public void setEffective(Instant effective) { - this.effective = effective; - } - - public Instant getEffective() { - return effective; - } - - public void setExpires(Instant expires) { - this.expires = expires; - } - - public Instant getExpires() { - return expires; - } - - public void setEvent(String event) { - this.event = event; - } - - public String getEvent() { - return event; - } - - public void setStatus(String status) { - this.status = status; - } - - public boolean isTest() { - return "Test".equalsIgnoreCase(status); - } - - public void setMsgType(String msgType) { - this.msgType = msgType; - } - - public boolean isCancel() { - return "Cancel".equalsIgnoreCase(msgType); - } - - public void setHeadline(String headline) { - this.headline = headline; - } - - public String getHeadline() { - return headline; - } - - public Instant getOnset() { - return onset; - } - - public void setOnset(Instant onset) { - this.onset = onset; - } - - public void setAltitude(BigDecimal altitude) { - this.altitude = altitude; - } - - public BigDecimal getAltitude() { - return altitude; - } - - public void setCeiling(BigDecimal ceiling) { - this.ceiling = ceiling; - } - - public BigDecimal getCeiling() { - return ceiling; - } - - public void setInstruction(String instruction) { - this.instruction = instruction; - } - - public String getInstruction() { - return instruction; - } - - public void setUrgency(Urgency urgency) { - this.urgency = urgency; - } - - public Urgency getUrgency() { - return urgency; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((id == null) ? 0 : id.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - DwdWarningData other = (DwdWarningData) obj; - if (id == null) { - if (other.id != null) { - return false; - } - } else if (!id.equals(other.id)) { - return false; - } - return true; - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningDataAccess.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningDataAccess.java deleted file mode 100644 index 9c54ae05cf..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningDataAccess.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import org.openhab.core.io.net.http.HttpUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Provides the access to the API Endpoint - * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningDataAccess { - - private final Logger logger = LoggerFactory.getLogger(DwdWarningDataAccess.class); - - // URL of the Service - private static final String DWD_URL = "https://maps.dwd.de/geoserver/dwd/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=dwd:Warnungen_Gemeinden"; - - /** - * Returns the raw Data from the Endpoint. - * In case of errors or empty cellId value, returns an empty String. - * - * @param cellId The warnCell-Id for which the warnings should be returned - * @return The raw data received or an empty string. - */ - public String getDataFromEndpoint(String cellId) { - try { - if (cellId == null || cellId.isBlank()) { - logger.warn("No cellId provided"); - return ""; - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(DWD_URL); - stringBuilder.append("&CQL_FILTER="); - stringBuilder.append(URLEncoder.encode("WARNCELLID LIKE '" + cellId + "'", StandardCharsets.UTF_8)); - logger.debug("Refreshing Data for cell {}", cellId); - String rawData = HttpUtil.executeUrl("GET", stringBuilder.toString(), 5000); - logger.trace("Raw request: {}", stringBuilder); - logger.trace("Raw response: {}", rawData); - - return rawData; - } catch (IOException e) { - logger.warn("Communication error occurred while getting data: {}", e.getMessage()); - logger.debug("Communication error trace", e); - } - - return ""; - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsData.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsData.java deleted file mode 100644 index 08d031e7eb..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsData.java +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.io.StringReader; -import java.math.BigDecimal; -import java.time.Duration; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.events.XMLEvent; - -import org.openhab.core.cache.ExpiringCache; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.library.unit.ImperialUnits; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Contains the Data for all retrieved warnings for one thing. - * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningsData { - - private static final int MIN_REFRESH_WAIT_MINUTES = 5; - - private final Logger logger = LoggerFactory.getLogger(DwdWarningsData.class); - - private List cityData = new LinkedList<>(); - - private DwdWarningCache cache = new DwdWarningCache(); - - private ExpiringCache dataAccessCached; - - private DateTimeFormatter formatter = new DateTimeFormatterBuilder() - // date/time - .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - // offset (hh:mm - "+00:00" when it's zero) - .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd() - // offset (hhmm - "+0000" when it's zero) - .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd() - // offset (hh - "Z" when it's zero) - .optionalStart().appendOffset("+HH", "Z").optionalEnd() - // create formatter - .toFormatter(); - - public DwdWarningsData(String cellId) { - DwdWarningDataAccess dataAccess = new DwdWarningDataAccess(); - this.dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES), - () -> dataAccess.getDataFromEndpoint(cellId)); - } - - private String getValue(XMLEventReader eventReader) throws XMLStreamException { - XMLEvent event = eventReader.nextEvent(); - return event.asCharacters().getData(); - } - - private BigDecimal getBigDecimalValue(XMLEventReader eventReader) throws XMLStreamException { - XMLEvent event = eventReader.nextEvent(); - try { - return new BigDecimal(event.asCharacters().getData()); - } catch (NumberFormatException e) { - logger.debug("Exception while parsing a BigDecimal", e); - return BigDecimal.ZERO; - } - } - - private Instant getTimestampValue(XMLEventReader eventReader) throws XMLStreamException { - XMLEvent event = eventReader.nextEvent(); - String dateTimeString = event.asCharacters().getData(); - try { - OffsetDateTime dateTime = OffsetDateTime.parse(dateTimeString, formatter); - return dateTime.toInstant(); - } catch (DateTimeParseException e) { - logger.debug("Exception while parsing a DateTime", e); - return Instant.MIN; - } - } - - /** - * Refreshes the Warnings Data - */ - public boolean refresh() { - String rawData = dataAccessCached.getValue(); - if (rawData == null || rawData.isEmpty()) { - logger.debug("No Data from Endpoint"); - return false; - } - - cityData.clear(); - - try { - XMLInputFactory inputFactory = XMLInputFactory.newInstance(); - XMLStreamReader reader = inputFactory.createXMLStreamReader(new StringReader(rawData)); - XMLEventReader eventReader = inputFactory.createXMLEventReader(reader); - DwdWarningData gemeindeData = new DwdWarningData(); - boolean insideGemeinde = false; - while (eventReader.hasNext()) { - XMLEvent event = eventReader.nextEvent(); - if (!insideGemeinde && event.isStartElement()) { - DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart()); - switch (xmlTag) { - case WARNUNGEN_GEMEINDEN: - gemeindeData = new DwdWarningData(); - insideGemeinde = true; - break; - default: - break; - } - } else if (insideGemeinde && event.isStartElement()) { - DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart()); - switch (xmlTag) { - case SEVERITY: - gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader))); - break; - case DESCRIPTION: - gemeindeData.setDescription(getValue(eventReader)); - break; - case EFFECTIVE: - gemeindeData.setEffective(getTimestampValue(eventReader)); - break; - case EXPIRES: - gemeindeData.setExpires(getTimestampValue(eventReader)); - break; - case EVENT: - gemeindeData.setEvent(getValue(eventReader)); - break; - case STATUS: - gemeindeData.setStatus(getValue(eventReader)); - break; - case MSGTYPE: - gemeindeData.setMsgType(getValue(eventReader)); - break; - case HEADLINE: - gemeindeData.setHeadline(getValue(eventReader)); - break; - case ONSET: - gemeindeData.setOnset(getTimestampValue(eventReader)); - break; - case ALTITUDE: - gemeindeData.setAltitude(getBigDecimalValue(eventReader)); - break; - case CEILING: - gemeindeData.setCeiling(getBigDecimalValue(eventReader)); - break; - case IDENTIFIER: - gemeindeData.setId(getValue(eventReader)); - break; - case INSTRUCTION: - gemeindeData.setInstruction(getValue(eventReader)); - break; - case URGENCY: - gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader))); - break; - default: - break; - } - } else if (insideGemeinde && event.isEndElement()) { - DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart()); - switch (xmlTag) { - case WARNUNGEN_GEMEINDEN: - if (!gemeindeData.isTest() && !gemeindeData.isCancel()) { - cityData.add(gemeindeData); - } - insideGemeinde = false; - break; - default: - break; - } - } - } - } catch (XMLStreamException e) { - logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage()); - logger.debug("Exception trace", e); - return false; - } - - Collections.sort(cityData, new SeverityComparator()); - return true; - } - - private DwdWarningData getGemeindeData(int number) { - return cityData.size() <= number ? null : cityData.get(number); - } - - public State getWarning(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? OnOffType.OFF : OnOffType.ON; - } - - public State getSeverity(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getSeverity().getText()); - } - - public State getDescription(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getDescription()); - } - - public State getEffective(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return UnDefType.NULL; - } - ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault()); - return new DateTimeType(zoned); - } - - public State getExpires(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return UnDefType.NULL; - } - ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault()); - return new DateTimeType(zoned); - } - - public State getOnset(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return UnDefType.NULL; - } - ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault()); - return new DateTimeType(zoned); - } - - public State getEvent(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getEvent()); - } - - public State getHeadline(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getHeadline()); - } - - public State getAltitude(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return UnDefType.NULL; - } - return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT); - } - - public State getCeiling(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return UnDefType.NULL; - } - return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT); - } - - public State getInstruction(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getInstruction()); - } - - public State getUrgency(int number) { - DwdWarningData data = getGemeindeData(number); - return data == null ? UnDefType.NULL : StringType.valueOf(data.getUrgency().getText()); - } - - public boolean isNew(int number) { - DwdWarningData data = getGemeindeData(number); - if (data == null) { - return false; - } - return cache.addEntry(data); - } - - public void updateCache() { - cache.deleteOldEntries(); - } - - /** - * Only for Tests - */ - protected void setDataAccess(DwdWarningDataAccess dataAccess) { - dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES), - () -> dataAccess.getDataFromEndpoint("")); - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdXmlTag.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdXmlTag.java deleted file mode 100644 index 58c73c1fad..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/DwdXmlTag.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.util.Arrays; - -/** - * The XML Tags to extract the relevant parts from the API response. - * The names map directly to the XML Tags of the API Response. - * - * @author Martin Koehler - Initial contribution - */ -public enum DwdXmlTag { - - UNKNOWN(""), - SEVERITY("SEVERITY"), - DESCRIPTION("DESCRIPTION"), - EFFECTIVE("EFFECTIVE"), - EXPIRES("EXPIRES"), - ONSET("ONSET"), - EVENT("EVENT"), - STATUS("STATUS"), - MSGTYPE("MSGTYPE"), - HEADLINE("HEADLINE"), - ALTITUDE("ALTITUDE"), - CEILING("CEILING"), - INSTRUCTION("INSTRUCTION"), - URGENCY("URGENCY"), - IDENTIFIER("IDENTIFIER"), - WARNUNGEN_GEMEINDEN("Warnungen_Gemeinden"); - - private String tag; - - private DwdXmlTag(String tag) { - this.tag = tag; - } - - String getTag() { - return tag; - } - - public static DwdXmlTag getDwdXmlTag(String tag) { - return Arrays.asList(DwdXmlTag.values()).stream().filter(t -> tag.equals(t.getTag())).findFirst() - .orElse(UNKNOWN); - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Severity.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Severity.java deleted file mode 100644 index 12cfb46cb0..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Severity.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.util.Arrays; - -/** - * Severity enum to make the severity comparable - * - * @author Martin Koehler - Initial contribution - */ -public enum Severity { - - EXTREME(1, "Extreme"), - SEVERE(2, "Severe"), - MODERATE(3, "Moderate"), - MINOR(4, "Minor"), - UNKNOWN(5, "Unknown"); - - private int order; - private String text; - - private Severity(int order, String text) { - this.order = order; - this.text = text; - } - - public int getOrder() { - return order; - } - - public String getText() { - return text; - } - - public static Severity getSeverity(String input) { - return Arrays.asList(Severity.values()).stream().filter(sev -> input.equalsIgnoreCase(sev.getText())).findAny() - .orElse(UNKNOWN); - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/SeverityComparator.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/SeverityComparator.java deleted file mode 100644 index e2bcadbc21..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/SeverityComparator.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.util.Comparator; - -/** - * Comperator to sort a Warning first by Severity, second by the onSet date. - * - * @author Martin Koehler - Initial contribution - */ -public class SeverityComparator implements Comparator { - - @Override - public int compare(DwdWarningData o1, DwdWarningData o2) { - Comparator.comparingInt(d -> ((DwdWarningData) d).getSeverity().getOrder()); - Comparator.comparing(DwdWarningData::getOnset); - - int result = Integer.compare(o1.getSeverity().getOrder(), o2.getSeverity().getOrder()); - if (result == 0) { - if (o1.getOnset() == o2.getOnset()) { - return 0; - } else if (o1.getOnset() == null) { - return -1; - } else if (o2.getOnset() == null) { - return 1; - } - return o1.getOnset().compareTo(o2.getOnset()); - } - return result; - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Urgency.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Urgency.java deleted file mode 100644 index 37c8664a5f..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/data/Urgency.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import java.util.Arrays; - -/** - * Enum for the urgency of the warning. - * - * @author Martin Koehler - Initial contribution - */ -public enum Urgency { - - IMMEDIATE("Immediate"), - FUTURE("Future"), - UNKNOWN("Unknown"); - - private final String text; - - private Urgency(String text) { - this.text = text; - } - - public String getText() { - return text; - } - - public static Urgency getUrgency(String input) { - return Arrays.asList(Urgency.values()).stream().filter(urg -> input.equalsIgnoreCase(urg.getText())).findAny() - .orElse(UNKNOWN); - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCache.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCache.java new file mode 100644 index 0000000000..308d597bb4 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCache.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +/** + * Cache of Warnings to update the {@link DwdUnwetterBindingConstants#CHANNEL_UPDATED} if a new warning is sent to a + * channel. + * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningCache { + + // Remove Entries 30 Minutes after they expired + private static final long WAIT_TIME_IN_MINUTES = 30; + + private final Map idExpiresMap; + + public DwdWarningCache() { + idExpiresMap = new HashMap<>(); + } + + private boolean isExpired(Entry entry) { + Instant expireTime = entry.getValue().plus(WAIT_TIME_IN_MINUTES, ChronoUnit.MINUTES); + return Instant.now().isAfter(expireTime); + } + + /** + * Adds a Warning + * + * @param data The warning data + * @return true if it is a new warning, false if the warning is not new. + */ + public boolean addEntry(DwdWarningData data) { + return idExpiresMap.put(data.getId(), data.getExpires()) == null; + } + + /** + * Removes the expired Entries + */ + public void deleteOldEntries() { + List oldEntries = idExpiresMap.entrySet().stream().filter(this::isExpired).map(Entry::getKey) + .collect(Collectors.toList()); + oldEntries.forEach(idExpiresMap::remove); + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningData.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningData.java new file mode 100644 index 0000000000..d33543fff1 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningData.java @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.math.BigDecimal; +import java.time.Instant; + +/** + * Data for one warning. + * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningData { + + private String id; + + private Severity severity; + private String description; + private Instant effective; + private Instant expires; + private Instant onset; + private String event; + private String status; + private String msgType; + private String headline; + private BigDecimal altitude; + private BigDecimal ceiling; + private String instruction; + private Urgency urgency; + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setSeverity(Severity severity) { + this.severity = severity; + } + + public Severity getSeverity() { + return severity == null ? Severity.UNKNOWN : severity; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setEffective(Instant effective) { + this.effective = effective; + } + + public Instant getEffective() { + return effective; + } + + public void setExpires(Instant expires) { + this.expires = expires; + } + + public Instant getExpires() { + return expires; + } + + public void setEvent(String event) { + this.event = event; + } + + public String getEvent() { + return event; + } + + public void setStatus(String status) { + this.status = status; + } + + public boolean isTest() { + return "Test".equalsIgnoreCase(status); + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public boolean isCancel() { + return "Cancel".equalsIgnoreCase(msgType); + } + + public void setHeadline(String headline) { + this.headline = headline; + } + + public String getHeadline() { + return headline; + } + + public Instant getOnset() { + return onset; + } + + public void setOnset(Instant onset) { + this.onset = onset; + } + + public void setAltitude(BigDecimal altitude) { + this.altitude = altitude; + } + + public BigDecimal getAltitude() { + return altitude; + } + + public void setCeiling(BigDecimal ceiling) { + this.ceiling = ceiling; + } + + public BigDecimal getCeiling() { + return ceiling; + } + + public void setInstruction(String instruction) { + this.instruction = instruction; + } + + public String getInstruction() { + return instruction; + } + + public void setUrgency(Urgency urgency) { + this.urgency = urgency; + } + + public Urgency getUrgency() { + return urgency; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DwdWarningData other = (DwdWarningData) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + return true; + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningDataAccess.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningDataAccess.java new file mode 100644 index 0000000000..b660f7b816 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningDataAccess.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.openhab.core.io.net.http.HttpUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the access to the API Endpoint + * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningDataAccess { + + private final Logger logger = LoggerFactory.getLogger(DwdWarningDataAccess.class); + + // URL of the Service + private static final String DWD_URL = "https://maps.dwd.de/geoserver/dwd/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=dwd:Warnungen_Gemeinden"; + + /** + * Returns the raw Data from the Endpoint. + * In case of errors or empty cellId value, returns an empty String. + * + * @param cellId The warnCell-Id for which the warnings should be returned + * @return The raw data received or an empty string. + */ + public String getDataFromEndpoint(String cellId) { + try { + if (cellId == null || cellId.isBlank()) { + logger.warn("No cellId provided"); + return ""; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(DWD_URL); + stringBuilder.append("&CQL_FILTER="); + stringBuilder.append(URLEncoder.encode("WARNCELLID LIKE '" + cellId + "'", StandardCharsets.UTF_8)); + logger.debug("Refreshing Data for cell {}", cellId); + String rawData = HttpUtil.executeUrl("GET", stringBuilder.toString(), 5000); + logger.trace("Raw request: {}", stringBuilder); + logger.trace("Raw response: {}", rawData); + + return rawData; + } catch (IOException e) { + logger.warn("Communication error occurred while getting data: {}", e.getMessage()); + logger.debug("Communication error trace", e); + } + + return ""; + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsData.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsData.java new file mode 100644 index 0000000000..5b92feb901 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsData.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.io.StringReader; +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.XMLEvent; + +import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains the Data for all retrieved warnings for one thing. + * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningsData { + + private static final int MIN_REFRESH_WAIT_MINUTES = 5; + + private final Logger logger = LoggerFactory.getLogger(DwdWarningsData.class); + + private List cityData = new LinkedList<>(); + + private DwdWarningCache cache = new DwdWarningCache(); + + private ExpiringCache dataAccessCached; + + private DateTimeFormatter formatter = new DateTimeFormatterBuilder() + // date/time + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + // offset (hh:mm - "+00:00" when it's zero) + .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd() + // offset (hhmm - "+0000" when it's zero) + .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd() + // offset (hh - "Z" when it's zero) + .optionalStart().appendOffset("+HH", "Z").optionalEnd() + // create formatter + .toFormatter(); + + public DwdWarningsData(String cellId) { + DwdWarningDataAccess dataAccess = new DwdWarningDataAccess(); + this.dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES), + () -> dataAccess.getDataFromEndpoint(cellId)); + } + + private String getValue(XMLEventReader eventReader) throws XMLStreamException { + XMLEvent event = eventReader.nextEvent(); + return event.asCharacters().getData(); + } + + private BigDecimal getBigDecimalValue(XMLEventReader eventReader) throws XMLStreamException { + XMLEvent event = eventReader.nextEvent(); + try { + return new BigDecimal(event.asCharacters().getData()); + } catch (NumberFormatException e) { + logger.debug("Exception while parsing a BigDecimal", e); + return BigDecimal.ZERO; + } + } + + private Instant getTimestampValue(XMLEventReader eventReader) throws XMLStreamException { + XMLEvent event = eventReader.nextEvent(); + String dateTimeString = event.asCharacters().getData(); + try { + OffsetDateTime dateTime = OffsetDateTime.parse(dateTimeString, formatter); + return dateTime.toInstant(); + } catch (DateTimeParseException e) { + logger.debug("Exception while parsing a DateTime", e); + return Instant.MIN; + } + } + + /** + * Refreshes the Warnings Data + */ + public boolean refresh() { + String rawData = dataAccessCached.getValue(); + if (rawData == null || rawData.isEmpty()) { + logger.debug("No Data from Endpoint"); + return false; + } + + cityData.clear(); + + try { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader reader = inputFactory.createXMLStreamReader(new StringReader(rawData)); + XMLEventReader eventReader = inputFactory.createXMLEventReader(reader); + DwdWarningData gemeindeData = new DwdWarningData(); + boolean insideGemeinde = false; + while (eventReader.hasNext()) { + XMLEvent event = eventReader.nextEvent(); + if (!insideGemeinde && event.isStartElement()) { + DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart()); + switch (xmlTag) { + case WARNUNGEN_GEMEINDEN: + gemeindeData = new DwdWarningData(); + insideGemeinde = true; + break; + default: + break; + } + } else if (insideGemeinde && event.isStartElement()) { + DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart()); + switch (xmlTag) { + case SEVERITY: + gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader))); + break; + case DESCRIPTION: + gemeindeData.setDescription(getValue(eventReader)); + break; + case EFFECTIVE: + gemeindeData.setEffective(getTimestampValue(eventReader)); + break; + case EXPIRES: + gemeindeData.setExpires(getTimestampValue(eventReader)); + break; + case EVENT: + gemeindeData.setEvent(getValue(eventReader)); + break; + case STATUS: + gemeindeData.setStatus(getValue(eventReader)); + break; + case MSGTYPE: + gemeindeData.setMsgType(getValue(eventReader)); + break; + case HEADLINE: + gemeindeData.setHeadline(getValue(eventReader)); + break; + case ONSET: + gemeindeData.setOnset(getTimestampValue(eventReader)); + break; + case ALTITUDE: + gemeindeData.setAltitude(getBigDecimalValue(eventReader)); + break; + case CEILING: + gemeindeData.setCeiling(getBigDecimalValue(eventReader)); + break; + case IDENTIFIER: + gemeindeData.setId(getValue(eventReader)); + break; + case INSTRUCTION: + gemeindeData.setInstruction(getValue(eventReader)); + break; + case URGENCY: + gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader))); + break; + default: + break; + } + } else if (insideGemeinde && event.isEndElement()) { + DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart()); + switch (xmlTag) { + case WARNUNGEN_GEMEINDEN: + if (!gemeindeData.isTest() && !gemeindeData.isCancel()) { + cityData.add(gemeindeData); + } + insideGemeinde = false; + break; + default: + break; + } + } + } + } catch (XMLStreamException e) { + logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage()); + logger.debug("Exception trace", e); + return false; + } + + Collections.sort(cityData, new SeverityComparator()); + return true; + } + + private DwdWarningData getGemeindeData(int number) { + return cityData.size() <= number ? null : cityData.get(number); + } + + public State getWarning(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? OnOffType.OFF : OnOffType.ON; + } + + public State getSeverity(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getSeverity().getText()); + } + + public State getDescription(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getDescription()); + } + + public State getEffective(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return UnDefType.UNDEF; + } + ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault()); + return new DateTimeType(zoned); + } + + public State getExpires(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return UnDefType.UNDEF; + } + ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault()); + return new DateTimeType(zoned); + } + + public State getOnset(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return UnDefType.UNDEF; + } + ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault()); + return new DateTimeType(zoned); + } + + public State getEvent(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getEvent()); + } + + public State getHeadline(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getHeadline()); + } + + public State getAltitude(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return UnDefType.UNDEF; + } + return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT); + } + + public State getCeiling(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return UnDefType.UNDEF; + } + return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT); + } + + public State getInstruction(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getInstruction()); + } + + public State getUrgency(int number) { + DwdWarningData data = getGemeindeData(number); + return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getUrgency().getText()); + } + + public boolean isNew(int number) { + DwdWarningData data = getGemeindeData(number); + if (data == null) { + return false; + } + return cache.addEntry(data); + } + + public void updateCache() { + cache.deleteOldEntries(); + } + + /** + * Only for Tests + */ + protected void setDataAccess(DwdWarningDataAccess dataAccess) { + dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES), + () -> dataAccess.getDataFromEndpoint("")); + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdXmlTag.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdXmlTag.java new file mode 100644 index 0000000000..1081a888fe --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/DwdXmlTag.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.util.Arrays; + +/** + * The XML Tags to extract the relevant parts from the API response. + * The names map directly to the XML Tags of the API Response. + * + * @author Martin Koehler - Initial contribution + */ +public enum DwdXmlTag { + + UNKNOWN(""), + SEVERITY("SEVERITY"), + DESCRIPTION("DESCRIPTION"), + EFFECTIVE("EFFECTIVE"), + EXPIRES("EXPIRES"), + ONSET("ONSET"), + EVENT("EVENT"), + STATUS("STATUS"), + MSGTYPE("MSGTYPE"), + HEADLINE("HEADLINE"), + ALTITUDE("ALTITUDE"), + CEILING("CEILING"), + INSTRUCTION("INSTRUCTION"), + URGENCY("URGENCY"), + IDENTIFIER("IDENTIFIER"), + WARNUNGEN_GEMEINDEN("Warnungen_Gemeinden"); + + private String tag; + + private DwdXmlTag(String tag) { + this.tag = tag; + } + + String getTag() { + return tag; + } + + public static DwdXmlTag getDwdXmlTag(String tag) { + return Arrays.asList(DwdXmlTag.values()).stream().filter(t -> tag.equals(t.getTag())).findFirst() + .orElse(UNKNOWN); + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Severity.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Severity.java new file mode 100644 index 0000000000..214ea8c4e3 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Severity.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.util.Arrays; + +/** + * Severity enum to make the severity comparable + * + * @author Martin Koehler - Initial contribution + */ +public enum Severity { + + EXTREME(1, "Extreme"), + SEVERE(2, "Severe"), + MODERATE(3, "Moderate"), + MINOR(4, "Minor"), + UNKNOWN(5, "Unknown"); + + private int order; + private String text; + + private Severity(int order, String text) { + this.order = order; + this.text = text; + } + + public int getOrder() { + return order; + } + + public String getText() { + return text; + } + + public static Severity getSeverity(String input) { + return Arrays.asList(Severity.values()).stream().filter(sev -> input.equalsIgnoreCase(sev.getText())).findAny() + .orElse(UNKNOWN); + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/SeverityComparator.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/SeverityComparator.java new file mode 100644 index 0000000000..feb6740603 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/SeverityComparator.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.util.Comparator; + +/** + * Comperator to sort a Warning first by Severity, second by the onSet date. + * + * @author Martin Koehler - Initial contribution + */ +public class SeverityComparator implements Comparator { + + @Override + public int compare(DwdWarningData o1, DwdWarningData o2) { + Comparator.comparingInt(d -> ((DwdWarningData) d).getSeverity().getOrder()); + Comparator.comparing(DwdWarningData::getOnset); + + int result = Integer.compare(o1.getSeverity().getOrder(), o2.getSeverity().getOrder()); + if (result == 0) { + if (o1.getOnset() == o2.getOnset()) { + return 0; + } else if (o1.getOnset() == null) { + return -1; + } else if (o2.getOnset() == null) { + return 1; + } + return o1.getOnset().compareTo(o2.getOnset()); + } + return result; + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Urgency.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Urgency.java new file mode 100644 index 0000000000..b998cd9ccd --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/dto/Urgency.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import java.util.Arrays; + +/** + * Enum for the urgency of the warning. + * + * @author Martin Koehler - Initial contribution + */ +public enum Urgency { + + IMMEDIATE("Immediate"), + FUTURE("Future"), + UNKNOWN("Unknown"); + + private final String text; + + private Urgency(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public static Urgency getUrgency(String input) { + return Arrays.asList(Urgency.values()).stream().filter(urg -> input.equalsIgnoreCase(urg.getText())).findAny() + .orElse(UNKNOWN); + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/handler/DwdUnwetterHandler.java b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/handler/DwdUnwetterHandler.java index 76080e99a6..e152337ae9 100644 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/handler/DwdUnwetterHandler.java +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/java/org/openhab/binding/dwdunwetter/internal/handler/DwdUnwetterHandler.java @@ -23,7 +23,7 @@ import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.dwdunwetter.internal.config.DwdUnwetterConfiguration; -import org.openhab.binding.dwdunwetter.internal.data.DwdWarningsData; +import org.openhab.binding.dwdunwetter.internal.dto.DwdWarningsData; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.Channel; diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/i18n/dwdunwetter_de.properties b/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/i18n/dwdunwetter_de.properties index 58de55932e..39e11adcb7 100644 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/i18n/dwdunwetter_de.properties +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/i18n/dwdunwetter_de.properties @@ -1,10 +1,10 @@ # binding binding.dwdunwetter.name = DWD Unwetter Binding -binding.dwdunwetter.description = Das DWD Unwetter Binding ermöglicht es über die API des DWD aktuelle Unwetterwarnungen abzurufen +binding.dwdunwetter.description = Das DWD Unwetter Binding ermöglicht es über die API des DWD aktuelle Unwetterwarnungen abzurufen. # thing types thing-type.dwdunwetter.dwdwarnings.label = DWD Unwetter Warnungen -thing-type.dwdunwetter.dwdwarnings.description = DWD Unwetterwarnungen für ein Gebiet +thing-type.dwdunwetter.dwdwarnings.description = DWD Unwetterwarnungen für ein Gebiet. thing-type.config.dwdunwetter.dwdwarnings.cellId.label=Cell-ID thing-type.config.dwdunwetter.dwdwarnings.cellId.description=ID der abzufragenden Zelle.\ @@ -20,29 +20,29 @@ Die Warnungen werden dabei nach Severity und dann nach Beginn sortiert. Die erst channel-type.dwdunwetter.warning.label = Warnung channel-type.dwdunwetter.warning.description = Steht auf ON, wenn eine Warning vorliegt, OFF wenn keine vorliegt.\ Es ist garantiert, dass wenn der Channel von OFF auf ON umspringt, die anderen Channels bereits gefüllt sind, mit Ausnahme des Trigger Channels.\ -Es ist garantiert, dass wenn der Channel von ON auf OFF umspringt, die anderen Channels erst danach auf NULL gesetzt werden. +Es ist garantiert, dass wenn der Channel von ON auf OFF umspringt, die anderen Channels erst danach auf UNDEF gesetzt werden. channel-type.dwdunwetter.updated.label = Aktualisiert channel-type.dwdunwetter.updated.description = Triggered, wenn eine Warnung das erste mal gesendet wird.\ Dies passiert als letztes, nachdem alle anderen Channels bereits gesetzt sind. channel-type.dwdunwetter.severity.label = Schwere-Grad channel-type.dwdunwetter.severity.description = Schwere-Grad der Warnung. Mögliche Werte sind Minor, Moderate, Severe und Extreme. channel-type.dwdunwetter.description.label = Beschreibung -channel-type.dwdunwetter.description.description = Klartext Beschreibung der Warnung +channel-type.dwdunwetter.description.description = Klartext Beschreibung der Warnung. channel-type.dwdunwetter.effective.label = Ausgegeben -channel-type.dwdunwetter.effective.description = Datum und Uhrzeit, wann die Warnung ausgegeben wurde +channel-type.dwdunwetter.effective.description = Datum und Uhrzeit, wann die Warnung ausgegeben wurde. channel-type.dwdunwetter.onset.label = Gültig ab -channel-type.dwdunwetter.onset.description = Datum und Uhrzeit, ab dem die Warnung gültig ist +channel-type.dwdunwetter.onset.description = Datum und Uhrzeit, ab dem die Warnung gültig ist. channel-type.dwdunwetter.expires.label = Gültig bis -channel-type.dwdunwetter.expires.description = Datum und Uhrzeit, bis zu dem die Warnung gültig ist +channel-type.dwdunwetter.expires.description = Datum und Uhrzeit, bis zu dem die Warnung gültig ist. channel-type.dwdunwetter.headline.label = Titel -channel-type.dwdunwetter.headline.description = Titel der Warnung z.B. "Amtliche Warnung vor FROST" +channel-type.dwdunwetter.headline.description = Titel der Warnung z.B. "Amtliche Warnung vor FROST". channel-type.dwdunwetter.event.label = Art -channel-type.dwdunwetter.event.description = Art der Warnung, z.B. FROST +channel-type.dwdunwetter.event.description = Art der Warnung, z.B. FROST. channel-type.dwdunwetter.altitude.label = Höhe von -channel-type.dwdunwetter.altitude.description = Höhe über dem Meeresspiegel, ab dem die Warnung gilt +channel-type.dwdunwetter.altitude.description = Höhe über dem Meeresspiegel, ab dem die Warnung gilt. channel-type.dwdunwetter.ceiling.label = Höhe bis -channel-type.dwdunwetter.ceiling.description = Höhe über dem Meeresspiegel, bis zu dem die Warnung gilt +channel-type.dwdunwetter.ceiling.description = Höhe über dem Meeresspiegel, bis zu dem die Warnung gilt. channel-type.dwdunwetter.urgency.label=Zeitrahmen -channel-type.dwdunwetter.urgency.description=Zeitrahmen der Meldung - Vorabinformation oder Warnung +channel-type.dwdunwetter.urgency.description=Zeitrahmen der Meldung - Vorabinformation oder Warnung. channel-type.dwdunwetter.instruction.label=Zusatztext -channel-type.dwdunwetter.instruction.description=Zusatztext zur Warnung (Instruktionen und Sicherheitshinweise) +channel-type.dwdunwetter.instruction.description=Zusatztext zur Warnung (Instruktionen und Sicherheitshinweise). diff --git a/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/thing/thing-types.xml index 5a25f8beef..94b644bc80 100644 --- a/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.dwdunwetter/src/main/resources/OH-INF/thing/thing-types.xml @@ -6,9 +6,9 @@ - Weather Warnings for an area + Weather Warnings for an area. - + @@ -35,7 +35,8 @@ The warnings are sorted by severity first and begin second so the first warning DateTime - Timestamp of the last update from the endpoint + Timestamp of the last update from the endpoint. + Time @@ -43,14 +44,14 @@ The warnings are sorted by severity first and begin second so the first warning +Will be switched to OFF before all other channels are updated to UNDEF.]]> trigger +This happens after all other channels are populated.]]> @@ -79,49 +80,52 @@ This happens after all other channels are populated]]> DateTime - Issued Date and Time + Issued Date and Time. + Time DateTime - Start Date and Time for which the warning is valid + Start Date and Time for which the warning is valid. + Time DateTime - End Date and Time for which the warning is valid + End Date and Time for which the warning is valid. + Time String - Headline of the warning like "Amtliche Warnung vor FROST" + Headline of the warning like "Amtliche Warnung vor FROST". String - Type of the warning, e.g. FROST + Type of the warning, e.g. FROST. Number:Length - Lower Height above sea level for which the warning is valid + Lower Height above sea level for which the warning is valid. Number:Length - Upper Height above sea level for which the warning is valid + Upper Height above sea level for which the warning is valid. String - Instructions and safety information + Instructions and safety information. diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCacheTest.java b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCacheTest.java deleted file mode 100644 index cd66497a89..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningCacheTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import java.time.Instant; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Tests for {@link DwdWarningCache} - * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningCacheTest { - - private DwdWarningCache cache; - - @BeforeEach - public void setUp() { - cache = new DwdWarningCache(); - } - - @Test - public void testAddEntry() { - DwdWarningData data = createData("ID", 0); - - assertThat(cache.addEntry(data), is(true)); - assertThat(cache.addEntry(data), is(false)); - } - - @Test - public void testDeleteOldEntries() { - DwdWarningData data = createData("ID", 0); - cache.addEntry(data); - - cache.deleteOldEntries(); - assertThat(cache.addEntry(data), is(false)); - - data = createData("ID", 60 * 60); - assertThat(cache.addEntry(data), is(false)); - cache.deleteOldEntries(); - assertThat(cache.addEntry(data), is(true)); - } - - private DwdWarningData createData(String id, long secondsBeforeNow) { - DwdWarningData data = new DwdWarningData(); - data.setId(id); - data.setExpires(Instant.now().minusSeconds(secondsBeforeNow)); - return data; - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsDataTest.java b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsDataTest.java deleted file mode 100644 index 1a7a09587a..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/data/DwdWarningsDataTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.dwdunwetter.internal.data; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.time.ZoneId; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.types.UnDefType; - -/** - * Tests for {@link DwdWarningsData} - * - * Uses the warnings.xml from the resources directory instead of accessing the api endpoint. - * - * The XML has 2 Entries: - *
    - *
  1. Amtliche WARNUNG vor WINDBÖEN, MINOR
  2. - *
  3. Amtliche WARNUNG vor STURMBÖEN, MODERATE
  4. - *
- * - * @author Martin Koehler - Initial contribution - */ -public class DwdWarningsDataTest { - - private TestDataProvider testDataProvider; - private DwdWarningsData warningsData; - - @BeforeEach - public void setUp() throws IOException { - this.testDataProvider = new TestDataProvider(); - loadXmlFromFile(); - warningsData = new DwdWarningsData(""); - warningsData.setDataAccess(testDataProvider); - warningsData.refresh(); - } - - @Test - public void testGetHeadline() { - assertThat(warningsData.getHeadline(0), is("Amtliche WARNUNG vor STURMBÖEN")); - assertThat(warningsData.getHeadline(1), is("Amtliche WARNUNG vor WINDBÖEN")); - assertThat(warningsData.getHeadline(2), is(UnDefType.NULL)); - } - - @Test - public void testGetSeverity() { - assertThat(warningsData.getSeverity(0), is("Moderate")); - assertThat(warningsData.getSeverity(1), is("Minor")); - assertThat(warningsData.getSeverity(2), is(UnDefType.NULL)); - } - - @Test - public void testGetEvent() { - assertThat(warningsData.getEvent(0), is("STURMBÖEN")); - assertThat(warningsData.getEvent(1), is("WINDBÖEN")); - assertThat(warningsData.getEvent(2), is(UnDefType.NULL)); - } - - @Test - public void testGetDescription() { - assertThat(warningsData.getDescription(0), is( - "Es treten Sturmböen mit Geschwindigkeiten zwischen 60 km/h (17m/s, 33kn, Bft 7) und 80 km/h (22m/s, 44kn, Bft 9) anfangs aus südwestlicher, später aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen um 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden.")); - assertThat(warningsData.getDescription(1), is( - "Es treten Windböen mit Geschwindigkeiten bis 60 km/h (17m/s, 33kn, Bft 7) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.")); - assertThat(warningsData.getDescription(2), is(UnDefType.NULL)); - } - - @Test - public void testGetAltitude() { - assertThat(warningsData.getAltitude(0).format("%.0f ft"), is("0 ft")); - assertThat(warningsData.getAltitude(1).format("%.0f ft"), is("0 ft")); - assertThat(warningsData.getAltitude(2), is(UnDefType.NULL)); - } - - @Test - public void testGetCeiling() { - assertThat(warningsData.getCeiling(0).format("%.0f ft"), is("9843 ft")); - assertThat(warningsData.getCeiling(1).format("%.0f ft"), is("9843 ft")); - assertThat(warningsData.getCeiling(2), is(UnDefType.NULL)); - } - - @Test - public void testGetExpires() { - // Conversion is needed as getExpires returns the Date with the System Default Zone - assertThat(((DateTimeType) warningsData.getExpires(0)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) - .toString(), is("2018-12-22T18:00Z[UTC]")); - assertThat(((DateTimeType) warningsData.getExpires(1)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) - .toString(), is("2018-12-23T01:00Z[UTC]")); - assertThat(warningsData.getExpires(2), is(UnDefType.NULL)); - } - - @Test - public void testGetOnset() { - // Conversion is needed as getOnset returns the Date with the System Default Zone - assertThat(((DateTimeType) warningsData.getOnset(0)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) - .toString(), is("2018-12-21T10:00Z[UTC]")); - assertThat(((DateTimeType) warningsData.getOnset(1)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) - .toString(), is("2018-12-22T18:00Z[UTC]")); - assertThat(warningsData.getOnset(2), is(UnDefType.NULL)); - } - - @Test - public void testGetEffective() { - // Conversion is needed as getEffective returns the Date with the System Default Zone - assertThat(((DateTimeType) warningsData.getEffective(0)).getZonedDateTime() - .withZoneSameInstant(ZoneId.of("UTC")).toString(), is("2018-12-22T03:02Z[UTC]")); - assertThat(((DateTimeType) warningsData.getEffective(1)).getZonedDateTime() - .withZoneSameInstant(ZoneId.of("UTC")).toString(), is("2018-12-22T10:15Z[UTC]")); - assertThat(warningsData.getEffective(2), is(UnDefType.NULL)); - } - - @Test - public void testGetWarning() { - assertThat(warningsData.getWarning(0), is(OnOffType.ON)); - assertThat(warningsData.getWarning(1), is(OnOffType.ON)); - assertThat(warningsData.getWarning(2), is(OnOffType.OFF)); - } - - @Test - public void testisNew() { - assertThat(warningsData.isNew(0), is(true)); - assertThat(warningsData.isNew(1), is(true)); - assertThat(warningsData.isNew(2), is(false)); - // No longer new - assertThat(warningsData.isNew(0), is(false)); - assertThat(warningsData.isNew(1), is(false)); - assertThat(warningsData.isNew(2), is(false)); - } - - private void loadXmlFromFile() throws IOException { - InputStream stream = getClass().getResourceAsStream("warnings.xml"); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); - String line = null; - - StringWriter stringWriter = new StringWriter(); - while ((line = reader.readLine()) != null) { - stringWriter.write(line); - } - reader.close(); - testDataProvider.rawData = stringWriter.toString(); - } - - private class TestDataProvider extends DwdWarningDataAccess { - - private String rawData = ""; - - @Override - public String getDataFromEndpoint(String cellId) { - return rawData; - } - } -} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCacheTest.java b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCacheTest.java new file mode 100644 index 0000000000..b56e28ab94 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningCacheTest.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.time.Instant; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link org.openhab.binding.dwdunwetter.internal.dto.DwdWarningCache} + * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningCacheTest { + + private DwdWarningCache cache; + + @BeforeEach + public void setUp() { + cache = new DwdWarningCache(); + } + + @Test + public void testAddEntry() { + DwdWarningData data = createData("ID", 0); + + assertThat(cache.addEntry(data), is(true)); + assertThat(cache.addEntry(data), is(false)); + } + + @Test + public void testDeleteOldEntries() { + DwdWarningData data = createData("ID", 0); + cache.addEntry(data); + + cache.deleteOldEntries(); + assertThat(cache.addEntry(data), is(false)); + + data = createData("ID", 60 * 60); + assertThat(cache.addEntry(data), is(false)); + cache.deleteOldEntries(); + assertThat(cache.addEntry(data), is(true)); + } + + private DwdWarningData createData(String id, long secondsBeforeNow) { + DwdWarningData data = new DwdWarningData(); + data.setId(id); + data.setExpires(Instant.now().minusSeconds(secondsBeforeNow)); + return data; + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsDataTest.java b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsDataTest.java new file mode 100644 index 0000000000..03278062ac --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/test/java/org/openhab/binding/dwdunwetter/internal/dto/DwdWarningsDataTest.java @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dwdunwetter.internal.dto; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.time.ZoneId; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.UnDefType; + +/** + * Tests for {@link org.openhab.binding.dwdunwetter.internal.dto.DwdWarningsData} + * + *

+ * Uses the warnings.xml from the resources directory instead of accessing the api endpoint. + * + *

+ * The XML has 2 Entries: + * + *

    + *
  1. Amtliche WARNUNG vor WINDBÖEN, MINOR + *
  2. Amtliche WARNUNG vor STURMBÖEN, MODERATE + *
+ * + * @author Martin Koehler - Initial contribution + */ +public class DwdWarningsDataTest { + + private TestDataProvider testDataProvider; + private DwdWarningsData warningsData; + + @BeforeEach + public void setUp() throws IOException { + this.testDataProvider = new TestDataProvider(); + loadXmlFromFile(); + warningsData = new DwdWarningsData(""); + warningsData.setDataAccess(testDataProvider); + warningsData.refresh(); + } + + @Test + public void testGetHeadline() { + assertThat(warningsData.getHeadline(0), is("Amtliche WARNUNG vor STURMBÖEN")); + assertThat(warningsData.getHeadline(1), is("Amtliche WARNUNG vor WINDBÖEN")); + assertThat(warningsData.getHeadline(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetSeverity() { + assertThat(warningsData.getSeverity(0), is("Moderate")); + assertThat(warningsData.getSeverity(1), is("Minor")); + assertThat(warningsData.getSeverity(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetEvent() { + assertThat(warningsData.getEvent(0), is("STURMBÖEN")); + assertThat(warningsData.getEvent(1), is("WINDBÖEN")); + assertThat(warningsData.getEvent(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetDescription() { + assertThat(warningsData.getDescription(0), is( + "Es treten Sturmböen mit Geschwindigkeiten zwischen 60 km/h (17m/s, 33kn, Bft 7) und 80 km/h (22m/s, 44kn, Bft 9) anfangs aus südwestlicher, später aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen um 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden.")); + assertThat(warningsData.getDescription(1), is( + "Es treten Windböen mit Geschwindigkeiten bis 60 km/h (17m/s, 33kn, Bft 7) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden.")); + assertThat(warningsData.getDescription(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetAltitude() { + assertThat(warningsData.getAltitude(0).format("%.0f ft"), is("0 ft")); + assertThat(warningsData.getAltitude(1).format("%.0f ft"), is("0 ft")); + assertThat(warningsData.getAltitude(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetCeiling() { + assertThat(warningsData.getCeiling(0).format("%.0f ft"), is("9843 ft")); + assertThat(warningsData.getCeiling(1).format("%.0f ft"), is("9843 ft")); + assertThat(warningsData.getCeiling(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetExpires() { + // Conversion is needed as getExpires returns the Date with the System Default Zone + assertThat(((DateTimeType) warningsData.getExpires(0)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) + .toString(), is("2018-12-22T18:00Z[UTC]")); + assertThat(((DateTimeType) warningsData.getExpires(1)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) + .toString(), is("2018-12-23T01:00Z[UTC]")); + assertThat(warningsData.getExpires(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetOnset() { + // Conversion is needed as getOnset returns the Date with the System Default Zone + assertThat(((DateTimeType) warningsData.getOnset(0)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) + .toString(), is("2018-12-21T10:00Z[UTC]")); + assertThat(((DateTimeType) warningsData.getOnset(1)).getZonedDateTime().withZoneSameInstant(ZoneId.of("UTC")) + .toString(), is("2018-12-22T18:00Z[UTC]")); + assertThat(warningsData.getOnset(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetEffective() { + // Conversion is needed as getEffective returns the Date with the System Default Zone + assertThat(((DateTimeType) warningsData.getEffective(0)).getZonedDateTime() + .withZoneSameInstant(ZoneId.of("UTC")).toString(), is("2018-12-22T03:02Z[UTC]")); + assertThat(((DateTimeType) warningsData.getEffective(1)).getZonedDateTime() + .withZoneSameInstant(ZoneId.of("UTC")).toString(), is("2018-12-22T10:15Z[UTC]")); + assertThat(warningsData.getEffective(2), is(UnDefType.UNDEF)); + } + + @Test + public void testGetWarning() { + assertThat(warningsData.getWarning(0), is(OnOffType.ON)); + assertThat(warningsData.getWarning(1), is(OnOffType.ON)); + assertThat(warningsData.getWarning(2), is(OnOffType.OFF)); + } + + @Test + public void testisNew() { + assertThat(warningsData.isNew(0), is(true)); + assertThat(warningsData.isNew(1), is(true)); + assertThat(warningsData.isNew(2), is(false)); + // No longer new + assertThat(warningsData.isNew(0), is(false)); + assertThat(warningsData.isNew(1), is(false)); + assertThat(warningsData.isNew(2), is(false)); + } + + private void loadXmlFromFile() throws IOException { + InputStream stream = getClass().getResourceAsStream("warnings.xml"); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); + String line = null; + + StringWriter stringWriter = new StringWriter(); + while ((line = reader.readLine()) != null) { + stringWriter.write(line); + } + reader.close(); + testDataProvider.rawData = stringWriter.toString(); + } + + private class TestDataProvider extends DwdWarningDataAccess { + + private String rawData = ""; + + @Override + public String getDataFromEndpoint(String cellId) { + return rawData; + } + } +} diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/data/warnings.xml b/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/data/warnings.xml deleted file mode 100644 index 888e531769..0000000000 --- a/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/data/warnings.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - 47.7798 12.874 - 47.8513 12.9787 - - - - - - - 47.7798 12.874 - 47.8513 12.9787 - - - Ainring - Gemeinde Ainring - 809172111 - 2.49.0.1.276.0.DWD.PVW.1545473700000.f6482b21-04d4-4811-b379-652602cb9703.DEU - CAP@dwd.de - 2018-12-22T10:15:00Z - Actual - Alert - PVW - Public - id:2.49.0.1.276.0.DWD.PVW.1545473700000.f6482b21-04d4-4811-b379-652602cb9703 - de-DE - Met - WINDBÖEN - None - Immediate - Minor - Likely - 2.1 - Geobasisdaten: Copyright Bundesamt für Kartographie und Geodäsie, Frankfurt am Main, 2013 - 51 - WIND - 255 255 0 - 2018-12-22T10:15:00Z - 2018-12-22T18:00:00Z - 2018-12-23T01:00:00Z - DWD / Nationales Warnzentrum Offenbach - Amtliche WARNUNG vor WINDBÖEN - Es treten Windböen mit Geschwindigkeiten bis 60 km/h (17m/s, 33kn, Bft 7) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden. - http://www.wettergefahren.de - Deutscher Wetterdienst - Windrichtung;Böen;Exponierte Böen - W;<60 [km/h];<80 [km/h] - 0 - 9842.5197 - - - - - - - 47.8513 12.9113 47.8327 12.8749 47.8241 12.874 47.8066 12.9062 47.8039 12.9186 47.7798 12.9396 47.8191 12.9787 47.8262 12.9556 47.8441 12.9405 47.8513 12.9113 - - - - - - - - - - - - - 47.7798 12.874 - 47.8513 12.9787 - - - Ainring - Gemeinde Ainring - 809172111 - 2.49.0.1.276.0.DWD.PVW.1545447720000.90650875-4c27-4ccd-8a11-ecfbdff8e3cf.DEU - CAP@dwd.de - 2018-12-22T03:02:00Z - Actual - Update - PVW - Public - id:2.49.0.1.276.0.DWD.PVW.1545447720000.90650875-4c27-4ccd-8a11-ecfbdff8e3cf - de-DE - Met - STURMBÖEN - Prepare - Immediate - Moderate - Likely - 2.1 - Geobasisdaten: Copyright Bundesamt für Kartographie und Geodäsie, Frankfurt am Main, 2013 - 52 - WIND - 255 153 0 - 2018-12-22T03:02:00Z - 2018-12-21T10:00:00Z - 2018-12-22T18:00:00Z - DWD / Nationales Warnzentrum Offenbach - Amtliche WARNUNG vor STURMBÖEN - Es treten Sturmböen mit Geschwindigkeiten zwischen 60 km/h (17m/s, 33kn, Bft 7) und 80 km/h (22m/s, 44kn, Bft 9) anfangs aus südwestlicher, später aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen um 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden. - ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände. - http://www.wettergefahren.de - Deutscher Wetterdienst - Böen;Exponierte Böen;Windrichtung;Windrichtung - 60 bis 80 [km/h];~90 [km/h];SW;W - 0 - 9842.5197 - - - - - - - 47.8513 12.9113 47.8327 12.8749 47.8241 12.874 47.8066 12.9062 47.8039 12.9186 47.7798 12.9396 47.8191 12.9787 47.8262 12.9556 47.8441 12.9405 47.8513 12.9113 - - - - - - - - - diff --git a/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/dto/warnings.xml b/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/dto/warnings.xml new file mode 100644 index 0000000000..888e531769 --- /dev/null +++ b/bundles/org.openhab.binding.dwdunwetter/src/test/resources/org/openhab/binding/dwdunwetter/internal/dto/warnings.xml @@ -0,0 +1,126 @@ + + + + + 47.7798 12.874 + 47.8513 12.9787 + + + + + + + 47.7798 12.874 + 47.8513 12.9787 + + + Ainring + Gemeinde Ainring + 809172111 + 2.49.0.1.276.0.DWD.PVW.1545473700000.f6482b21-04d4-4811-b379-652602cb9703.DEU + CAP@dwd.de + 2018-12-22T10:15:00Z + Actual + Alert + PVW + Public + id:2.49.0.1.276.0.DWD.PVW.1545473700000.f6482b21-04d4-4811-b379-652602cb9703 + de-DE + Met + WINDBÖEN + None + Immediate + Minor + Likely + 2.1 + Geobasisdaten: Copyright Bundesamt für Kartographie und Geodäsie, Frankfurt am Main, 2013 + 51 + WIND + 255 255 0 + 2018-12-22T10:15:00Z + 2018-12-22T18:00:00Z + 2018-12-23T01:00:00Z + DWD / Nationales Warnzentrum Offenbach + Amtliche WARNUNG vor WINDBÖEN + Es treten Windböen mit Geschwindigkeiten bis 60 km/h (17m/s, 33kn, Bft 7) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit Sturmböen bis 80 km/h (22m/s, 44kn, Bft 9) gerechnet werden. + http://www.wettergefahren.de + Deutscher Wetterdienst + Windrichtung;Böen;Exponierte Böen + W;<60 [km/h];<80 [km/h] + 0 + 9842.5197 + + + + + + + 47.8513 12.9113 47.8327 12.8749 47.8241 12.874 47.8066 12.9062 47.8039 12.9186 47.7798 12.9396 47.8191 12.9787 47.8262 12.9556 47.8441 12.9405 47.8513 12.9113 + + + + + + + + + + + + + 47.7798 12.874 + 47.8513 12.9787 + + + Ainring + Gemeinde Ainring + 809172111 + 2.49.0.1.276.0.DWD.PVW.1545447720000.90650875-4c27-4ccd-8a11-ecfbdff8e3cf.DEU + CAP@dwd.de + 2018-12-22T03:02:00Z + Actual + Update + PVW + Public + id:2.49.0.1.276.0.DWD.PVW.1545447720000.90650875-4c27-4ccd-8a11-ecfbdff8e3cf + de-DE + Met + STURMBÖEN + Prepare + Immediate + Moderate + Likely + 2.1 + Geobasisdaten: Copyright Bundesamt für Kartographie und Geodäsie, Frankfurt am Main, 2013 + 52 + WIND + 255 153 0 + 2018-12-22T03:02:00Z + 2018-12-21T10:00:00Z + 2018-12-22T18:00:00Z + DWD / Nationales Warnzentrum Offenbach + Amtliche WARNUNG vor STURMBÖEN + Es treten Sturmböen mit Geschwindigkeiten zwischen 60 km/h (17m/s, 33kn, Bft 7) und 80 km/h (22m/s, 44kn, Bft 9) anfangs aus südwestlicher, später aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen um 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden. + ACHTUNG! Hinweis auf mögliche Gefahren: Es können zum Beispiel einzelne Äste herabstürzen. Achten Sie besonders auf herabfallende Gegenstände. + http://www.wettergefahren.de + Deutscher Wetterdienst + Böen;Exponierte Böen;Windrichtung;Windrichtung + 60 bis 80 [km/h];~90 [km/h];SW;W + 0 + 9842.5197 + + + + + + + 47.8513 12.9113 47.8327 12.8749 47.8241 12.874 47.8066 12.9062 47.8039 12.9186 47.7798 12.9396 47.8191 12.9787 47.8262 12.9556 47.8441 12.9405 47.8513 12.9113 + + + + + + + + +