added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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