]> git.basschouten.com Git - openhab-addons.git/blob
853f25fa8b2ba98d76e1a964dce6a59322312f83
[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.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.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.enturno.internal.connection.EnturCommunicationException;
27 import org.openhab.binding.enturno.internal.connection.EnturConfigurationException;
28 import org.openhab.binding.enturno.internal.connection.EnturNoConnection;
29 import org.openhab.binding.enturno.internal.dto.simplified.DisplayData;
30 import org.openhab.core.library.types.DateTimeType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.thing.type.ChannelKind;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
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 import com.google.gson.JsonSyntaxException;
47
48 /**
49  * The {@link EnturNoHandler} is responsible for handling commands, which are
50  * sent to one of the channels.
51  *
52  * @author Michal Kloc - Initial contribution
53  */
54 @NonNullByDefault
55 public class EnturNoHandler extends BaseThingHandler {
56
57     private final Logger logger = LoggerFactory.getLogger(EnturNoHandler.class);
58
59     private final HttpClient httpClient;
60
61     private @NonNullByDefault({}) EnturNoConfiguration config;
62
63     private @NonNullByDefault({}) EnturNoConnection connection;
64
65     private static final long INITIAL_DELAY_IN_SECONDS = 15;
66
67     private static final long REFRESH_INTERVAL_IN_SECONDS = 30;
68
69     private @Nullable ScheduledFuture<?> refreshJob;
70
71     private @Nullable String stopId;
72
73     private List<DisplayData> processedData = new ArrayList<>();
74
75     public EnturNoHandler(Thing thing, HttpClient httpClient) {
76         super(thing);
77         this.httpClient = httpClient;
78     }
79
80     @Override
81     public void handleCommand(ChannelUID channelUID, Command command) {
82         if (command instanceof RefreshType) {
83             updateChannel(channelUID);
84         } else {
85             logger.debug("Entur binding is a read-only binding and cannot handle command '{}'.", command);
86         }
87     }
88
89     @Override
90     public void initialize() {
91         logger.debug("Initialize Entur EnturTimeTable API handler '{}'.", getThing().getUID());
92         config = getConfigAs(EnturNoConfiguration.class);
93         stopId = config.getStopPlaceId();
94
95         logger.debug("Stop place id: {}", stopId);
96         boolean configValid = true;
97         if (stopId == null || stopId.isBlank()) {
98             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
99                     "@text/offline.conf-error-missing-stopId");
100             configValid = false;
101         }
102
103         String lineCode = config.getLineCode();
104         logger.debug("Line code: {}", lineCode);
105         if (lineCode == null || lineCode.isBlank()) {
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             int departuresCount = departures.size();
217             List<String> estimatedFlags = processedData.get(i).estimatedFlags;
218             int esitmatedFlagsCount = estimatedFlags.size();
219             switch (channelId) {
220                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_01:
221                     state = departuresCount > 0 ? getDateTimeTypeState(departures.get(0)) : state;
222                     break;
223                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_02:
224                     state = departuresCount > 1 ? getDateTimeTypeState(departures.get(1)) : state;
225                     break;
226                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_03:
227                     state = departuresCount > 2 ? getDateTimeTypeState(departures.get(2)) : state;
228                     break;
229                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_04:
230                     state = departuresCount > 3 ? getDateTimeTypeState(departures.get(3)) : state;
231                     break;
232                 case EnturNoBindingConstants.CHANNEL_DEPARTURE_05:
233                     state = departuresCount > 4 ? getDateTimeTypeState(departures.get(4)) : state;
234                     break;
235                 case EnturNoBindingConstants.ESTIMATED_FLAG_01:
236                     state = esitmatedFlagsCount > 0 ? getStringTypeState(estimatedFlags.get(0)) : state;
237                     break;
238                 case EnturNoBindingConstants.ESTIMATED_FLAG_02:
239                     state = esitmatedFlagsCount > 1 ? getStringTypeState(estimatedFlags.get(1)) : state;
240                     break;
241                 case EnturNoBindingConstants.ESTIMATED_FLAG_03:
242                     state = esitmatedFlagsCount > 2 ? getStringTypeState(estimatedFlags.get(2)) : state;
243                     break;
244                 case EnturNoBindingConstants.ESTIMATED_FLAG_04:
245                     state = esitmatedFlagsCount > 3 ? getStringTypeState(estimatedFlags.get(3)) : state;
246                     break;
247                 case EnturNoBindingConstants.ESTIMATED_FLAG_05:
248                     state = esitmatedFlagsCount > 4 ? getStringTypeState(estimatedFlags.get(4)) : state;
249                     break;
250                 case EnturNoBindingConstants.CHANNEL_LINE_CODE:
251                     state = getStringTypeState(processedData.get(i).lineCode);
252                     break;
253                 case EnturNoBindingConstants.CHANNEL_FRONT_DISPLAY:
254                     state = getStringTypeState(processedData.get(i).frontText);
255                     break;
256                 default:
257                     break;
258             }
259             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
260             updateState(channelUID, state);
261         } else {
262             logger.debug("No real-time data available to update channel '{}' of group '{}'.", channelId,
263                     channelGroupId);
264         }
265     }
266
267     /**
268      * Update the channel from the last Entur data retrieved.
269      *
270      * @param channelUID the id identifying the channel to be updated
271      */
272     private void updateStopPlaceChannel(ChannelUID channelUID) {
273         String channelId = channelUID.getIdWithoutGroup();
274         String channelGroupId = channelUID.getGroupId();
275         if (!processedData.isEmpty()) {
276             State state = UnDefType.UNDEF;
277             switch (channelId) {
278                 case EnturNoBindingConstants.CHANNEL_STOP_ID:
279                     state = getStringTypeState(processedData.get(0).stopPlaceId);
280                     break;
281                 case EnturNoBindingConstants.CHANNEL_STOP_NAME:
282                     state = getStringTypeState(processedData.get(0).stopName);
283                     break;
284                 case EnturNoBindingConstants.CHANNEL_STOP_TRANSPORT_MODE:
285                     state = getStringTypeState(processedData.get(0).transportMode);
286                     break;
287                 default:
288                     break;
289             }
290             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
291             updateState(channelUID, state);
292         } else {
293             logger.debug("No real-time data available to update channel '{}' of group '{}'.", channelId,
294                     channelGroupId);
295         }
296     }
297
298     private State getDateTimeTypeState(@Nullable String value) {
299         return (value == null) ? UnDefType.UNDEF
300                 : new DateTimeType(ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME)
301                         .withZoneSameInstant(ZoneId.of(EnturNoBindingConstants.TIME_ZONE)));
302     }
303
304     private State getStringTypeState(@Nullable String value) {
305         return (value == null) ? UnDefType.UNDEF : new StringType(value);
306     }
307 }