playful 0.1.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +6 -0
  7. data/History.rdoc +3 -0
  8. data/LICENSE.rdoc +22 -0
  9. data/README.rdoc +194 -0
  10. data/Rakefile +20 -0
  11. data/features/control_point.feature +13 -0
  12. data/features/device.feature +22 -0
  13. data/features/device_discovery.feature +9 -0
  14. data/features/step_definitions/control_point_steps.rb +19 -0
  15. data/features/step_definitions/device_discovery_steps.rb +40 -0
  16. data/features/step_definitions/device_steps.rb +28 -0
  17. data/features/support/common.rb +9 -0
  18. data/features/support/env.rb +17 -0
  19. data/features/support/fake_upnp_device_collection.rb +108 -0
  20. data/features/support/world_extensions.rb +15 -0
  21. data/lib/core_ext/hash_patch.rb +5 -0
  22. data/lib/core_ext/socket_patch.rb +16 -0
  23. data/lib/core_ext/to_upnp_s.rb +65 -0
  24. data/lib/playful.rb +5 -0
  25. data/lib/playful/control_point.rb +175 -0
  26. data/lib/playful/control_point/base.rb +74 -0
  27. data/lib/playful/control_point/device.rb +511 -0
  28. data/lib/playful/control_point/error.rb +13 -0
  29. data/lib/playful/control_point/service.rb +404 -0
  30. data/lib/playful/device.rb +28 -0
  31. data/lib/playful/logger.rb +8 -0
  32. data/lib/playful/ssdp.rb +195 -0
  33. data/lib/playful/ssdp/broadcast_searcher.rb +114 -0
  34. data/lib/playful/ssdp/error.rb +6 -0
  35. data/lib/playful/ssdp/listener.rb +38 -0
  36. data/lib/playful/ssdp/multicast_connection.rb +112 -0
  37. data/lib/playful/ssdp/network_constants.rb +17 -0
  38. data/lib/playful/ssdp/notifier.rb +41 -0
  39. data/lib/playful/ssdp/searcher.rb +87 -0
  40. data/lib/playful/version.rb +3 -0
  41. data/lib/rack/upnp_control_point.rb +70 -0
  42. data/playful.gemspec +38 -0
  43. data/spec/spec_helper.rb +16 -0
  44. data/spec/support/search_responses.rb +134 -0
  45. data/spec/unit/core_ext/to_upnp_s_spec.rb +105 -0
  46. data/spec/unit/playful/control_point/device_spec.rb +7 -0
  47. data/spec/unit/playful/control_point_spec.rb +45 -0
  48. data/spec/unit/playful/ssdp/listener_spec.rb +29 -0
  49. data/spec/unit/playful/ssdp/multicast_connection_spec.rb +157 -0
  50. data/spec/unit/playful/ssdp/notifier_spec.rb +76 -0
  51. data/spec/unit/playful/ssdp/searcher_spec.rb +110 -0
  52. data/spec/unit/playful/ssdp_spec.rb +214 -0
  53. data/tasks/control_point.html +30 -0
  54. data/tasks/control_point.thor +43 -0
  55. data/tasks/search.thor +128 -0
  56. data/tasks/test_js/FABridge.js +1425 -0
  57. data/tasks/test_js/WebSocketMain.swf +807 -0
  58. data/tasks/test_js/swfobject.js +825 -0
  59. data/tasks/test_js/web_socket.js +1133 -0
  60. data/test/test_ssdp.rb +298 -0
  61. data/test/test_ssdp_notification.rb +74 -0
  62. data/test/test_ssdp_response.rb +31 -0
  63. data/test/test_ssdp_search.rb +23 -0
  64. metadata +339 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9760e12299c1d42be1a43596cc66d5d70ad07065
4
+ data.tar.gz: 4f64c01cb96411ca5f93174bfb145a12a34cc3f3
5
+ SHA512:
6
+ metadata.gz: 51668cea99a82a52dbcbba1e08f134fb2300db97327fa7f7be0f15135646e53ecf4c5ab4f4a6bdf7a5b2be4b3823b9bff083df61717bc03c7332939ff9295053
7
+ data.tar.gz: 76f05414e46d0683230d8c38856efb78a2d412880a31bf9a00878b359d899caf9d83d0a8609e39e375276f8f138ac36056d3b0d550fab0487f2f366901c8484b
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+
5
+ .autotest
6
+ .DS_Store
7
+ .loadpath
8
+ .project
9
+ .yardoc/
10
+ .bundle/
11
+ .idea/
12
+
13
+ coverage/
14
+ doc/
15
+ pkg/
16
+ tmp/
17
+
18
+ */.DS_Store
19
+ atlassian-ide-plugin.xml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'coveralls', require: false
5
+ #gem "httpi", '>=2.0.0.rc1'
6
+ #gem 'em-synchrony'
data/History.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ === 0.1.0 TBD
2
+
3
+ * I do stuffs!
data/LICENSE.rdoc ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012 Steve Loveless
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,194 @@
1
+ = playful
2
+
3
+ * {Homepage}[http://github.com/turboladen/playful]
4
+ * {UPnP Device Architecture Documentation}[http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf]
5
+ * previously `upnp`
6
+
7
+ {<img src="https://travis-ci.org/turboladen/playful.png?branch=master" alt="Build Status" />}[https://travis-ci.org/turboladen/playful]
8
+ {<img src="https://coveralls.io/repos/turboladen/playful/badge.png" alt="Coverage Status" />}[https://coveralls.io/r/turboladen/playful]
9
+
10
+
11
+ == Description
12
+
13
+ Ruby's UPnP RubyGem was outdated in Ruby 1.9 when support for Soap4r was
14
+ dropped. This gem intends to fill that void for Ruby >= 1.9 and allow for SSDP
15
+ search, discovery, advertisement, the ability to act as a UPnP control point, as
16
+ well as provide UPnP devices and services.
17
+
18
+ This uses EventMachine[http://github.com/eventmachine/eventmachine], so if
19
+ you're not already, getting familiar with its concepts will be helpful here.
20
+
21
+ === Getting Started
22
+
23
+ I'm still working out the overall design of these components, and thus won't be
24
+ working towards a gem release until this settles down. As such, don't expect
25
+ interfaces to stay the same if you update. In the mean time, I do intend to
26
+ keep the master branch (mostly) stable, so please feel free to give it whirl and
27
+ report any issues you encounter.
28
+
29
+ * <code>gem install bundler</code>
30
+ * <code>bundle install</code>
31
+
32
+ == Features
33
+
34
+ === Implemented
35
+
36
+ * SSDP search, discovery. (almost settled down)
37
+ * Ability to act as a UPnP Control Point. (in progress)
38
+ * Rack middleware to allow for device access in a Rack app.
39
+
40
+ === Coming
41
+
42
+ * UPnP Devices & Services (server)
43
+
44
+ == Examples
45
+
46
+ Take a look at the +tasks+ directory; I've created some working examples using
47
+ Thor[https://github.com/wycats/thor]. You can get a list of these tasks by
48
+ doing `thor -T`.
49
+
50
+ There's also a more involved, in-progress, working example at
51
+ http://github.com/turboladen/upnp_cp_on_sinatra that uses the Rack middleware
52
+ to build a Sinatra app that allows for controling devices in your network.
53
+
54
+ === SSDP Searches
55
+
56
+ An SSDP search simply sends the M-SEARCH out to the multicast group and listens
57
+ for responses for a given (or default of 5 seconds) amount of time. The return
58
+ from this depends on if you're running it within an EventMachine reactor or not.
59
+ If not, it returns is an Array of responses as Hashes, where keys are the header
60
+ names, values are the header values. Take a look at the SSDP.search docs for
61
+ more on the options here.
62
+
63
+ require 'playful/ssdp'
64
+
65
+ # Search for all devices (do an M-SEARCH with the ST header set to 'ssdp:all')
66
+ all_devices = UPnP::SSDP.search # this is default
67
+ all_devices = UPnP::SSDP.search 'ssdp:all' # or be explicit
68
+ all_devices = UPnP::SSDP.search :all # or use short-hand
69
+
70
+ # Search for root devices (do an M-SEARCH with ST header set to 'upnp:rootdevices')
71
+ root_devices = UPnP::SSDP.search 'upnp:rootdevices'
72
+ root_devices = UPnP::SSDP.search :root # or use short-hand
73
+
74
+ # Search for a device with a specific UUID
75
+ my_device = UPnP::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
76
+
77
+ # Search for devices of a specific type
78
+ my_media_server = UPnP::SSDP.search 'urn:schemas-upnp-org:device:MediaServer:1'
79
+
80
+ # All of these searches will return something that looks like
81
+ # => [
82
+ # {
83
+ # :control => "max-age=1200",
84
+ # :date => "Sun, 23 Sep 2012 20:31:48 GMT",
85
+ # :location => "http://192.168.10.3:5001/description/fetch",
86
+ # :server => "Linux-i386-2.6.38-15-generic-pae, UPnP/1.0, PMS/1.50.0",
87
+ # :st => "upnp:rootdevice",
88
+ # :ext => "",
89
+ # :usn => "uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice",
90
+ # :length => "0"
91
+ # }
92
+ # ]
93
+
94
+ If you do the search inside of an EventMachine reactor, as the
95
+ UPnP::SSDP::Searcher receives and parses responses, it adds them to the accessor
96
+ #discovery_responses, which is an EventMachine::Channel. This lets you subscribe
97
+ to the resposnes and do what you want with them (most likely you'll want to create
98
+ UPnP::ControlPoint::Device objects so you can control your device) as you
99
+ receive them.
100
+
101
+ require 'playful/ssdp'
102
+ require 'playful/control_point/device'
103
+
104
+ EM.run do
105
+ searcher = UPnP::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
106
+
107
+ # Create a deferrable object that can be notified when the device we want
108
+ # has been found and created.
109
+ device_controller = EventMachine::DefaultDeferrable.new
110
+
111
+ # This callback will get called when the device_creator callback is called
112
+ # (which is called after the device has been created).
113
+ device_controller.callback do |device|
114
+ p device.service_list.first.class # UPnP::ControlPoint::Service
115
+ p device.service_list.first.service_type # "urn:schemas-upnp-org:service:ContentDirectory:1"
116
+
117
+ # SOAP actions are converted to Ruby methods--show those
118
+ p device.service_list.first.singleton_methods # [:GetSystemUpdateID, :Search, :GetSearchCapabilities, :GetSortCapabilities, :Browse]
119
+
120
+ # Call a SOAP method defined in the service. The response is extracted from the
121
+ # XML SOAP response and the value is converted from the UPnP dataType to
122
+ # the related Ruby type. Reponses are always contained in a Hash, so as
123
+ # to maintain the relation defined in the service.
124
+ p device.service_list.first.GetSystemUpdateID # { :Id => 1 }
125
+ end
126
+
127
+ # Note that you don't have to check for items in the Channel or for when the
128
+ # Channel is empty: EventMachine will pop objects off the Channel as soon as
129
+ # they're put there and stop when there are none left.
130
+ searcher.discovery_responses.pop do |notification|
131
+
132
+ # UPnP::ControlPoint::Device objects are EventMachine::Deferrables, so you
133
+ # need to define callback and errback blocks to handle when the Device
134
+ # object is done being created.
135
+ device_creator = UPnP::ControlPoint::Device.new(ssdp_notification: notification)
136
+
137
+ device_creator.errback do
138
+ puts "Failed creating the device."
139
+ exit!
140
+ end
141
+
142
+ device_creator.callback do |built_device|
143
+ puts "Device has been created now."
144
+
145
+ # This lets the device_controller know that the device has been created,
146
+ # calls its callback, and passes the built device to it.
147
+ device_controller.set_deferred_status(:succeeded, built_device)
148
+ end
149
+
150
+ # This actually starts the Device creation process and will call the
151
+ # callback or errback (above) when it's done.
152
+ device_creator.fetch
153
+ end
154
+ end
155
+
156
+ === ControlPoints
157
+
158
+ If you're wanting to control devices and their services, you'll probably be more
159
+ interested in using a UPnP::ControlPoint, instead of doing all that work (above)
160
+ to create a UPnP::ControlPoint::Device. The ControlPoint will handle doing the
161
+ search and device/service creation for you and will hand you over Devices to
162
+ control them (and present them in a UI, perhaps?) as you need. More to come on
163
+ this as the design settles down.
164
+
165
+ == Requirements
166
+
167
+ * Rubies (tested)
168
+ * 1.9.3
169
+ * 2.0.0
170
+ * 2.1.0
171
+ * Gems
172
+ * eventmachine
173
+ * em-http-request
174
+ * em-synchrony
175
+ * nori
176
+ * log_switch
177
+ * savon
178
+ * Gems (development)
179
+ * bundler
180
+ * rspec
181
+ * simplecov
182
+ * thin
183
+ * yard
184
+
185
+ == Install
186
+
187
+ # This won't work yet, as the gem has not yet been released...
188
+ $ gem install playful
189
+
190
+ == Copyright
191
+
192
+ Copyright (c) 2012 sloveless
193
+
194
+ See LICENSE.rdoc for details.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+ require 'yard'
5
+
6
+
7
+ YARD::Rake::YardocTask.new
8
+ Cucumber::Rake::Task.new(:features)
9
+ RSpec::Core::RakeTask.new
10
+
11
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
12
+ spec.pattern = 'spec/**/*_spec.rb'
13
+ spec.rcov = true
14
+ end
15
+
16
+ # Alias for rubygems-test
17
+ task test: :spec
18
+
19
+ task default: :test
20
+
@@ -0,0 +1,13 @@
1
+ Feature: Control Point
2
+ As a consumer of UPnP devices and services
3
+ I want to act as a UPnP control point
4
+ So that I can control the devices and services that fulfill my needs
5
+
6
+ Scenario: Search for devices on startup
7
+ Given I have a non-local IP address
8
+ And a UDP port on that IP is free
9
+ When I create my control point
10
+ And tell it to find all root devices
11
+ And tell it to find all services
12
+ Then it gets a list of root devices
13
+ And it gets a list of services
@@ -0,0 +1,22 @@
1
+ Feature: Controlled Device
2
+ As a UPnP device user
3
+ I want to use the device that offers some service
4
+ So that I can consume that service
5
+
6
+ Scenario: Device added to the network
7
+ Given I have a non-local IP address
8
+ And a UDP port on that IP is free
9
+ When I start my device on that IP address and port
10
+ Then the device multicasts a discovery message
11
+
12
+ @negative
13
+ Scenario: Device startup without an IP
14
+ Given I don't have an IP address
15
+ When I start the device
16
+ Then I get an error message saying I don't have an IP address
17
+
18
+ Scenario: Device startup with a local-link IP
19
+ Given I have a local-link IP address
20
+ When I start the device
21
+ Then the device starts running normally
22
+
@@ -0,0 +1,9 @@
1
+ Feature: Device discovery
2
+ As a device control point, I want to be able to discover devices
3
+ so that I can use the services those devices provide
4
+
5
+ Scenario: A single root device
6
+ Given there's at least 1 root device in my network
7
+ When I come online
8
+ Then I should discover at least 1 root device
9
+ And the location of that device should match my fake device's location
@@ -0,0 +1,19 @@
1
+ When /^I create my control point$/ do
2
+ @control_point = UPnP::ControlPoint.new
3
+ end
4
+
5
+ When /^tell it to find all root devices$/ do
6
+ @control_point.find_devices(:root, 5)
7
+ end
8
+
9
+ When /^tell it to find all services$/ do
10
+ @control_point.find_services
11
+ end
12
+
13
+ Then /^it gets a list of root devices$/ do
14
+ @control_point.device_list.should_not be_empty
15
+ end
16
+
17
+ Then /^it gets a list of services$/ do
18
+ @control_point.services.should_not be_empty
19
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../support/fake_upnp_device_collection'
2
+ require 'cucumber/rspec/doubles'
3
+
4
+ Thread.abort_on_exception = true
5
+ UPnP::SSDP.log = false
6
+
7
+ Given /^there's at least (\d+) root device in my network$/ do |device_count|
8
+ fake_device_collection.respond_with = <<-ROOT_DEVICE
9
+ HTTP/1.1 200 OK\r
10
+ CACHE-CONTROL: max-age=1200\r
11
+ DATE: Mon, 26 Sep 2011 06:40:19 GMT\r
12
+ LOCATION: http://#{local_ip}:4567\r
13
+ SERVER: Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1\r
14
+ ST: upnp:rootdevice\r
15
+ EXT:\r
16
+ USN: uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice\r
17
+ Content-Length: 0\r
18
+
19
+ ROOT_DEVICE
20
+
21
+ Thread.start { fake_device_collection.start_ssdp_listening }
22
+ Thread.start { fake_device_collection.start_serving_description }
23
+ sleep 0.2
24
+ end
25
+
26
+ When /^I come online$/ do
27
+ control_point.should be_a UPnP::ControlPoint
28
+ end
29
+
30
+ Then /^I should discover at least (\d+) root device$/ do |device_count|
31
+ control_point.find_devices(:root)
32
+ fake_device_collection.stop_ssdp_listening
33
+ fake_device_collection.stop_serving_description
34
+ control_point.device_list.should have_at_least(device_count.to_i).items
35
+ end
36
+
37
+ Then /^the location of that device should match my fake device's location$/ do
38
+ locations = control_point.device_list.map { |device| device[:location] }
39
+ locations.should include "http://#{local_ip}:4567"
40
+ end
@@ -0,0 +1,28 @@
1
+ When /^I start my device on that IP address and port$/ do
2
+ @device = UPnP::Device.new(@local_ip, @port)
3
+ @device.start.should be_true
4
+ end
5
+
6
+ Then /^the device multicasts a discovery message$/ do
7
+ pending # express the regexp above with the code you wish you had
8
+ end
9
+
10
+ Given /^I don't have an IP address$/ do
11
+ pending # express the regexp above with the code you wish you had
12
+ end
13
+
14
+ When /^I start the device$/ do
15
+ pending # express the regexp above with the code you wish you had
16
+ end
17
+
18
+ Then /^I get an error message saying I don't have an IP address$/ do
19
+ pending # express the regexp above with the code you wish you had
20
+ end
21
+
22
+ Given /^I have a local\-link IP address$/ do
23
+ pending # express the regexp above with the code you wish you had
24
+ end
25
+
26
+ Then /^the device starts running normally$/ do
27
+ pending # express the regexp above with the code you wish you had
28
+ end
@@ -0,0 +1,9 @@
1
+ Given /^I have a non-local IP address$/ do
2
+ @local_ip, @port = local_ip_and_port
3
+ @local_ip.should_not be_nil
4
+ @local_ip.should_not match /^127.0.0/
5
+ end
6
+
7
+ Given /^a UDP port on that IP is free$/ do
8
+ @port.should_not be_nil
9
+ end