Creating a UPnP interface

UPnP is based on HTTP and extensions of HTTP onto UDP. As mentioned earlier, UPnP is originally thought of to work in protected networks where devices communicate with each other, unhindered by firewalls. Since we also aim for our device to be connected to the Internet, we need to create two different HTTP interfaces: one for the Internet that is protected and one for the local area network that is unprotected. The protected interface will work on the standard HTTP port 80, and the UPnP server will, in our case, work on port 8080. This port number is not fixed by any standard; it can be any free port. Port numbers below 1024 will require superuser privileges when you run the application. Since notification of presence is done using multicast UDP, everybody will be aware of the IP and port number to use when communicating with the device. We begin by defining our HTTP server to use for UPnP:

private static HttpServer upnpServer;

We then create our second HTTP server like we did for our first:

upnpServer = new HttpServer(8080, 10, true, true, 1);
Log.Information("UPnP Server receiving requests on port " + upnpServer.Port.ToString ());

We also publish the web interface through the UPnP interface, but this time with the unprotected version:

upnpServer.Register("/", HttpGetRootUnprotected, 
  HttpPostRoot, false);

When the application closes, we need to dispose of this object too to make sure any threads are closed. Otherwise, the application will not close properly:

upnpServer.Dispose();

Registering UPnP resources

Next, we publish our device and service description documents. These are built into the executable file as embedded resources. Each embedded resource has its own unique resource name, which is the namespace of the project. In our case, this is Camera and is followed by a period (.); this is in turn followed by the path of the embedded file where the directory separator (/ or ) is replaced by dots. The service description document requires no changes, so we return it as is. But the device description document requires that we replace placeholders with actual values, so we register a method with the resource to be able to modify the document when requested. This is done as follows:

upnpServer.Register("/CameraDevice.xml", 
  HttpGetCameraDevice, false);
upnpServer.Register(new HttpServerEmbeddedResource 
  ("/StillImageService.xml", 
  "Camera.UPnP.StillImageService.xml"));

Tip

To avoid problems for potential receivers—if you're handling text files as binary files, as is the case with our service file where the content type is set manually (from the file extension in our case)—make sure you save the corresponding files using a text editor that lets you save them without a byte order mark. Alternatively, you can provide a preamble to avoid problems with conflicting encodings. Refer to Appendix L, Text Encoding on the Web, for a discussion on the different types of encodings for text content on the Web.

We register the path to our icons in a similar manner. Here, the 16 x 16 icon is registered, and the 24 x 24, 32 x 32, 48 x 48, 64 x 64, 128 x 128, and 256 x 256 are registered in a similar manner:

  ("/Icon/16x16.png", "Camera.UPnP.16x16.png"));

Tip

In .NET on Windows, filenames that start with a digit, as is the case with the filenames of our icons, get resource names prefixed by underscores (_). This is not the case when you run an application on MONO. If you're running the application on Windows, the resource name for the first icon would be, for instance, Camera.UPnP._16x16.png.

Replacing placeholders

Our device description document (DDD) contains three placeholders that need to be replaced with actual values: {IP} with the IP address that others can use to reach the device, {PORT} with the port number that we will use while communicating with the device, and finally, {UDN} that has to be replaced by the unique device name generated for our device instance. We start by defining our method as follows:

private static void HttpGetCameraDevice (HttpServerResponse resp, HttpServerRequest req)
{
  networkLed.High();
  try
  {

We then load the XML from our embedded resource into a string that we can modify:

    string Xml;
    byte[] Data;
    int c;

    using (Stream stream = Assembly.GetExecutingAssembly ().
  GetManifestResourceStream ("Camera.UPnP.CameraDevice.xml"))
    {
      c = (int)stream.Length;
      Data = new byte[c];
      stream.Position = 0;
      stream.Read(Data, 0, c);
      Xml = TextDecoder.DecodeString (Data, System.Text.Encoding.UTF8);
    }

We then need to find the IP address of the device to return. This IP address would normally depend on what network interface the request is made on, and the underlying protocol that is being used (for instance, IPv4 or IPv6). Since we are running our application on Raspberry Pi, we assume there is only one network interface, and it'll be great if we find an IP address that matches the same protocol as that of the request:

string HostName = System.Net.Dns.GetHostName ();
System.Net.IPHostEntry HostEntry = System.Net.Dns.GetHostEntry (HostName);

foreach(System.Net.IPAddress Address in HostEntry.AddressList)
{
  if(Address.AddressFamily == req.ClientEndPoint.AddressFamily)
  {
    Xml = Xml.Replace("{IP}", Address.ToString());
    break;
  }
}

Setting up the port number and unique device name is easier. The first is chosen by us; the second is generated during the first execution of the application and is available in our defaultSettings object:

Xml = Xml.Replace("{PORT}", upnpServer.Port.ToString ());
Xml = Xml.Replace("{UDN}", defaultSettings.UDN);

We then returned the finished XML as follows:

    resp.ContentType = "text/xml";
    resp.Encoding = System.Text.Encoding.UTF8;
    resp.ReturnCode = HttpStatusCode.Successful_OK;
    resp.Write(Xml);
  }
  finally
  {
    networkLed.Low();
  }
}

Adding support for SSDP

To add support for SSDP, we will also need to add an SSDP client from the Clayster.Library.Internet.SSDP library to the main class:

private static SsdpClient ssdpClient;

We then instantiate it after the creation of the UPnP HTTP server:

ssdpClient = new SsdpClient(upnpServer, 10, true, true, false, false, false, 30);

The first parameter connects the SSDP client with our newly created HTTP server, dedicated to UPnP. Any multicast messages sent will have a time-to-live (TTL) of 10 router hops. We will activate it for the IPv4 and IPv6 link-local UPnP multicast addresses (the first two true-valued arguments), but not for the IPv6 site-local, organization-local, or global multicast addresses (the following three false-valued arguments). Searching for new devices on the network will be performed every 30 seconds.

When the application closes, we need to dispose of this object too to make sure any threads are closed as well. Otherwise, the application will not close properly.

ssdpClient.Dispose();

We attach event handlers to the SSDP client. The OnNotify event is raised when searching is done and can be used to notify the network of the available interfaces on the device itself. OnDiscovery event is raised when an incoming search request has been received:

ssdpClient.OnNotify += OnSsdpNotify;
ssdpClient.OnDiscovery += OnSsdpDiscovery;

We will also need to use the random number generator defined for session management to generate random delay times:

private static Random gen = new Random ();

Notifying the network

You can notify the network of your presence and service capabilities. This is done by multicasting NOTIFY messages to the network. We can do this from the OnNotify event handler that is called when the SSDP client itself searches the network for devices. In our case, this is done every 30 seconds. We only have two interfaces to publish on the network, and we begin by publishing the interface for the root device:

private static void OnSsdpNotify(object Sender, SsdpNotifyEventArgs e)
  {
  e.SendNotification(DateTime.Now.AddMinutes (30), "/CameraDevice.xml", SsdpClient.UpnpRootDevice, "uuid:" + defaultSettings.UDN + "::upnp:rootdevice");

The first parameter determines the lifetime of the interface. This means clients do not need to fetch a new interface description during the next 30 minutes. We then point to our device description document resource and tell the network that the notification concerns a root device and the unique device name it has. We then publish the service interface:

e.SendNotification (DateTime.Now.AddMinutes (30), "/StillImageService.xml", "urn:schemas-upnp-" + "org:service:DigitalSecurityCameraStillImage:1", "uuid:" + defaultSettings.UDN + ":service:DigitalSecurityCameraStillImage:1");
}

In this case, we also state that the service file is valid for 30 minutes. We then point to its resource and tell the network that it concerns a DigitalSecurityCameraStillImage:1 service and the unique service name it has.

After these two notifications, recipients in the network will note of the presence and capabilities of our camera, even if they do not actively search for it.

Responding to searches

When a search is performed, it is sent to the network that uses multicast addressing. Anybody listening to the corresponding multicast address will receive the search request. To avoid spam in a large network, with possible packet loss as a result, the search lets clients respond within a random amount of time, provided the response is sent before the given maximum response time elapses. Furthermore, the search can be restricted to certain device types or services types to avoid unwanted responses being sent. Responding to search requests is much like notifying the network about your interfaces, except that you must check whether the corresponding interface is desired and you send the response as a unicast message back to the one who requested it within the time period specified. And if you're sending multiple notifications, it's recommended that you spread them out and send them over the allotted time to avoid bursts.

We begin by analyzing the search request to see whether any of our interfaces are desired, how many and which ones:

private static void OnSsdpDiscovery(object Sender, SsdpDiscoveryEventArgs e)
{
  int i, c = 0;
  bool ReportDevice = false;
  bool ReportService = false;

  if (e.ReportInterface(SsdpClient.UpnpRootDevice) || e.ReportInterface("urn:clayster:device:learningIotCamera:1"))
  {
    ReportDevice = true;
    c++;
  }

  if (e.ReportInterface("urn:schemas-upnp-org:service:" + "DigitalSecurityCameraStillImage:1"))
  {
    ReportService = true;
    c++;
  }

We then create a random number of points in the response interval where we will return the responses:

double[] k = new double[c];
lock (lastAccessBySessionId)
{
  for (i = 0; i < c; i++)


  k [i] = gen.NextDouble ();
}

The random number generator is not thread-safe, so we need to make sure that access to it is done from only one thread at a time. Since we use the random number generator defined for session management, we perform the lock on lastAccessBySessionId, which is the same object that is locked when the session management generates a random number.

Since we always want the order of interfaces reported to be the same, we need to sort our set of random time points:

Array.Sort (k);
i = 0;

If our device description document is desired, we start a timer counting down to the first random time point, scaled for the allowed number of seconds to respond, and make sure that the timer elapses only once and reports it to our interface:

if(ReportDevice)
{
  System.Timers.Timer t = new System.Timers.Timer ( e.MaximumWaitTime * 1000 * k[i++] + 1);
  t.AutoReset = false;
  t.Elapsed += (o2, e2) =>
  {
    e.SendResponse (DateTime.Now.AddMinutes (30), "/CameraDevice.xml", SsdpClient.UpnpRootDevice, "uuid:" + defaultSettings.UDN + "::upnp:rootdevice");
  };
  t.Start ();
}

We do the same with our service interface:

  if (ReportService)
  {
    System.Timers.Timer t = new System.Timers.Timer ( e.MaximumWaitTime * 1000 * k[i++] + 1);
    t.AutoReset = false;
    t.Elapsed += (o2, e2) =>
    {
      e.SendResponse (DateTime.Now.AddMinutes (30), 
        "/StillImageService.xml",
        "urn:schemas-upnp-org:service:" +
        "DigitalSecurityCameraStillImage:1",
        "uuid:" + defaultSettings.UDN +
        ":service:" +
        "DigitalSecurityCameraStillImage:1");
    };
    t.Start ();
  }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.136.84