Class: Playful::ControlPoint

Inherits:
Object
  • Object
show all
Includes:
LogSwitch::Mixin
Defined in:
lib/playful/control_point.rb,
lib/playful/control_point/base.rb,
lib/playful/control_point/error.rb,
lib/playful/control_point/device.rb,
lib/playful/control_point/service.rb

Overview

Allows for controlling a UPnP device as defined in the UPnP spec for control points.

It uses Nori for parsing the description XML files, which will use Nokogiri if you have it installed.

Defined Under Namespace

Classes: ActionError, Base, Device, Error, Service

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(search_target, search_options = {}) ⇒ ControlPoint

Returns a new instance of ControlPoint.

Parameters:

  • search_target (String)

    The device(s) to control.

  • search_options (Hash) (defaults to: {})

    Options to pass on to SSDP search and listen calls.

  • options (Hash)

    a customizable set of options


38
39
40
41
42
43
44
45
# File 'lib/playful/control_point.rb', line 38

def initialize(search_target, search_options = {})
  @search_target = search_target
  @search_options = search_options
  @search_options[:ttl] ||= 4
  @devices = []
  @new_device_channel = EventMachine::Channel.new
  @old_device_channel = EventMachine::Channel.new
end

Class Attribute Details

.raise_on_remote_errorObject

Returns the value of attribute raise_on_remote_error


26
27
28
# File 'lib/playful/control_point.rb', line 26

def raise_on_remote_error
  @raise_on_remote_error
end

Instance Attribute Details

#devicesObject (readonly)

Returns the value of attribute devices


31
32
33
# File 'lib/playful/control_point.rb', line 31

def devices
  @devices
end

Class Method Details

.config {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:


21
22
23
# File 'lib/playful/control_point.rb', line 21

def self.config
  yield self
end

Instance Method Details

#add_device(built_device) ⇒ Object


145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/playful/control_point.rb', line 145

def add_device(built_device)
  if (@devices.any? { |d| d.usn == built_device.usn }) ||
    (@devices.any? { |d| d.udn == built_device.udn })
    log 'Newly created device already exists in internal list. Not adding.'
  #if @devices.any? { |d| d.usn == built_device.usn }
  #  log "Newly created device (#{built_device.usn}) already exists in internal list. Not adding."
  else
    log 'Adding newly created device to internal list..'
    @new_device_channel << built_device
    @devices << built_device
  end
end

#create_device(notification) ⇒ Object


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/playful/control_point.rb', line 125

def create_device(notification)
  deferred_device = Device.new(ssdp_notification: notification)

  deferred_device.errback do |partially_built_device, message|
    log message
    #add_device(partially_built_device)

    if self.class.raise_on_remote_error
      raise ControlPoint::Error, message
    end
  end

  deferred_device.callback do |built_device|
    log "Device created from #{notification}"
    add_device(built_device)
  end

  deferred_device.fetch
end

#listen(ttl) ⇒ Object


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/playful/control_point.rb', line 74

def listen(ttl)
  EM.defer do
    listener = SSDP.listen(ttl)

    listener.alive_notifications.subscribe do |advertisement|
      log "Got alive #{advertisement}"

      if @devices.any? { |d| d.usn == advertisement[:usn] }
        log "Device with USN #{advertisement[:usn]} already exists."
      else
        log "Device with USN #{advertisement[:usn]} not found. Creating..."
        create_device(advertisement)
      end
    end

    listener.byebye_notifications.subscribe do |advertisement|
      log "Got bye-bye from #{advertisement}"

      @devices.reject! do |device|
        device.usn == advertisement[:usn]
      end

      @old_device_channel << advertisement
    end
  end
end

#running?Boolean

Returns:

  • (Boolean)

165
166
167
# File 'lib/playful/control_point.rb', line 165

def running?
  @running
end

#ssdp_search_and_listen(search_for, options = {}) ⇒ Object


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/playful/control_point.rb', line 101

def ssdp_search_and_listen(search_for, options = {})
  searcher = SSDP.search(search_for, options)

  searcher.discovery_responses.subscribe do |notification|
    create_device(notification)
  end

  # Do I need to do this?
  EM.add_timer(options[:response_wait_time]) do
    searcher.close_connection
    listen(options[:ttl])
  end

  EM.add_periodic_timer(5) do
    log "Time since last timer: #{Time.now - @timer_time}" if @timer_time
    log "Connections: #{EM.connection_count}"
    @timer_time = Time.now
    puts "<#{self.class}> Device count: #{@devices.size}"
    puts "<#{self.class}> Device unique: #{@devices.uniq.size}"
  end

  trap_signals
end

#start {|new_device_channel, old_device_channel| ... } ⇒ Object

Starts the ControlPoint. If an EventMachine reactor is running already, it'll join that reactor, otherwise it'll start the reactor.

Yield Parameters:

  • new_device_channel (EventMachine::Channel)

    The means through which clients can get notified when a new device has been discovered either through SSDP searching or from an ssdp:alive notification.

  • old_device_channel (EventMachine::Channel)

    The means through which clients can get notified when a device has gone offline (have sent out a ssdp:byebye notification).


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/playful/control_point.rb', line 56

def start(&blk)
  @stopping = false

  starter = -> do
    ssdp_search_and_listen(@search_target, @search_options)
    blk.call(@new_device_channel, @old_device_channel)
    @running = true
  end

  if EM.reactor_running?
    log 'Joining reactor...'
    starter.call
  else
    log 'Starting reactor...'
    EM.synchrony(&starter)
  end
end

#stopObject


158
159
160
161
162
163
# File 'lib/playful/control_point.rb', line 158

def stop
  @running = false
  @stopping = false

  EM.stop if EM.reactor_running?
end

#trap_signalsObject


169
170
171
172
173
# File 'lib/playful/control_point.rb', line 169

def trap_signals
  trap('INT') { stop }
  trap('TERM') { stop }
  trap('HUP') { stop } if RUBY_PLATFORM !~ /mswin|mingw/
end