]> git.basschouten.com Git - openhab-addons.git/blob
bf07435c8c44868bfe5dc8fde203b0bc257fd530
[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.tankerkoenig.internal.handler;
14
15 import java.math.BigDecimal;
16 import java.text.ParseException;
17 import java.time.DayOfWeek;
18 import java.time.LocalDate;
19 import java.time.LocalTime;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.openhab.binding.tankerkoenig.internal.TankerkoenigBindingConstants;
26 import org.openhab.binding.tankerkoenig.internal.data.TankerkoenigService;
27 import org.openhab.binding.tankerkoenig.internal.dto.LittleStation;
28 import org.openhab.binding.tankerkoenig.internal.dto.OpeningTime;
29 import org.openhab.binding.tankerkoenig.internal.dto.OpeningTimes;
30 import org.openhab.binding.tankerkoenig.internal.dto.TankerkoenigListResult;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.binding.BaseBridgeHandler;
39 import org.openhab.core.types.Command;
40 import org.osgi.framework.FrameworkUtil;
41 import org.osgi.framework.Version;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link WebserviceHandler} is responsible for handling the things (stations)
47  *
48  * @author Dennis Dollinger - Initial contribution
49  * @author Jürgen Baginski - Initial contribution
50  */
51 public class WebserviceHandler extends BaseBridgeHandler {
52     private final Logger logger = LoggerFactory.getLogger(WebserviceHandler.class);
53
54     private String apiKey;
55     private int refreshInterval;
56     private boolean modeOpeningTime;
57     private String userAgent;
58     private boolean isHoliday;
59     private final TankerkoenigService service = new TankerkoenigService();
60     private TankerkoenigListResult tankerkoenigListResult;
61
62     private final Map<String, LittleStation> stationMap = new HashMap<>();
63
64     private ScheduledFuture<?> pollingJob;
65
66     public WebserviceHandler(Bridge bridge) {
67         super(bridge);
68     }
69
70     @Override
71     public void handleCommand(ChannelUID channelUID, Command command) {
72         if (channelUID.getId().equals(TankerkoenigBindingConstants.CHANNEL_HOLIDAY)) {
73             logger.debug("HandleCommand recieved: {}", channelUID.getId());
74             isHoliday = (command == OnOffType.ON);
75         }
76     }
77
78     @Override
79     public void initialize() {
80         logger.debug("Initialize Bridge");
81         Configuration config = getThing().getConfiguration();
82         setApiKey((String) config.get(TankerkoenigBindingConstants.CONFIG_API_KEY));
83         setRefreshInterval(((BigDecimal) config.get(TankerkoenigBindingConstants.CONFIG_REFRESH)).intValue());
84         setModeOpeningTime((boolean) config.get(TankerkoenigBindingConstants.CONFIG_MODE_OPENINGTIME));
85         // set the UserAgent, this string is used by TankerkoenigService
86         // to set a custom UserAgent for the WebRequest as specifically requested by Tankerkoening.de!
87         StringBuilder sb = new StringBuilder();
88         sb.append("openHAB, Tankerkoenig-Binding Version ");
89         Version version = FrameworkUtil.getBundle(getClass()).getVersion();
90         sb.append(version.toString());
91         userAgent = sb.toString();
92
93         updateStatus(ThingStatus.UNKNOWN);
94
95         int pollingPeriod = getRefreshInterval();
96         pollingJob = scheduler.scheduleWithFixedDelay(() -> {
97             logger.debug("Try to refresh data");
98             try {
99                 updateStationData();
100                 updateStationThings();
101             } catch (RuntimeException r) {
102                 logger.debug("Caught exception in ScheduledExecutorService of BridgeHandler. RuntimeException: ", r);
103                 updateStatus(ThingStatus.OFFLINE);
104             }
105         }, pollingPeriod, pollingPeriod, TimeUnit.MINUTES);
106         logger.debug("Refresh job scheduled to run every {} min. for '{}'", pollingPeriod, getThing().getUID());
107     }
108
109     @Override
110     public void dispose() {
111         if (pollingJob != null) {
112             pollingJob.cancel(true);
113         }
114     }
115
116     @Override
117     public void updateStatus(ThingStatus status) {
118         updateStatus(status, ThingStatusDetail.NONE, null);
119     }
120
121     /***
122      * Updates the data from tankerkoenig api (no update on things)
123      */
124     public void updateStationData() {
125         // Get data
126         try {
127             String locationIDsString = "";
128             if (modeOpeningTime) {
129                 logger.debug("Opening times are used");
130                 locationIDsString = generateOpenLocationIDsString();
131             } else {
132                 logger.debug("No opening times are used");
133                 locationIDsString = generateLocationIDsString();
134             }
135             if (locationIDsString.isEmpty()) {
136                 logger.debug("No tankstellen id's found. Nothing to update");
137                 return;
138             }
139             TankerkoenigListResult result = service.getStationListData(getApiKey(), locationIDsString, userAgent);
140             if (!result.isOk()) {
141                 // two possibel reasons for result.isOK=false
142                 // A-tankerkoenig returns false on a web-request
143                 // in this case the field "message" holds information for the reason.
144                 // B-the web-request does not return a valid json-string,
145                 // in this case an emptyReturn object is created with the message "No valid response from the
146                 // web-request!"
147                 // in both cases the Webservice and the Station(s) will go OFFLINE
148                 // only in case A the pollingJob gets canceled!
149                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, result.getMessage());
150                 // if the Bridge goes OFFLINE, all connected Stations will go OFFLINE as well.
151                 // The bridge reports its statusUpdate and the things react using the bridgeStatusChanged-Method!
152                 // Only if the message is NOT "No valid response from the web-request!" the scheduled job gets stopped!
153                 if (!result.getMessage().equals(TankerkoenigBindingConstants.NO_VALID_RESPONSE)) {
154                     pollingJob.cancel(true);
155                 }
156             } else {
157                 updateStatus(ThingStatus.ONLINE);
158                 setTankerkoenigListResult(result);
159                 stationMap.clear();
160                 for (LittleStation station : result.getPrices().getStations()) {
161                     station.setOpen("open".equals(station.getStatus()));
162                     stationMap.put(station.getID(), station);
163                 }
164                 logger.debug("UpdateStationData: tankstellenList.size {}", stationMap.size());
165             }
166         } catch (ParseException e) {
167             logger.error("ParseException: ", e);
168             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
169         }
170     }
171
172     /***
173      * Updates all registered station with new data
174      */
175     public void updateStationThings() {
176         logger.debug("UpdateStationThings: getThing().getThings().size {}", getThing().getThings().size());
177         for (Thing thing : getThing().getThings()) {
178             StationHandler tkh = (StationHandler) thing.getHandler();
179             LittleStation s = stationMap.get(tkh.getLocationID());
180             if (s == null) {
181                 logger.debug("Station with id {}  is not updated!", tkh.getLocationID());
182             } else {
183                 tkh.updateData(s);
184             }
185         }
186     }
187
188     /***
189      * Generates a comma separated string with all station id's
190      *
191      * @return
192      */
193     private String generateLocationIDsString() {
194         StringBuilder sb = new StringBuilder();
195         for (Thing thing : getThing().getThings()) {
196             StationHandler tkh = (StationHandler) thing.getHandler();
197             if (sb.length() > 0) {
198                 sb.append(",");
199             }
200             sb.append(tkh.getLocationID());
201         }
202         return sb.toString();
203     }
204
205     /***
206      * Generates a comma separated string of all open station id's
207      * calculated using the data stored in opentimesList
208      * The settings in the section "override" from the json detail response are NOT used!
209      *
210      * @return String
211      * @throws ParseException
212      */
213     private String generateOpenLocationIDsString() throws ParseException {
214         StringBuilder sb = new StringBuilder();
215         LocalDate today = LocalDate.now();
216         for (Thing thing : getThing().getThings()) {
217             String start = "00:00";
218             String ende = "00:00";
219             StationHandler tkh = (StationHandler) thing.getHandler();
220             Boolean foundIt = false;
221             OpeningTimes oTimes = tkh.getOpeningTimes();
222             // oTimes could be NULL, assume wholeDay open in this case!
223             if (oTimes != null) {
224                 if (oTimes.getWholeDay()) {
225                     // WholeDay open, use this ID!
226                     foundIt = true;
227                     logger.debug("Found a setting for WholeDay.");
228                     // "start" and "ende" are set manually!
229                     start = "00:00";
230                     ende = "23:59";
231                 } else {
232                     OpeningTime[] o = oTimes.getOpeningTimes();
233                     logger.debug("o.length: {}", o.length);
234                     int i = 0;
235                     do {
236                         logger.debug("Checking opening time i: {}", i);
237                         String day = o[i].getText();
238                         String open = o[i].getStart();
239                         String close = o[i].getEnd();
240                         DayOfWeek weekday = today.getDayOfWeek();
241                         logger.debug("Checking day: {}", day);
242                         logger.debug("Todays weekday: {}", weekday);
243                         if (isHoliday) {
244                             weekday = DayOfWeek.SUNDAY;
245                             logger.debug("Today is a holiday using : {}", weekday);
246                         }
247                         // if Daily, further checking not needed!
248                         if (day.contains("täglich")) {
249                             logger.debug("Found a setting for daily opening times.");
250                             foundIt = true;
251                         } else {
252                             switch (weekday) {
253                                 case MONDAY:
254                                     if ((day.contains("Werktags")) || (day.contains("Mo"))) {
255                                         logger.debug("Found a setting which is valid for today (Monday).");
256                                         foundIt = true;
257                                     }
258                                     break;
259                                 case TUESDAY:
260                                     if ((day.contains("Werktags")) || (day.contains("Di")) || (day.contains("Mo-Fr"))) {
261                                         logger.debug("Found a setting which is valid for today (Tuesday).");
262                                         foundIt = true;
263                                     }
264                                     break;
265                                 case WEDNESDAY:
266                                     if ((day.contains("Werktags")) || (day.contains("Mi")) || (day.contains("Mo-Fr"))) {
267                                         logger.debug("Found a setting which is valid for today (Wednesday).");
268                                         foundIt = true;
269                                     }
270                                     break;
271                                 case THURSDAY:
272                                     if ((day.contains("Werktags")) || (day.contains("Do")) || (day.contains("Mo-Fr"))) {
273                                         logger.debug("Found a setting which is valid for today (Thursday).");
274                                         foundIt = true;
275                                     }
276                                     break;
277                                 case FRIDAY:
278                                     if ((day.contains("Werktags")) || (day.contains("Fr"))) {
279                                         logger.debug("Found a setting which is valid for today (Fryday).");
280                                         foundIt = true;
281                                     }
282                                     break;
283                                 case SATURDAY:
284                                     if ((day.contains("Wochendende")) || (day.contains("Sa"))) {
285                                         logger.debug("Found a setting which is valid for today (Saturday).");
286                                         foundIt = true;
287                                     }
288                                     break;
289                                 case SUNDAY:
290                                     if ((day.contains("Wochenende")) || (day.contains("So"))) {
291                                         logger.debug("Found a setting which is valid for today (Sunday).");
292                                         foundIt = true;
293                                     }
294                                     break;
295                             }
296                         }
297                         if (foundIt) {
298                             start = open;
299                             ende = close;
300                             break;
301                         }
302                         i = i + 1;
303                     } while (i < o.length);
304                 }
305             } else {
306                 // no OpeningTimes found, assuming WholeDay open!
307                 foundIt = true;
308                 logger.debug("No OpeningTimes are found, assuming WholeDay.");
309                 // "start" and "ende" are set manually!
310                 start = "00:00";
311                 ende = "23:59";
312             }
313             LocalTime opening = LocalTime.parse(start);
314             LocalTime closing = LocalTime.parse(ende);
315             LocalTime now = LocalTime.now();
316             if (!opening.equals(closing)) {
317                 // Tankerkoenig.de does update the status "open" every 4 minutes
318                 // due to this the status "open" could be sent up to 4 minutes after the published opening time
319                 // therefore the first update is called 4 minutes after opening time!
320                 opening = opening.plusMinutes(4);
321             }
322             if ((opening.equals(closing)) || ((now.isAfter(opening) & (now.isBefore(closing))))) {
323                 logger.debug("Now is within opening times for today.");
324                 if (sb.length() > 0) {
325                     sb.append(",");
326                 }
327                 sb.append(tkh.getLocationID());
328             }
329         }
330         return sb.toString();
331     }
332
333     public String getApiKey() {
334         return apiKey;
335     }
336
337     public void setApiKey(String apiKey) {
338         this.apiKey = apiKey;
339     }
340
341     public int getRefreshInterval() {
342         return refreshInterval;
343     }
344
345     public void setRefreshInterval(int refreshInterval) {
346         this.refreshInterval = refreshInterval;
347     }
348
349     public TankerkoenigListResult getTankerkoenigListResult() {
350         return tankerkoenigListResult;
351     }
352
353     public void setTankerkoenigListResult(TankerkoenigListResult tankerkoenigListResult) {
354         this.tankerkoenigListResult = tankerkoenigListResult;
355     }
356
357     public boolean isModeOpeningTime() {
358         return modeOpeningTime;
359     }
360
361     public void setModeOpeningTime(boolean modeOpeningTime) {
362         this.modeOpeningTime = modeOpeningTime;
363     }
364
365     public String getUserAgent() {
366         return userAgent;
367     }
368 }