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 inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
123 inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
124 XMLStreamReader reader = inputFactory.createXMLStreamReader(new StringReader(rawData));
125 XMLEventReader eventReader = inputFactory.createXMLEventReader(reader);
126 DwdWarningData gemeindeData = new DwdWarningData();
127 boolean insideGemeinde = false;
128 while (eventReader.hasNext()) {
129 XMLEvent event = eventReader.nextEvent();
130 if (!insideGemeinde && event.isStartElement()) {
131 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
133 case WARNUNGEN_GEMEINDEN:
134 gemeindeData = new DwdWarningData();
135 insideGemeinde = true;
140 } else if (insideGemeinde && event.isStartElement()) {
141 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
144 gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader)));
147 gemeindeData.setDescription(getValue(eventReader));
150 gemeindeData.setEffective(getTimestampValue(eventReader));
153 gemeindeData.setExpires(getTimestampValue(eventReader));
156 gemeindeData.setEvent(getValue(eventReader));
159 gemeindeData.setStatus(getValue(eventReader));
162 gemeindeData.setMsgType(getValue(eventReader));
165 gemeindeData.setHeadline(getValue(eventReader));
168 gemeindeData.setOnset(getTimestampValue(eventReader));
171 gemeindeData.setAltitude(getBigDecimalValue(eventReader));
174 gemeindeData.setCeiling(getBigDecimalValue(eventReader));
177 gemeindeData.setId(getValue(eventReader));
180 gemeindeData.setInstruction(getValue(eventReader));
183 gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader)));
188 } else if (insideGemeinde && event.isEndElement()) {
189 DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart());
191 case WARNUNGEN_GEMEINDEN:
192 if (!gemeindeData.isTest() && !gemeindeData.isCancel()) {
193 cityData.add(gemeindeData);
195 insideGemeinde = false;
202 } catch (XMLStreamException e) {
203 logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage());
204 logger.debug("Exception trace", e);
208 Collections.sort(cityData, new SeverityComparator());
212 private DwdWarningData getGemeindeData(int number) {
213 return cityData.size() <= number ? null : cityData.get(number);
216 public State getWarning(int number) {
217 DwdWarningData data = getGemeindeData(number);
218 return OnOffType.from(data != null);
221 public State getSeverity(int number) {
222 DwdWarningData data = getGemeindeData(number);
223 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getSeverity().getText());
226 public State getDescription(int number) {
227 DwdWarningData data = getGemeindeData(number);
228 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getDescription());
231 public State getEffective(int number) {
232 DwdWarningData data = getGemeindeData(number);
234 return UnDefType.UNDEF;
236 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault());
237 return new DateTimeType(zoned);
240 public State getExpires(int number) {
241 DwdWarningData data = getGemeindeData(number);
243 return UnDefType.UNDEF;
245 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault());
246 return new DateTimeType(zoned);
249 public State getOnset(int number) {
250 DwdWarningData data = getGemeindeData(number);
252 return UnDefType.UNDEF;
254 ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault());
255 return new DateTimeType(zoned);
258 public State getEvent(int number) {
259 DwdWarningData data = getGemeindeData(number);
260 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getEvent());
263 public State getHeadline(int number) {
264 DwdWarningData data = getGemeindeData(number);
265 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getHeadline());
268 public State getAltitude(int number) {
269 DwdWarningData data = getGemeindeData(number);
271 return UnDefType.UNDEF;
273 return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT);
276 public State getCeiling(int number) {
277 DwdWarningData data = getGemeindeData(number);
279 return UnDefType.UNDEF;
281 return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT);
284 public State getInstruction(int number) {
285 DwdWarningData data = getGemeindeData(number);
286 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getInstruction());
289 public State getUrgency(int number) {
290 DwdWarningData data = getGemeindeData(number);
291 return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getUrgency().getText());
294 public boolean isNew(int number) {
295 DwdWarningData data = getGemeindeData(number);
299 return cache.addEntry(data);
302 public void updateCache() {
303 cache.deleteOldEntries();
309 protected void setDataAccess(DwdWarningDataAccess dataAccess) {
310 dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
311 () -> dataAccess.getDataFromEndpoint("TestCity"));