XQuery/Pachube 馈送
您想为 Pachube 应用程序创建馈送。Pachube 应用程序允许您存储、共享和发现来自世界各地物体、设备和建筑物的实时传感器、能源和环境数据。这为传感器数据集成提供了一个平台。Pachube 收集的历史记录可以以各种格式呈现,并被其他应用程序用来混合馈送。
- eXist httpclient 用于 GET、POST 和 PUT
- eXist 调度程序用于作业调度
- eXist 更新扩展
- 服务器端 XSLT
伦敦塔桥的开/关状态馈送的想法借鉴了 @ni。
一个 Twitter 流提供了一个简单的状态馈送的基础数据。来自该流的 RSS 馈送 由 XQuery 脚本读取,状态从文本中推断出来,并更新了表示当前状态的 XML 文件。
此 XML 文件附加了一个 XSLT 样式表,因此当文件按计划从 eXist 数据库中提取时,它首先在服务器端转换为 Pachube 馈送所需的 EEML 格式。在 UWE 服务器上配置时,这将使用 Saxon XSLT-2。
let $rss := httpclient:get(xs:anyURI( "http://twitter.com/statuses/user_timeline/14012942.rss" ),false(),())/httpclient:body let $lastChange:= $rss//item[1] let $bridgeStatus := doc("/db/Wiki/Pachube/bridge.xml")/data/status return if (exists($lastChange) and exists($bridgeStatus)) then let $open := if(contains($lastChange/description,"opening")) then "1" else "0" return update replace $bridgeStatus with element status { attribute bridge {$open}, attribute lastChange {$lastChange/pubDate}, attribute lastUpdate {current-dateTime()} } else ()
1. 这里使用 httpclient 是因为 doc() 会抛出关于重复命名空间声明的错误 - 正在调查中
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://www.cems.uwe.ac.uk/xmlwiki/Pachube/bridge.xsl"?>
<data>
<status bridge="0" lastChange="Mon, 14 Dec 2009 12:09:02 +0000" lastUpdate="2009-12-14T16:57:00.679Z"/>
</data>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output media-type="application/xml" method="xml" indent="yes"/>
<xsl:template match="/data">
<eeml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.eeml.org/xsd/005" xsi:schemaLocation="http://www.eeml.org/xsd/005 http://www.eeml.org/xsd/005/005.xsd" version="5">
<environment updated="{current-dateTime()}">
<title>Tower Bridge </title>
<feed>http://www.cems.uwe.ac.uk/xmlwiki/Pachube/bridge.xml</feed>
<description>The status of the lifting Tower Bridge: 1 is open , 0 is closed. </description>
<email>[email protected]</email>
<location exposure="outdoor" domain="physical" disposition="fixed">
<name>Tower Bridge</name>
<lat>51.5064186</lat>
<lon>-0.074865818</lon>
</location>
<data id="0">
<tag>bridge open</tag>
<value minValue="0" maxValue="1">
<xsl:value-of select="status/@bridge"/>
</value>
</data>
</environment>
</eeml>
</xsl:template>
</xsl:stylesheet>
eXist 作业调度程序每 1 分钟调用一次 XQuery 更新脚本
let $login := xmldb:login( "/db", "user", "password" ) let $del := scheduler:delete-scheduled-job("BRIDGE") let $job := scheduler:schedule-xquery-cron-job("/db/Wiki/Pachube/pollbridgerss.xq" , "0 0/1 * * * ?","BRIDGE") return $job
有一个公开的馈送视图,由 Pachube 处理:http://www.pachube.com/feeds/3922
Pachube 接口每 15 分钟刷新一次自动馈送(对于免费服务)。由于典型的桥梁升降持续 10 分钟,因此有可能错过升降。另一种方法是在检测到更改时将更改推送到 Pachube。
许多业余气象站使用 Weather Display 软件。该软件将当前观测结果写入一个空格分隔的文本文件,以支持与查看软件的接口,例如 Flash Weather Display Live。文本文件通常可供 Web 访问,因此任何客户端都可以访问此原始数据,尽管礼貌地要求访问是可取的。
其中一个气象站由 Martyn Hicks 运营,位于 http://www.martynhicks.co.uk/weather/data.php,位于布里斯托尔的霍菲尔德。原始数据文件是 http://www.martynhicks.co.uk/weather/clientraw.txt
在此推送实现中,一个手动馈送通过 API 在 Pachube 中定义,方法是 POST 一个完整的 EEML 文档。一个 XML 描述符文件定义了数据文件中的值与馈送中的数据流之间的映射。一个计划的 XQuery 脚本读取数据文件,并通过映射文件将其转换为 EEML 格式,然后再 PUT 到 Pachube API。
馈送是在 EEML 文档中定义的,该文档 POST 到 Pachube API。返回代码 201 表示馈送已创建。
let $url := xs:anyURI("http://www.pachube.com/api/feeds/") let $headers := <headers> <header name="X-PachubeApiKey" value="...api key ...."/> </headers> let $feed := <eeml xmlns="http://www.eeml.org/xsd/005"> <environment updated="{current-dateTime()}"> <title>Horfield Weather</title> <description>The weather observed by a weather station run by Martyn Hicks. Public interface is http://www.martynhicks.co.uk/weather/data.php </description> <email>[email protected]</email> <location exposure="outdoor" domain="physical" disposition="fixed"> <name>Horfield</name> <lat>51.4900</lat> <lon>-2.5805</lon> </location> </environment> </eeml> return httpclient:post($url,$feed,false(),$headers)
一个 XML 文档定义了原始数据的来源、Pachube appid 以及从数据值(从 1 开始)到数据流(从文档顺序中的 1 开始编号)的映射。
<weatherfeed xmlns = "http://www.cems.uwe.ac.uk/xmlwiki/wdl">
<data>http://www.martynhicks.co.uk/weather/clientraw.txt</data>
<appid>4013</appid>
<format>
<field n="2" unit="kts">Average Wind Speed</field>
<field n="4" unit="degrees">Wind Direction</field>
<field n="5" unit="Celcius">Temperature</field>
<field n="7" unit="hPa">Barometer</field>
</format>
</weatherfeed>
此脚本计划每分钟运行一次(如上所述)。
映射文件命名空间需要声明
declare namespace wdl = "http://www.cems.uwe.ac.uk/xmlwiki/wdl";
首先是一个函数来读取原始数据文件并将其标记化为一系列值
declare function local:client-data ($rawuri) { let $headers := element headers{ element header { attribute name {"Cache-Control"}, attribute value {"no-cache"} } } let $raw := httpclient:get(xs:anyURI($rawuri),false(),$headers )/httpclient:body return tokenize($raw,"\+") };
然后是一个函数来将一系列值转换为 Pachube 数据通道
declare function local:data-to-eeml ($data,$format) { for $field at $id in $format/wdl:field let $name := string($field) let $index := xs:integer($field/@n) return element data { attribute id {$id}, element tag { string($field)}, element value {$data[$index] }, element unit {string($field/@unit)} } };
主线获取馈送定义文件(这里硬编码,但可以作为参数传入)。获取数据值,生成 EEML 并 PUT 到 Pachube API。
let $feed := doc("/db/Wiki/Pachube/horfieldweather.xml")/wdl:weatherfeed let $data := local:client-data($feed/wdl:data) let $appid := $feed/wdl:appid let $APIKey := "eeda7c27ff8b7c49e8529e4eb4b3f57724c5b609db0d22904df11edd4742e92c" let $url := xs:anyURI(concat( "http://www.pachube.com/api/",$appid)) let $headers := <headers> <header name="X-PachubeApiKey" value="{$APIKey}"/> </headers> let $eeml:= <eeml xmlns="http://www.eeml.org/xsd/005"> <environment updated="{current-dateTime()}"> {local:data-to-eeml($data,$feed/wdl:format)} </environment> </eeml> return httpclient:put($url,$eeml,false(),$headers)
Pachube 馈送的公开视图位于 http://www.pachube.com/feeds/4013
另一种方法更简单,依赖于 Pachube 按其计划拉取数据。
在这个例子中,天气站数据由 WeatherUnderground 整合,并以 XML 格式重新发布,被转换为 EEML。
Weatherunderground 上一个典型的天气站的 XML 数据源是 http://api.wunderground.com/weatherstation/WXCurrentObXML.asp?ID=IBAYOFPL1
可以使用 XSLT 将此 XML 转换为 EEML。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output media-type="application/xml" method="xml" indent="yes"/>
<xsl:template match="/current_observation">
<eeml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.eeml.org/xsd/005"
xsi:schemaLocation="http://www.eeml.org/xsd/005 http://www.eeml.org/xsd/005/005.xsd"
version="5">
<environment updated="{current-dateTime()}">
<title>Weather Report</title>
<location exposure="outdoor" domain="physical" disposition="fixed">
<name>
<xsl:value-of select="location/full"/>
</name>
<lat>
<xsl:value-of select="location/latitude"/>
</lat>
<lon><xsl:value-of select="location/longitude"/>
</lon>
</location>
<data id="1">
<tag>Average Wind speed</tag>
<value>
<xsl:value-of select="round-half-to-even(wind_mph * 1.15077945,1)"/>
</value>
<unit>kts</unit>
</data>
<data id="2">
<tag>Wind Direction</tag>
<value>
<xsl:value-of select="wind_degrees"/>
</value>
<unit>degrees</unit>
</data>
<data id="3">
<tag>Temperature</tag>
<value>
<xsl:value-of select="temp_c"/>
</value>
<unit>Celcius</unit>
</data>
<data id="4">
<tag>Barometric Pressure</tag>
<value>
<xsl:value-of select="pressure_mb"/>
</value>
<unit>hPA</unit>
</data>
</environment>
</eeml>
</xsl:template>
</xsl:stylesheet>
一个简单的 XQuery 脚本接受一个参数,即站点 ID,获取 XML 数据源,并使用 XSLT 转换为 EEML。
let $id := request:get-parameter("id",()) let $ss := doc("/db/Wiki/Pachube/weatherunderground.xsl") let $data := doc(concat("http://api.wunderground.com/weatherstation/WXCurrentObXML.asp?ID=",$id)) return transform:transform($data,$ss,())
该脚本可以被调用:http://www.cems.uwe.ac.uk/xmlwiki/Pachube/weatherunderground.xq?id=IBAYOFPL1。
由于此脚本是参数化的,因此可以与任何 WeatherUnderground 站点一起使用。
可以创建一个自动数据源 - http://www.pachube.com/feeds/4037,它使用此数据源。
我们可以对 美国 ICAO 站点 的数据源采用类似的方法。NOAA 提供 XML 数据源,例如 http://www.weather.gov/xml/current_obs/KEWR.xml。格式与 Weatherunderground 数据源几乎相同,并有文档:http://www.weather.gov/view/current_observation.xsd。更新频率为每小时,但目前无法配置 Pachube 以该频率更新。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output media-type="application/xml" method="xml" indent="yes"/>
<xsl:template match="/current_observation">
<eeml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.eeml.org/xsd/005"
xsi:schemaLocation="http://www.eeml.org/xsd/005 http://www.eeml.org/xsd/005/005.xsd"
version="5">
<environment updated="{current-dateTime()}">
<title>NOAA Weather Report</title>
<location exposure="outdoor" domain="physical" disposition="fixed">
<name>
<xsl:value-of select="location"/>
</name>
<lat>
<xsl:value-of select="latitude"/>
</lat>
<lon><xsl:value-of select="longitude"/>
</lon>
</location>
<data id="1">
<tag>Average Wind speed</tag>
<value>
<xsl:value-of select="wind_kt"/>
</value>
<unit>kts</unit>
</data>
<data id="2">
<tag>Wind Direction</tag>
<value>
<xsl:value-of select="wind_degrees"/>
</value>
<unit>degrees</unit>
</data>
<data id="3">
<tag>Temperature</tag>
<value>
<xsl:value-of select="temp_c"/>
</value>
<unit>Celcius</unit>
</data>
<data id="4">
<tag>Barometric Pressure</tag>
<value>
<xsl:value-of select="pressure_mb"/>
</value>
<unit>hPA</unit>
</data>
</environment>
</eeml>
</xsl:template>
</xsl:stylesheet>
let $id := request:get-parameter("id",()) let $ss := doc("/db/Wiki/Pachube/NOAA.xsl") let $data := doc(concat("http://www.weather.gov/xml/current_obs/",$id,".xml")) return transform:transform($data,$ss,())
转换后的 XML http://www.cems.uwe.ac.uk/xmlwiki/Pachube/NOAA.xq?id=KEWR 是手动数据源 http://www.pachube.com/feeds/4047 的基础。
如果 Pachube 在服务器端支持 XSLT,则整个任务都可以由单个 XSLT 脚本处理。为了通用性,提供一个接口,允许将参数传递给脚本,但这不是必需的。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output media-type="application/xml" method="xml" indent="yes"/>
<xsl:param name="station" select="'KEWR'"/>
<xsl:template match="/">
<xsl:variable name="url" select='concat("http://www.weather.gov/xml/current_obs/",$station,".xml")'></xsl:variable>
<xsl:apply-templates select="doc($url)/current_observation"/>
</xsl:template>
<xsl:template match="current_observation">
<eeml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.eeml.org/xsd/005"
xsi:schemaLocation="http://www.eeml.org/xsd/005 http://www.eeml.org/xsd/005/005.xsd"
version="5">
<environment updated="{current-dateTime()}">
<title>NOAA Current weather for {station_id}</title>
<location exposure="outdoor" domain="physical" disposition="fixed">
<name>
<xsl:value-of select="location"/>
</name>
<lat>
<xsl:value-of select="latitude"/>
</lat>
<lon>
<xsl:value-of select="longitude"/>
</lon>
</location>
<data id="1">
<tag>Average Wind speed</tag>
<value>
<xsl:value-of select="wind_kt"/>
</value>
<unit>kts</unit>
</data>
<data id="2">
<tag>Wind Direction</tag>
<value>
<xsl:value-of select="wind_degrees"/>
</value>
<unit>degrees</unit>
</data>
<data id="3">
<tag>Temperature</tag>
<value>
<xsl:value-of select="temp_c"/>
</value>
<unit>Celcius</unit>
</data>
<data id="4">
<tag>Barometric Pressure</tag>
<value>
<xsl:value-of select="pressure_mb"/>
</value>
<unit>hPA</unit>
</data>
</environment>
</eeml>
</xsl:template>
</xsl:stylesheet>
服务器可以简单地运行此独立脚本以生成 EEML 数据源。此小型 XQuery 脚本使用 eXist 平台上的 SAXON 处理器。
transform:transform((),doc("/db/Wiki/Pachube/NOAA3.xsl"),())
XSLT 转换可以由 eXist 数据库作为服务提供。它需要
- 一个连接定义的 XML 数据库,例如:
<PachubeFeeds>
<PachubeFeed id="2001">
<data>http://www.weather.gov/xml/current_obs/KORS.xml</data>
<xslt>http://www.cems.uwe.ac.uk/xmlwiki/Pachube/NOAA7.xsl</xslt>
<params/>
</PachubeFeed>
<PachubeFeed id="2002">
<data>http://www.weather.gov/xml/current_obs/KEWR.xml</data>
<xslt>http://www.cems.uwe.ac.uk/xmlwiki/Pachube/NOAA7.xsl</xslt>
<params/>
</PachubeFeed>
</PachubeFeeds>
- 一个用于定位和执行数据源的脚本
let $id := request:get-parameter("id",()) let $feed := doc("/db/Wiki/Pachube/feeds.xml")//PachubeFeed[@id=$id] return transform:transform( doc($feed/data), doc($feed/xslt), $feed/params )
- Pachube 自动数据源现在可以创建,URL 类似于
http://www.cems.uwe.ac.uk/xmlwiki/Pachube/getFeed.xq?id=2001
例如 http://www.pachube.com/feeds/4661
- 用户界面和数据库,允许用户注册、创建和编辑数据源
这里存在加载问题以及存储在 XSLT 中的不安全代码问题。
类似地,可以通过一些代码和 XSLT 来提供当前 EEML 或特定数据流的 CSV 历史记录的输出处理。由于这可能需要身份验证,因此 API 密钥也必须存储在此数据库中。可以生成和计划作业来实现触发器,但这需要定时拉取所需的数据。
需要代码将 Pachube 提供的历史记录数据源转换为 XML,因为这些数据源仅以 CSV 格式提供。转换为 XML 后,XSLT 可以转换为所需格式。当然,如果 Pachube 除了 CSV 数据源外还能提供 XML 数据源,那将是最好的。
完整存档以 CSV 文件形式提供。我们可以使用以下脚本将其转换为 XML
import module namespace csv = "http://www.cems.uwe.ac.uk/xmlwiki/csv" at "../lib/csv.xqm"; let $feed := request:get-parameter("feed","") let $stream := request:get-parameter("stream","") let $archiveurl := concat("http://www.pachube.com/feeds/",$feed,"/datastreams/",$stream,"/archive.csv") let $data:= csv:get-data($archiveurl) let $rows := tokenize($data,$csv:newline) let $now := current-dateTime() return <history feed="{$feed}" stream="{$stream}" dateTime="{$now}" count="{count($rows)}"> {for $row in $rows let $point := tokenize($row,",") return <value dateTime="{$point[1]}">{$point[2]}</value> } </history>
http://www.cems.uwe.ac.uk/xmlwiki/Pachube/getArchive.xq?feed=4037&stream=2
在 CSV 数据流中,这些数据是无时间戳的。时间必须使用 xs:dateTimeDuration 估算和计算
import module namespace csv = "http://www.cems.uwe.ac.uk/xmlwiki/csv" at "../lib/csv.xqm"; let $feed := request:get-parameter("feed","") let $stream := request:get-parameter("stream","") let $historyurl := concat("http://www.pachube.com/feeds/",$feed,"/datastreams/",$stream,"/history.csv") let $data:= csv:get-data($historyurl) let $values :=tokenize($data,",") let $now := current-dateTime() let $then := $now - xs:dayTimeDuration("P1D") return <history feed="{$feed}" stream="{$stream}" dateTime="{$now}" count="{count($values)}"> {for $value at $i in $values let $dt := $then + xs:dayTimeDuration(concat("PT",15*$i,"M")) return <value dateTime="{$dt}">{$value}</value> } </history>
http://www.cems.uwe.ac.uk/xmlwiki/Pachube/getHistory.xq?feed=4037&stream=2