plug.play.js

A Network Service Messaging framework for use on top of the W3C Network Service Discovery specification

View the Project on GitHub richtr/plug.play.js

Plug.Play.js is a JavaScript API for communicating with Universal Plug and Play (UPnP) Services obtained via the W3C Network Service Discovery draft specification (4th October 2012 version).

Setup

This API requires a web browser that supports navigator.getNetworkServices.

Opera released a Labs build that provides support for this API. You can read more and download the browser builds at the Dev.Opera blog.

Download this library

You can download a ZIP or TAR.GZ file containing all the Plug.Play library code or you can clone this repo via Git as follows:

git clone git://github.com/richtr/plug.play.js.git

To get started, a UPnP service demo (example.html) is available and a test suite is also included for checking the validity of the API.

Work flow

Plug.Play is an API to make working with UPnP Services simple and easy.

The basic work flow for using this API is as follows:

  1. Obtain one or more NetworkService objects via navigator.getNetworkServices.
  2. Create a new Plug.UPnP object for each NetworkService object returned.
  3. Invoke UPnP Service Actions via the Plug.UPnP object's .action() API method.

Each step above is explained in more detail below.

Step 1: Obtain NetworkService objects

If you are running this API in a supported browser, as discussed in the Setup section above, you will be able to obtain a set of NetworkService objects as follows:

if(navigator.getNetworkServices) {
  navigator.getNetworkServices(
    'upnp:urn:schemas-upnp-org:service:RenderingControl:1',
    successCallback
  );
}

The successCallback function takes one argument, a NetworkServices object that acts like an array of NetworkService objects. When services are found in the current network that match the requested service type(s) the web browser will trigger execution of this function.

function successCallback( servicesManager ) {
  // Dump services to console
  for( var i = 0; i < servicesManager.length; i++ ) {
    console.log( "Service w/ type [" +
                    servicesManager[i].type  + "] available" );
  }
}

Step 2: Create a new Plug.UPnP object

Once we have a successCallback function defined, we can create new Plug.UPnP objects.

To create a new Plug.UPnP object we need pass in a single (UPnP-based) NetworkService object as the first argument.

var upnpService = new Plug.UPnP( servicesManager[i] );

Here's a full example, expanding and replacing the successCallback function we defined earler:

function successCallback( servicesManager ) {

  // Create a new UPnP object for each service returned
  var upnpServices = [];

  for( var i = 0; i < servicesManager.length; i++ ) {

    upnpServices.push(
      new Plug.UPnP( servicesManager[i] );
    );

    // doUPnPGetMute code provided in Step 3...
    doUPnPGetMute( upnpServices[ upnpServices.length - 1 ] );

  }

}

You can optionally, include a second argument to this constructor to override the default Plug.UPnP options:

new Plug.UPnP( servicesManager[i], { debug: true } );

The current list of values that can be provided in the options argument are as follows:

debug

Boolean. Whether to spit out more debug messages to the console. Useful when debugging errors in your web app.

Step 3: Invoke UPnP actions and process UPnP responses

The Plug.Play API is built to work on top of rsvp.js and therefore provides a Promises/A-based interaction model. As part of the Promises model this API supports method chaining and promise sequences.

Here is a simple example of querying the mute state of a UPnP RenderingControl:1 service on a created Plug.UPnP object called upnpService:

function doUPnPGetMute( upnpService ) {

  upnpService.action('GetMute', {
    InstanceId: 0,
    Channel: 'Master'
  })
  .then(function( response ) {

    console.log("Service is reporting MUTE=[" +
      (response.data.CurrentMute == "1" ? 'on' : 'off' +
        "]");

  })
  .then( null, function( error ) { // All errors will propagate here

    console.log( error.description );

  });

}

The Plug.Play library also comes with some convenience classes for particular service types. The demo provided above can also be written against the well-defined UPnPRenderingControl API also included in this repository. More service-specific wrappers will be added to this repository soon.

Advanced Usage

Type Checking & Conversion

You can enforce type checking and type conversion controls in this API according to the data types defined for UPnP in the UPnP Device Architecture specification.

The following code is equivalent to the doUPnPGetMute() function defined above but this time we enforce the variable types and constrain inputs to a series of allowed value types for each input variable:

function doUPnPGetMute( upnpService ) {

  upnpService.action('GetMute', {
    InstanceId: {
      type: upnpService.types.ui4, // or just write 'ui4'
      value: 0
    },
    Channel: {
      type: upnpService.types.string, // or just write 'string'
      value: 'Master',
      allowedValueList:
        ['Master', 'LF', 'RF', 'CF', 'LFE', 'LS', 'RS',
            'LFC', 'RFC', 'SD', 'SL', 'SR', 'T', 'B']
    }
  })
  .then(function( response ) {

    console.log("Service is reporting MUTE=[" +
      (response.data.CurrentMute == "1" ? 'on' : 'off' +
        "]");

  })
  .then( null, function( error ) {

    console.log( "An error occurred: " + error.description );

  });

}

Furthermore, we can use the same structure to enforce variable type conversion on UPnP message response objects. This is useful in particular where we need or want to convert XML types to native JavaScript object types (such as booleans, numbers or dates).

To do this we supply a more structured argument consisting of two parts: request parameters and response parameters. We assign our type checking/conversion rules as required in this structure:

function doUPnPGetMute( upnpService ) {

  upnpService.action('GetMute', {
    request: {
      InstanceId: {
        type: upnpService.types.ui4,
        value: 0
      },
      Channel: {
        type: upnpService.types.string,
        value: 'Master',
        allowedValueList:
          ['Master', 'LF', 'RF', 'CF', 'LFE', 'LS', 'RS',
              'LFC', 'RFC', 'SD', 'SL', 'SR', 'T', 'B']
      }
    },
    response: {
      CurrentMute: {
        type: upnpService.types.boolean,
        value: false // default response value if
                     // none is provided (optional)
      }
    }
  })
  .then( function( response ) {

    // Note: we no longer need to check for '== "1"' below because
    // response.data.CurrentMute is now a native JS boolean as we
    // defined in our <actionParameters> above:

    console.log("Service is reporting MUTE=[" +
      (response.data.CurrentMute ? 'on' : 'off') +
        "]");

  }, function( error ) { // Handle any errors

    console.log( "An error occurred: " + error.description );

  });

}

Plug.Play API calls can be chained as follows:

function doUPnPGetThenSetThenGetMute( upnpService ) {

  upnpService.action('GetMute', {
    request: {
      InstanceId: 0,
      Channel: 'Master'
    },
    response: {
      CurrentMute: {
        type: upnpService.types.boolean
      }
    }
  })
  .then( function( response ) {

    console.log("Service is reporting MUTE=[" +
      (response.data.CurrentMute ? 'on' : 'off') +
        "]");

    return upnpService.action('SetMute', {
      request: {
        InstanceId: 0,
        Channel: 'Master',
        DesiredMute: response.data.CurrentMute ? 0 : 1
      }
    });

  })
  .then( function( response ) {

    return upnpService.action('GetMute', {
      request: {
        InstanceId: 0,
        Channel: 'Master'
      },
      response: {
        CurrentMute: {
          type: upnpService.types.boolean
        }
      }
    })

  })
  .then( function( response ) {

    console.log("Service is reporting MUTE=[" +
      (response.data.CurrentMute ? 'on' : 'off') +
        "]");

  })
  .then( null, function( error ) { // Handle any errors

    console.log( "An error occurred: " + error.description );

  });

}
UPnP data types

The list of valid UPnP data types are as follows:

Plug.UPnP.prototype.types.i1

1 Byte int

Plug.UPnP.prototype.types.i2

2 Byte int

Plug.UPnP.prototype.types.i4

4 Byte int

Plug.UPnP.prototype.types.ui1

Unsigned 1 Byte int

Plug.UPnP.prototype.types.ui2

Unsigned 2 Byte int

Plug.UPnP.prototype.types.ui4

Unsigned 4 Byte int

Plug.UPnP.prototype.types.int

Fixed point, integer number that may have leading sign

Plug.UPnP.prototype.types.r4

4 Byte float

Plug.UPnP.prototype.types.r8

8 Byte float

Plug.UPnP.prototype.types.number

Same as Plug.UPnP.prototype.types.r8

Plug.UPnP.prototype.types.fixed_14_4

Same as Plug.UPnP.prototype.types.r8 but no more than 14 digits to the left of the decimal point and no more than 4 to the right

Plug.UPnP.prototype.types.float

Floating point number (same as Plug.UPnP.prototype.types.r8)

Plug.UPnP.prototype.types.char

Unicode string. One character long

Plug.UPnP.prototype.types.string

Unicode string. No limit on length

Plug.UPnP.prototype.types.date

Date without time data (accepts ISO-8601 strings or Date objects and returns ECMA Date objects)

Plug.UPnP.prototype.types.dateTime

Date with optional time but no time zone (accepts ISO-8601 strings or Date objects and returns ECMA Date objects)

Plug.UPnP.prototype.types.dateTime_tz

Date with optional time and optional time zone (accepts ISO-8601 strings or Date objects and returns ECMA Date objects)

Plug.UPnP.prototype.types.time

Time with no date and no time zone (accepts ISO-8601 strings or Date objects and returns ECMA Date objects)

Plug.UPnP.prototype.types.time_tz

Time with optional time zone but no date (accepts ISO-8601 strings or Date objects and returns ECMA Date objects)

Plug.UPnP.prototype.types.boolean

true or false (accepts 'true', 'false', 'yes', 'y', 'no', 'n' and returns ECMA Boolean objects)

Plug.UPnP.prototype.types.bin_base64

MIME-style Base64 encoded binary blob

Plug.UPnP.prototype.types.bin_hex

Hexadecimal digits representing octets

Plug.UPnP.prototype.types.uri

Universal Resource Identifier

Plug.UPnP.prototype.types.uuid

Universally Unique Identifier

Feedback

If you find any bugs or issues please report them on the Plug.Play Issue Tracker.

If you would like to contribute to this project please consider forking this repo and then creating a new Pull Request back to the main code base.

License

Copyright © 2012 Opera Software ASA

See the LICENSE file.