2 * Copyright (c) 2010-2021 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.xmltv.internal.handler;
15 import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
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;
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;
55 * The {@link ChannelHandler} is responsible for handling information
56 * made available in regard of the channel and current program
58 * @author Gaƫl L'hopital - Initial contribution
61 public class ChannelHandler extends BaseThingHandler {
62 private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
64 private @NonNullByDefault({}) ScheduledFuture<?> globalJob;
65 private @Nullable MediaChannel mediaChannel;
66 private @Nullable RawType mediaIcon = new RawType(new byte[0], RawType.DEFAULT_MIME_TYPE);
68 public final List<Programme> programmes = new ArrayList<>();
70 public ChannelHandler(Thing thing) {
75 public void initialize() {
76 XmlChannelConfiguration config = getConfigAs(XmlChannelConfiguration.class);
78 logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID());
80 if (globalJob == null || globalJob.isCancelled()) {
81 globalJob = scheduler.scheduleWithFixedDelay(() -> {
82 if (programmes.size() < 2) {
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())) {
92 getThing().getChannels().forEach(channel -> updateChannel(channel.getUID()));
94 }, 3, config.refresh, TimeUnit.SECONDS);
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();
105 String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID);
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());
117 tv.getProgrammes().stream().filter(
118 p -> p.getChannel().equals(channelId) && p.getProgrammeStop().isAfter(Instant.now()))
119 .forEach(p -> programmes.add(p));
121 updateStatus(ThingStatus.ONLINE);
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No file available");
126 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
129 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
134 public void dispose() {
135 if (globalJob != null && !globalJob.isCancelled()) {
136 globalJob.cancel(true);
142 public void handleCommand(ChannelUID channelUID, Command command) {
143 logger.debug("handleCommand {} for {}", command, channelUID);
144 if (command == RefreshType.REFRESH) {
145 refreshProgramList();
150 * Update the channel from the last OpenUV data retrieved
152 * @param channelUID the id identifying the channel to be updated
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);
162 switch (uidElements[1]) {
165 if (GROUP_CHANNEL_PROPERTIES.equals(uidElements[0])) {
168 icon = downloadIcon(programme.getIcons());
170 updateState(channelUID, icon != null ? icon : UnDefType.UNDEF);
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);
178 case CHANNEL_PROGRAMME_START:
179 Instant is = programme.getProgrammeStart();
180 ZonedDateTime zds = ZonedDateTime.ofInstant(is, ZoneId.systemDefault());
181 updateState(channelUID, new DateTimeType(zds));
183 case CHANNEL_PROGRAMME_END:
184 ZonedDateTime zde = ZonedDateTime.ofInstant(programme.getProgrammeStop(),
185 ZoneId.systemDefault());
186 updateState(channelUID, new DateTimeType(zde));
188 case CHANNEL_PROGRAMME_TITLE:
189 List<WithLangType> titles = programme.getTitles();
190 updateState(channelUID,
191 !titles.isEmpty() ? new StringType(programme.getTitles().get(0).getValue())
194 case CHANNEL_PROGRAMME_CATEGORY:
195 List<WithLangType> categories = programme.getCategories();
196 updateState(channelUID,
197 !categories.isEmpty() ? new StringType(programme.getCategories().get(0).getValue())
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);
205 case CHANNEL_PROGRAMME_ELAPSED:
206 updateState(channelUID, getDurationInSeconds(programme.getProgrammeStart(), Instant.now()));
208 case CHANNEL_PROGRAMME_REMAINING:
209 updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStop()));
211 case CHANNEL_PROGRAMME_TIMELEFT:
212 updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
214 case CHANNEL_PROGRAMME_PROGRESS:
215 Duration totalLength = Duration.between(programme.getProgrammeStart(),
216 programme.getProgrammeStop());
217 Duration elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now());
219 long secondsElapsed1 = elapsed1.toMillis() / 1000;
220 long secondsLength = totalLength.toMillis() / 1000;
222 double progress = 100.0 * secondsElapsed1 / secondsLength;
223 if (progress > 100 || progress < 0) {
224 logger.debug("Outstanding process");
226 updateState(channelUID, new QuantityType<>(progress, Units.PERCENT));
231 logger.warn("Not enough programs in XML file, think to refresh it");
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);
242 private @Nullable RawType downloadIcon(List<Icon> icons) {
243 if (!icons.isEmpty()) {
244 String url = icons.get(0).getSrc();
245 return HttpUtil.downloadImage(url);