Linux下UPnP sample分析
一、UPnP简介
UPnP(Universal Plug and Play)技术是一种屏蔽各种数字设备的硬件和操作系统的通信协议。它是一种数字网络中间件技术,建立在TCP/IP、HTTP协议之上,采用XML来描述设备和控制信息。这种技术最开始是被微软,因特尔等公司使用在数字家庭中的,用来在家庭网络中完成多媒体资源共享,家电一体化等功能,例如在各种家电上实现IP数字化,加入网络传输和控制部分,从而让家电能组建UPnP网络,这种技术能让人们在上班或者外地出差时能查看家庭情况,如:是否有家电忘记关闭;监控家庭情况;或者在回家之前打开家里的热水器并设置水温;在天热时提前打开空调;打开电饭煲开始蒸饭……
这些应用由于家电厂商的不积极(目前能连接到网络的家电可能只有电视吧),或许是成本问题等,UPnP设计之初想要达到的数字家庭并未得到普及和广泛应用。但是UPnP的特有特性还是很吸引人的,它最大的特性是它的消息发送是通过HTTP协议发送,所有的消息内容都是通过XML包装的,这种消息传输模式使得我们能通过浏览器访问和控制支持UPnP协议的设备,再来让我们看看传统的嵌入式网络的通信吧,大多数设备之间网络通信直接使用TCP/IP上的socket编程,这种通信模式需要通信的双方制定好通信数据的格式包,在客户端和服务器端都需要进行编程,而且一旦项目需求发生变化,通信格式发生变化时,这时通信两端的程序都需要修改,如果客户还提出跨平台的问题,那需要修改的就更多了。UPnP协议本身是利用的现有的通用协议而来的,它占用资源少,特别适用于嵌入式网络领域,能通过浏览器访问和控制设备的特性使得UPnP的编程通常只涉及一方,即设备端。通信数据格式的修改和客户需求的变化也只涉及到设备端的修改,它便于设备的升级,UPnP设备在添加到网络中时,就可以立即被网络中拥有浏览器的客户端访问和控制。
二、UPnP的组成
UPnP网络是指通过采用UPnP协议的设备所组成的网络,它的基本组件是服务、设备和控制点:
服务是UPnP网络中最小逻辑功能单元,由状态表、事件和控制服务器组成。状态表描述服务的当前状态;事件服务器用来管理来自控制点的事件订阅请求,并在服务的状态改变时向订阅者发送状态更新事件;而控制服务器则负责处理来自控制点的动作请求。
UPnP设备由一组子设备或服务构成,同时多个设备下的子设备也通过逻辑组合构成新的设备。设备中的每个功能也可以当做一个逻辑子设备向外发布。
控制点的功能是发现和控制其它设备,在控制点发现一个网络设备后,它会发出请求先获取设备描述和服务列表,再获取感兴趣的服务描述,然后通过动作请求来控制服务。控制点订阅设备的服务事件时,服务会在状态变化时向控制点发送的状态更新事件。一个控制点可以控制多个UPnP设备,也可以作为一个设备被其它控制点控制。
三、UPnP的工作过程
UPnP的工作过程
UPnP协议栈
四、UPnP的描述文档
在UPnP的工作是基于UPnP描述文件的。UPnP描述文件分为设备描述文件和服务描述文件。
设备描述文件包括设备属性和它所提供的服务,一个标准设备描述文档如下所示:
- <span style="font-size:16px;"><?xml version="1.0"?>
- <root xmlns="urn:schemas-upnp-org:XXXXXX">
- ......
- <device>
- <deviceType>urn:schemas-upnp-org:device: XXXXXX: X </deviceType>
- ……
- <UDN>uuid:XXXXXX</UDN>
- <UPC>……</UPC>
- <serviceList>
- <service>…… </service>
- </serviceList>
- <presentationURL>/设备展示URL地址</presentationURL>
- </device>
- </root></span>
在设备描述文档中,<root>到</root>即描述的一个根设备,specVersion配置的是UPnP协议版本编号,<device>到</device>之间描述的是一个完整的UPnP设备,deviceType表示设备的类型,它的标准命名方式是urn:schemas-upnp-org:device:deviceType:ver,其中deviceType:ver是由用户来设定的。presentationURL参数表示设备展示所对应的请求页面地址。
设备描述中UDN(Unique Device Name)是必须设定的,这个参数用来在UPnP组建的网络中用来唯一标识一个设备,产品编号UPC(Universal Product Code)是可选的,然后<serviceList>到</serviceList>之间是来描述设备提供的服务,每个服务的参数说明在<service>到</service>之间,服务的主要参数如表1所示:
表1设备服务XML描述字段
参数 |
说明 |
<serviceType> |
服务类型,UPnP论坛规定为urn:schemas-upnp-org:service:serviceType: ver,serviceType:ver由用户自己设定 |
<serviceId> |
服务ID,它在一个设备描述里命名需要唯一 |
<SCPDURL> |
服务描述的URL |
<controlURL> |
服务控制的URL |
<eventSubURL> |
服务事件的URL |
它们描述了设备支持的服务类型和数量,以及所有服务支持的动作和变量。
UPnP服务描述文件定义了动作、动作参数、变量、变量类型和及其范围、事件特性,每个服务提供的动作有参数选择(分为输入参数和输出参数),每个参数必须对应服务描述文件中的服务状态变量表(serviceStateTable)中的一个状态变量(stateVariable),UPnP服务描述文件的基本格式如下:
- <span style="font-size:16px;"><?xml version="1.0"?>
- <scpd xmlns="urn:schemas-upnp-org:service-1-0">
- <actionList>
- <action>
- <name>XXX<name>
- <argumentList>
- <argument>……</argument>
- ……
- </argumentList>
- </action>
- ……
- </actionList>
- <serviceStateTable>
- <stateVariable>…… </stateVariable>
- </serviceStateTable>
- </scpd>
- </span>
每个动作在一个设备服务中由动作名称name唯一标识,动作的参数是在<argumentList>中由<argument>描述的,argument的参数如表2:
表2 设备服务XML描述文档中动作字段
参数 |
说明 |
<name> |
参数名,以字母开头,可以以数字、字母和下划线组成 |
<direction> |
“in”或“out”,表示输入和输出参数 |
<retval> |
可选,最多指定一个参数为动作返回值 |
<relatedStateVariable> |
必须为一个状态变量名 |
再来看状态变量< stateVariable >的参数,如表3:
表3设备服务XML描述文档中状态变量字段
参数 |
说明 |
<name> |
状态变量名,以字母开头,可以以数字、字母和下划线组成 |
<dataType> |
数据类型,详细见注 |
<defaultValue> |
默认值 |
<allowedValueList> |
有效的字符值列表,每个有效字符值在<allowedValue>中定义 |
<allowedValueRange> |
包括最小值在<minimum>中定义,最大值在<maximum>中定义,<step>步进值 |
上面叙述的设备描述和服务描述是通过XML文件完成的,在程序中就是对应不同的XML文件,首先设备在XML文档中描述设备名称和它所包含的服务信息(服务信息中包括服务名称和ID,最重要的是指定服务描述对应XML文件地址)。这样程序才能在解析设备文档后能根据它提供的信息去解析对应的服务XML文档。服务XML文档指定了服务的状态表和动作。状态表可以理解为服务的测量数据,动作则可理解为服务向用户提供的控制点。
五、UPnP在ARM Linux上的使用
UPnP应用在arm平台有它的特点和优势,这些我都在前文简单介绍了,但实际开发时可以现在PC机上测试,测试成功后再通过交叉编译工具编译放在ARM平台运行即可,开发UPnP应用程序先要下载UPnP开发套件,下载后在Linux虚拟机中解压执行./configure;然后make 即可测试SDK中提供的sample程序了(目录为:libupnp-1.6.14/upnp/sample),下面有三个测试程序tv_ctrlpt ,tv_combo, tv_device。这三个程序只是程序的启动脚本,本文只分析tv_device程序,这个脚本文件最终实际上执行了libupnp-1.6.14/upnp/sample/.libs/tv_device 程序,程序代码在sample目录下的common和linux中,它所使用的XML描述文件在web目录下。
执行tv_device脚本:./tv_device,效果如下:
将图中的网址http://192.168.128.128:49152/tvdevicedesc.xml输入到另一台计算机的浏览器上,最后的关键部分显示如下:
tvdevicedesc.xml是一个设备描述文档,我们可以清楚的看到,它的设备描述文档中指明了设备拥有两个服务和服务的描述文档地址(tvcontrolSCPD.xml和tvpictureSCPD.xml),设备文档中还指定了设备展示文档的地址<presentationURL>/tvdevicepres.html</presentationURL> ,这个地址是提供给用户在浏览器中访问和操作设备,将之前的网址改为http://192.168.128.128:49152/tvdevicepres.html ,(注意:这里由于例子中的设备展示文档中使用了VBscript脚本,故暂时只能通过IE内核的浏览器正常访问):
当你操作这些按钮时,虚拟机中的Linux程序中会输入对应的操作信息,在局域网中的任何用户都能通过浏览器访问和控制设备。如果你想要在arm平台中测试这些,首先需要在程序配置时执行: ./configure --host=arm-linux
--prefix=/home/yanghao/Desktop/libupnp --enable-shared 然后执行make,make install,先把程序运行所需要的lib库编译生成出来,然后将sample下的编译好的程序复制到开发板,将/home/yanghao/Desktop/libupnp中的lib库复制到开发板的lib目录下,或者是在其中脚本中设置LD_LIBRARY_PATH变量也行,这样程序就可以在arm开发板中执行了。
通过之前的演示,我们可以看到UPnP的模式有些像一个网页服务器,的确如此,UPnP协议通过网页来完成用户数据的操作和访问,但它又和网页服务器有着重大的区别,网页的操作模式只是它众多特性中的一种,UPnP架构中消息的发布和传输是基于HTTP和XML,UPnP占用资源小,协议架构简单,我们可以看到它编译出的支持库大小1.5M,这还是动态链接库和静态链接库的总大小,网页是它数据显示的一种方式,它的数据更多的是应用程序间的交互。控制点可以通过订阅一个UPnP设备服务,当UPnP设备服务中的状态变量发生变化时会主动向订阅该服务的控制点发送状态变化信息,同时控制点可以在程序中利用远程调用完成对设备的控制。这种简单灵活的方式非常适合于嵌入式领域,通常嵌入式领域中网络通信无非就是传感器测量数据的传输和远程用户对设备的控制,UPnP的状态变量和动作刚好可以完成这两方面的功能,状态变量可以等同于测量数据的实时显示,动作等同于控制,动作提供远程接口供用户调用。
六、UPnP程序的编写
UPnP开发包中使用很简单,可以参考sample例子中的源代码,大部分API函数的调用是固定的,需要我们做的工作主要集中在XML文档的编写上,我这里以一例子中的TvDevice Control State Table中的Power选项为例。首先查看它的定义,在sample下的web目录下的tvdevicepres.html中:
- <TR>
- <TD BGCOLOR="#FFFFFF" VALIGN=center ALIGN=center><span style="color:#000000;">Power</span></TD>
- <TD BGCOLOR="#FFFFFF" valign="top"><P ID=Power></P></TD>
- <TD BGCOLOR="#FFFFFF" valign="top">
- <INPUT type="button" onclick="SetPowerOn()" value=" On ">
- <INPUT type="button" onclick="SetPowerOff()" value=" Off ">
- </TD>
- </TR>
- ……
- If (callbackType = "VARIABLE_UPDATE") Then
- select case svcObj.Id case "urn:upnp-org:serviceId:tvcontrol1"
- select case varName Case "Power" Power.innerText = value
- ……
- function SetPowerOn()
- Dim inArgs(0)
- Dim outArgs(0)
- TvControlService.InvokeAction "PowerOn", inArgs, outArgs
- end function
- function SetPowerOff()
- Dim inArgs(0)
- Dim outArgs(0)
- TvControlService.InvokeAction "PowerOff", inArgs, outArgs
- end function
Power旁边的编辑框ID为Power,后面紧跟的两个按钮On和Off触发的函数依次为SetPowerOn()和SetPowerOff()。由VBscript中的判断语句中可以看到当服务ID为"urn:upnp-org:serviceId:tvcontrol1"的变量"Power"值发生变化时,编辑框ID为Power的内容为服务的"Power"变量值。这里将网页中的编辑框内容与服务中的变量值绑定起来了。再来看按钮On的触发函数SetPowerOn(),函数定义中说明函数最终执行TvControlService中的PowerOn动作。再来看tvcontrol服务的描述文件对状态变量和动作的说明(在web目录下tvcontrolSCPD.xml文件中):
- <actionList>
- <action>
- <name>PowerOn</name>
- <argumentList>
- <argument>
- <retval />
- <name>Power</name>
- <relatedStateVariable>Power</relatedStateVariable>
- <direction>out</direction>
- </argument>
- </argumentList>
- </action>
- ……
- </actionList>
- <serviceStateTable>
- <stateVariable sendEvents="yes">
- <name>Power</name>
- <dataType>Boolean</dataType>
- <defaultValue>0</defaultValue>
- </stateVariable>
- ……
tvcontrol服务的状态变量Power是一个bool类型的变量,PowerOn动作与状态变量Power绑定,这样我们在执行PowerOn时会影响状态变量Power,而这个具体的影响是在程序代码中完成的,这里只是完成一个关系建立的工作。在UPnP代码中也是根据这里设定好的动作名称和状态变量名称去执行不同动作所对应的函数和改变对应变量的值。