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,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>

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>