universal plug and play dirk grunwald university of colorado
TRANSCRIPT
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