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.tankerkoenig.internal.handler;
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;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
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;
46 * The {@link WebserviceHandler} is responsible for handling the things (stations)
48 * @author Dennis Dollinger - Initial contribution
49 * @author Jürgen Baginski - Initial contribution
51 public class WebserviceHandler extends BaseBridgeHandler {
52 private final Logger logger = LoggerFactory.getLogger(WebserviceHandler.class);
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;
62 private final Map<String, LittleStation> stationMap = new HashMap<>();
64 private ScheduledFuture<?> pollingJob;
66 public WebserviceHandler(Bridge bridge) {
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);
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();
93 updateStatus(ThingStatus.UNKNOWN);
95 int pollingPeriod = getRefreshInterval();
96 pollingJob = scheduler.scheduleWithFixedDelay(() -> {
97 logger.debug("Try to refresh data");
100 updateStationThings();
101 } catch (RuntimeException r) {
102 logger.debug("Caught exception in ScheduledExecutorService of BridgeHandler. RuntimeException: ", r);
103 updateStatus(ThingStatus.OFFLINE);
105 }, pollingPeriod, pollingPeriod, TimeUnit.MINUTES);
106 logger.debug("Refresh job scheduled to run every {} min. for '{}'", pollingPeriod, getThing().getUID());
110 public void dispose() {
111 if (pollingJob != null) {
112 pollingJob.cancel(true);
117 public void updateStatus(ThingStatus status) {
118 updateStatus(status, ThingStatusDetail.NONE, null);
122 * Updates the data from tankerkoenig api (no update on things)
124 public void updateStationData() {
127 String locationIDsString = "";
128 if (modeOpeningTime) {
129 logger.debug("Opening times are used");
130 locationIDsString = generateOpenLocationIDsString();
132 logger.debug("No opening times are used");
133 locationIDsString = generateLocationIDsString();
135 if (locationIDsString.isEmpty()) {
136 logger.debug("No tankstellen id's found. Nothing to update");
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
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);
157 updateStatus(ThingStatus.ONLINE);
158 setTankerkoenigListResult(result);
160 for (LittleStation station : result.getPrices().getStations()) {
161 station.setOpen("open".equals(station.getStatus()));
162 stationMap.put(station.getID(), station);
164 logger.debug("UpdateStationData: tankstellenList.size {}", stationMap.size());
166 } catch (ParseException e) {
167 logger.error("ParseException: ", e);
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
173 * Updates all registered station with new data
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());
181 logger.debug("Station with id {} is not updated!", tkh.getLocationID());
189 * Generates a comma separated string with all station id's
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) {
200 sb.append(tkh.getLocationID());
202 return sb.toString();
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!
211 * @throws ParseException
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!
227 logger.debug("Found a setting for WholeDay.");
228 // "start" and "ende" are set manually!
232 OpeningTime[] o = oTimes.getOpeningTimes();
233 logger.debug("o.length: {}", o.length);
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);
244 weekday = DayOfWeek.SUNDAY;
245 logger.debug("Today is a holiday using : {}", weekday);
247 // if Daily, further checking not needed!
248 if (day.contains("täglich")) {
249 logger.debug("Found a setting for daily opening times.");
254 if ((day.contains("Werktags")) || (day.contains("Mo"))) {
255 logger.debug("Found a setting which is valid for today (Monday).");
260 if ((day.contains("Werktags")) || (day.contains("Di")) || (day.contains("Mo-Fr"))) {
261 logger.debug("Found a setting which is valid for today (Tuesday).");
266 if ((day.contains("Werktags")) || (day.contains("Mi")) || (day.contains("Mo-Fr"))) {
267 logger.debug("Found a setting which is valid for today (Wednesday).");
272 if ((day.contains("Werktags")) || (day.contains("Do")) || (day.contains("Mo-Fr"))) {
273 logger.debug("Found a setting which is valid for today (Thursday).");
278 if ((day.contains("Werktags")) || (day.contains("Fr"))) {
279 logger.debug("Found a setting which is valid for today (Fryday).");
284 if ((day.contains("Wochendende")) || (day.contains("Sa"))) {
285 logger.debug("Found a setting which is valid for today (Saturday).");
290 if ((day.contains("Wochenende")) || (day.contains("So"))) {
291 logger.debug("Found a setting which is valid for today (Sunday).");
303 } while (i < o.length);
306 // no OpeningTimes found, assuming WholeDay open!
308 logger.debug("No OpeningTimes are found, assuming WholeDay.");
309 // "start" and "ende" are set manually!
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);
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) {
327 sb.append(tkh.getLocationID());
330 return sb.toString();
333 public String getApiKey() {
337 public void setApiKey(String apiKey) {
338 this.apiKey = apiKey;
341 public int getRefreshInterval() {
342 return refreshInterval;
345 public void setRefreshInterval(int refreshInterval) {
346 this.refreshInterval = refreshInterval;
349 public TankerkoenigListResult getTankerkoenigListResult() {
350 return tankerkoenigListResult;
353 public void setTankerkoenigListResult(TankerkoenigListResult tankerkoenigListResult) {
354 this.tankerkoenigListResult = tankerkoenigListResult;
357 public boolean isModeOpeningTime() {
358 return modeOpeningTime;
361 public void setModeOpeningTime(boolean modeOpeningTime) {
362 this.modeOpeningTime = modeOpeningTime;
365 public String getUserAgent() {