Class: Playful::SSDP

Inherits:
Object
  • Object
show all
Includes:
LogSwitch::Mixin, NetworkConstants
Defined in:
lib/playful/ssdp.rb,
lib/playful/ssdp/error.rb,
lib/playful/ssdp/network_constants.rb,
lib/playful/ssdp/broadcast_searcher.rb,
lib/playful/ssdp/multicast_connection.rb

Overview

TODO:

Add docs for Playful::Device perspective.

This is the main class for doing SSDP stuff. You can have a look at child classes, but you'll probably want to just use these methods here.

SSDP is “Simple Service Discovery Protocol”, which lets you find and learn about UPnP devices on your network. Of the six “steps” of UPnP (given in the UPnP spec–that's counting step 0), SSDP is what provides step 1, or the “discovery” step.

Before you can do anything with any of the UPnP devices on your network, you need to search your network to see what devices are available. Once you've found what's available, you can then decide device(s) you'd like to control (that's where Control Points come in; take a look at Playful::ControlPoint). After searching, you should then listen to the activity on your network. New devices on your network may come online (via ssdp:alive) and devices that you care about may go offline (via ssdp:byebye), in which case you probably shouldn't try to talk to them anymore.

Defined Under Namespace

Modules: NetworkConstants Classes: BroadcastSearcher, Error, Listener, MulticastConnection, Notifier, Searcher

Constant Summary

Constants included from NetworkConstants

NetworkConstants::BROADCAST_IP, NetworkConstants::MULTICAST_IP, NetworkConstants::MULTICAST_PORT, NetworkConstants::TTL

Class Method Summary collapse

Class Method Details

.listen(ttl = TTL) ⇒ Hash<Array>, Playful::SSDP::Listener

Opens a multicast UDP socket on 239.255.255.250:1900 and listens for alive and byebye notifications from devices.

Parameters:

  • ttl (Fixnum) (defaults to: TTL)

    The TTL to use on the UDP socket.

Returns:

  • (Hash<Array>, Playful::SSDP::Listener)

    If the EventMachine reactor is not running, it returns two key/value pairs–one for alive_notifications, one for byebye_notifications. If the reactor is running, it returns a Playful::SSDP::Listener so that that object can be used however desired. The latter method is used in Playful::ControlPoints so that an object of that type can keep track of devices it cares about.


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/playful/ssdp.rb', line 49

def self.listen(ttl=TTL)
  alive_notifications = Set.new
  byebye_notifications = Set.new

  listener = proc do
    l = EM.open_datagram_socket(MULTICAST_IP, MULTICAST_PORT,
      Playful::SSDP::Listener, ttl)
    i = 0
    EM.add_periodic_timer(5) { i += 5; Playful.log "Listening for #{i}\n" }
    l
  end

  if EM.reactor_running?
    return listener.call
  else
    EM.synchrony do
      l = listener.call

      alive_getter = Proc.new do |notification|
        alive_notifications << notification
        EM.next_tick { l.alive_notifications.pop(&live_getter) }
      end
      l.alive_notifications.pop(&alive_getter)

      byebye_getter = Proc.new do |notification|
        byebye_notifications << notification
        EM.next_tick { l.byebye_notifications.pop(&byebye_getter) }
      end
      l.byebye_notifications.pop(&byebye_getter)

      trap_signals
    end
  end

  {
    alive_notifications: alive_notifications.to_a.flatten,
    byebye_notifications: byebye_notifications.to_a.flatten
  }
end

.notify(notification_type, usn, ddf_url, valid_for_duration = 1800) ⇒ Object

TODO:

This is for Playful::Devices, which aren't implemented yet, and thus this may not be working.


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/playful/ssdp.rb', line 161

def self.notify(notification_type, usn, ddf_url, valid_for_duration=1800)
  responses = []
  notification_type = notification_type.to_upnp_s

  EM.synchrony do
    s = send_notification(notification_type, usn, ddf_url, valid_for_duration)
    EM.add_shutdown_hook { responses = s.discovery_responses }

    EM.add_periodic_timer(valid_for_duration) do
      s = send_notification(notification_type, usn, ddf_url, valid_for_duration)
    end

    trap_signals
  end

  responses
end

.search(search_target = :all, options = {}) ⇒ Array<Hash>, Playful::SSDP::Searcher

Opens a UDP socket on 0.0.0.0, on an ephemeral port, has Playful::SSDP::Searcher build and send the search request, then receives the responses. The search will stop after response_wait_time.

Parameters:

  • search_target (String) (defaults to: :all)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • response_wait_time (Fixnum)
  • ttl (Fixnum)
  • m_search_count (Fixnum)
  • do_broadcast_search (Boolean)

    Tells the search call to also send a M-SEARCH over 255.255.255.255. This is NOT part of the UPnP spec; it's merely a hack for working with some types of devices that don't properly implement the UPnP spec.

Returns:

  • (Array<Hash>, Playful::SSDP::Searcher)

    Returns a Hash that represents the headers from the M-SEARCH response. Each one of these can be passed in to Playful::ControlPoint::Device.new to download the device's description file, parse it, and interact with the device's devices and/or services. If the reactor is already running this will return a a Playful::SSDP::Searcher which will make its accessors available so you can get responses in real time.


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/playful/ssdp.rb', line 112

def self.search(search_target=:all, options = {})
  response_wait_time = options[:response_wait_time] || 5
  ttl = options[:ttl] || TTL
  do_broadcast_search = options[:do_broadcast_search]

  searcher_options = options
  searcher_options.delete :do_broadcast_search

  responses = []
  search_target = search_target.to_upnp_s

  multicast_searcher = proc do
    EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::Searcher,
      search_target, searcher_options)
  end

  broadcast_searcher = proc do
    EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::BroadcastSearcher,
      search_target, response_wait_time, ttl)
  end

  if EM.reactor_running?
    return multicast_searcher.call
  else
    EM.synchrony do
      ms = multicast_searcher.call

      ms.discovery_responses.subscribe do |notification|
        responses << notification
      end

      if do_broadcast_search
        bs = broadcast_searcher.call

        bs.discovery_responses.subscribe do |notification|
          responses << notification
        end
      end

      EM.add_timer(response_wait_time) { EM.stop }
      trap_signals
    end
  end

  responses.flatten
end

.send_notification(notification_type, usn, ddf_url, valid_for_duration) ⇒ Object

TODO:

This is for Playful::Devices, which aren't implemented yet, and thus this may not be working.


181
182
183
184
# File 'lib/playful/ssdp.rb', line 181

def self.send_notification(notification_type, usn, ddf_url, valid_for_duration)
  EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::Notifier, notification_type,
    usn, ddf_url, valid_for_duration)
end