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.

Direct Known Subclasses

TestService

Defined Under Namespace

Classes: Error, Filter, TestService

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
171
172
# File 'lib/UPnP/service.rb', line 155

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

  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



177
178
179
# File 'lib/UPnP/service.rb', line 177

def actions
  ACTIONS[self.class]
end

#add_actionsObject

Adds RPC actions to this service



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

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_dirObject

A directory for storing service-specific persistent data



202
203
204
205
206
207
208
209
210
211
# File 'lib/UPnP/service.rb', line 202

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_urlObject

The control URL for this service



216
217
218
# File 'lib/UPnP/service.rb', line 216

def control_url
  File.join service_path, 'control'
end

#create_configObject

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



223
224
225
226
227
# File 'lib/UPnP/service.rb', line 223

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

#description(xml) ⇒ Object

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



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

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_pathObject

The path for this service’s parent device



245
246
247
248
249
250
251
252
253
254
255
# File 'lib/UPnP/service.rb', line 245

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



260
261
262
# File 'lib/UPnP/service.rb', line 260

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.



268
269
270
271
272
273
# File 'lib/UPnP/service.rb', line 268

def marshal_dump
  [
    @device,
    @type
  ]
end

#marshal_load(data) ⇒ Object

Loads data and initializes the server



278
279
280
281
282
283
284
285
# File 'lib/UPnP/service.rb', line 278

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



290
291
# File 'lib/UPnP/service.rb', line 290

def mount_extra(http_server)
end

#root_deviceObject

The root device for this service



296
297
298
# File 'lib/UPnP/service.rb', line 296

def root_device
  @device.root_device
end

#scpdObject

The SCPD for this service



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/UPnP/service.rb', line 303

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.



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/UPnP/service.rb', line 322

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.



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/UPnP/service.rb', line 344

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



366
367
368
# File 'lib/UPnP/service.rb', line 366

def scpd_url
  service_path
end

#service_pathObject

The HTTP path to this service



373
374
375
# File 'lib/UPnP/service.rb', line 373

def service_path
  File.join device_path, @type
end

#type_urnObject

URN of this service’s type



380
381
382
# File 'lib/UPnP/service.rb', line 380

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

#variablesObject

Returns a Hash of state variables for this service



387
388
389
# File 'lib/UPnP/service.rb', line 387

def variables
  VARIABLES[self.class]
end