620 likes | 878 Views
Universal Plug and Play. Dirk Grunwald University 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
E N D
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 presentationprepared 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
What’s Next • Walk through specific activities • Intersperse code snips from TV example
Root Device #1 Root Device #2 Service Service Device Device Service Service Icons for Protocol Example ControlPoint #1 ControlPoint #2 ControlPoint #3
ControlPoint #1 Root Device #1 Service Device Service Actions When New Device Is Added • Device Initialization • Announcement • Start periodic re-announcements • Service actions • Disconnect
Root Device #1 Service Device Service ControlPoint #2 Actions When New Controller Added • Control Initialization • Search for devices • Start periodic timeout checker • Issue actions • Disconnect
Control Flow in Device Thread started by Device application Main Initialize Announce Command Loop Periodic Announce HandleSubscriptionRequest CallbackEventHandler HandleGetVarRequest Thread started by UPNP system HandleActionRequest …
Device Initialization Specified port (NULL defaults to 80) Specified host IP address or NULL if (ret = UpnpInit(ip_address, port)) { printf("Error with UpnpInit -- %d\n", ret); UpnpFinish(); exit(1); } Cleanup routine – must be last API routine called
Device Registration URL With device description Routine to handle asynchronous events UpnpRegisterRootDevice(desc_doc_url, TvDeviceCallbackEventHandler, &device_handle,&device_handle))); … TvDeviceStateTableInit(desc_doc_url); … Void* passed to asynch handler routine OUT UpnpDevice_Handle used to identify device in API
Device State Table Initialization Each device is described by an XML document. The device needs to know where that document lives 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.
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 UpnpDevice_Handle used to identify device in API UpnpSendAdvertisement(device_handle, default_advr_expire; Each advertisement has a default timeout, expressed in an integral number of seonds
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); } In a real application, you’d actually do something useful here List of variable that changed Number of variables in change list
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 The support routines provide assistance for extracting the values from the DOM document } 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; } This greatly simplifies the parsing of complex actions. Note that TvDeviceSetChannel will UpnpNotify any subscribers of the changed value
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 Routine to handle asynchronous events UpnpInit(ip_address, port);UpnpRegisterClient(TvCtrlPointCallbackEventHandler, &ctrlpt_handle, &ctrlpt_handle) … OUT UpnpClient_Handle used to identify controller in API Void* passed to asynch handler routine
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