class UPnP::Service

A service contains a SOAP endpoint and the Service Control Protocol Definition (SCPD). It acts as a SOAP server that is mounted onto the RootServer along with the containing devices and other devices and services in a UPnP device.

Creating a UPnP::Service class

A concrete UPnP service looks like this:

require 'UPnP/service'

class UPnP::Service::ContentDirectory < UPnP::Service

  add_action 'Browse',
    [IN, 'ObjectID',       'A_ARG_TYPE_ObjectID'],
    # ...

    [OUT, 'Result',         'A_ARG_TYPE_Result'],
    # ...

  add_variable 'A_ARG_TYPE_ObjectID', 'string'
  add_variable 'A_ARG_TYPE_Result',   'string'

  def Browse(object_id, ...)
    # ...

    [nil, result]
  end

end

Subclass UPnP::Service in the UPnP::Service namespace. UPnP::Service looks in its own namespace for various information when instantiating the service.

Service Control Protocol Definition

add_action defines a service’s action. The action’s arguments follow the name as arrays of direction (IN, OUT, RETVAL), argument name, and related state variable.

add_variable defines a state table variable. The name is followed by the type, allowed values, default value and whether or not the variable is evented.

Implementing methods

Define a regular ruby method matching the name in ::add_action for soap4r to call when it receives a request. It will be called with the IN parameters in order. The method needs to return an Array of OUT parameters in-order. If there is no RETVAL, the first item in the Array should be nil.

Instantiating a UPnP::Service

A UPnP::Service will be instantiated automatically for you if you call add_service in the UPnP::Device initialization block. If you want to instantiate a service by hand, use ::create to pick the correct subclass automatically.

Constants

ACTIONS

Maps actions for a service to their arguments

IN

SOAP input argument type

OUT

SOAP output argument type

RETVAL

SOAP return value argument type

SCHEMA_URN

UPnP 1.0 service schema

VARIABLES

Maps state variables for a service to their variable information

Attributes

device[R]

This service’s parent

type[R]

Type of UPnP service. Use #type_urn for the full URN

Public Class Methods

add_action(name, *arguments) click to toggle source

Adds the action name to this class with arguments

# File lib/UPnP/service.rb, line 131
def self.add_action(name, *arguments)
  ACTIONS[self][name] = arguments
end
add_variable(name, type, allowed_values = nil, default = nil, evented = false) click to toggle source

Adds a state variable name to this class

# File lib/UPnP/service.rb, line 138
def self.add_variable(name, type, allowed_values = nil, default = nil,
                      evented = false)
  VARIABLES[self][name] = [type, allowed_values, default, evented]
end
create(device, type, &block) click to toggle source

Creates a new service under device of the given type. Requires a concrete subclass of UPnP::Service.

# File lib/UPnP/service.rb, line 147
def self.create(device, type, &block)
  klass = const_get type
  klass.new(device, type, &block)
rescue NameError => e
  raise unless e.message =~ %rUPnP::Service::#{type}/
  raise Error, "unknown service type #{type}"
end
new(device, type) { |self| ... } click to toggle source

Creates a new service under device of the given type

# File lib/UPnP/service.rb, line 158
def initialize(device, type, &block)
  @device = device
  @type = type

  @cache_dir = nil

  # HACK PS3 disobeys spec
  SOAP::NS::KNOWN_TAG[type_urn] = 'u'
  SOAP::NS::KNOWN_TAG[SOAP::EnvelopeNamespace] = 's'

  super @type, type_urn

  filterchain.add Filter.new

  mapping_registry = UPnP::SOAPRegistry.new

  add_actions

  yield self if block_given?
end

Public Instance Methods

actions() click to toggle source

Actions for this service

# File lib/UPnP/service.rb, line 182
def actions
  ACTIONS[self.class]
end
add_actions() click to toggle source

Adds RPC actions to this service

# File lib/UPnP/service.rb, line 189
def add_actions
  opts = {
    :request_style => :rpc,
    :response_style => :rpc,
    :request_use => :encoded,
    :response_use => :literal,
  }

  actions.each do |name, params|
    qname = XSD::QName.new @default_namespace, name
    param_def = SOAP::RPC::SOAPMethod.derive_rpc_param_def self, name, params
    @router.add_method self, qname, nil, name, param_def, opts
  end
end
cache_dir() click to toggle source

A directory for storing service-specific persistent data

# File lib/UPnP/service.rb, line 207
def cache_dir
  return @cache_dir if @cache_dir

  @cache_dir = File.join '~', '.UPnP', '_cache', "#{@device.name}-#{@type}"
  @cache_dir = File.expand_path @cache_dir

  FileUtils.mkdir_p @cache_dir

  @cache_dir
end
control_url() click to toggle source

The control URL for this service

# File lib/UPnP/service.rb, line 221
def control_url
  File.join service_path, 'control'
end
create_config() click to toggle source

Tell the StandaloneServer to not listen, RootServer does this for us

# File lib/UPnP/service.rb, line 228
def create_config
  hash = super
  hash[:DoNotListen] = true
  hash
end
description(xml) click to toggle source

Adds a description of this service to the Nokogiri::XML::Builder xml

# File lib/UPnP/service.rb, line 237
def description(xml)
  xml.service do
    xml.serviceType type_urn
    xml.serviceId   root_device.service_id(self)
    xml.SCPDURL     scpd_url
    xml.controlURL  control_url
    xml.eventSubURL event_sub_url
  end
end
device_path() click to toggle source

The path for this service’s parent device

# File lib/UPnP/service.rb, line 250
def device_path
  devices = []
  device = @device

  until device.nil? do
    devices << device
    device = device.parent
  end

  File.join('/', *devices.map { |d| d.type })
end
event_sub_url() click to toggle source

The event subscription url for this service

# File lib/UPnP/service.rb, line 265
def event_sub_url
  File.join service_path, 'event_sub'
end
marshal_dump() click to toggle source

Dumps only information necessary to run initialize. Server state is not persisted.

# File lib/UPnP/service.rb, line 273
def marshal_dump
  [
    @device,
    @type
  ]
end
marshal_load(data) click to toggle source

Loads data and initializes the server

# File lib/UPnP/service.rb, line 283
def marshal_load(data)
  device = data.shift
  type   = data.shift

  initialize device, type

  add_actions
end
mount_extra(http_server) click to toggle source

Callback to mount extra WEBrick servlets

# File lib/UPnP/service.rb, line 295
def mount_extra(http_server)
end
root_device() click to toggle source

The root device for this service

# File lib/UPnP/service.rb, line 301
def root_device
  @device.root_device
end
scpd() click to toggle source

The SCPD for this service

# File lib/UPnP/service.rb, line 308
def scpd
  Nokogiri::XML::Builder.new do |xml|
    xml.scpd :xmlns => SCHEMA_URN do
      xml.specVersion do
        xml.major 1
        xml.minor 0
      end

      scpd_action_list xml

      scpd_service_state_table xml
    end
  end.to_xml
end
scpd_action_list(xml) click to toggle source

Adds the SCPD actionList to the Nokogiri::XML::Builder xml.

# File lib/UPnP/service.rb, line 326
def scpd_action_list(xml)
  xml.actionList do
    actions.sort_by { |name,| name }.each do |name, arguments|
      xml.action do
        xml.name name
        xml.argumentList do
          arguments.each do |direction, arg_name, state_variable|
            xml.argument do
              xml.direction direction
              xml.name arg_name
              xml.relatedStateVariable state_variable
            end
          end
        end
      end
    end
  end
end
scpd_service_state_table(xml) click to toggle source

Adds the SCPD serviceStateTable to the Nokogiri::XML::Builder xml.

# File lib/UPnP/service.rb, line 348
def scpd_service_state_table(xml)
  xml.serviceStateTable do
    variables.each do |name, (type, allowed_values, default, send_events)|
      send_events = send_events ? 'yes' : 'no'
      xml.stateVariable :sendEvents => send_events do
        xml.name name
        xml.dataType type
        if allowed_values then
          xml.allowedValueList do
            allowed_values.each do |value|
              xml.allowedValue value
            end
          end
        end
      end
    end
  end
end
scpd_url() click to toggle source

The SCPD url for this service

# File lib/UPnP/service.rb, line 370
def scpd_url
  service_path
end
service_path() click to toggle source

The HTTP path to this service

# File lib/UPnP/service.rb, line 377
def service_path
  File.join device_path, @type
end
type_urn() click to toggle source

URN of this service’s type

# File lib/UPnP/service.rb, line 384
def type_urn
  "#{UPnP::SERVICE_SCHEMA_PREFIX}:#{@type}:1"
end
variables() click to toggle source

Returns a Hash of state variables for this service

# File lib/UPnP/service.rb, line 391
def variables
  VARIABLES[self.class]
end