2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dwdunwetter.internal.dto;
15 import java.io.StringReader;
16 import java.math.BigDecimal;
17 import java.time.Duration;
18 import java.time.Instant;
19 import java.time.OffsetDateTime;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.time.format.DateTimeFormatterBuilder;
24 import java.time.format.DateTimeParseException;
25 import java.util.Collections;
26 import java.util.LinkedList;
27 import java.util.List;
29 import javax.xml.stream.XMLEventReader;
30 import javax.xml.stream.XMLInputFactory;
31 import javax.xml.stream.XMLStreamException;
32 import javax.xml.stream.XMLStreamReader;
33 import javax.xml.stream.events.XMLEvent;
35 import org.openhab.core.cache.ExpiringCache;
36 import org.openhab.core.library.types.DateTimeType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.ImperialUnits;
41 import org.openhab.core.types.State;
42 import org.openhab.core.types.UnDefType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * Contains the Data for all retrieved warnings for one thing.
49 * @author Martin Koehler - Initial contribution
51 public class DwdWarningsData {
53 private static final int MIN_REFRESH_WAIT_MINUTES = 5;
55 private final Logger logger = LoggerFactory.getLogger(DwdWarningsData.class);
57 private List<DwdWarningData> cityData = new LinkedList<>();
59 private DwdWarningCache cache = new DwdWarningCache();
61 private ExpiringCache<String> dataAccessCached;
63 private DateTimeFormatter formatter = new DateTimeFormatterBuilder()
65 .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
66 // offset (hh:mm - "+00:00" when it's zero)
67 .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
68 // offset (hhmm - "+0000" when it's zero)
69 .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
70 // offset (hh - "Z" when it's zero)
71 .optionalStart().appendOffset("+HH", "Z").optionalEnd()
75 public DwdWarningsData(String cellId) {
76 DwdWarningDataAccess dataAccess = new DwdWarningDataAccess();
77 this.dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
78 () -> dataAccess.getDataFromEndpoint(cellId));
81 private String getValue(XMLEventReader eventReader) throws XMLStreamException {
82 XMLEvent event = eventReader.nextEvent();
83 return event.asCharacters().getData();
86 private BigDecimal getBigDecimalValue(XMLEventReader eventReader) throws XMLStreamException {
87 XMLEvent event = eventReader.nextEvent();
89 return new BigDecimal(event.asCharacters().getData());
90 } catch (NumberFormatException e) {
91 logger.debug("Exception while parsing a BigDecimal", e);
92 return BigDecimal.ZERO;
96 private Instant getTimestampValue(XMLEventReader eventReader) throws XMLStreamException {
97 XMLEvent event = eventReader.nextEvent();
98 String dateTimeString = event.asCharacters().getData();
100 OffsetDateTime dateTime = OffsetDateTime.parse(dateTimeString, formatter);
101 return dateTime.toInstant();
102 } catch (DateTimeParseException e) {
103 logger.debug("Exception while parsing a DateTime", e);
109 * Refreshes the Warnings Data
111 public boolean refresh() {
112 String rawData = dataAccessCached.getValue();
113 if (rawData == null || rawData.isEmpty()) {
114 logger.debug("No Data from Endpoint");
121 XMLInputFactory inputFactory = XMLInputFactory.newInstance();
122 XMLStreamReader reader = inputFactory.createXMLStreamReader(new StringReader(rawData));
123 XMLEventReader eventReader = inputFactory.createXMLEventReader(reader);
124 DwdWarningData gemeindeData = new DwdWarningData();
125 boolean insideGemeinde = false;
126 while (eventReader.hasNext()) {
127 XMLEvent event = eventReader.nextEvent();
128 if (!insideGemeinde && event.isStartElement()) {
129 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
131 case WARNUNGEN_GEMEINDEN:
132 gemeindeData = new DwdWarningData();
133 insideGemeinde = true;
138 } else if (insideGemeinde && event.isStartElement()) {
139 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
142 gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader)));
145 gemeindeData.setDescription(getValue(eventReader));
148 gemeindeData.setEffective(getTimestampValue(eventReader));
151 gemeindeData.setExpires(getTimestampValue(eventReader));
154 gemeindeData.setEvent(getValue(eventReader));
157 gemeindeData.setStatus(getValue(eventReader));
160 gemeindeData.setMsgType(getValue(eventReader));
163 gemeindeData.setHeadline(getValue(eventReader));
166 gemeindeData.setOnset(getTimestampValue(eventReader));
169 gemeindeData.setAltitude(getBigDecimalValue(eventReader));
172 gemeindeData.setCeiling(getBigDecimalValue(eventReader));
175 gemeindeData.setId(getValue(eventReader));
178 gemeindeData.setInstruction(getValue(eventReader));
181 gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader)));
186 } else if (insideGemeinde && event.isEndElement()) {
187 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart());
189 case WARNUNGEN_GEMEINDEN:
190 if (!gemeindeData.isTest() && !gemeindeData.isCancel()) {
191 cityData.add(gemeindeData);
193 insideGemeinde = false;
200 } catch (XMLStreamException e) {
201 logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage());
202 logger.debug("Exception trace", e);
206 Collections.sort(cityData, new SeverityComparator());
210 private DwdWarningData getGemeindeData(int number) {
211 return cityData.size() <= number ? null : cityData.get(number);
214 public State getWarning(int number) {
215 DwdWarningData data = getGemeindeData(number);
216 return data == null ? OnOffType.OFF : OnOffType.ON;
219 public State getSeverity(int number) {
220 DwdWarningData data = getGemeindeData(number);
221 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getSeverity().getText());
224 public State getDescription(int number) {
225 DwdWarningData data = getGemeindeData(number);
226 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getDescription());
229 public State getEffective(int number) {
230 DwdWarningData data = getGemeindeData(number);
232 return UnDefType.UNDEF;
234 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault());
235 return new DateTimeType(zoned);
238 public State getExpires(int number) {
239 DwdWarningData data = getGemeindeData(number);
241 return UnDefType.UNDEF;
243 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault());
244 return new DateTimeType(zoned);
247 public State getOnset(int number) {
248 DwdWarningData data = getGemeindeData(number);
250 return UnDefType.UNDEF;
252 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault());
253 return new DateTimeType(zoned);
256 public State getEvent(int number) {
257 DwdWarningData data = getGemeindeData(number);
258 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getEvent());
261 public State getHeadline(int number) {
262 DwdWarningData data = getGemeindeData(number);
263 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getHeadline());
266 public State getAltitude(int number) {
267 DwdWarningData data = getGemeindeData(number);
269 return UnDefType.UNDEF;
271 return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT);
274 public State getCeiling(int number) {
275 DwdWarningData data = getGemeindeData(number);
277 return UnDefType.UNDEF;
279 return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT);
282 public State getInstruction(int number) {
283 DwdWarningData data = getGemeindeData(number);
284 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getInstruction());
287 public State getUrgency(int number) {
288 DwdWarningData data = getGemeindeData(number);
289 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getUrgency().getText());
292 public boolean isNew(int number) {
293 DwdWarningData data = getGemeindeData(number);
297 return cache.addEntry(data);
300 public void updateCache() {
301 cache.deleteOldEntries();
307 protected void setDataAccess(DwdWarningDataAccess dataAccess) {
308 dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
309 () -> dataAccess.getDataFromEndpoint(""));