]> git.basschouten.com Git - openhab-addons.git/blob
7e9642450005e7119e5b55fbb7e84e774709a340
[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.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.WithLangType;
34 import org.openhab.core.io.net.http.HttpUtil;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.RawType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.library.unit.Units;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingStatus;
44 import org.openhab.core.thing.ThingStatusDetail;
45 import org.openhab.core.thing.binding.BaseThingHandler;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.UnDefType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * The {@link ChannelHandler} is responsible for handling information
55  * made available in regard of the channel and current program
56  *
57  * @author GaĆ«l L'hopital - Initial contribution
58  */
59 @NonNullByDefault
60 public class ChannelHandler extends BaseThingHandler {
61     private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
62
63     private @Nullable ScheduledFuture<?> globalJob;
64     private @Nullable MediaChannel mediaChannel;
65     private State mediaIcon = UnDefType.UNDEF;
66
67     public final List<Programme> programmes = new ArrayList<>();
68     private final ZoneId zoneId;
69
70     public ChannelHandler(Thing thing, ZoneId zoneId) {
71         super(thing);
72         this.zoneId = zoneId;
73     }
74
75     @Override
76     public void initialize() {
77         XmlChannelConfiguration config = getConfigAs(XmlChannelConfiguration.class);
78
79         logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID());
80
81         ScheduledFuture<?> job = globalJob;
82         if (job == null || job.isCancelled()) {
83             globalJob = scheduler.scheduleWithFixedDelay(() -> {
84                 if (programmes.size() < 2) {
85                     refreshProgramList();
86                 }
87                 if (programmes.isEmpty()) {
88                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-more-programs");
89                 } else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) {
90                     programmes.remove(0);
91                 }
92
93                 getThing().getChannels().forEach(channel -> updateChannel(channel.getUID()));
94
95             }, 3, config.refresh, TimeUnit.SECONDS);
96         }
97     }
98
99     private void refreshProgramList() {
100         Bridge bridge = getBridge();
101         if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
102             XmlTVHandler handler = (XmlTVHandler) bridge.getHandler();
103             if (handler != null) {
104                 handler.getXmlFile().ifPresentOrElse(tv -> {
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                 }, () -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-file-available"));
123             } else {
124                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
125             }
126         } else {
127             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
128         }
129     }
130
131     @Override
132     public void dispose() {
133         ScheduledFuture<?> job = globalJob;
134         if (job != null && !job.isCancelled()) {
135             job.cancel(true);
136             globalJob = null;
137         }
138     }
139
140     @Override
141     public void handleCommand(ChannelUID channelUID, Command command) {
142         logger.debug("handleCommand {} for {}", command, channelUID);
143         if (command == RefreshType.REFRESH) {
144             refreshProgramList();
145         }
146     }
147
148     /**
149      * Update the channel from the last OpenUV data retrieved
150      *
151      * @param channelUID the id identifying the channel to be updated
152      *
153      */
154     private void updateChannel(ChannelUID channelUID) {
155         // TODO : usage extraction of groupname
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 = GROUP_CHANNEL_PROPERTIES.equals(uidElements[0]) ? mediaIcon
165                                 : downloadIcon(programme.getIcons());
166                         updateState(channelUID, icon);
167                         break;
168                     case CHANNEL_CHANNEL_URL:
169                         MediaChannel channel = mediaChannel;
170                         updateState(channelUID,
171                                 channel != null ? !channel.getIcons().isEmpty()
172                                         ? new StringType(channel.getIcons().get(0).getSrc())
173                                         : UnDefType.UNDEF : UnDefType.UNDEF);
174                         break;
175                     case CHANNEL_PROGRAMME_START:
176                         updateDateTimeChannel(channelUID, programme.getProgrammeStart());
177                         break;
178                     case CHANNEL_PROGRAMME_END:
179                         updateDateTimeChannel(channelUID, programme.getProgrammeStop());
180                         break;
181                     case CHANNEL_PROGRAMME_TITLE:
182                         List<WithLangType> titles = programme.getTitles();
183                         updateState(channelUID,
184                                 !titles.isEmpty() ? new StringType(programme.getTitles().get(0).getValue())
185                                         : UnDefType.UNDEF);
186                         break;
187                     case CHANNEL_PROGRAMME_CATEGORY:
188                         List<WithLangType> categories = programme.getCategories();
189                         updateState(channelUID,
190                                 !categories.isEmpty() ? new StringType(programme.getCategories().get(0).getValue())
191                                         : UnDefType.UNDEF);
192                         break;
193                     case CHANNEL_PROGRAMME_ICON:
194                         List<Icon> icons = programme.getIcons();
195                         updateState(channelUID,
196                                 !icons.isEmpty() ? new StringType(icons.get(0).getSrc()) : UnDefType.UNDEF);
197                         break;
198                     case CHANNEL_PROGRAMME_ELAPSED:
199                         updateState(channelUID, getDurationInSeconds(programme.getProgrammeStart(), Instant.now()));
200                         break;
201                     case CHANNEL_PROGRAMME_REMAINING:
202                         updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStop()));
203                         break;
204                     case CHANNEL_PROGRAMME_TIMELEFT:
205                         updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
206                         break;
207                     case CHANNEL_PROGRAMME_PROGRESS:
208                         long totalLength = Duration.between(programme.getProgrammeStart(), programme.getProgrammeStop())
209                                 .toSeconds();
210                         long elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now()).toSeconds();
211
212                         double progress = 100.0 * elapsed1 / totalLength;
213                         if (progress > 100 || progress < 0) {
214                             logger.debug("Outstanding process");
215                         }
216                         updateState(channelUID, new QuantityType<>((int) progress, Units.PERCENT));
217
218                         break;
219                 }
220             } else {
221                 logger.warn("Not enough programs in XML file, think to refresh it");
222             }
223         }
224     }
225
226     private void updateDateTimeChannel(ChannelUID channelUID, Instant instant) {
227         ZonedDateTime zds = ZonedDateTime.ofInstant(instant, zoneId);
228         updateState(channelUID, new DateTimeType(zds));
229     }
230
231     private QuantityType<?> getDurationInSeconds(Instant from, Instant to) {
232         long elapsed = Duration.between(from, to).toSeconds();
233         return new QuantityType<>(elapsed, Units.SECOND);
234     }
235
236     private State downloadIcon(List<Icon> icons) {
237         if (!icons.isEmpty()) {
238             String url = icons.get(0).getSrc();
239             RawType result = HttpUtil.downloadImage(url);
240             if (result != null) {
241                 return result;
242             }
243         }
244         return UnDefType.NULL;
245     }
246 }