]> git.basschouten.com Git - openhab-addons.git/blob
41ae77917f9a8cb6c8ea55bae0e5417d1387db9c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dwdunwetter.internal.dto;
14
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;
28
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;
34
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;
45
46 /**
47  * Contains the Data for all retrieved warnings for one thing.
48  *
49  * @author Martin Koehler - Initial contribution
50  */
51 public class DwdWarningsData {
52
53     private static final int MIN_REFRESH_WAIT_MINUTES = 5;
54
55     private final Logger logger = LoggerFactory.getLogger(DwdWarningsData.class);
56
57     private List<DwdWarningData> cityData = new LinkedList<>();
58
59     private DwdWarningCache cache = new DwdWarningCache();
60
61     private ExpiringCache<String> dataAccessCached;
62
63     private DateTimeFormatter formatter = new DateTimeFormatterBuilder()
64             // date/time
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()
72             // create formatter
73             .toFormatter();
74
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));
79     }
80
81     private String getValue(XMLEventReader eventReader) throws XMLStreamException {
82         XMLEvent event = eventReader.nextEvent();
83         return event.asCharacters().getData();
84     }
85
86     private BigDecimal getBigDecimalValue(XMLEventReader eventReader) throws XMLStreamException {
87         XMLEvent event = eventReader.nextEvent();
88         try {
89             return new BigDecimal(event.asCharacters().getData());
90         } catch (NumberFormatException e) {
91             logger.debug("Exception while parsing a BigDecimal", e);
92             return BigDecimal.ZERO;
93         }
94     }
95
96     private Instant getTimestampValue(XMLEventReader eventReader) throws XMLStreamException {
97         XMLEvent event = eventReader.nextEvent();
98         String dateTimeString = event.asCharacters().getData();
99         try {
100             OffsetDateTime dateTime = OffsetDateTime.parse(dateTimeString, formatter);
101             return dateTime.toInstant();
102         } catch (DateTimeParseException e) {
103             logger.debug("Exception while parsing a DateTime", e);
104             return Instant.MIN;
105         }
106     }
107
108     /**
109      * Refreshes the Warnings Data
110      */
111     public boolean refresh() {
112         String rawData = dataAccessCached.getValue();
113         if (rawData == null || rawData.isEmpty()) {
114             logger.debug("No Data from Endpoint");
115             return false;
116         }
117
118         cityData.clear();
119
120         try {
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());
130                     switch (xmlTag) {
131                         case WARNUNGEN_GEMEINDEN:
132                             gemeindeData = new DwdWarningData();
133                             insideGemeinde = true;
134                             break;
135                         default:
136                             break;
137                     }
138                 } else if (insideGemeinde && event.isStartElement()) {
139                     DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
140                     switch (xmlTag) {
141                         case SEVERITY:
142                             gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader)));
143                             break;
144                         case DESCRIPTION:
145                             gemeindeData.setDescription(getValue(eventReader));
146                             break;
147                         case EFFECTIVE:
148                             gemeindeData.setEffective(getTimestampValue(eventReader));
149                             break;
150                         case EXPIRES:
151                             gemeindeData.setExpires(getTimestampValue(eventReader));
152                             break;
153                         case EVENT:
154                             gemeindeData.setEvent(getValue(eventReader));
155                             break;
156                         case STATUS:
157                             gemeindeData.setStatus(getValue(eventReader));
158                             break;
159                         case MSGTYPE:
160                             gemeindeData.setMsgType(getValue(eventReader));
161                             break;
162                         case HEADLINE:
163                             gemeindeData.setHeadline(getValue(eventReader));
164                             break;
165                         case ONSET:
166                             gemeindeData.setOnset(getTimestampValue(eventReader));
167                             break;
168                         case ALTITUDE:
169                             gemeindeData.setAltitude(getBigDecimalValue(eventReader));
170                             break;
171                         case CEILING:
172                             gemeindeData.setCeiling(getBigDecimalValue(eventReader));
173                             break;
174                         case IDENTIFIER:
175                             gemeindeData.setId(getValue(eventReader));
176                             break;
177                         case INSTRUCTION:
178                             gemeindeData.setInstruction(getValue(eventReader));
179                             break;
180                         case URGENCY:
181                             gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader)));
182                             break;
183                         default:
184                             break;
185                     }
186                 } else if (insideGemeinde && event.isEndElement()) {
187                     DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart());
188                     switch (xmlTag) {
189                         case WARNUNGEN_GEMEINDEN:
190                             if (!gemeindeData.isTest() && !gemeindeData.isCancel()) {
191                                 cityData.add(gemeindeData);
192                             }
193                             insideGemeinde = false;
194                             break;
195                         default:
196                             break;
197                     }
198                 }
199             }
200         } catch (XMLStreamException e) {
201             logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage());
202             logger.debug("Exception trace", e);
203             return false;
204         }
205
206         Collections.sort(cityData, new SeverityComparator());
207         return true;
208     }
209
210     private DwdWarningData getGemeindeData(int number) {
211         return cityData.size() <= number ? null : cityData.get(number);
212     }
213
214     public State getWarning(int number) {
215         DwdWarningData data = getGemeindeData(number);
216         return data == null ? OnOffType.OFF : OnOffType.ON;
217     }
218
219     public State getSeverity(int number) {
220         DwdWarningData data = getGemeindeData(number);
221         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getSeverity().getText());
222     }
223
224     public State getDescription(int number) {
225         DwdWarningData data = getGemeindeData(number);
226         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getDescription());
227     }
228
229     public State getEffective(int number) {
230         DwdWarningData data = getGemeindeData(number);
231         if (data == null) {
232             return UnDefType.UNDEF;
233         }
234         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault());
235         return new DateTimeType(zoned);
236     }
237
238     public State getExpires(int number) {
239         DwdWarningData data = getGemeindeData(number);
240         if (data == null) {
241             return UnDefType.UNDEF;
242         }
243         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault());
244         return new DateTimeType(zoned);
245     }
246
247     public State getOnset(int number) {
248         DwdWarningData data = getGemeindeData(number);
249         if (data == null) {
250             return UnDefType.UNDEF;
251         }
252         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault());
253         return new DateTimeType(zoned);
254     }
255
256     public State getEvent(int number) {
257         DwdWarningData data = getGemeindeData(number);
258         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getEvent());
259     }
260
261     public State getHeadline(int number) {
262         DwdWarningData data = getGemeindeData(number);
263         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getHeadline());
264     }
265
266     public State getAltitude(int number) {
267         DwdWarningData data = getGemeindeData(number);
268         if (data == null) {
269             return UnDefType.UNDEF;
270         }
271         return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT);
272     }
273
274     public State getCeiling(int number) {
275         DwdWarningData data = getGemeindeData(number);
276         if (data == null) {
277             return UnDefType.UNDEF;
278         }
279         return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT);
280     }
281
282     public State getInstruction(int number) {
283         DwdWarningData data = getGemeindeData(number);
284         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getInstruction());
285     }
286
287     public State getUrgency(int number) {
288         DwdWarningData data = getGemeindeData(number);
289         return data == null ? UnDefType.UNDEF : StringType.valueOf(data.getUrgency().getText());
290     }
291
292     public boolean isNew(int number) {
293         DwdWarningData data = getGemeindeData(number);
294         if (data == null) {
295             return false;
296         }
297         return cache.addEntry(data);
298     }
299
300     public void updateCache() {
301         cache.deleteOldEntries();
302     }
303
304     /**
305      * Only for Tests
306      */
307     protected void setDataAccess(DwdWarningDataAccess dataAccess) {
308         dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
309                 () -> dataAccess.getDataFromEndpoint(""));
310     }
311 }