added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.xmltv-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-xmltv" description="XMLTV Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature dependency="true">openhab.tp-jaxb</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.xmltv/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link XmlTVBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class XmlTVBindingConstants {
private static final String BINDING_ID = "xmltv";
// List of all Bridge Type UIDs
public static final ThingTypeUID XMLTV_FILE_BRIDGE_TYPE = new ThingTypeUID(BINDING_ID, "xmltvfile");
public static final ThingTypeUID XMLTV_CHANNEL_THING_TYPE = new ThingTypeUID(BINDING_ID, "channel");
// Channel groups
public static final String GROUP_CURRENT_PROGRAMME = "currentprog";
public static final String GROUP_NEXT_PROGRAMME = "nextprog";
public static final String GROUP_CHANNEL_PROPERTIES = "channelprops";
// List of all Channel ids
public static final String CHANNEL_CHANNEL_URL = "iconUrl";
public static final String CHANNEL_ICON = "icon";
public static final String CHANNEL_PROGRAMME_START = "progStart";
public static final String CHANNEL_PROGRAMME_END = "progEnd";
public static final String CHANNEL_PROGRAMME_TITLE = "progTitle";
public static final String CHANNEL_PROGRAMME_CATEGORY = "progCategory";
public static final String CHANNEL_PROGRAMME_ICON = "progIconUrl";
public static final String CHANNEL_PROGRAMME_ELAPSED = "elapsedTime";
public static final String CHANNEL_PROGRAMME_REMAINING = "remainingTime";
public static final String CHANNEL_PROGRAMME_PROGRESS = "progress";
public static final String CHANNEL_PROGRAMME_TIMELEFT = "timeLeft";
// Supported Thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(XMLTV_FILE_BRIDGE_TYPE, XMLTV_CHANNEL_THING_TYPE).collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,115 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal;
import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javax.xml.bind.JAXBException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.discovery.XmlTVDiscoveryService;
import org.openhab.binding.xmltv.internal.handler.ChannelHandler;
import org.openhab.binding.xmltv.internal.handler.XmlTVHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link XmlTVHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.xmltv", service = ThingHandlerFactory.class)
public class XmlTVHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(XmlTVHandlerFactory.class);
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
if (XMLTV_FILE_BRIDGE_TYPE.equals(thingTypeUID)) {
return super.createThing(thingTypeUID, configuration, thingUID, null);
} else if (XMLTV_CHANNEL_THING_TYPE.equals(thingTypeUID)) {
ThingUID newThingUID;
if (bridgeUID != null && thingUID != null) {
newThingUID = new ThingUID(thingTypeUID, bridgeUID, thingUID.getId());
} else {
newThingUID = thingUID;
}
return super.createThing(thingTypeUID, configuration, newThingUID, bridgeUID);
}
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the XmlTV binding.");
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (XMLTV_FILE_BRIDGE_TYPE.equals(thingTypeUID)) {
try {
XmlTVHandler bridgeHandler = new XmlTVHandler((Bridge) thing);
registerDeviceDiscoveryService(bridgeHandler);
return bridgeHandler;
} catch (JAXBException e) {
logger.error("Unable to create XmlTVHandler : {}", e.getMessage());
}
} else if (XMLTV_CHANNEL_THING_TYPE.equals(thingTypeUID)) {
return new ChannelHandler(thing);
}
return null;
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof XmlTVHandler) {
Thing thing = thingHandler.getThing();
unregisterDeviceDiscoveryService(thing);
}
super.removeHandler(thingHandler);
}
private synchronized void registerDeviceDiscoveryService(XmlTVHandler bridgeHandler) {
XmlTVDiscoveryService discoveryService = new XmlTVDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
private synchronized void unregisterDeviceDiscoveryService(Thing thing) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thing.getUID());
serviceReg.unregister();
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.configuration;
/**
* The {@link XmlChannelConfiguration} class contains fields mapping
* Channel thing configuration parameters.
*
* @author Gaël L'hopital - Initial contribution
*/
public class XmlChannelConfiguration {
public static final String CHANNEL_ID = "channelId";
public String channelId;
public Integer offset;
public Integer refresh;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.configuration;
/**
* The {@link XmlTVConfiguration} class contains fields mapping TV bridge
* configuration parameters.
*
* @author Gaël L'hopital - Initial contribution
*/
public class XmlTVConfiguration {
public String filePath;
public Integer refresh;
public String encoding;
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.discovery;
import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.XMLTV_CHANNEL_THING_TYPE;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.xmltv.internal.XmlTVBindingConstants;
import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration;
import org.openhab.binding.xmltv.internal.handler.XmlTVHandler;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link XmlTVDiscoveryService} is responsible for discovering all channels
* declared in the XmlTV file
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class XmlTVDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(XmlTVDiscoveryService.class);
private static final int SEARCH_TIME = 10;
private XmlTVHandler bridgeHandler;
/**
* Creates a XmlTVDiscoveryService with background discovery disabled.
*/
public XmlTVDiscoveryService(XmlTVHandler bridgeHandler) {
super(XmlTVBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
this.bridgeHandler = bridgeHandler;
}
@Override
protected void startScan() {
logger.debug("Starting XmlTV discovery scan");
if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
Tv tv = bridgeHandler.getXmlFile();
if (tv != null) {
tv.getMediaChannels().stream().forEach(channel -> {
String channelId = channel.getId();
String uid = channelId.replaceAll("[^A-Za-z0-9_]", "_");
ThingUID thingUID = new ThingUID(XMLTV_CHANNEL_THING_TYPE, bridgeHandler.getThing().getUID(), uid);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withBridge(bridgeHandler.getThing().getUID())
.withLabel(channel.getDisplayNames().get(0).getValue()).withRepresentationProperty(uid)
.withProperty(XmlChannelConfiguration.CHANNEL_ID, channelId).build();
thingDiscovered(discoveryResult);
});
}
}
}
}

View File

@@ -0,0 +1,249 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.handler;
import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration;
import org.openhab.binding.xmltv.internal.jaxb.Icon;
import org.openhab.binding.xmltv.internal.jaxb.MediaChannel;
import org.openhab.binding.xmltv.internal.jaxb.Programme;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.binding.xmltv.internal.jaxb.WithLangType;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ChannelHandler} is responsible for handling information
* made available in regard of the channel and current program
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ChannelHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
private @NonNullByDefault({}) ScheduledFuture<?> globalJob;
private @Nullable MediaChannel mediaChannel;
private @Nullable RawType mediaIcon = new RawType(new byte[0], RawType.DEFAULT_MIME_TYPE);
public final List<Programme> programmes = new ArrayList<>();
public ChannelHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
XmlChannelConfiguration config = getConfigAs(XmlChannelConfiguration.class);
logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID());
if (globalJob == null || globalJob.isCancelled()) {
globalJob = scheduler.scheduleWithFixedDelay(() -> {
if (programmes.size() < 2) {
refreshProgramList();
}
if (programmes.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"No programmes to come in the current XML file for this channel");
} else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) {
programmes.remove(0);
}
getThing().getChannels().forEach(channel -> updateChannel(channel.getUID()));
}, 3, config.refresh, TimeUnit.SECONDS);
}
}
private void refreshProgramList() {
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
XmlTVHandler handler = (XmlTVHandler) bridge.getHandler();
if (handler != null) {
Tv tv = handler.getXmlFile();
if (tv != null) {
String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID);
if (mediaChannel == null) {
Optional<MediaChannel> channel = tv.getMediaChannels().stream()
.filter(mediaChannel -> mediaChannel.getId().equals(channelId)).findFirst();
if (channel.isPresent()) {
mediaChannel = channel.get();
mediaIcon = downloadIcon(mediaChannel.getIcons());
}
}
programmes.clear();
tv.getProgrammes().stream().filter(
p -> p.getChannel().equals(channelId) && p.getProgrammeStop().isAfter(Instant.now()))
.forEach(p -> programmes.add(p));
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No file available");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void dispose() {
if (globalJob != null && !globalJob.isCancelled()) {
globalJob.cancel(true);
globalJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand {} for {}", command, channelUID);
if (command == RefreshType.REFRESH) {
refreshProgramList();
}
}
/**
* Update the channel from the last OpenUV data retrieved
*
* @param channelUID the id identifying the channel to be updated
*
*/
private void updateChannel(ChannelUID channelUID) {
String[] uidElements = channelUID.getId().split("#");
if (uidElements.length == 2) {
int target = GROUP_NEXT_PROGRAMME.equals(uidElements[0]) ? 1 : 0;
if (programmes.size() > target) {
Programme programme = programmes.get(target);
switch (uidElements[1]) {
case CHANNEL_ICON:
State icon = null;
if (GROUP_CHANNEL_PROPERTIES.equals(uidElements[0])) {
icon = mediaIcon;
} else {
icon = downloadIcon(programme.getIcons());
}
updateState(channelUID, icon != null ? icon : UnDefType.UNDEF);
break;
case CHANNEL_CHANNEL_URL:
updateState(channelUID,
mediaChannel != null ? !mediaChannel.getIcons().isEmpty()
? new StringType(mediaChannel.getIcons().get(0).getSrc())
: UnDefType.UNDEF : UnDefType.UNDEF);
break;
case CHANNEL_PROGRAMME_START:
Instant is = programme.getProgrammeStart();
ZonedDateTime zds = ZonedDateTime.ofInstant(is, ZoneId.systemDefault());
updateState(channelUID, new DateTimeType(zds));
break;
case CHANNEL_PROGRAMME_END:
ZonedDateTime zde = ZonedDateTime.ofInstant(programme.getProgrammeStop(),
ZoneId.systemDefault());
updateState(channelUID, new DateTimeType(zde));
break;
case CHANNEL_PROGRAMME_TITLE:
List<WithLangType> titles = programme.getTitles();
updateState(channelUID,
!titles.isEmpty() ? new StringType(programme.getTitles().get(0).getValue())
: UnDefType.UNDEF);
break;
case CHANNEL_PROGRAMME_CATEGORY:
List<WithLangType> categories = programme.getCategories();
updateState(channelUID,
!categories.isEmpty() ? new StringType(programme.getCategories().get(0).getValue())
: UnDefType.UNDEF);
break;
case CHANNEL_PROGRAMME_ICON:
List<Icon> icons = programme.getIcons();
updateState(channelUID,
!icons.isEmpty() ? new StringType(icons.get(0).getSrc()) : UnDefType.UNDEF);
break;
case CHANNEL_PROGRAMME_ELAPSED:
updateState(channelUID, getDurationInSeconds(programme.getProgrammeStart(), Instant.now()));
break;
case CHANNEL_PROGRAMME_REMAINING:
updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStop()));
break;
case CHANNEL_PROGRAMME_TIMELEFT:
updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
break;
case CHANNEL_PROGRAMME_PROGRESS:
Duration totalLength = Duration.between(programme.getProgrammeStart(),
programme.getProgrammeStop());
Duration elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now());
long secondsElapsed1 = elapsed1.toMillis() / 1000;
long secondsLength = totalLength.toMillis() / 1000;
double progress = 100.0 * secondsElapsed1 / secondsLength;
if (progress > 100 || progress < 0) {
logger.debug("Outstanding process");
}
updateState(channelUID, new QuantityType<>(progress, SmartHomeUnits.PERCENT));
break;
}
} else {
logger.warn("Not enough programs in XML file, think to refresh it");
}
}
}
private QuantityType<?> getDurationInSeconds(Instant from, Instant to) {
Duration elapsed = Duration.between(from, to);
long secondsElapsed = TimeUnit.MILLISECONDS.toSeconds(elapsed.toMillis());
return new QuantityType<>(secondsElapsed, SmartHomeUnits.SECOND);
}
private @Nullable RawType downloadIcon(List<Icon> icons) {
if (!icons.isEmpty()) {
String url = icons.get(0).getSrc();
return HttpUtil.downloadImage(url);
}
return null;
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.handler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.configuration.XmlTVConfiguration;
import org.openhab.binding.xmltv.internal.jaxb.Programme;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link XmlTVHandler} is responsible for handling XMLTV file and dispatch
* information made available to according Media Channels
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class XmlTVHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(XmlTVHandler.class);
private final XMLInputFactory xif = XMLInputFactory.newFactory();
private final JAXBContext jc;
private @Nullable Tv currentXmlFile;
private @NonNullByDefault({}) ScheduledFuture<?> reloadJob;
public XmlTVHandler(Bridge thing) throws JAXBException {
super(thing);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
jc = JAXBContext.newInstance(Tv.class);
}
@Override
public void initialize() {
XmlTVConfiguration config = getConfigAs(XmlTVConfiguration.class);
logger.debug("Initializing {} for input file '{}'", getClass(), config.filePath);
reloadJob = scheduler.scheduleWithFixedDelay(() -> {
currentXmlFile = null;
XMLStreamReader xsr = null;
try {
// This can take some seconds depending upon weight of the XmlTV source file
xsr = xif.createXMLStreamReader(new FileInputStream(new File(config.filePath)), config.encoding);
try {
Unmarshaller unmarshaller = jc.createUnmarshaller();
Tv xmlFile = (Tv) unmarshaller.unmarshal(xsr);
// Remove all finished programmes
xmlFile.getProgrammes().removeIf(programme -> Instant.now().isAfter(programme.getProgrammeStop()));
if (!xmlFile.getProgrammes().isEmpty()) {
// Sort programmes by starting instant
Collections.sort(xmlFile.getProgrammes(), Comparator.comparing(Programme::getProgrammeStart));
// Ready to deliver data to ChannelHandlers
currentXmlFile = xmlFile;
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, "XMLTV file seems outdated");
}
xsr.close();
} catch (JAXBException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
}
} catch (XMLStreamException | FileNotFoundException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} finally {
try {
if (xsr != null) {
xsr.close();
}
} catch (XMLStreamException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}
}, 0, config.refresh, TimeUnit.HOURS);
}
@Override
public void dispose() {
logger.debug("Running dispose");
if (reloadJob != null && !reloadJob.isCancelled()) {
reloadJob.cancel(true);
reloadJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// nothing to do
}
@Nullable
public Tv getXmlFile() {
return currentXmlFile;
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import java.math.BigInteger;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
/**
* Java class for an icon XML element
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "icon")
public class Icon {
@XmlAttribute(required = true)
protected String src;
@XmlAttribute
@XmlSchemaType(name = "positiveInteger")
protected BigInteger width;
@XmlAttribute
@XmlSchemaType(name = "positiveInteger")
protected BigInteger height;
public String getSrc() {
return src;
}
public BigInteger getWidth() {
return width;
}
public BigInteger getHeight() {
return height;
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Java class for an channel XML element
* Renamed to MediaChannel in order to avoid confusion with ESH Channels
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@NonNullByDefault
public class MediaChannel {
@XmlElement(name = "display-name", required = true)
protected List<WithLangType> displayNames = new ArrayList<>();
@XmlElement(name = "icon")
protected List<Icon> icons = new ArrayList<>();
@XmlAttribute(required = true)
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
@XmlSchemaType(name = "token")
protected String id = "";
public List<WithLangType> getDisplayNames() {
return this.displayNames;
}
public List<Icon> getIcons() {
return this.icons;
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import javax.xml.bind.annotation.XmlRegistry;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This object contains factory methods for each Java content
* interface and Java element interface generated in the
* org.openhab.binding.xmltv.internal.jaxb package.
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlRegistry
@NonNullByDefault
public class ObjectFactory {
/**
* Create an instance of {@link Tv }
*
*/
public Tv createTv() {
return new Tv();
}
/**
* Create an instance of {@link Programme }
*
*/
public Programme createProgramme() {
return new Programme();
}
/**
* Create an instance of {@link MediaChannel }
*
*/
public MediaChannel createChannel() {
return new MediaChannel();
}
/**
* Create an instance of {@link Icon }
*
*/
public Icon createIcon() {
return new Icon();
}
/**
* Create an instance of {@link WithLangType }
*
*/
public WithLangType createWithLangType() {
return new WithLangType();
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Java class for a programme XML element
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@NonNullByDefault
public class Programme {
private static final DateTimeFormatter XMLTV_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss Z");
@XmlElement(name = "title", required = true)
protected List<WithLangType> titles = new ArrayList<>();
@XmlElement(name = "category")
protected List<WithLangType> categories = new ArrayList<>();
@XmlElement(name = "icon")
protected List<Icon> icons = new ArrayList<>();
@XmlAttribute(required = true)
private String start = "";
@XmlAttribute
private String stop = "";
@XmlAttribute(required = true)
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
protected String channel = "";
public List<WithLangType> getTitles() {
return titles;
}
public List<WithLangType> getCategories() {
return categories;
}
public Instant getProgrammeStart() {
long epoch = iso860DateToEpoch(start);
return Instant.ofEpochMilli(epoch);
}
public Instant getProgrammeStop() {
long epoch = iso860DateToEpoch(stop);
return Instant.ofEpochMilli(epoch);
}
private long iso860DateToEpoch(String date) {
long epoch = ZonedDateTime.parse(date, XMLTV_DATE_FORMAT).toInstant().toEpochMilli();
return epoch;
}
public List<Icon> getIcons() {
return icons;
}
public String getChannel() {
return channel;
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Java class for a TV XML root element
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "tv")
@NonNullByDefault
public class Tv {
@XmlElement(name = "channel")
protected List<MediaChannel> channels = new ArrayList<>();
@XmlElement(name = "programme")
protected List<Programme> programmes = new ArrayList<>();
public List<MediaChannel> getMediaChannels() {
return this.channels;
}
public List<Programme> getProgrammes() {
return this.programmes;
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.xmltv.internal.jaxb;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Java class for an XML element value including a language attribute
* information made available to according Media Channels
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "withLangType")
public class WithLangType {
@XmlValue
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
protected String value;
@XmlAttribute
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
protected String lang;
public String getValue() {
return value;
}
public String getLang() {
return lang;
}
}

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
@javax.xml.bind.annotation.XmlSchema(namespace = "", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package org.openhab.binding.xmltv.internal.jaxb;

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="xmltv" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>XmlTV Binding</name>
<description>This is the binding for reading and parsing XmlTV files</description>
<author>Gaël L'hopital</author>
</binding:binding>

View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="xmltv"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="xmltvfile">
<label>XmlTVFile</label>
<description>This is the interface to a XmlTV file</description>
<config-description>
<parameter name="filePath" type="text" required="true">
<label>XmlTV File Path</label>
<description>Path to an XmlTV file.</description>
</parameter>
<parameter name="refresh" type="integer" unit="h">
<label>Refresh Interval</label>
<description>Specifies the XMLTV file reload interval in hours</description>
<default>24</default>
</parameter>
<parameter name="encoding" type="text" required="true">
<label>File encoding</label>
<description>Specifies the XMLTV file encoding</description>
<default>UTF8</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="channel">
<supported-bridge-type-refs>
<bridge-type-ref id="xmltvfile"/>
</supported-bridge-type-refs>
<label>Channel</label>
<description>This represent a channel on a given TV file</description>
<channel-groups>
<channel-group id="channelprops" typeId="channelprops"/>
<channel-group id="currentprog" typeId="currentprog"/>
<channel-group id="nextprog" typeId="nextprog"/>
</channel-groups>
<config-description>
<parameter name="channelId" type="text" required="true">
<label>Channel Id</label>
<description>Id of the channel as presented in the XmlTV file.</description>
</parameter>
<parameter name="offset" type="integer" min="-1440" max="1440" unit="min">
<label>Offset</label>
<description>Moves an event or datetime value forward or backward (in minutes)</description>
<default>0</default>
</parameter>
<parameter name="refresh" type="integer" unit="s">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="channelprops">
<label>Channel Properties</label>
<description>Properties of the current channel</description>
<channels>
<channel id="iconUrl" typeId="iconUrl"/>
<channel id="icon" typeId="icon"/>
</channels>
</channel-group-type>
<channel-group-type id="currentprog">
<label>Current Program</label>
<description>Program currently on air on this channel</description>
<channels>
<channel id="progStart" typeId="progStart"/>
<channel id="progEnd" typeId="progEnd"/>
<channel id="progTitle" typeId="progTitle"/>
<channel id="progCategory" typeId="progCategory"/>
<channel id="progIconUrl" typeId="progIconUrl"/>
<channel id="icon" typeId="icon"/>
<channel id="elapsedTime" typeId="elapsedTime"/>
<channel id="remainingTime" typeId="remainingTime"/>
<channel id="progress" typeId="progress"/>
</channels>
</channel-group-type>
<channel-group-type id="nextprog">
<label>Next Program</label>
<description>Program which will follow current program on this channel</description>
<channels>
<channel id="progStart" typeId="progStart"/>
<channel id="timeLeft" typeId="timeLeft"/>
<channel id="progEnd" typeId="progEnd"/>
<channel id="progTitle" typeId="progTitle"/>
<channel id="progCategory" typeId="progCategory"/>
<channel id="progIconUrl" typeId="progIconUrl"/>
<channel id="icon" typeId="icon"/>
</channels>
</channel-group-type>
<channel-type id="iconUrl" advanced="true">
<item-type>String</item-type>
<label>Channel Icon URL</label>
<description>Icon URL of the TV channel.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="progIconUrl" advanced="true">
<item-type>String</item-type>
<label>Program URL</label>
<description>URL to an image of the program.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="progTitle">
<item-type>String</item-type>
<label>Title</label>
<description>Program Title.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="progCategory">
<item-type>String</item-type>
<label>Category</label>
<description>Program Category.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="progStart">
<item-type>DateTime</item-type>
<label>Start Time</label>
<description>Program Start Time</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="progEnd">
<item-type>DateTime</item-type>
<label>End Time</label>
<description>Program End Time</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="elapsedTime">
<item-type>Number:Time</item-type>
<label>Current Time</label>
<description>Current time of currently playing program.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="remainingTime" advanced="true">
<item-type>Number:Time</item-type>
<label>Remaining Time</label>
<description>Time remaining until end of the program.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="timeLeft" advanced="true">
<item-type>Number:Time</item-type>
<label>Time Left</label>
<description>Time left before program start</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="progress">
<item-type>Number:Dimensionless</item-type>
<label>Progress</label>
<description>Relative progression of the current program.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="icon">
<item-type>Image</item-type>
<label>Icon</label>
<description>Icon of the channel / program.</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>