playful 0.1.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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