Class: UPnP::Service

Inherits:
SOAP::RPC::StandaloneServer
  • Object
show all
Defined in:
lib/UPnP/service.rb

Overview

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.

Defined Under Namespace

Classes: Error, Filter

Constant Summary collapse

ACTIONS =

Maps actions for a service to their arguments

Hash.new { |h, service| h[service] = {} }
VARIABLES =

Maps state variables for a service to their variable information

Hash.new { |h, service| h[service] = {} }
IN =

SOAP input argument type

SOAP::RPC::SOAPMethod::IN
OUT =

SOAP output argument type

SOAP::RPC::SOAPMethod::OUT
RETVAL =

SOAP return value argument type

SOAP::RPC::SOAPMethod::RETVAL
SCHEMA_URN =

UPnP 1.0 service schema

'urn:schemas-upnp-org:service-1-0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(device, type) {|_self| ... } ⇒ Service

Creates a new service under device of the given type

Yields:

  • (_self)

Yield Parameters:

  • _self (UPnP::Service)

    the object that the method was called on



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/UPnP/service.rb', line 155

def initialize(device, type, &block)
  @device = device
  @type = type

  # 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

  add_actions

  yield self if block_given?
end

Instance Attribute Details

#deviceObject (readonly)

This service’s parent



118
119
120
# File 'lib/UPnP/service.rb', line 118

def device
  @device
end

#typeObject (readonly)

Type of UPnP service. Use type_urn for the full URN



123
124
125
# File 'lib/UPnP/service.rb', line 123

def type
  @type
end

Class Method Details

.add_action(name, *arguments) ⇒ Object

Adds the action name to this class with arguments



128
129
130
# File 'lib/UPnP/service.rb', line 128

def self.add_action(name, *arguments)
  ACTIONS[self][name] = arguments
end

.add_variable(name, type, allowed_values = nil, default = nil, evented = false) ⇒ Object

Adds a state variable name to this class



135
136
137
138
# File 'lib/UPnP/service.rb', line 135

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) ⇒ Object

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



144
145
146
147
148
149
150
# File 'lib/UPnP/service.rb', line 144

def self.create(device, type, &block)
  klass = const_get type
  klass.new(device, type, &block)
rescue NameError => e
  raise unless e.message =~ /UPnP::Service::#{type}/
  raise Error, "unknown service type #{type}"
end

Instance Method Details

#actionsObject

Actions for this service



175
176
177
# File 'lib/UPnP/service.rb', line 175

def actions
  ACTIONS[self.class]
end

#add_actionsObject

Adds RPC actions to this service



182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/UPnP/service.rb', line 182

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

#control_urlObject

The control URL for this service



200
201
202
# File 'lib/UPnP/service.rb', line 200

def control_url
  File.join service_path, 'control'
end

#create_configObject

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



207
208
209
210
211
# File 'lib/UPnP/service.rb', line 207

def create_config
  hash = super
  hash[:DoNotListen] = true
  hash
end

#description(xml) ⇒ Object

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



216
217
218
219
220
221
222
223
224
# File 'lib/UPnP/service.rb', line 216

def description(xml)
  xml.service do
    xml.serviceType type_urn
    xml.serviceId   "urn:upnp-org:serviceId:#{root_device.service_id self}"
    xml.SCPDURL     scpd_url
    xml.controlURL  control_url
    xml.eventSubURL event_sub_url
  end
end

#device_pathObject

The path for this service’s parent device



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/UPnP/service.rb', line 229

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_urlObject

The event subscription url for this service



244
245
246
# File 'lib/UPnP/service.rb', line 244

def event_sub_url
  File.join service_path, 'event_sub'
end

#marshal_dumpObject

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



252
253
254
255
256
257
# File 'lib/UPnP/service.rb', line 252

def marshal_dump
  [
    @device,
    @type
  ]
end

#marshal_load(data) ⇒ Object

Loads data and initializes the server



262
263
264
265
266
267
268
269
# File 'lib/UPnP/service.rb', line 262

def marshal_load(data)
  device = data.shift
  type   = data.shift

  initialize device, type

  add_actions
end

#mount_extra(http_server) ⇒ Object

Callback to mount extra WEBrick servlets



274
275
# File 'lib/UPnP/service.rb', line 274

def mount_extra(http_server)
end

#root_deviceObject

The root device for this service



280
281
282
# File 'lib/UPnP/service.rb', line 280

def root_device
  @device.root_device
end

#scpdObject

The SCPD for this service



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/UPnP/service.rb', line 287

def scpd
  xml = Builder::XmlMarkup.new :indent => 2
  xml.instruct!

  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

#scpd_action_list(xml) ⇒ Object

Adds the SCPD actionList to XML::Builder xml.



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/UPnP/service.rb', line 306

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) ⇒ Object

Adds the SCPD serviceStateTable to XML::Builder xml.



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/UPnP/service.rb', line 328

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_urlObject

The SCPD url for this service



350
351
352
# File 'lib/UPnP/service.rb', line 350

def scpd_url
  service_path
end

#service_pathObject

The HTTP path to this service



357
358
359
# File 'lib/UPnP/service.rb', line 357

def service_path
  File.join device_path, @type
end

#type_urnObject

URN of this service’s type



364
365
366
# File 'lib/UPnP/service.rb', line 364

def type_urn
  "#{UPnP::SERVICE_SCHEMA_PREFIX}:#{@type}:1"
end

#variablesObject

Returns a Hash of state variables for this service



371
372
373
# File 'lib/UPnP/service.rb', line 371

def variables
  VARIABLES[self.class]
end