added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.feed/.classpath
Normal file
32
bundles/org.openhab.binding.feed/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.feed/.project
Normal file
23
bundles/org.openhab.binding.feed/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.feed</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
27
bundles/org.openhab.binding.feed/NOTICE
Normal file
27
bundles/org.openhab.binding.feed/NOTICE
Normal file
@@ -0,0 +1,27 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
JDOM Version: 2.0.6
|
||||
* License: JDOM License (modified Apache)
|
||||
* Project: http://www.jdom.org
|
||||
* Source: https://github.com/hunterhacker/jdom
|
||||
|
||||
Rome Version 1.12.0
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://rometools.github.io/rome/
|
||||
* Source: https://github.com/rometools/rome
|
||||
|
||||
|
||||
68
bundles/org.openhab.binding.feed/README.md
Normal file
68
bundles/org.openhab.binding.feed/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Feed Binding
|
||||
|
||||
This binding allows you to integrate feeds in the openHAB environment.
|
||||
The Feed binding downloads the content, tracks for changes, and displays information like feed author, feed title and description, number of entries, last update date.
|
||||
|
||||
It can be used in combination with openHAB rules to trigger events on feed change.
|
||||
It uses the [ROME library](https://rometools.github.io/rome/index.html) for parsing
|
||||
and supports a wide range of popular feed formats - RSS 2.00, RSS 1.00, RSS 0.94, RSS 0.93, RSS 0.92, RSS 0.91 UserLand,
|
||||
RSS 0.91 Netscape, RSS 0.90, Atom 1.0, Atom 0.3.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports one Thing type: `feed`.
|
||||
|
||||
## Discovery
|
||||
|
||||
Discovery is not necessary.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
No binding configuration required.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Required configuration:
|
||||
|
||||
- **URL** - the URL of the feed (e.g <http://example.com/path/file>). The binding uses this URL to download data
|
||||
|
||||
Optional configuration:
|
||||
|
||||
- **refresh** - a refresh interval defines after how many minutes the binding will check, if new content is available. Default value is 20 minutes
|
||||
|
||||
## Channels
|
||||
|
||||
The binding supports following channels
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|--------------------|-----------|-----------------------------------------------------|
|
||||
| latest-title | String | Contains the title of the last feed entry. |
|
||||
| latest-description | String | Contains the description of last feed entry. |
|
||||
| latest-date | DateTime | Contains the published date of the last feed entry. |
|
||||
| author | String | The name of the feed author, if author is present |
|
||||
| title | String | The title of the feed |
|
||||
| description | String | Description of the feed |
|
||||
| last-update | DateTime | The last update date of the feed |
|
||||
| number-of-entries | Number | Number of entries in the feed |
|
||||
|
||||
## Example
|
||||
|
||||
Things:
|
||||
|
||||
```java
|
||||
feed:feed:bbc [ URL="http://feeds.bbci.co.uk/news/video_and_audio/news_front_page/rss.xml?edition=uk"]
|
||||
feed:feed:techCrunch [ URL="http://feeds.feedburner.com/TechCrunch/", refresh=60]
|
||||
```
|
||||
|
||||
Items:
|
||||
|
||||
```java
|
||||
String latest_title {channel="feed:feed:bbc:latest-title"}
|
||||
String latest_description {channel="feed:feed:bbc:latest-description"}
|
||||
DateTime latest_date {channel="feed:feed:bbc:latest-date"}
|
||||
Number number_of_entries {channel="feed:feed:bbc:number-of-entries"}
|
||||
String description {channel="feed:feed:bbc:description"}
|
||||
String author {channel="feed:feed:bbc:author"}
|
||||
DateTime published_date {channel="feed:feed:bbc:last-update"}
|
||||
String title {channel="feed:feed:bbc:title"}
|
||||
```
|
||||
48
bundles/org.openhab.binding.feed/pom.xml
Normal file
48
bundles/org.openhab.binding.feed/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.feed</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Feed Binding</name>
|
||||
|
||||
<properties>
|
||||
<bnd.importpackage>org.jaxen.*;resolution:=optional</bnd.importpackage>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>1.12.0</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome-utils</artifactId>
|
||||
<version>1.12.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>2.0.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.feed-${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-feed" description="Feed Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.feed/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.feed.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link FeedBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Svilen Valkanov - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FeedBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "feed";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID FEED_THING_TYPE_UID = new ThingTypeUID("feed", "feed");
|
||||
|
||||
// List of all Channel IDs
|
||||
/**
|
||||
* Contains the published date of the last feed entry.
|
||||
*/
|
||||
public static final String CHANNEL_LATEST_PUBLISHED_DATE = "latest-date";
|
||||
|
||||
/**
|
||||
* Contains the title of the last feed entry.
|
||||
*/
|
||||
public static final String CHANNEL_LATEST_TITLE = "latest-title";
|
||||
|
||||
/**
|
||||
* Contains the description of last feed entry.
|
||||
*/
|
||||
public static final String CHANNEL_LATEST_DESCRIPTION = "latest-description";
|
||||
|
||||
/**
|
||||
* Description of the feed.
|
||||
*/
|
||||
public static final String CHANNEL_DESCRIPTION = "description";
|
||||
|
||||
/**
|
||||
* The last update date of the feed.
|
||||
*/
|
||||
public static final String CHANNEL_LAST_UPDATE = "last-update";
|
||||
|
||||
/**
|
||||
* The name of the feed author, if author is present.
|
||||
*/
|
||||
public static final String CHANNEL_AUTHOR = "author";
|
||||
|
||||
/**
|
||||
* The title of the feed.
|
||||
*/
|
||||
public static final String CHANNEL_TITLE = "title";
|
||||
|
||||
/**
|
||||
* Number of entries in the feed
|
||||
*/
|
||||
public static final String CHANNEL_NUMBER_OF_ENTRIES = "number-of-entries";
|
||||
|
||||
// Configuration parameters
|
||||
/**
|
||||
* * The URL of the feed document.
|
||||
*/
|
||||
public static final String URL = "URL";
|
||||
|
||||
/**
|
||||
* The refresh time in minutes.
|
||||
*/
|
||||
public static final String REFRESH_TIME = "refresh";
|
||||
|
||||
/**
|
||||
* The default auto refresh time in minutes.
|
||||
*/
|
||||
public static final BigDecimal DEFAULT_REFRESH_TIME = new BigDecimal(20);
|
||||
|
||||
/**
|
||||
* The minimum refresh time in milliseconds. Any REFRESH command send to a Thing, before this time has expired, will
|
||||
* not trigger an attempt to dowload new data form the server.
|
||||
**/
|
||||
public static final int MINIMUM_REFRESH_TIME = 3000;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.feed.internal;
|
||||
|
||||
import static org.openhab.binding.feed.internal.FeedBindingConstants.FEED_THING_TYPE_UID;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openhab.binding.feed.internal.handler.FeedHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link FeedHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Svilen Valkanov - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.feed")
|
||||
public class FeedHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(FEED_THING_TYPE_UID);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(FEED_THING_TYPE_UID)) {
|
||||
return new FeedHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* 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.feed.internal.handler;
|
||||
|
||||
import static org.openhab.binding.feed.internal.FeedBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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;
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndEntry;
|
||||
import com.rometools.rome.feed.synd.SyndFeed;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
import com.rometools.rome.io.SyndFeedInput;
|
||||
|
||||
/**
|
||||
* The {@link FeedHandler } is responsible for handling commands, which are
|
||||
* sent to one of the channels and for the regular updates of the feed data.
|
||||
*
|
||||
* @author Svilen Valkanov - Initial contribution
|
||||
*/
|
||||
public class FeedHandler extends BaseThingHandler {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(FeedHandler.class);
|
||||
|
||||
private String urlString;
|
||||
private BigDecimal refreshTime;
|
||||
private ScheduledFuture<?> refreshTask;
|
||||
private SyndFeed currentFeedState;
|
||||
private long lastRefreshTime;
|
||||
|
||||
public FeedHandler(Thing thing) {
|
||||
super(thing);
|
||||
currentFeedState = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
checkConfiguration();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
startAutomaticRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the provided configuration is valid.
|
||||
* When invalid parameter is found, default value is assigned.
|
||||
*/
|
||||
private void checkConfiguration() {
|
||||
logger.debug("Start reading Feed Thing configuration.");
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
// It is not necessary to check if the URL is valid, this will be done in fetchFeedData() method
|
||||
urlString = (String) configuration.get(URL);
|
||||
|
||||
try {
|
||||
refreshTime = (BigDecimal) configuration.get(REFRESH_TIME);
|
||||
if (refreshTime.intValue() <= 0) {
|
||||
throw new IllegalArgumentException("Refresh time must be positive number!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Refresh time [{}] is not valid. Falling back to default value: {}. {}", refreshTime,
|
||||
DEFAULT_REFRESH_TIME, e.getMessage());
|
||||
refreshTime = DEFAULT_REFRESH_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
private void startAutomaticRefresh() {
|
||||
refreshTask = scheduler.scheduleWithFixedDelay(this::refreshFeedState, 0, refreshTime.intValue(),
|
||||
TimeUnit.MINUTES);
|
||||
logger.debug("Start automatic refresh at {} minutes", refreshTime.intValue());
|
||||
}
|
||||
|
||||
private void refreshFeedState() {
|
||||
SyndFeed feed = fetchFeedData(urlString);
|
||||
boolean feedUpdated = updateFeedIfChanged(feed);
|
||||
|
||||
if (feedUpdated) {
|
||||
List<Channel> channels = getThing().getChannels();
|
||||
for (Channel channel : channels) {
|
||||
publishChannelIfLinked(channel.getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void publishChannelIfLinked(ChannelUID channelUID) {
|
||||
String channelID = channelUID.getId();
|
||||
|
||||
if (currentFeedState == null) {
|
||||
// This will happen if the binding could not download data from the server
|
||||
logger.trace("Cannot update channel with ID {}; no data has been downloaded from the server!", channelID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLinked(channelUID)) {
|
||||
logger.trace("Cannot update channel with ID {}; not linked!", channelID);
|
||||
return;
|
||||
}
|
||||
|
||||
State state = null;
|
||||
SyndEntry latestEntry = getLatestEntry(currentFeedState);
|
||||
|
||||
switch (channelID) {
|
||||
case CHANNEL_LATEST_TITLE:
|
||||
if (latestEntry == null || latestEntry.getTitle() == null) {
|
||||
state = UnDefType.UNDEF;
|
||||
} else {
|
||||
String title = latestEntry.getTitle();
|
||||
state = new StringType(getValueSafely(title));
|
||||
}
|
||||
break;
|
||||
case CHANNEL_LATEST_DESCRIPTION:
|
||||
if (latestEntry == null || latestEntry.getDescription() == null) {
|
||||
state = UnDefType.UNDEF;
|
||||
} else {
|
||||
String description = latestEntry.getDescription().getValue();
|
||||
state = new StringType(getValueSafely(description));
|
||||
}
|
||||
break;
|
||||
case CHANNEL_LATEST_PUBLISHED_DATE:
|
||||
case CHANNEL_LAST_UPDATE:
|
||||
if (latestEntry == null || latestEntry.getPublishedDate() == null) {
|
||||
logger.debug("Cannot update date channel. No date found in feed.");
|
||||
return;
|
||||
} else {
|
||||
Date date = latestEntry.getPublishedDate();
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
|
||||
state = new DateTimeType(zdt);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_AUTHOR:
|
||||
String author = currentFeedState.getAuthor();
|
||||
state = new StringType(getValueSafely(author));
|
||||
break;
|
||||
case CHANNEL_DESCRIPTION:
|
||||
String channelDescription = currentFeedState.getDescription();
|
||||
state = new StringType(getValueSafely(channelDescription));
|
||||
break;
|
||||
case CHANNEL_TITLE:
|
||||
String channelTitle = currentFeedState.getTitle();
|
||||
state = new StringType(getValueSafely(channelTitle));
|
||||
break;
|
||||
case CHANNEL_NUMBER_OF_ENTRIES:
|
||||
int numberOfEntries = currentFeedState.getEntries().size();
|
||||
state = new DecimalType(numberOfEntries);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unrecognized channel: {}", channelID);
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
updateState(channelID, state);
|
||||
} else {
|
||||
logger.debug("Cannot update channel with ID {}; state not defined!", channelID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method updates the {@link #currentFeedState}, only if there are changes on the server, since the last check.
|
||||
* It compares the content on the server with the local
|
||||
* stored {@link #currentFeedState} in the {@link FeedHandler}.
|
||||
*
|
||||
* @return <code>true</code> if new content is available on the server since the last update or <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
private synchronized boolean updateFeedIfChanged(SyndFeed newFeedState) {
|
||||
// SyndFeed class has implementation of equals ()
|
||||
if (newFeedState != null && !newFeedState.equals(currentFeedState)) {
|
||||
currentFeedState = newFeedState;
|
||||
logger.debug("New content available!");
|
||||
return true;
|
||||
}
|
||||
logger.debug("Feed content has not changed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to make connection with the server and fetch data from the feed.
|
||||
* The status of the feed thing is set to {@link ThingStatus#ONLINE}, if the fetching was successful.
|
||||
* Otherwise the status will be set to {@link ThingStatus#OFFLINE} with
|
||||
* {@link ThingStatusDetail#CONFIGURATION_ERROR} or
|
||||
* {@link ThingStatusDetail#COMMUNICATION_ERROR} and adequate message.
|
||||
*
|
||||
* @param urlString URL of the Feed
|
||||
* @return {@link SyndFeed} instance with the feed data, if the connection attempt was successful and
|
||||
* <code>null</code> otherwise
|
||||
*/
|
||||
private SyndFeed fetchFeedData(String urlString) {
|
||||
SyndFeed feed = null;
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setRequestProperty("Accept-Encoding", "gzip");
|
||||
|
||||
BufferedReader in = null;
|
||||
if ("gzip".equals(connection.getContentEncoding())) {
|
||||
in = new BufferedReader(new InputStreamReader(new GZIPInputStream(connection.getInputStream())));
|
||||
} else {
|
||||
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
}
|
||||
|
||||
SyndFeedInput input = new SyndFeedInput();
|
||||
feed = input.build(in);
|
||||
in.close();
|
||||
|
||||
if (this.thing.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
logger.warn("Url '{}' is not valid: ", urlString, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error accessing feed: {}", urlString, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
return null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Feed URL is null ", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
return null;
|
||||
} catch (FeedException e) {
|
||||
logger.warn("Feed content is not valid: {} ", urlString, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent entry or null, if no entries are found.
|
||||
*/
|
||||
private SyndEntry getLatestEntry(SyndFeed feed) {
|
||||
List<SyndEntry> allEntries = feed.getEntries();
|
||||
SyndEntry lastEntry = null;
|
||||
if (!allEntries.isEmpty()) {
|
||||
/*
|
||||
* The entries are stored in the SyndFeed object in the following order -
|
||||
* the newest entry has index 0. The order is determined from the time the entry was posted, not the
|
||||
* published time of the entry.
|
||||
*/
|
||||
lastEntry = allEntries.get(0);
|
||||
} else {
|
||||
logger.debug("No entries found");
|
||||
}
|
||||
return lastEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
// safeguard for multiple REFRESH commands for different channels in a row
|
||||
if (isMinimumRefreshTimeExceeded()) {
|
||||
SyndFeed feed = fetchFeedData(urlString);
|
||||
updateFeedIfChanged(feed);
|
||||
}
|
||||
publishChannelIfLinked(channelUID);
|
||||
} else {
|
||||
logger.debug("Command {} is not supported for channel: {}. Supported command: REFRESH", command,
|
||||
channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (refreshTask != null) {
|
||||
refreshTask.cancel(true);
|
||||
}
|
||||
lastRefreshTime = 0;
|
||||
}
|
||||
|
||||
private boolean isMinimumRefreshTimeExceeded() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long timeSinceLastRefresh = currentTime - lastRefreshTime;
|
||||
if (timeSinceLastRefresh < MINIMUM_REFRESH_TIME) {
|
||||
return false;
|
||||
}
|
||||
lastRefreshTime = currentTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getValueSafely(String value) {
|
||||
return value == null ? new String() : value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="feed" 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>Feed Binding</name>
|
||||
<description>The Feed Binding downloads Feed data from URL, displays information about the feed (number of Entries,
|
||||
last published date, author, feed title and description, last entry) and tracks for changes in the Feed at intervals.</description>
|
||||
<author>Svilen Valkanov</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="feed"
|
||||
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">
|
||||
|
||||
<!-- DEFINITIONS of terms used in the binding: Feed is a XML document used for providing users with frequently updated content.
|
||||
The most popular feed formats are RSS and Atom. Entry is a single element in the Feed, that contains reference (link),
|
||||
short description and other information like images, comments and etc. Entry in this binding is abstraction for RSS item
|
||||
element and Atom entry element. -->
|
||||
|
||||
<!-- Feed Thing Type -->
|
||||
<thing-type id="feed">
|
||||
<label>Feed</label>
|
||||
<description>Provides information about a feed.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="latest-title" typeId="latest-title"/>
|
||||
<channel id="latest-description" typeId="latest-description"/>
|
||||
<channel id="latest-date" typeId="latest-date"/>
|
||||
<channel id="author" typeId="author"/>
|
||||
<channel id="description" typeId="description"/>
|
||||
<channel id="title" typeId="title"/>
|
||||
<channel id="last-update" typeId="last-update"/>
|
||||
<channel id="number-of-entries" typeId="number-of-entries"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="URL" type="text" required="true">
|
||||
<label>Feed URL</label>
|
||||
<description>The URL of the feed</description>
|
||||
</parameter>
|
||||
|
||||
<!--After the refresh time interval expires, the bindings checks for updates in the Feed, and if the information is not
|
||||
up to date, updates the feed content stored in the channel -->
|
||||
|
||||
<parameter name="refresh" type="integer">
|
||||
<label>Refresh Time Interval</label>
|
||||
<description>Refresh time interval in minutes.</description>
|
||||
<default>20</default>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="latest-title">
|
||||
<item-type>String</item-type>
|
||||
<label>Latest Title</label>
|
||||
<description>Contains the title of the last feed entry.</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="latest-description">
|
||||
<item-type>String</item-type>
|
||||
<label>Latest Description</label>
|
||||
<description>Contains the description of last feed entry.</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="latest-date">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Latest Published Date</label>
|
||||
<description>Contains the published date of the last feed entry.</description>
|
||||
<state readOnly="true" pattern="%tc %n"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="author" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Author</label>
|
||||
<description>The name of the feed author, if author is present</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Title</label>
|
||||
<description>The title of the feed</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="description" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Description</label>
|
||||
<description>Description of the feed</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="last-update" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>The last update date of the feed</description>
|
||||
<state readOnly="true" pattern="%tc %n"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="number-of-entries" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number of Entries</label>
|
||||
<description>Number of entries in the feed</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user