Universal Plug and Play Dirk Grunwald University of Colorado

Preview:

Citation preview

Universal Plug and Play

Dirk GrunwaldUniversity of Colorado

Outline

What problem is UPNP trying to solve? What are the components in UPNP? Example Programming API

What is UPNP?

Architecture for pervasive peer-to-peer network connectivity of intelligent appliances

Allows appliances to present “controls” to computers

Peer-to-peer – does not use a central registry

Largely based on IETF standards

What is UPNP - Example

UPNP supports “devices” and “control points”. A device may have multiple “services”

Sample Device A TV registers itself as a “device” The TV exports a “Control Service” for

volume, power, channel It exports a “Picture” service for color, tint,

contrast and brightness

What is UPNP - Example

An intelligent TV Remote or PC may be a “controller”

Both devices and controllers may exist in an “unmanaged” network (no DNS or DHCP)

AutoIP is a mechanism to allocate an IP address

Multicast DNS is used to decenteralize name service

Important Actions in UPNP

Discover devices & services Get a description of the device Control discovered devices Be informed of events indicating

changes in the device Use a presentation prepared by the

device to present a control

Overview - Discovery

Based on SSDP (simple service discovery protocol – IETF draft)

When a device is added to the network, that device can advertise its services to control points

When a control point is added, it can search for existing devices

Discovery exchanges information about the device type, an identifier and a URL for more detailed information

Overview - Description

A control point retrieves a description using the URL provided during discovery

An XML document describes the device and services

Vendor specific info, manufacturer information (model name, version), serial number, URL’s for vendor specific web sites

Overview - Control

Control point can send actions to a device’s service

Send Control Message to Control URL Control messages encodded in XML using

the Simple Object Access Protocol (SOAP)

Overview - Eventing

A service description includes variables that model the service state

Publish / subscribe model Special first event provides initial values

Events are formatted in XML using GENA (General Event Notification Architecture)

Overview - Presentation

A device can offer a URL for presentation

Control point can retrieve the page, load into a browser and allow user to control or observe the device

UPNP only covers retrieving the page

Overview - Components

                                                      

             

What’s Next

Walk through specific activities

Intersperse code snips from TV example

Icons for Protocol Example

Service

Service

Root Device #1

Device

Service

Service

Root Device #2

Device

ControlPoint #1

ControlPoint #2

ControlPoint #3

Actions When New Device Is Added

Service

Service

Root Device #1

Device

ControlPoint #1

1. Device Initialization

2. Announcement

1. Start periodic re-announcements

3. Service actions

4. Disconnect

Actions When New Controller Added

Service

Service

Root Device #1

Device

ControlPoint #2

1. Control Initialization

2. Search for devices

1. Start periodic timeout checker

3. Issue actions

4. Disconnect

Coding the Device

Control Flow in Device

Main

Initialize Announce Command Loop

Periodic Announce

CallbackEventHandler

HandleSubscriptionRequest

HandleGetVarRequest

HandleActionRequest …Thread started by UPNP system

Thread started by Device application

Device Initialization

if (ret = UpnpInit(ip_address, port)) {

printf("Error with UpnpInit -- %d\n", ret);

UpnpFinish();

exit(1);

}

Specified host IP address or NULL

Specified port (NULL defaults to 80)

Cleanup routine – must be last API routine

called

Device Registration

UpnpRegisterRootDevice(desc_doc_url,TvDeviceCallbackEventHandler,&device_handle,&device_handle)));

TvDeviceStateTableInit(desc_doc_url);…

URL With device description

Routine to handle asynchronous

events

Void* passed to asynch handler routine OUT

UpnpDevice_Handle used to identify device

in API

Device State Table Initialization

if (UpnpDownloadXmlDoc(DescDocURL, &DescDoc)!= UPNP_E_SUCCESS) {

printf("Error Parsing %s\n", DescDocURL);

ret = UPNP_E_INVALID_DESC;

}

This returns a UpnpDownloadXmlDoc item, which

is a parsed DOM document. The application needs to free this data.

Each device is described by an XML document. The

device needs to know where that document lives

Device Description URL

<?xml version="1.0"?>

<root xmlns="urn:schemas-upnp-org:device-1-0">

<specVersion> <major>1</major> <minor>0</minor> </specVersion>

<URLBase>http://192.168.1.1</URLBase>

<device>

<deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>

<friendlyName>UPnP Television Emulator</friendlyName>

... model information...

<serviceList>

...exported services...

</serviceList>

<presentationURL> tvdevicepres.html </presentationURL>

</device>

</root>

Device Info

<deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>

<friendlyName>UPnP Television Emulator</friendlyName>

<manufacturer>TV Manufacturer Name</manufacturer>

<manufacturerURL>http://www.manufacturer.com

</manufacturerURL>

<modelDescription>

UPnP Television Device Emulator 1.0

</modelDescription>

<modelName>TVEmulator</modelName>

<modelNumber>1.0</modelNumber>

<modelURL>http://www.manufacturer.com/TVEmulator/</modelURL>

<serialNumber>123456789001</serialNumber>

<UDN>uuid:Upnp-TVEmulator-1_0-1234567890001</UDN>

<UPC>123456789</UPC>

Device Service List

<serviceList>

<service>

<serviceId> urn:upnp-org:serviceId:tvcontrol1

</serviceId>

…Remainder of TV Controller Device Specification…

</service>

<service>

<serviceType>

urn:schemas-upnp-org:service:tvpicture:1

</serviceType>

…Remainder of TV Controller Device Specification…

</service>

</serviceList>

Device Service Description

<service>

<serviceType>urn:schemas-upnp-org:service:tvcontrol:1

</serviceType>

<serviceId> urn:upnp-org:serviceId:tvcontrol1 </serviceId>

<controlURL>http://192.168.1.1:5431/upnp/control/tvcontrol1

</controlURL>

<eventSubURL>http://192.168.1.1:5431/upnp/event/tvcontrol1

</eventSubURL>

<SCPDURL>http://192.168.1.1/tvcontrolSCPD.xml

</SCPDURL>

</service>

Address specified by application

“Service Control Protocol Definition”What controls / events are available?

Device SCPD

?xml version="1.0"?>

<scpd xmlns="urn:schemas-upnp-org:service-1-0">

<serviceStateTable>

<stateVariable> … </stateVariable>

… More state variables …

</serviceStateTable>

<actionList><action>

<name>PowerOn</name></action>

… More action items …

</actionList>

</scpd>

State Variables Specify Type, Range and Initial Value

<stateVariable>

<name> Channel </name>

<dataType> i4 </dataType>

<allowedValueRange>

<minimum> 1 </minimum>

<maximum> 100 </maximum>

<step> 1 </step>

</allowedValueRange>

<defaultValue> 1 </defaultValue>

</stateVariable>

Actions Specify Arguments, Values

<action>

<name>SetChannel</name>

<argumentList>

<argument>

<name>NewChannel</name>

<relatedStateVariable>Channel

</relatedStateVariable>

<direction>in

</direction>

</argument>

</argumentList>

</action>

Device state represented by strings in this sample application

* Global arrays for storing Tv Control Service

variable names, values, and defaults */

char *tvc_varname[] = {"Power","Channel","Volume"};

char tvc_varval[3][5];

char *tvc_varval_def[] = {"0", "1", "5"};

/* Global arrays for storing Tv Picture Service

variable names, values, and defaults */

char *tvp_varname[] = {"Color","Tint","Contrast","Brightness"};

char tvp_varval[4][5];

char *tvp_varval_def[] = {"5","5","5","5"};

Filled in with default value later

Supplemental RoutinesSimplify Document Access

SampleUtil_FindAndParseService(DescDoc,TvControlServiceType,&servid_ctrl, &evnturl_ctrl,&ctrlurl_ctrl);

udn = SampleUtil_GetFirstDocumentItem(DescDoc, "UDN");

strcpy(tvcontrol_service.UDN, udn);

strcpy(tvcontrol_service.ServiceId, servid_ctrl);

strcpy(tvcontrol_service.ServiceType, TvControlServiceType);

tvcontrol_service.VariableCount=3;

for (i=0; i<tvcontrol_service.VariableCount; i++) {

tvcontrol_service.VariableName[i] = tvc_varname[i];

tvcontrol_service.VariableStrVal[i] = tvc_varval[i];

strcpy(tvcontrol_service.VariableStrVal[i], tvc_varval_def[i]);

}

Variables maintained as strings in example

Coding the DeviceWhere were we?

Device has initialized the UPNP system Device has registered the root device,

making it available to receive messages

Device has read the device description and initialized state variables

Probably should have used the values in the SCPD

Device Announcement

UpnpSendAdvertisement(device_handle, default_advr_expire;

Each advertisement has a default timeout, expressed in an integral

number of seonds

UpnpDevice_Handle used to identify device

in API

Details about SSDP / Announcment

Broadcasts to 239.255.255.250:1900 “Site local” multicast address

Messages delivered usingHTTPMU and HTTPU

Thread is spawned for Device (Re)Announcement

code = pthread_create( &advr_thread, NULL, TvCtrlPointAdvrLoop, NULL );

void* TvCtrlPointAdvrLoop(void *args)

{

int ret;

while (1) {

sleep(default_advr_interval);

if (ret = UpnpSendAdvertisement(device_handle, default_advr_expire))

printf("Error sending updated advert : %d\n", ret);

printf("Updated Advertisements Sent\n");

}

}

The application starts a thread to periodically renew

advertisements. This could also be done with timers &

signals or other mechanisms.

The Main thread now enters a command loop

void TvDeviceCommandLoop(){ int stoploop=0; char cmdline[100]; char cmd[100]; int i;

while (!stoploop) { sprintf(cmdline, ""); sprintf(cmd, "");

printf("\n>> ");

// Get a command line fgets(cmdline, 100, stdin);

sscanf(cmdline, "%s", &cmd);

if (strcasecmp(cmd, "exit") == 0) { printf("Shutting down...\n"); UpnpUnRegisterRootDevice(device_handle); UpnpFinish(); exit(0); } else { printf("\n Unknown command: %s\n\n", cmd); printf(" Valid Commands:\n"); printf(" Exit\n\n"); }

}}

The UPNP thread periodically called the “callback handler”

int TvDeviceCallbackEventHandler(Upnp_EventType EventType, void *Event, void *Cookie){ struct Upnp_Event * event;

/* Print a summary of the event received */ SampleUtil_PrintEvent(EventType, Event);

switch ( EventType) {

case UPNP_EVENT_SUBSCRIPTION_REQUEST: TvDeviceHandleSubscriptionRequest( (struct Upnp_Subscription_Request *) Event); break;

case UPNP_CONTROL_GET_VAR_REQUEST: TvDeviceHandleGetVarRequest( (struct Upnp_State_Var_Request *) Event); break;

case UPNP_CONTROL_ACTION_REQUEST: TvDeviceHandleActionRequest( (struct Upnp_Action_Request *) Event); break;…

Accepting a subscription sends out current value of state variable

int TvDeviceHandleSubscriptionRequest(struct Upnp_Subscription_Request *sr_event){

pthread_mutex_lock(&TVDevMutex);

if ((strcmp(sr_event->UDN,tvcontrol_service.UDN) == 0) && (strcmp(sr_event->ServiceId,

tvcontrol_service.ServiceId) == 0)) { /* This is a request for the TvDevice Control Service */ UpnpAcceptSubscription( device_handle sr_event->UDN, sr_event->ServiceId, (char **)tvcontrol_service.VariableName, (char **)tvcontrol_service.VariableStrVal, tvcontrol_service.VariableCount, sr_event->Sid); } else if () .. {

.. }

pthread_mutex_unlock(&TVDevMutex);

return(1);}

This identifies the controller being

registered

State changing routines notify subscribed listeners

int TvDeviceSetChannel(int channel){ if (channel < 1 || channel > 100) { printf("error: can't change to channel %d\n", channel); return(0); }

/* Vendor-specific code to set the channel goes here */

pthread_mutex_lock(&TVDevMutex);

sprintf(tvcontrol_service.VariableStrVal[1], "%d", channel);

/* Send updated channel setting notification to subscribed control points */ UpnpNotify(device_handle, tvcontrol_service.UDN, tvcontrol_service.ServiceId, (char **)&tvcontrol_service.VariableName[1], (char **)&tvcontrol_service.VariableStrVal[1], 1);

pthread_mutex_unlock(&TVDevMutex);

return(1);}

Number of variables in change list

List of variable that changed

In a real application, you’d

actually do something useful

here

Asynchronous State Changes

This example only contains synchronous event changes (caused by an external controller)

The device may change by itself (e.g. GPS)

Just call UpnpNotify in device change routine

The device copys values for GetVarRequest

int TvDeviceHandleGetVarRequest(struct Upnp_State_Var_Request *cgv_event)

{ int i; int getvar_succeeded = 0;

cgv_event->CurrentVal = NULL;

pthread_mutex_lock(&TVDevMutex);

if ((strcmp(cgv_event->DevUDN,tvcontrol_service.UDN)==0) && (strcmp(cgv_event->ServiceID,

tvcontrol_service.ServiceId)==0)) { /* Request for variable in the TvDevice Control Service */ for (i=0; i< tvcontrol_service.VariableCount; i++) { if (strcmp(cgv_event->StateVarName,

tvcontrol_service.VariableName[i])==0) { getvar_succeeded = 1; cgv_event->CurrentVal = (Upnp_DOMString)

malloc(sizeof(tvcontrol_service.VariableStrVal[i])); strcpy(cgv_event->CurrentVal,

tvcontrol_service.VariableStrVal[i]); break; } …

Check for correct device

Copy value to event datatype

The application is responsible for decoding and performing actions

int TvDeviceHandleActionRequest(struct Upnp_Action_Request *ca_event){ Upnp_DOMString bufReq; char result_str[500]; char service_type[500]; char *value=NULL;

/* Defaults if action not found */ int action_succeeded = -1; int err=401;

ca_event->ErrCode = 0; ca_event->ActionResult = NULL;

if ((strcmp(ca_event->DevUDN,tvcontrol_service.UDN)==0) && (strcmp(ca_event->ServiceID,tvcontrol_service.ServiceId)==0)) {

/* Request for action in the TvDevice Control Service */

strcpy(service_type, tvcontrol_service.ServiceType);

if (strcmp(ca_event->ActionName, "PowerOn") == 0) { action_succeeded = TvDevicePowerOn(); } else if (strcmp(ca_event->ActionName, "PowerOff") == 0) { action_succeeded = TvDevicePowerOff(); } else

Check for correct device

The application is responsible for decoding and performing actions

} else if (strcmp(ca_event->ActionName, "SetChannel") == 0) {

if (value = SampleUtil_GetFirstDocumentItem( ca_event->ActionRequest, "Channel")) {

action_succeeded = TvDeviceSetChannel(atoi(value)); } else { // invalid args error err = 402; action_succeeded = 0; }

The support routines provide assistance for extracting the

values from the DOM document

This greatly simplifies the parsing of complex actions. Note that

TvDeviceSetChannel will UpnpNotify any subscribers of the changed value

Coding the Client

High Level View of Client

Initialize UPNP system Register Ask devices to advertise themselves Subscribe to any devices we find by

advertisement Accept user commands to examine

variables of devices or cause actions

Client Registration

UpnpInit(ip_address, port);UpnpRegisterClient(TvCtrlPointCallbackEventHandler,

&ctrlpt_handle, &ctrlpt_handle)…

Routine to handle asynchronous

events

Void* passed to asynch handler routine

OUT UpnpClient_Handle

used to identify controller in API

Client Requests Notification

/* Search for all devices of type tvdevice version 1,waiting for up to 5 seconds for the response */

/* ret = UpnpSearchAsync(ctrlpt_handle, 5, "urn:schemas-upnp-org:device:tvdevice:1",

NULL); */

/* Search for all services of type tvcontrol version 1,waiting for up to 5 seconds for the response */

/* ret = UpnpSearchAsync(ctrlpt_handle, 5, “urn:schemas-upnp-org:service:tvcontrol:1”, NULL); */

/* Search for all root devices,waiting for up to 5 seconds for the response */

ret = UpnpSearchAsync(ctrlpt_handle, 5, "upnp:rootdevice", NULL);

If the device is found, the callback routine will be called

Notifications Invoke Callback

int TvCtrlPointCallbackEventHandler(Upnp_EventType EventType,

void *Event,

void *Cookie)

{

struct Upnp_Event * event;

int ret;

SampleUtil_PrintEvent(EventType, Event);

switch ( EventType) {

/* SSDP Stuff */

case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:

case UPNP_DISCOVERY_SEARCH_RESULT:

{ if ((ret=UpnpDownloadXmlDoc(d_event->Location, &DescDoc))

!= UPNP_E_SUCCESS) {

printf("Error obtaining device description from %s -- error = %d\n",d_event->Location, ret );

} else { TvCtrlPointAddDevice(DescDoc, d_event->Location, d_event->Expires); }

Controller downloads device description from specified URL

Keeps private device

list

This client uses asingle callback routine

int TvCtrlPointCallbackEventHandler(Upnp_EventType EventType, void *Event, void *Cookie)

{ switch ( EventType) {

/* SSDP Stuff */case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:case UPNP_DISCOVERY_SEARCH_RESULT:case UPNP_DISCOVERY_SEARCH_TIMEOUT:case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:

/* SOAP Stuff */case UPNP_CONTROL_ACTION_COMPLETE:case UPNP_CONTROL_GET_VAR_COMPLETE:

/* GENA Stuff */case UPNP_EVENT_RECEIVED:case UPNP_EVENT_SUBSCRIBE_COMPLETE:case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:case UPNP_EVENT_RENEWAL_COMPLETE:

/* ignore these cases, since this is not a device */case UPNP_EVENT_SUBSCRIPTION_REQUEST:case UPNP_CONTROL_GET_VAR_REQUEST:case UPNP_CONTROL_ACTION_REQUEST:

}

Client starts thread to keep subscriptions fresh

pthread_create( &timer_thread, NULL, TvCtrlPointTimerLoop, NULL );

..

void* TvCtrlPointTimerLoop(void *args)

{

int incr = 30; // how often to verify the timeouts

while (1) {

sleep(incr);

TvCtrlPointVerifyTimeouts(incr);

}

}

Subscribes to all services that we know about, possibly renewing subscriptions or asking for new subscriptions.

If a previously announced device expires, it subscribes again..

if (strcmp(curdevnode->device.TvControl.SID, "") != 0) {

/* We have a valid TvControl SID, so lets check the

subscription timeout */

curdevnode->device.TvControl.SubsTimeOut -= incr;

if (curdevnode->device.TvControl.SubsTimeOut <= 0) {

/* The subscription has expired,so delete it and request a new one */

strcpy(curdevnode->device.TvControl.SID, "");

ret = UpnpSubscribeAsync(ctrlpt_handle,

curdevnode->device.TvControl.EventURL,

default_timeout,

TvCtrlPointCallbackEventHandler, NULL);

}

Async means that callback will be called later

But if the device is only about to expire, it renews subscription

} else if (curdevnode->device.TvControl.SubsTimeOut < 2*incr) {

/* The subscription is about to expire, so try to renew it */ret = UpnpRenewSubscriptionAsync(ctrlpt_handle,

default_timeout,curdevnode->device.TvControl.SID,

TvCtrlPointCallbackEventHandler, NULL);

This routine may eventually be called with a

RENEWAL_EVENT_COMPLETE

event

Mean while, the main thread is accepting user commands

void TvCtrlPointCommandLoop()

{

while (!stoploop) {

..

fgets(cmdline, 100, stdin);

...

if (cmdfound) {

switch(cmdnum) {

case PRTHELP:

TvCtrlPointPrintHelp();

break;

case POWON:

if (arg1 == arg_val_err)

invalid_args = 1;

else

TvCtrlPointSendControlAction(arg1, "PowerOn");

break;

..and sending actions to the device…

int TvCtrlPointSendControlAction(int devnum, char *actionname)

{

struct TvDeviceNode *devnode;

char ActionXml[250];

Upnp_Document actionNode=NULL;

int ret=0;

pthread_mutex_lock(&DeviceListMutex);

if (!TvCtrlPointGetDevice(devnum, &devnode)) {

ret = 0;;

} else {

sprintf(ActionXml, "<u:%s xmlns:u=\"%s\"></u:%s>",

actionname, TvControlServiceType, actionname);

actionNode = UpnpParse_Buffer( ActionXml);

ret = UpnpSendActionAsync( ctrlpt_handle,

devnode->device.TvControl.ControlURL, "tvcontrol:1",

devnode->device.DevUUID, actionNode,

TvCtrlPointCallbackEventHandler, NULL);

Format action requestion and turn into a DOM

document for UpnpSendAction

..or requesting values from the device.

int TvCtrlPointControlGetVar(int devnum, char* varname)

{

struct TvDeviceNode *devnode;

int ret=0;

pthread_mutex_lock(&DeviceListMutex);

if (!TvCtrlPointGetDevice(devnum, &devnode)) {

ret = 0;;

} else {

ret = UpnpGetServiceVarStatusAsync( ctrlpt_handle,

devnode->device.TvControl.ControlURL,

varname,

TvCtrlPointCallbackEventHandler,

NULL);

The value is eventually returned

to this callback routine

The actions are acknowledged to the callback routine

/* SOAP Stuff */ case UPNP_CONTROL_ACTION_COMPLETE: { struct Upnp_Action_Complete *a_event = (struct Upnp_Action_Complete * ) Event;

if (a_event->ErrCode != UPNP_E_SUCCESS) printf("Error in Action Complete Callback -- %d\n", a_event->ErrCode);

/* No need for any processing here, just print out results. Service state table updates are handled by events. */ } break;

case UPNP_CONTROL_GET_VAR_COMPLETE: { struct Upnp_State_Var_Complete *sv_event = (struct Upnp_State_Var_Complete * ) Event;

if (sv_event->ErrCode != UPNP_E_SUCCESS) printf("Error in Get Var Complete Callback -- %d\n", sv_event->ErrCode);

} break;

Actual variable values are passed by events

case UPNP_EVENT_RECEIVED: { struct Upnp_Event *e_event =

(struct Upnp_Event * ) Event;

TvCtrlPointHandleEvent(e_event->Sid, e_event->EventKey, e_event->ChangedVariables);

} break;

That are passed to service specific decoders..

void TvCtrlPointHandleEvent(SID sid, int evntkey, Upnp_Document changes){ struct TvDeviceNode *tmpdevnode; pthread_mutex_lock(&DeviceListMutex); tmpdevnode = GlobalDeviceList; while (tmpdevnode) { if (strcmp(tmpdevnode->device.TvControl.SID,sid) == 0) { printf("Received TvControl Event: %d for SID %s\n", evntkey, sid); TvControlStateUpdate(changes,

(char **)&tmpdevnode->device.TvControl.VariableStrVal); break; } else if (strcmp(tmpdevnode->device.TvPicture.SID,sid) == 0) { printf("Received TvPicture Event: %d for SID %s\n", evntkey, sid); TvPictureStateUpdate(changes,

(char **)&tmpdevnode->device.TvPicture.VariableStrVal); break; } tmpdevnode = tmpdevnode->next; } pthread_mutex_unlock(&DeviceListMutex);}

ReviewOf Concepts

and Coding Style

Review

Discovery / description / control / events / presentation

Discovery is by multicast Control & Events are unicast

support routines provide encoding / decoding

Services and controllers are written using an asynchronous, multithreaded application model

System Requirements

Web server to transmit device & and control descriptions

Does not need to be on device! HTTP / HTTPU / HTTPMU parser

Built into library DOM / XML parser

Combination of CDOM and sample code handles this

Unanswered Questions

What about security? Basic UPNP library abstracts away

communication layer Authentication / permission models?

What about “far away access”? If you know the service exists on a device,

you can simply query it using HTTPU But, that’s not supported in current UPNP

library

Recommended