]> git.basschouten.com Git - openhab-addons.git/blob
6faf915a6b171be3d995269b4048764eba861040
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.enturno.internal;
14
15 import java.time.ZoneId;
16 import java.time.ZonedDateTime;
17 import java.time.format.DateTimeFormatter;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.apache.commons.lang.StringUtils;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.enturno.internal.connection.EnturCommunicationException;
28 import org.openhab.binding.enturno.internal.connection.EnturConfigurationException;
29 import org.openhab.binding.enturno.internal.connection.EnturNoConnection;
30 import org.openhab.binding.enturno.internal.model.simplified.DisplayData;
31 import org.openhab.core.library.types.DateTimeType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.Channel;
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.BaseThingHandler;
39 import org.openhab.core.thing.type.ChannelKind;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import com.google.gson.JsonSyntaxException;
48
49 /**
50  * The {@link EnturNoHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Michal Kloc - Initial contribution
54  */
55 @NonNullByDefault
56 public class EnturNoHandler extends BaseThingHandler {
57
58     private final Logger logger = LoggerFactory.getLogger(EnturNoHandler.class);
59
60     private final HttpClient httpClient;
61
62     private @NonNullByDefault({}) EnturNoConfiguration config;
63
64     private @NonNullByDefault({}) EnturNoConnection connection;
65
66     private static final long INITIAL_DELAY_IN_SECONDS = 15;
67
68     private static final long REFRESH_INTERVAL_IN_SECONDS = 30;
69
70     private @Nullable ScheduledFuture<?> refreshJob;
71
72     private @Nullable String stopId;
73
74     private List<DisplayData> processedData = new ArrayList<>();
75
76     public EnturNoHandler(Thing thing, HttpClient httpClient) {
77         super(thing);
78         this.httpClient = httpClient;
79     }
80
81     @Override
82     public void handleCommand(ChannelUID channelUID, Command command) {
83         if (command instanceof RefreshType) {
84             updateChannel(channelUID);
85         } else {
86             logger.debug("Entur binding is a read-only binding and cannot handle command '{}'.", command);
87         }
88     }
89
90     @Override
91     public void initialize() {
92         logger.debug("Initialize Entur EnturTimeTable API handler '{}'.", getThing().getUID());
93         config = getConfigAs(EnturNoConfiguration.class);
94         stopId = config.getStopPlaceId();
95
96         logger.debug("Stop place id: {}", stopId);
97         boolean configValid = true;
98         if (StringUtils.trimToNull(stopId) == null) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
100                     "@text/offline.conf-error-missing-stopId");
101             configValid = false;
102         }
103
104         logger.debug("Line code: {}", config.getLineCode());
105         if (StringUtils.trimToNull(config.getLineCode()) == null) {
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
107                     "@text/offline.conf-error-missing-lineCode");
108             configValid = false;
109         }
110
111         if (configValid) {
112             connection = new EnturNoConnection(this, httpClient);
113
114             updateStatus(ThingStatus.UNKNOWN);
115
116             if (refreshJob == null || refreshJob.isCancelled()) {
117                 logger.debug("Start refresh job at interval {} sec.", REFRESH_INTERVAL_IN_SECONDS);
118                 refreshJob = scheduler.scheduleWithFixedDelay(this::updateThing, INITIAL_DELAY_IN_SECONDS,
119                         REFRESH_INTERVAL_IN_SECONDS, TimeUnit.SECONDS);
120             }
121         }
122     }
123
124     @Override
125     public void dispose() {
126         logger.debug("Dispose Entur real-time timetable API handler '{}'.", getThing().getUID());
127         if (refreshJob != null && !refreshJob.isCancelled()) {
128             logger.debug("Stop refresh job.");
129             if (refreshJob.cancel(true)) {
130                 refreshJob = null;
131             }
132         }
133     }
134
135     public EnturNoConfiguration getEnturNoConfiguration() {
136         return config;
137     }
138
139     private void updateThing() {
140         ThingStatus status = ThingStatus.OFFLINE;
141         if (connection != null) {
142             logger.trace("Updating data");
143             updateData(connection);
144             status = thing.getStatus();
145         } else {
146             logger.debug("Cannot update real-time data of thing '{}' as connection is null.", thing.getUID());
147             status = ThingStatus.OFFLINE;
148         }
149
150         updateStatus(status);
151     }
152
153     public void updateData(EnturNoConnection connection) {
154         try {
155             if (requestData(connection)) {
156                 updateChannels();
157                 updateStatus(ThingStatus.ONLINE);
158             }
159         } catch (EnturCommunicationException e) {
160             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
161         } catch (EnturConfigurationException e) {
162             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
163         }
164     }
165
166     private boolean requestData(EnturNoConnection connection)
167             throws EnturConfigurationException, EnturCommunicationException {
168         logger.debug("Update real-time data of thing '{}'.", getThing().getUID());
169         try {
170             processedData = connection.getEnturTimeTable(stopId, config.getLineCode());
171
172             return true;
173         } catch (JsonSyntaxException e) {
174             logger.debug("JsonSyntaxException occurred during execution: {}", e.getLocalizedMessage(), e);
175             return false;
176         }
177     }
178
179     private void updateChannels() {
180         for (Channel channel : getThing().getChannels()) {
181             ChannelUID channelUID = channel.getUID();
182             if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
183                     && isLinked(channelUID)) {
184                 updateChannel(channelUID);
185             }
186         }
187     }
188
189     private void updateChannel(ChannelUID channelUID) {
190         String channelGroupId = channelUID.getGroupId();
191         logger.trace("Channel group id: {}", channelGroupId);
192         if (channelGroupId != null) {
193             switch (channelGroupId) {
194                 case EnturNoBindingConstants.CHANNEL_GROUP_STOP_PLACE:
195                     updateStopPlaceChannel(channelUID);
196                     break;
197                 case EnturNoBindingConstants.CHANNEL_GROUP_DIRECTION_1:
198                     updateDirectionChannel(channelUID, 0);
199                     break;
200                 case EnturNoBindingConstants.CHANNEL_GROUP_DIRECTION_2:
201                     updateDirectionChannel(channelUID, 1);
202                     break;
203                 default:
204                     break;
205             }
206         }
207     }
208
209     private void updateDirectionChannel(ChannelUID channelUID, int i) {
210         String channelId = channelUID.getIdWithoutGroup();
211         String channelGroupId = channelUID.getGroupId();
212         logger.trace("Channel id: {}, Channel group id: {}", channelId, channelGroupId);
213         if (processedData.size() > i) {
214             State state = UnDefType.UNDEF;
215             List<String> departures = processedData.get(i).departures;
216             List<String> estimatedFlags = processedData.get(i).estimatedFlags;
217             switch (channelId) {
218                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_01:
219                     state = departures.size() > 0 ? getDateTimeTypeState(departures.get(0)) : state;
220                     break;
221                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_02:
222                     state = departures.size() > 1 ? getDateTimeTypeState(departures.get(1)) : state;
223                     break;
224                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_03:
225                     state = departures.size() > 2 ? getDateTimeTypeState(departures.get(2)) : state;
226                     break;
227                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_04:
228                     state = departures.size() > 3 ? getDateTimeTypeState(departures.get(3)) : state;
229                     break;
230                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_05:
231                     state = departures.size() > 4 ? getDateTimeTypeState(departures.get(4)) : state;
232                     break;
233                 case EnturNoBindingConstants.ESTIMATED_FLAG_01:
234                     state = estimatedFlags.size() > 0 ? getStringTypeState(estimatedFlags.get(0)) : state;
235                     break;
236                 case EnturNoBindingConstants.ESTIMATED_FLAG_02:
237                     state = estimatedFlags.size() > 1 ? getStringTypeState(estimatedFlags.get(1)) : state;
238                     break;
239                 case EnturNoBindingConstants.ESTIMATED_FLAG_03:
240                     state = estimatedFlags.size() > 2 ? getStringTypeState(estimatedFlags.get(2)) : state;
241                     break;
242                 case EnturNoBindingConstants.ESTIMATED_FLAG_04:
243                     state = estimatedFlags.size() > 3 ? getStringTypeState(estimatedFlags.get(3)) : state;
244                     break;
245                 case EnturNoBindingConstants.ESTIMATED_FLAG_05:
246                     state = estimatedFlags.size() > 4 ? getStringTypeState(estimatedFlags.get(4)) : state;
247                     break;
248                 case EnturNoBindingConstants.CHANNEL_LINE_CODE:
249                     state = getStringTypeState(processedData.get(i).lineCode);
250                     break;
251                 case EnturNoBindingConstants.CHANNEL_FRONT_DISPLAY:
252                     state = getStringTypeState(processedData.get(i).frontText);
253                     break;
254                 default:
255                     break;
256             }
257             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
258             updateState(channelUID, state);
259         } else {
260             logger.debug("No real-time data available to update channel '{}' of group '{}'.", channelId,
261                     channelGroupId);
262         }
263     }
264
265     /**
266      * Update the channel from the last Entur data retrieved.
267      *
268      * @param channelUID the id identifying the channel to be updated
269      */
270     private void updateStopPlaceChannel(ChannelUID channelUID) {
271         String channelId = channelUID.getIdWithoutGroup();
272         String channelGroupId = channelUID.getGroupId();
273         if (!processedData.isEmpty()) {
274             State state = UnDefType.UNDEF;
275             switch (channelId) {
276                 case EnturNoBindingConstants.CHANNEL_STOP_ID:
277                     state = getStringTypeState(processedData.get(0).stopPlaceId);
278                     break;
279                 case EnturNoBindingConstants.CHANNEL_STOP_NAME:
280                     state = getStringTypeState(processedData.get(0).stopName);
281                     break;
282                 case EnturNoBindingConstants.CHANNEL_STOP_TRANSPORT_MODE:
283                     state = getStringTypeState(processedData.get(0).transportMode);
284                     break;
285                 default:
286                     break;
287             }
288             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
289             updateState(channelUID, state);
290         } else {
291             logger.debug("No real-time data available to update channel '{}' of group '{}'.", channelId,
292                     channelGroupId);
293         }
294     }
295
296     private State getDateTimeTypeState(@Nullable String value) {
297         return (value == null) ? UnDefType.UNDEF
298                 : new DateTimeType(ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME)
299                         .withZoneSameInstant(ZoneId.of(EnturNoBindingConstants.TIME_ZONE)));
300     }
301
302     private State getStringTypeState(@Nullable String value) {
303         return (value == null) ? UnDefType.UNDEF : new StringType(value);
304     }
305 }