]> git.basschouten.com Git - openhab-addons.git/blob
53dfc6640b0c47efb5be22f0eb9787e07fe246db
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.xmltv.internal.handler;
14
15 import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
16
17 import java.time.Duration;
18 import java.time.Instant;
19 import java.time.ZoneId;
20 import java.time.ZonedDateTime;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration;
30 import org.openhab.binding.xmltv.internal.jaxb.Icon;
31 import org.openhab.binding.xmltv.internal.jaxb.MediaChannel;
32 import org.openhab.binding.xmltv.internal.jaxb.Programme;
33 import org.openhab.binding.xmltv.internal.jaxb.Tv;
34 import org.openhab.binding.xmltv.internal.jaxb.WithLangType;
35 import org.openhab.core.io.net.http.HttpUtil;
36 import org.openhab.core.library.types.DateTimeType;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.types.RawType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.Units;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.binding.BaseThingHandler;
47 import org.openhab.core.types.Command;
48 import org.openhab.core.types.RefreshType;
49 import org.openhab.core.types.State;
50 import org.openhab.core.types.UnDefType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * The {@link ChannelHandler} is responsible for handling information
56  * made available in regard of the channel and current program
57  *
58  * @author GaĆ«l L'hopital - Initial contribution
59  */
60 @NonNullByDefault
61 public class ChannelHandler extends BaseThingHandler {
62     private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
63
64     private @NonNullByDefault({}) ScheduledFuture<?> globalJob;
65     private @Nullable MediaChannel mediaChannel;
66     private @Nullable RawType mediaIcon = new RawType(new byte[0], RawType.DEFAULT_MIME_TYPE);
67
68     public final List<Programme> programmes = new ArrayList<>();
69
70     public ChannelHandler(Thing thing) {
71         super(thing);
72     }
73
74     @Override
75     public void initialize() {
76         XmlChannelConfiguration config = getConfigAs(XmlChannelConfiguration.class);
77
78         logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID());
79
80         if (globalJob == null || globalJob.isCancelled()) {
81             globalJob = scheduler.scheduleWithFixedDelay(() -> {
82                 if (programmes.size() < 2) {
83                     refreshProgramList();
84                 }
85                 if (programmes.isEmpty()) {
86                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
87                             "No programmes to come in the current XML file for this channel");
88                 } else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) {
89                     programmes.remove(0);
90                 }
91
92                 getThing().getChannels().forEach(channel -> updateChannel(channel.getUID()));
93
94             }, 3, config.refresh, TimeUnit.SECONDS);
95         }
96     }
97
98     private void refreshProgramList() {
99         Bridge bridge = getBridge();
100         if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
101             XmlTVHandler handler = (XmlTVHandler) bridge.getHandler();
102             if (handler != null) {
103                 Tv tv = handler.getXmlFile();
104                 if (tv != null) {
105                     String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID);
106
107                     if (mediaChannel == null) {
108                         Optional<MediaChannel> channel = tv.getMediaChannels().stream()
109                                 .filter(mediaChannel -> mediaChannel.getId().equals(channelId)).findFirst();
110                         if (channel.isPresent()) {
111                             mediaChannel = channel.get();
112                             mediaIcon = downloadIcon(mediaChannel.getIcons());
113                         }
114                     }
115
116                     programmes.clear();
117                     tv.getProgrammes().stream().filter(
118                             p -> p.getChannel().equals(channelId) && p.getProgrammeStop().isAfter(Instant.now()))
119                             .forEach(p -> programmes.add(p));
120
121                     updateStatus(ThingStatus.ONLINE);
122                 } else {
123                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No file available");
124                 }
125             } else {
126                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
127             }
128         } else {
129             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
130         }
131     }
132
133     @Override
134     public void dispose() {
135         if (globalJob != null && !globalJob.isCancelled()) {
136             globalJob.cancel(true);
137             globalJob = null;
138         }
139     }
140
141     @Override
142     public void handleCommand(ChannelUID channelUID, Command command) {
143         logger.debug("handleCommand {} for {}", command, channelUID);
144         if (command == RefreshType.REFRESH) {
145             refreshProgramList();
146         }
147     }
148
149     /**
150      * Update the channel from the last OpenUV data retrieved
151      *
152      * @param channelUID the id identifying the channel to be updated
153      *
154      */
155     private void updateChannel(ChannelUID channelUID) {
156         String[] uidElements = channelUID.getId().split("#");
157         if (uidElements.length == 2) {
158             int target = GROUP_NEXT_PROGRAMME.equals(uidElements[0]) ? 1 : 0;
159             if (programmes.size() > target) {
160                 Programme programme = programmes.get(target);
161
162                 switch (uidElements[1]) {
163                     case CHANNEL_ICON:
164                         State icon = null;
165                         if (GROUP_CHANNEL_PROPERTIES.equals(uidElements[0])) {
166                             icon = mediaIcon;
167                         } else {
168                             icon = downloadIcon(programme.getIcons());
169                         }
170                         updateState(channelUID, icon != null ? icon : UnDefType.UNDEF);
171                         break;
172                     case CHANNEL_CHANNEL_URL:
173                         updateState(channelUID,
174                                 mediaChannel != null ? !mediaChannel.getIcons().isEmpty()
175                                         ? new StringType(mediaChannel.getIcons().get(0).getSrc())
176                                         : UnDefType.UNDEF : UnDefType.UNDEF);
177                         break;
178                     case CHANNEL_PROGRAMME_START:
179                         Instant is = programme.getProgrammeStart();
180                         ZonedDateTime zds = ZonedDateTime.ofInstant(is, ZoneId.systemDefault());
181                         updateState(channelUID, new DateTimeType(zds));
182                         break;
183                     case CHANNEL_PROGRAMME_END:
184                         ZonedDateTime zde = ZonedDateTime.ofInstant(programme.getProgrammeStop(),
185                                 ZoneId.systemDefault());
186                         updateState(channelUID, new DateTimeType(zde));
187                         break;
188                     case CHANNEL_PROGRAMME_TITLE:
189                         List<WithLangType> titles = programme.getTitles();
190                         updateState(channelUID,
191                                 !titles.isEmpty() ? new StringType(programme.getTitles().get(0).getValue())
192                                         : UnDefType.UNDEF);
193                         break;
194                     case CHANNEL_PROGRAMME_CATEGORY:
195                         List<WithLangType> categories = programme.getCategories();
196                         updateState(channelUID,
197                                 !categories.isEmpty() ? new StringType(programme.getCategories().get(0).getValue())
198                                         : UnDefType.UNDEF);
199                         break;
200                     case CHANNEL_PROGRAMME_ICON:
201                         List<Icon> icons = programme.getIcons();
202                         updateState(channelUID,
203                                 !icons.isEmpty() ? new StringType(icons.get(0).getSrc()) : UnDefType.UNDEF);
204                         break;
205                     case CHANNEL_PROGRAMME_ELAPSED:
206                         updateState(channelUID, getDurationInSeconds(programme.getProgrammeStart(), Instant.now()));
207                         break;
208                     case CHANNEL_PROGRAMME_REMAINING:
209                         updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStop()));
210                         break;
211                     case CHANNEL_PROGRAMME_TIMELEFT:
212                         updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
213                         break;
214                     case CHANNEL_PROGRAMME_PROGRESS:
215                         Duration totalLength = Duration.between(programme.getProgrammeStart(),
216                                 programme.getProgrammeStop());
217                         Duration elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now());
218
219                         long secondsElapsed1 = elapsed1.toMillis() / 1000;
220                         long secondsLength = totalLength.toMillis() / 1000;
221
222                         double progress = 100.0 * secondsElapsed1 / secondsLength;
223                         if (progress > 100 || progress < 0) {
224                             logger.debug("Outstanding process");
225                         }
226                         updateState(channelUID, new QuantityType<>(progress, Units.PERCENT));
227
228                         break;
229                 }
230             } else {
231                 logger.warn("Not enough programs in XML file, think to refresh it");
232             }
233         }
234     }
235
236     private QuantityType<?> getDurationInSeconds(Instant from, Instant to) {
237         Duration elapsed = Duration.between(from, to);
238         long secondsElapsed = TimeUnit.MILLISECONDS.toSeconds(elapsed.toMillis());
239         return new QuantityType<>(secondsElapsed, Units.SECOND);
240     }
241
242     private @Nullable RawType downloadIcon(List<Icon> icons) {
243         if (!icons.isEmpty()) {
244             String url = icons.get(0).getSrc();
245             return HttpUtil.downloadImage(url);
246         }
247         return null;
248     }
249 }