这篇是翻译官网的编程指南,官网地址:http://www.cybergarage.org/pdfdoc/clinkjavaproguide.pdf
目录:
1 简介
2 步骤
3 设备
3.1 类概述
3.2 描述
3.3 初始化
3.4 通知
3.5 嵌入的设备
3.6 服务
3.7 控制
3.8 事件
4. 控制点
4.1 类概述
4.2 初始化
4.3 通知
4.4 搜索
4.5 根设备
4.6 控制
4.7 事件
__________________________________________________
1. 简介
UPnP™*1 技术基于开放的网络,能够发现并控制网络上的设备和服务,比如在家里使用的多媒体提供者和播放者。(如DMS,DMP)
UPnP™ 技术基于许多标准协议,如:GENA, SSDP, SOAP, HTTPU 和 HTTP。因此为了创建你的基于 UPnP™ 的设备你必须懂得并实现这些协议。
C 语言版的 CyberLink 是一个提供给 UPnP™ 开发者的开发包。CyberLink 自动控制这些协议,以此帮助你快速地开发设备和控制点。
请查阅以下站点和文档去了解关于 UPnP™ 的更多细节。
文档 地址
UPnP™ Forum http://www.upnp.org/
2. 步骤
要使用这个开发包,需要将开发包复制到你的 JDK 和 JRE 库所在的目录。例如,
cp clink???.jar C:¥jdk1.x¥jre¥lib¥ext¥
cp clink???.jar C:¥Program Files¥JavaSoft¥JRE¥1.x¥lib¥ext¥ (这里本来就是这样)
当前版本的这个开发包需要以下包用于解析 XML 和 SOAP 请求。CyberLink 会自动加载以下 XML 包。如果你的开发框架没有这些 XML 包,请先安装它们。
包名 地址
Apache Xerces http://xml.apache.org/xerces2-j/index.html
kXML2 http://www.kxml.org/
JAXP https://jaxp.dev.java.net/
XMLPull http://www.xmlpull.org/
3 设备
3.1 类概述
以下静态结构图列出了 CyberLink 中用于创建 UPnP™ 设备的相关类。一个设备可以含有数量不等的内嵌的设备和服务,服务则包含一些动作和变量。
以上思维结构图为了显示而做了简化。
3.2 描述
首先,在创建 UPnP™ 设备之前,你必须完成一些对应你的设备和服务的描述文件。
根设备的描述文件不要有 URLBase 元素,因为在使用描述文件创建设备的时候该元素会被自动添加。
服务描述文件需要创建一个设备,但是 presentationURL 和 iconList 元素只是推荐选项。请阅读 UPnP™ 说明书中关于描述文件格式的章节获得更多的细节。
3.3. 初始化
为了创建 UPnP™ 设备,需要根据根描述文件创建一个设备类的实例。这个创建出的设备是一个根设备,只有根设备可以使用 Device::start()。设备创建完成后会向
UPnP™ 网络发送通知。以下内容展示初始化一个设备。
- import org.cybergarage.upnp.*;
- import org.cybergarage.upnp.device.*;
- ……
- String descriptionFileName = "description/description.xml";
- Try {
- Device upnpDev = new Device(descriptionFileName);
- ……
- upnpDev.start();
- } catch (InvalidDescriptionException e){
- String errMsg = e.getMessage();
- System.out.println(“InvalidDescriptionException = ” + errMsg);
- }
当描述文件无效时会产生 InvalidDescriptionException 异常。可以使用 getMessage() 获得异常原因的更多细节。
有两种方案可供选择,你可以向下面这样使用 Device::loadDescription() 和 Service::loadSCPD() 加载描述代替代替使用描述文件。加载出错会使加载方法抛出异常。
- String DEVICE_DESCRIPTION =
- "<?xml version=¥"1.0¥" ?>¥n" +
- "<root xmlns=¥"urn:schemas-upnp-org:device-1-0¥">¥n" +
- . . . .
- "</root>";
- String SERVICE_DESCRIPTION[] =
- "<?xml version=¥"1.0¥"?>¥n" +
- "<scpd xmlns=¥"urn:schemas-upnp-org:service-1-0¥" >¥n" +
- . . . .
- “</scpd>”;
- try {
- Device upnpDev = new Device();
- boolean descSuccess = upnpDev.loadDescription(DEVICE_DESCRIPTION);
- Service upnpService = getService("urn:schemas-upnp-org:service:****:1");
- boolean scpdSuccess = upnpService.loadSCPD(SERVICE_DESCRIPTION[);
- }
- catch (InvalidDescriptionException e){
- String errMsg = e.getMessage();
- System.out.println(“InvalidDescriptionException = ” + errMsg);
- }
活动的根设备拥有一些服务线程(这里写 processes),在控制点发向设备发送请求的时候会自动回应。例如,根设备有一个 HTTP 服务,当控制点使用 GET 请求希望获得描述文件时该服务会回应一个描述文件。在设备启动时它支自动为 HTTP 服务寻找一个有效的端口。
根设备使用以下默认的参数创建,你可以在根设备创建之前使用以下方法改变这些参数。
Parameter Default Function
1 HTTP port 4004 setHTTPPort()
2 Description URI /description.xml setDescriptionURI()
3 Lease time 1800 setLeaseTime()
3.4 通知
当设备通过 Device::start() 启动并加入 UPnP™ 网络时会自动发出一个带有 ssdp::alive 标头的通知消息。当设备通过 Device::stop() 停止运行,
一个带有ssdp::byebye 标头的通知自动发送。你也可以使用方法 Device::announce() 和 Device::byebye() 发送这些通知。
当一个控制点向 UPnP™ 网络发送 M-SEARCH 请求时,活动的设备会在规定时间内自动回应。
3.5 内嵌设备
一个设备可能拥有一些内嵌设备。使用 Device::getDeviceList() 可以得到内嵌设备的列表。下面的例子输出所有内嵌设备的友好名称。
- public void printDevice(Device dev)
- {
- String devName = dev.getFriendlyName();
- System.out.println(devName);
- DeviceList childDevList = dev.getDeviceList();
- int nChildDevs = childDevList.size();
- for (int n =0; n <nChildDevs; n ++) {
- Device childDev = childDevList.getDevice(n);
- printDevice(childDev);
- }
- }
- ......
- Dev rootDev = ....;
- ……
- DeviceList childDevList = rootDev.getDeviceList();
- int childDevs = childDevList.size();
- for (int n=0; n< childDevs; n++) {
- Device childDev = rootDevList.getDevice(n);
- printDevice(childDev);
- }
你可以通过使用 Device::getDevice() 并传入一个 friendly name 或者 UDN 找到内嵌设备。下面以例子通过 friendly name 找到一个内嵌设备。
- Device homeServerDev ....
- Device musicDev = homeServerDev.getDevice(“music”);
3.6 服务
使用 Device::getServiceList() 可以访问设备的所有内嵌服务。服务可以有许多动作和变量。使用 Service::getActionList() 得到这些动作,使用
Service::getServiceStateTable() 得到这些变量。下面的例子输出一个设备的所有动作和变量。
- Device dev ....
- ServiceList serviceList = dev.getServiceList();
- int serviceCnt = serviceList.size();
- for (int n=0; n<serviceCnt; n++) {
- Service service = serviceList.getService(n);
- ActionList actionList = service.getActionList();
- int actionCnt = actionList.size();
- for (int i=0; i<actionCnt; i++) {
- Action action = actionList.getAction(i);
- System.out.println(“action [“ + i + “] = “ + action.getName());
- }
- ServiceStateTable stateTable = service. getServiceStateTable ();
- int varCnt = stateTable.size();
- for (int i=0; i<actionCnt; i++) {
- StateVariable stateVar = stateTable.getServiceStateVariable(i);
- System.out.println(“stateVar [“ + i + “] = “ + stateVar.getName());
- }
- }
你可以使用 Device::getService() 并以 service ID 为参数找到服务,同样你可以通过名字得到服务中的动作和状态变量。使用 Device::getAction() 或 Service::getAction() 找到动作,Device::getStateriable() 或 Service::getStateVariable() 去找到状态变量。下面的例子通过名字找到一个服务,一个动作和一个状态变量。
- Device clockDev ....
- Service timerSev = clockDev.getService(“timer”);
- Action getTimeAct = clockDev.getAction(“GetTime”);
- StateVariable timeStat = clockDev.getStateVariable(“time”);
3.7 控制
想要接受来自控制点的动作控制,设备需要实现 ActionListener 接口。这个监听接口必须实现拥有动作及参数列表为参数的 acctionControlReceived() 方法。输入参数有从控制点传来的值,如果有请求有效就设置回应值作为输出参数并返回 true。如果请求无效则返回 false。当返回值是 false 或设备没有实现该接口时会向控制点返回 UPnPError 作为结果。UPnPError 的默认值是 INVALID_ACTION,但是可以使用 Action::setSetStatus() 来返回其它 UPnP 错误。
想要接受来自控制点的查询控制,设备需要实现 QueryListener 接口。这个监听接口必须实现含有服务变量为参数的 queryControlReceived() 方法。请求有效时返回 true,其它情况返回 false。当返回值为 false 或设备没有实现这个接口时 UPnPError 反馈会自动被返回给控制点。
UPnPError 的默认值是 INVALID_ACTION,可以使用 ServiceVariable::setSetStatus() 返回其它 UPnP 错误
下面的例子是个时钟设备。这个设备处理两个动作请求和一个查询请求。
- public class ClockDevice extends Device implements ActionListener, QueryListener {
- public ClockDevice() {
- super (“/clock/www/description.xml”);
- Action setTimeAction = getAction(“SetTime”);
- setTimeAction.setActionListener(this);
- Action getTimeAction = getAction(“GetTime”);
- getTimeAction.setActionListener(this);
- StateVariable stateVar = getStateVariable(“Timer”);
- stateVar.setQueryListener(this);
- }
- public boolean actionControlRecieved(Action action) {
- ArgumentList argList = action.getArgumentList();
- String actionName = action.getName();
- if (actionName.equals("SetTime") == true) {
- Argument inTime = argList.getArgument(“time”);
- String timeValue = inTime.getValue();
- If (timeValue == null || timeValue.length() <= 0)
- return false;
- ……..
- Argument outResult = argList.getArgument(“result”);
- arg.setValue(“TRUE”);
- return true;
- }
- else if (actionName.equals(“GetTime”) == true) {
- String currTimeStr = ….. Argument currTimeArg = argList.getArgument(“currTime”);
- currTimeArg.setValue(currTimeStrs);
- return true;
- }
- action.setStatus(UPnP::INVALID_ACTION, “…..”);
- return false;
- }
- public bool queryControlReceived(StateVariable stateVar) {
- if (varName.equals(“Time”) == true) {
- String currTimeStr = ….;
- stateVar.setValue(currTimeStr);
- return true;
- }
- stateVar.setStatus(UPnP::INVALID_VAR, “…..”);
- return false;
- }
- }
使用 Device::setActionListener() 或 Service::setActionListener() 给设备或服务添加针对控制点动作控制的监听。使用 Device::setQueryListener() 或 Service::setQueryListener() 给设备或服务添加针对控制点查询控制的监听。下面的例子设置了一个监听用于设备的所有控制。
- class ClockDevice : public Device, public ActionListener, public QueryListener
- {
- public:
- ClockDevice() : Device(“/clock/www/description.xml”) {
- setActionListner(this);
- setQueryListener (this);
- }
- bool actionControlRecieved(Action *action) { ……. }
- bool queryControlReceived(StateVariable *stateVar) { ……. }
- }
3.8 事件
控制点也许会订阅设备的一些事件。你不需要管理控制点的订阅,因为设备会自动管理订阅事件。例如,当一个控制点发送订阅消息给设备时时务会自动添加该控制点进入订阅者列表,当控制点发送一个取消订阅的消息时设备会自动将其从订阅者列表中删除。
当你希望向订阅者发送状态时可以使用 ServiceStateVariable::setValue() 方法,这样当状态变量更新的时候设备会自动向订阅者发送事件通知。下面的例子更新了一个状态变量,然后状态变量的改变自动发布到订阅者中。
- Device clockDevice = ....
- StateVariable timeVar = clockDevice.getStateVariable("Time");
- String timeStr = .....
- timeVar.setValue(timeStr);
4 控制点
4.1 类概述
以下静态结构图显示了创建一个 UPnP™ 控制点相关的类。控制点在 UPnP™ 中拥有一些根设备。
4.2 初始化
为了创建一个 UPnP™ 控制点需要创建一个 ControlPoint 类的实例。使用 ControlPoint::start() 去启动控制点。当控制点启动后它会向 UPnP™ 网络多播一个发现消息。
- import org.cybergarage.upnp.*;
- import org.cybergarage.upnp.device.*;
- ……
- ControlPoint ctrlPoint = new ControlPoint();
- ……
- ctrlPoint.start();
活动的控制点拥有一些服务线程(server processes),会自动回复一些 UPnP™ 发送过来的消息。例如,控制点有一个 SSDP 服务用来处理 SEARCH 回应,当控制点启动时会自动找一个合适的端口用于 SSDP 服务。控制点以下面的默认参数启动。
Parameter Default Methods
1 HTTP port 39500 setHTTPPort()
2 SSDP port 39400 setSSDPPort()
3 Subscription URI /eventSub setEventSubURI()
4 Search Response 3 setSerchMx()
4.3 通知
控制点接收来自 UPnP™ 网络中的设备的通知事件,并据些自动增加或删除设备。同样过期的设备也自动同设备列表中移除。你不需要管理通知事件,但是你可以实现 NotifyListener 接口来接收事件。下面的例子展示了接收通知消息。
- public class MyCtrlPoint extends ControlPoint implements NotifyListener {
- public MyCtrlPoint() {
- ........
- addNotifyListener(this);
- start();
- }
- public void deviceNotifyReceived(SSDPPacket ssdpPacket) {
- String uuid = ssdpPacket.getUSN();
- String target = ssdpPacket.getNT();
- String subType = ssdpPacket.getNTS();
- String location = ssdpPacket.getLocation();
- .......
- }
- }
只有在增加或删除设备的时候才会调用下面的接口, DeviceChangeListener。
- public class MyCtrlPoint extends ControlPoint implements DeviceChangeListener {
- public MyCtrlPoint() {
- ........
- addDeviceChangeListener (this);
- start();
- }
- public void deviceAdded (Device dev) {
- ……..
- }
- public void deviceRemoved(Device dev) {
- ……..
- }
- }
4.4 搜索
你可以调用 ControlPoint::search() 方法来更新设备。被发现的设备会自动添加到设备列表中,同时你可以实现 SearchResponseListener 接口来接收反馈。下面的例子展示了接收通知消息。
- public class MyCtrlPoint extends ControlPoint implements SearchResponseListener {
- public MyCtrlPoint() {
- ........
- addSearchResponseListener(this);
- start();
- ........
- search(“upnp:rootdevice”);
- }
- public void deviceSearchResponseReceived(SSDPPacket ssdpPacket) {
- String uuid = ssdpPacket.getUSN();
- String target = ssdpPacket.getST();
- String location = ssdpPacket.getLocation();
- ........
- }
- }
4.5 根设备
使用 ControlPoint::getDeviceList() 仅返回设备列表中的根设备。下面的例子展示了输出所有根设备的友好名字。
- ControlPoint ctrlPoint = new ControlPoint();
- ……
- ctrlPoint.start();
- ……
- DeviceList rootDevList = ctrlPoint.getDeviceList();
- int nRootDevs = rootDevList.size();
- for (int n=0; n<nRootDevs; n++) {
- Device dev = rootDevList.getDevice(n);
- String devName = dev.getFriendlyName();
- System.out.println(“[“ + n + “] = ” + devName);
- }
你可以通过根设备的友好名字,设备类型,或 UDN 结合 ControlPoint::getDevice() 找到根设备。下面的例子展示了通过友好名字找到根设备。
- ControlPoint ctrlPoint = new ControlPoint();
- ……
- ctrlPoint.start();
- ……
- Device homeServerDev = ctrlPoint.getDevice(“xxxx-home-server”);
4.6 控制
控制点可以发送动作或查询控制消息给被发现的设备。如果要发送动作控制消息,使用 Action::setArgumentValue() 和 Action::postControlAction()。你可以设置动作的值给所有输入参数,如果你设置了输出参数它会被自动忽略。下面的例子发送一个动作控制请求设置一个新的时候,然后显示返回的值。
- Device clockDev = ....
- Action setTimeAct = clockDev.getAction(“SetTime”);
- String newTime = ....
- setTimeAct.setArgumentValue(“time”, newTime); // setTimeAct.getArgument(“time”).setValue(newTime);
- if (setTimeAct.postControlAction() == true) {
- ArgumentList outArgList = setTimeAct.getOutputArgumentList();
- int nOutArgs = outArgList.size();
- for (int n=0; n<nOutArgs; n++) {
- Argument outArg = outArgList.getArgument(n);
- String name = outArg.getName();
- String value = outArg.getValue();
- ......
- }
- }
- else {
- UPnPStatus err = setTimeAct.getUPnPStatus();
- System.out.println("Error Code = " + err.getCode());
- System.out.println("Error Desc = " + err.getDescription());
- }
可以使用 StateVariable::postQueryControl() 发送一个查询控制消息。下面的例子发送一个查询控制消息,然后输出返回值。
- Device clockDev = ....
- StateVariable timeStateVar = clockDev.getStateVariable(“time”);
- if (timeStateVar.postQueryControl() == true) {
- String value = timeStateVar.getValue();
- ......
- }
- else {
- UPnPStatus err = timeStateVar.getUPnPStatus();
- System.out.println("Error Code = " + err.getCode());
- System.out.println("Error Desc = " + err.getDescription());
- }
使用 Argument::getRelatedStateVariable() 去获得参数相关联的 StatiVariable,使用 StateVariable::getAllowedValueRange() 或 getAllowedValueList() 去获得值允许的范围或列表。
- Device clockDev = ....
- Action timeAct = clockDev.getAction(“SetTime”);
- Argument timeArg = timeAct.getArgument(“time”);
- StataVariable stateVar = timeArg.getRelatedStateVariable();
- if (stateVar != null) {
- if (stateVar.hasAllowedValueRange() == true) {
- AllowedValueRange valRange = stateVar.getAllowedValueRange();
- ......
- }
- if (stateVar.hasAllowedValueList() == true) {
- AllowedValueList valList = stateVar.getAllowedValueList ();
- ......
- }
- }
4.7 事件
控制点可以订阅被发现的设备的事件,使用 ControlPoint::subscribe() 并实现 EventListener 接口来获得服务状态的变化。这个监听必段实现 eventNotifyReceived()。
- public MyControlPoint extends ControlPoint implements EventListener {
- public MyControlPoint() {
- .....
- addEventListener(this);
- }
- .....
- public void eventNotifyReceived(String uuid, long seq, String name, String value) {
- ....
- }
- }
如果服务接受了订阅, ControlPoint::subscribe() 将返回 true,这时你可以获得订阅 id 和 过期时间。
- ControlPoint ctrlPoint = .....
- Device clockDev = ctrlPoint.getDevice(“xxxx-clock”);
- Service timeService = clockDev.getService(“time:1”);
- boolean subRet = ctrlPoint.subscribe(timeService);
- if (subRet == true) {
- String sid = timeService.getSID();
- long timeout = timeService.getTimeout();
- }