artoo 1.0.0.pre → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +9 -20
- data/Guardfile +0 -5
- data/artoo.gemspec +4 -4
- data/examples/ardrone.rb +1 -1
- data/examples/ardrone_multi.rb +37 -0
- data/examples/ardrone_wiiclassic.rb +1 -1
- data/examples/conway_sphero.rb +8 -4
- data/examples/sphero.rb +3 -3
- data/examples/sphero_color.rb +7 -7
- data/examples/sphero_color_wiichuck.rb +30 -0
- data/examples/sphero_wiichuck.rb +2 -2
- data/lib/artoo/adaptors/adaptor.rb +1 -0
- data/lib/artoo/api/api.rb +103 -0
- data/lib/artoo/api/device_event_client.rb +35 -0
- data/lib/artoo/api/route_helpers.rb +222 -0
- data/lib/artoo/connection.rb +2 -1
- data/lib/artoo/robot.rb +1 -1
- data/lib/artoo/robot_class_methods.rb +1 -1
- data/lib/artoo/version.rb +1 -1
- data/test/{api_test.rb → api/api_test.rb} +5 -5
- metadata +16 -14
- data/lib/artoo/api.rb +0 -101
- data/lib/artoo/api_route_helpers.rb +0 -220
- data/lib/artoo/device_event_client.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd71026c937b88a49c8386760572abc6744fb74c
|
4
|
+
data.tar.gz: 0d9006bcde225de347974ca52d3a961c45af4e9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28dad43c2ea5426571f389b3198777c13495794dde7ae09b2868614206889d2dfd295a69b041ea2b038b7d4b7a76dae8779cf773b0969153d5894c008a08004b
|
7
|
+
data.tar.gz: 63c0b5190bc20dc0508128aca8178f6a8f537e4a6ea522abb81a6f2c661c584b347b3c6af23b8edbaf957c06dd7f73bc839649fa015f64d9c8541086f521df0c
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
artoo (1.0.0.
|
5
|
-
celluloid (~> 0.14.
|
6
|
-
celluloid-io (~> 0.14.
|
4
|
+
artoo (1.0.0.rc1)
|
5
|
+
celluloid (~> 0.14.1)
|
6
|
+
celluloid-io (~> 0.14.1)
|
7
7
|
multi_json (~> 1.6)
|
8
8
|
pry (~> 0.9)
|
9
9
|
rake (~> 10.0)
|
10
|
-
reel (~> 0.4.pre)
|
10
|
+
reel (~> 0.4.0.pre)
|
11
11
|
|
12
12
|
GEM
|
13
13
|
remote: http://rubygems.org/
|
14
14
|
specs:
|
15
15
|
bootstrap-sass (2.3.1.0)
|
16
16
|
sass (~> 3.2)
|
17
|
-
celluloid (0.14.
|
17
|
+
celluloid (0.14.1)
|
18
18
|
timers (>= 1.0.0)
|
19
|
-
celluloid-io (0.14.
|
20
|
-
celluloid (>= 0.
|
19
|
+
celluloid-io (0.14.1)
|
20
|
+
celluloid (>= 0.14.1)
|
21
21
|
nio4r (>= 0.4.5)
|
22
22
|
certified (0.1.1)
|
23
23
|
chunky_png (1.2.8)
|
@@ -31,11 +31,6 @@ GEM
|
|
31
31
|
fssm (>= 0.2.7)
|
32
32
|
sass (~> 3.1)
|
33
33
|
dotenv (0.7.0)
|
34
|
-
em-websocket (0.5.0)
|
35
|
-
eventmachine (>= 0.12.9)
|
36
|
-
http_parser.rb (~> 0.5.3)
|
37
|
-
eventmachine (1.0.3)
|
38
|
-
eventmachine (1.0.3-java)
|
39
34
|
execjs (1.4.0)
|
40
35
|
multi_json (~> 1.0)
|
41
36
|
ffi (1.8.1)
|
@@ -57,10 +52,6 @@ GEM
|
|
57
52
|
guard-compass (0.0.6)
|
58
53
|
compass (>= 0.10.5)
|
59
54
|
guard (>= 0.2.1)
|
60
|
-
guard-livereload (1.3.0)
|
61
|
-
em-websocket (>= 0.2.0)
|
62
|
-
guard (>= 1.5.0)
|
63
|
-
multi_json (~> 1.0)
|
64
55
|
guard-sprockets (0.4.3)
|
65
56
|
execjs (~> 1.0)
|
66
57
|
guard (>= 1.1.0)
|
@@ -70,7 +61,6 @@ GEM
|
|
70
61
|
certified
|
71
62
|
http_parser.rb
|
72
63
|
http_parser.rb (0.5.3)
|
73
|
-
http_parser.rb (0.5.3-java)
|
74
64
|
json (1.7.7)
|
75
65
|
json (1.7.7-java)
|
76
66
|
kramdown (1.0.2)
|
@@ -84,7 +74,7 @@ GEM
|
|
84
74
|
mocha (0.14.0)
|
85
75
|
metaclass (~> 0.0.1)
|
86
76
|
multi_json (1.7.3)
|
87
|
-
nio4r (0.4.
|
77
|
+
nio4r (0.4.6)
|
88
78
|
pry (0.9.12.2)
|
89
79
|
coderay (~> 1.0.5)
|
90
80
|
method_source (~> 0.8)
|
@@ -120,7 +110,7 @@ GEM
|
|
120
110
|
thor (0.18.1)
|
121
111
|
tilt (1.4.1)
|
122
112
|
timers (1.1.0)
|
123
|
-
websocket_parser (0.1.
|
113
|
+
websocket_parser (0.1.4)
|
124
114
|
http
|
125
115
|
yard (0.8.6.1)
|
126
116
|
yard-sinatra (1.0.0)
|
@@ -139,7 +129,6 @@ DEPENDENCIES
|
|
139
129
|
foreman
|
140
130
|
guard
|
141
131
|
guard-compass
|
142
|
-
guard-livereload
|
143
132
|
guard-sprockets
|
144
133
|
json (~> 1.7.7)
|
145
134
|
kramdown
|
data/Guardfile
CHANGED
data/artoo.gemspec
CHANGED
@@ -19,13 +19,13 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_runtime_dependency 'celluloid', '~> 0.14.
|
23
|
-
s.add_runtime_dependency 'celluloid-io', '~> 0.14.
|
24
|
-
s.add_runtime_dependency 'reel', '~> 0.4.pre'
|
22
|
+
s.add_runtime_dependency 'celluloid', '~> 0.14.1'
|
23
|
+
s.add_runtime_dependency 'celluloid-io', '~> 0.14.1'
|
24
|
+
s.add_runtime_dependency 'reel', '~> 0.4.0.pre'
|
25
25
|
s.add_runtime_dependency 'multi_json', '~> 1.6'
|
26
26
|
s.add_runtime_dependency 'rake', '~> 10.0'
|
27
27
|
s.add_runtime_dependency 'pry', '~> 0.9'
|
28
28
|
s.add_development_dependency 'minitest', '~> 5.0'
|
29
29
|
s.add_development_dependency 'minitest-happy'
|
30
|
-
s.add_development_dependency 'mocha', '~> 0.14.0
|
30
|
+
s.add_development_dependency 'mocha', '~> 0.14.0'
|
31
31
|
end
|
data/examples/ardrone.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'artoo/robot'
|
2
|
+
|
3
|
+
class DroneRobot < Artoo::Robot
|
4
|
+
connection :drone, :adaptor => :ardrone
|
5
|
+
device :drone, :driver => :ardrone
|
6
|
+
|
7
|
+
#api :host => '127.0.0.1', :port => '8080'
|
8
|
+
|
9
|
+
work do
|
10
|
+
drone.start
|
11
|
+
|
12
|
+
after(10.seconds){
|
13
|
+
drone.take_off
|
14
|
+
drone.hover
|
15
|
+
}
|
16
|
+
after(15.seconds){
|
17
|
+
drone.turn_right(0.3)
|
18
|
+
}
|
19
|
+
after(25.seconds){
|
20
|
+
drone.hover
|
21
|
+
}
|
22
|
+
after(30.seconds){
|
23
|
+
drone.land
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
DRONES = {"192.168.0.43:5556" => "wedge",
|
29
|
+
"192.168.0.44:5556" => "biggs"}
|
30
|
+
robots = []
|
31
|
+
DRONES.each_key {|p|
|
32
|
+
robots << DroneRobot.new(:connections =>
|
33
|
+
{:drone =>
|
34
|
+
{:port => p}})
|
35
|
+
}
|
36
|
+
|
37
|
+
DroneRobot.work!(robots)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'artoo'
|
2
2
|
|
3
|
-
connection :ardrone, :adaptor => :ardrone, :port => '192.168.
|
3
|
+
connection :ardrone, :adaptor => :ardrone, :port => '192.168.0.43:5556'
|
4
4
|
device :drone, :driver => :ardrone, :connection => :ardrone
|
5
5
|
|
6
6
|
connection :arduino, :adaptor => :firmata, :port => "8023"
|
data/examples/conway_sphero.rb
CHANGED
@@ -12,7 +12,7 @@ class ConwaySpheroRobot < Artoo::Robot
|
|
12
12
|
on sphero, :collision => proc { contact }
|
13
13
|
|
14
14
|
every(3.seconds) { movement if alive? }
|
15
|
-
every(
|
15
|
+
every(16.seconds) { birthday if alive? }
|
16
16
|
end
|
17
17
|
|
18
18
|
def alive?; (@alive == true); end
|
@@ -44,7 +44,7 @@ class ConwaySpheroRobot < Artoo::Robot
|
|
44
44
|
|
45
45
|
puts "Happy birthday, #{name}, you are #{@age} and had #{@contacts} contacts."
|
46
46
|
#return if @age <= 3
|
47
|
-
death unless @contacts >=
|
47
|
+
death unless @contacts >= 6 && @contacts < 11
|
48
48
|
reset_contacts
|
49
49
|
end
|
50
50
|
|
@@ -53,13 +53,17 @@ class ConwaySpheroRobot < Artoo::Robot
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
SPHEROS = {
|
56
|
+
SPHEROS = {
|
57
|
+
"127.0.0.1:4560" => "/dev/tty.Sphero-BRG-RN-SPP",
|
57
58
|
"127.0.0.1:4561" => "/dev/tty.Sphero-YBW-RN-SPP",
|
58
59
|
"127.0.0.1:4562" => "/dev/tty.Sphero-BWY-RN-SPP",
|
59
60
|
"127.0.0.1:4563" => "/dev/tty.Sphero-YRR-RN-SPP",
|
60
61
|
"127.0.0.1:4564" => "/dev/tty.Sphero-OBG-RN-SPP",
|
61
62
|
"127.0.0.1:4565" => "/dev/tty.Sphero-GOB-RN-SPP",
|
62
|
-
"127.0.0.1:4566" => "/dev/tty.Sphero-PYG-RN-SPP"
|
63
|
+
"127.0.0.1:4566" => "/dev/tty.Sphero-PYG-RN-SPP",
|
64
|
+
"127.0.0.1:4567" => "/dev/tty.Sphero-PYG-RN-SPP",
|
65
|
+
"127.0.0.1:4568" => "/dev/tty.Sphero-PYG-RN-SPP",
|
66
|
+
"127.0.0.1:4569" => "/dev/tty.Sphero-PYG-RN-SPP"}
|
63
67
|
robots = []
|
64
68
|
SPHEROS.each_key {|p|
|
65
69
|
robots << ConwaySpheroRobot.new(:connections =>
|
data/examples/sphero.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'artoo'
|
2
2
|
|
3
|
-
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:
|
3
|
+
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4569'
|
4
4
|
device :sphero, :driver => :sphero
|
5
5
|
|
6
6
|
work do
|
7
|
-
every(
|
7
|
+
every(1.seconds) do
|
8
8
|
puts "Rolling..."
|
9
9
|
sphero.roll 90, rand(360)
|
10
10
|
end
|
11
|
-
end
|
11
|
+
end
|
data/examples/sphero_color.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'artoo'
|
2
2
|
|
3
|
-
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:
|
3
|
+
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4569'
|
4
4
|
device :sphero, :driver => :sphero
|
5
|
-
|
5
|
+
|
6
6
|
work do
|
7
|
-
|
8
|
-
|
9
|
-
sphero.set_color(
|
10
|
-
|
11
|
-
sphero.roll 60, rand(360)
|
7
|
+
every(1.seconds) do
|
8
|
+
puts "Rolling..."
|
9
|
+
sphero.set_color(rand(255),rand(255),rand(255))
|
10
|
+
sphero.roll 20, rand(360)
|
12
11
|
end
|
13
12
|
end
|
13
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'artoo'
|
2
|
+
|
3
|
+
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4569'
|
4
|
+
device :sphero, :driver => :sphero
|
5
|
+
|
6
|
+
connection :arduino, :adaptor => :firmata, :port => "8023"
|
7
|
+
device :wiichuck, :driver => :wiichuck, :connection => :arduino, :interval => 0.1
|
8
|
+
|
9
|
+
work do
|
10
|
+
init_settings
|
11
|
+
|
12
|
+
on wiichuck, :joystick => proc { |*value|
|
13
|
+
@heading = heading(value[1])
|
14
|
+
}
|
15
|
+
|
16
|
+
every(1.seconds) do
|
17
|
+
puts "Rolling..."
|
18
|
+
sphero.set_color(rand(255),rand(255),rand(255))
|
19
|
+
sphero.roll 20, @heading
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def init_settings
|
24
|
+
sphero.stop
|
25
|
+
@heading = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def heading(value)
|
29
|
+
(180.0 - (Math.atan2(value[:y],value[:x]) * (180.0 / Math::PI))).round
|
30
|
+
end
|
data/examples/sphero_wiichuck.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'artoo'
|
2
2
|
|
3
|
-
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:
|
3
|
+
connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4568'
|
4
4
|
device :sphero, :driver => :sphero
|
5
5
|
|
6
6
|
connection :arduino, :adaptor => :firmata, :port => "8023"
|
@@ -15,7 +15,7 @@ work do
|
|
15
15
|
}
|
16
16
|
every(1.seconds) do
|
17
17
|
puts "Rolling..."
|
18
|
-
sphero.roll
|
18
|
+
sphero.roll 50, @heading
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'reel'
|
2
|
+
require 'artoo/api/route_helpers'
|
3
|
+
require 'artoo/api/device_event_client'
|
4
|
+
|
5
|
+
module Artoo
|
6
|
+
module Api
|
7
|
+
# Artoo API Server provides an interface to communicate with
|
8
|
+
# master class and retrieve information about robots being
|
9
|
+
# controlled
|
10
|
+
class Server < Reel::Server
|
11
|
+
include RouteHelpers
|
12
|
+
|
13
|
+
# Create new API server
|
14
|
+
# @param [String] host
|
15
|
+
# @param [Int] port
|
16
|
+
def initialize(host = "127.0.0.1", port = 3000)
|
17
|
+
super(host, port, &method(:on_connection))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Dispatches connection requests
|
21
|
+
def on_connection(connection)
|
22
|
+
while request = connection.request
|
23
|
+
dispatch!(connection, request)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieve list of robots
|
28
|
+
# @return [JSON] robots
|
29
|
+
get '/robots' do
|
30
|
+
MultiJson.dump(master.robots.collect {|r|r.to_hash})
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieve robot by id
|
34
|
+
# @return [JSON] robot
|
35
|
+
get '/robots/:robotid' do
|
36
|
+
master.robot(@params['robotid']).as_json
|
37
|
+
end
|
38
|
+
|
39
|
+
# Retrieve robot devices
|
40
|
+
# @return [JSON] devices
|
41
|
+
get '/robots/:robotid/devices' do
|
42
|
+
MultiJson.dump(master.robot_devices(@params['robotid']).each_value.collect {|d| d.to_hash})
|
43
|
+
end
|
44
|
+
|
45
|
+
# Retrieve robot device
|
46
|
+
# @return [JSON] device
|
47
|
+
get '/robots/:robotid/devices/:deviceid' do
|
48
|
+
device(@params['robotid'], @params['deviceid']).as_json
|
49
|
+
end
|
50
|
+
|
51
|
+
# Retrieve robot commands
|
52
|
+
# @return [JSON] commands
|
53
|
+
get '/robots/:robotid/devices/:deviceid/commands' do
|
54
|
+
MultiJson.dump(device(@params['robotid'], @params['deviceid']).commands)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Execute robot command
|
58
|
+
# @return [JSON] command
|
59
|
+
post '/robots/:robotid/devices/:deviceid/commands/:commandid' do
|
60
|
+
result = device(@params['robotid'], @params['deviceid']).command(@params['commandid'], command_params)
|
61
|
+
return MultiJson.dump({'result' => result})
|
62
|
+
end
|
63
|
+
|
64
|
+
# Subscribte to robot device events
|
65
|
+
# @return [nil]
|
66
|
+
get_ws '/robots/:robotid/devices/:deviceid/events' do
|
67
|
+
DeviceEventClient.new(@req, device(@params['robotid'], @params['deviceid']).event_topic_name('update'))
|
68
|
+
return nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieve robot connections
|
72
|
+
# @return [JSON] connections
|
73
|
+
get '/robots/:robotid/connections' do
|
74
|
+
MultiJson.dump(master.robot_connections(@params['robotid']).each_value.collect {|c| c.to_hash})
|
75
|
+
end
|
76
|
+
|
77
|
+
# Retrieve robot connection
|
78
|
+
# @return [JSON] connection
|
79
|
+
get '/robots/:robotid/connections/:connectionid' do
|
80
|
+
master.robot_connection(@params['robotid'], @params['connectionid']).as_json
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def master
|
86
|
+
Actor[:master]
|
87
|
+
end
|
88
|
+
|
89
|
+
def device(robot_id, device_id)
|
90
|
+
master.robot_device(robot_id, device_id)
|
91
|
+
end
|
92
|
+
|
93
|
+
def command_params
|
94
|
+
data = MultiJson.load(@req.body, :symbolize_keys => true)
|
95
|
+
if data && params = data[:params]
|
96
|
+
params.size == 1 ? params.first : params
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Artoo
|
2
|
+
module Api
|
3
|
+
# The Artoo::Api::DeviceEventClient class is how a websocket client can subscribe
|
4
|
+
# to event notifications for a specific device.
|
5
|
+
# Example: ardrone nav data
|
6
|
+
class DeviceEventClient
|
7
|
+
include Celluloid
|
8
|
+
include Celluloid::Notifications
|
9
|
+
include Celluloid::Logger
|
10
|
+
|
11
|
+
attr_reader :topic
|
12
|
+
|
13
|
+
# Create new event client
|
14
|
+
# @param [Socket] websocket
|
15
|
+
# @param [String] topic
|
16
|
+
def initialize(websocket, topic)
|
17
|
+
@topic = topic
|
18
|
+
info "Streaming #{@topic} to websocket..."
|
19
|
+
@socket = websocket
|
20
|
+
subscribe(@topic, :notify_event)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Event notification
|
24
|
+
# @param [String] topic
|
25
|
+
# @param [Object] data
|
26
|
+
def notify_event(topic, *data)
|
27
|
+
# TODO: send which topic sent the notification
|
28
|
+
@socket << data.last.to_s
|
29
|
+
rescue Reel::SocketError, Errno::EPIPE
|
30
|
+
info "Device event notification #{topic} websocket disconnected"
|
31
|
+
terminate
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module Artoo
|
2
|
+
module Api
|
3
|
+
# Route helpers used within the Artoo::Api::Server class
|
4
|
+
module RouteHelpers
|
5
|
+
class ResponseHandled < StandardError; end
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
# Path to api/public directory
|
9
|
+
# @return [String] static path
|
10
|
+
def static_path(default=File.join(File.dirname(__FILE__), "..", "..", "..","api/public"))
|
11
|
+
@static_path ||= default
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Hash] routes
|
15
|
+
def routes
|
16
|
+
@routes ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds compiled signature to routes hash
|
20
|
+
# @return [Array] signature
|
21
|
+
def route(verb, path, &block)
|
22
|
+
signature = compile!(verb, path, block, {})
|
23
|
+
(routes[verb] ||= []) << signature
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates method from block, ripped from Sinatra
|
27
|
+
# 'cause it's so sexy in there
|
28
|
+
# @return [Method] generated method
|
29
|
+
def generate_method(method_name, &block)
|
30
|
+
define_method(method_name, &block)
|
31
|
+
method = instance_method method_name
|
32
|
+
remove_method method_name
|
33
|
+
method
|
34
|
+
end
|
35
|
+
|
36
|
+
#@todo Add documentation
|
37
|
+
def compile!(verb, path, block, options = {})
|
38
|
+
options.each_pair { |option, args| send(option, *args) }
|
39
|
+
method_name = "#{verb} #{path}"
|
40
|
+
unbound_method = generate_method(method_name, &block)
|
41
|
+
pattern, keys = compile path
|
42
|
+
conditions, @conditions = @conditions, []
|
43
|
+
|
44
|
+
[ pattern, keys, conditions, block.arity != 0 ?
|
45
|
+
proc { |a,p| unbound_method.bind(a).call(*p) } :
|
46
|
+
proc { |a,p| unbound_method.bind(a).call } ]
|
47
|
+
end
|
48
|
+
|
49
|
+
#@todo Add documentation
|
50
|
+
def compile(path)
|
51
|
+
keys = []
|
52
|
+
if path.respond_to? :to_str
|
53
|
+
ignore = ""
|
54
|
+
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
|
55
|
+
ignore << escaped(c).join if c.match(/[\.@]/)
|
56
|
+
patt = encoded(c)
|
57
|
+
patt.gsub(/%[\da-fA-F]{2}/) do |match|
|
58
|
+
match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
62
|
+
if match == "*"
|
63
|
+
keys << 'splat'
|
64
|
+
"(.*?)"
|
65
|
+
else
|
66
|
+
keys << $2[1..-1]
|
67
|
+
ignore_pattern = safe_ignore(ignore)
|
68
|
+
|
69
|
+
ignore_pattern
|
70
|
+
end
|
71
|
+
end
|
72
|
+
[/\A#{pattern}\z/, keys]
|
73
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
74
|
+
[path, path.keys]
|
75
|
+
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
76
|
+
[path, path.names]
|
77
|
+
elsif path.respond_to? :match
|
78
|
+
[path, keys]
|
79
|
+
else
|
80
|
+
raise TypeError, path
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#@todo Add documentation
|
85
|
+
def safe_ignore(ignore)
|
86
|
+
unsafe_ignore = []
|
87
|
+
ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
|
88
|
+
unsafe_ignore << hex[1..2]
|
89
|
+
''
|
90
|
+
end
|
91
|
+
unsafe_patterns = unsafe_ignore.map do |unsafe|
|
92
|
+
chars = unsafe.split(//).map do |char|
|
93
|
+
if char =~ /[A-Z]/
|
94
|
+
char <<= char.tr('A-Z', 'a-z')
|
95
|
+
end
|
96
|
+
char
|
97
|
+
end
|
98
|
+
|
99
|
+
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
100
|
+
end
|
101
|
+
if unsafe_patterns.length > 0
|
102
|
+
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
103
|
+
else
|
104
|
+
"([^#{ignore}/?#]+)"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Route function for get
|
109
|
+
def get(path, &block)
|
110
|
+
route 'GET', path, &block
|
111
|
+
end
|
112
|
+
|
113
|
+
# Route function for get_ws
|
114
|
+
def get_ws(path, &block)
|
115
|
+
route 'GET', path, &block
|
116
|
+
end
|
117
|
+
|
118
|
+
# Route function for post
|
119
|
+
def post(path, &block)
|
120
|
+
route 'POST', path, &block
|
121
|
+
end
|
122
|
+
|
123
|
+
# Route function for put
|
124
|
+
def put(path, &block)
|
125
|
+
route 'PUT', path, &block
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module InstanceMethods
|
130
|
+
## Handle the request
|
131
|
+
def dispatch!(connection, req)
|
132
|
+
resp = catch(:halt) do
|
133
|
+
try_static! connection, req
|
134
|
+
route! connection, req
|
135
|
+
end
|
136
|
+
if resp && !resp.nil?
|
137
|
+
return if req.is_a?(Reel::WebSocket)
|
138
|
+
status, body = resp
|
139
|
+
begin
|
140
|
+
req.respond status, body
|
141
|
+
rescue Errno::EAGAIN
|
142
|
+
retry
|
143
|
+
end
|
144
|
+
else
|
145
|
+
req.respond :not_found, "NOT FOUND"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Exit the current block, halts any further processing
|
150
|
+
# of the request, and returns the specified response.
|
151
|
+
def halt(*response)
|
152
|
+
response = response.first if response.length == 1
|
153
|
+
throw :halt, response
|
154
|
+
end
|
155
|
+
|
156
|
+
def try_static!(connection, req)
|
157
|
+
fpath = req.url == '/' ? 'index.html' : req.url[1..-1]
|
158
|
+
filepath = File.expand_path(fpath, self.class.static_path)
|
159
|
+
if File.file?(filepath)
|
160
|
+
# TODO: stream this?
|
161
|
+
data = open(filepath).read
|
162
|
+
halt :ok, data
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def route!(connection, req)
|
167
|
+
if routes = self.class.routes[req.method]
|
168
|
+
routes.each do |pattern, keys, conditions, block|
|
169
|
+
route = req.url
|
170
|
+
next unless match = pattern.match(route)
|
171
|
+
values = match.captures.to_a.map { |v| URI.decode_www_form_component(v) if v }
|
172
|
+
if values.any?
|
173
|
+
params = {}
|
174
|
+
keys.zip(values) { |k,v| Array === params[k] ? params[k] << v : params[k] = v if v }
|
175
|
+
@params = params
|
176
|
+
end
|
177
|
+
|
178
|
+
@connection = connection
|
179
|
+
@req = req
|
180
|
+
|
181
|
+
begin
|
182
|
+
body = block ? block[self, values] : yield(self, values)
|
183
|
+
halt [:ok, body]
|
184
|
+
rescue Exception => e
|
185
|
+
p [:e, e]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
192
|
+
# Fixes encoding issues by
|
193
|
+
# * defaulting to UTF-8
|
194
|
+
# * casting params to Encoding.default_external
|
195
|
+
#
|
196
|
+
# The latter might not be necessary if Rack handles it one day.
|
197
|
+
# Keep an eye on Rack's LH #100.
|
198
|
+
def force_encoding(*args) settings.force_encoding(*args) end
|
199
|
+
if defined? Encoding
|
200
|
+
def self.force_encoding(data, encoding = default_encoding)
|
201
|
+
return if data == settings || data.is_a?(Tempfile)
|
202
|
+
if data.respond_to? :force_encoding
|
203
|
+
data.force_encoding(encoding).encode!
|
204
|
+
elsif data.respond_to? :each_value
|
205
|
+
data.each_value { |v| force_encoding(v, encoding) }
|
206
|
+
elsif data.respond_to? :each
|
207
|
+
data.each { |v| force_encoding(v, encoding) }
|
208
|
+
end
|
209
|
+
data
|
210
|
+
end
|
211
|
+
else
|
212
|
+
def self.force_encoding(data, *) data end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.included(receiver)
|
217
|
+
receiver.extend ClassMethods
|
218
|
+
receiver.send :include, InstanceMethods
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/artoo/connection.rb
CHANGED
@@ -13,12 +13,13 @@ module Artoo
|
|
13
13
|
|
14
14
|
# Create new connection
|
15
15
|
# @param [Hash] params
|
16
|
+
# @option params :id [String]
|
16
17
|
# @option params :name [String]
|
17
18
|
# @option params :parent [String]
|
18
19
|
# @option params :adaptor [String]
|
19
20
|
# @option params :port [Integer]
|
20
21
|
def initialize(params={})
|
21
|
-
@connection_id = rand(10000)
|
22
|
+
@connection_id = params[:id] || rand(10000).to_s
|
22
23
|
@name = params[:name].to_s
|
23
24
|
@port = Port.new(params[:port])
|
24
25
|
@parent = params[:parent]
|
data/lib/artoo/robot.rb
CHANGED
data/lib/artoo/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + "
|
2
|
-
require File.expand_path(File.dirname(__FILE__) + "
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../lib/artoo/api/api")
|
3
3
|
|
4
4
|
class ExampleRequest
|
5
5
|
extend Forwardable
|
@@ -34,11 +34,11 @@ class ExampleRequest
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
describe Artoo::Api do
|
38
|
-
describe Artoo::
|
37
|
+
describe Artoo::Api::Server do
|
38
|
+
describe Artoo::Api::RouteHelpers do
|
39
39
|
|
40
40
|
class DummyClass
|
41
|
-
include Artoo::
|
41
|
+
include Artoo::Api::RouteHelpers
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should have a list of routes" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: artoo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ron Evans
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-
|
15
|
+
date: 2013-07-01 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: celluloid
|
@@ -20,42 +20,42 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.14.
|
23
|
+
version: 0.14.1
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
28
|
- - ~>
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: 0.14.
|
30
|
+
version: 0.14.1
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: celluloid-io
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 0.14.
|
37
|
+
version: 0.14.1
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 0.14.
|
44
|
+
version: 0.14.1
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
46
|
name: reel
|
47
47
|
requirement: !ruby/object:Gem::Requirement
|
48
48
|
requirements:
|
49
49
|
- - ~>
|
50
50
|
- !ruby/object:Gem::Version
|
51
|
-
version: 0.4.pre
|
51
|
+
version: 0.4.0.pre
|
52
52
|
type: :runtime
|
53
53
|
prerelease: false
|
54
54
|
version_requirements: !ruby/object:Gem::Requirement
|
55
55
|
requirements:
|
56
56
|
- - ~>
|
57
57
|
- !ruby/object:Gem::Version
|
58
|
-
version: 0.4.pre
|
58
|
+
version: 0.4.0.pre
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: multi_json
|
61
61
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,14 +132,14 @@ dependencies:
|
|
132
132
|
requirements:
|
133
133
|
- - ~>
|
134
134
|
- !ruby/object:Gem::Version
|
135
|
-
version: 0.14.0
|
135
|
+
version: 0.14.0
|
136
136
|
type: :development
|
137
137
|
prerelease: false
|
138
138
|
version_requirements: !ruby/object:Gem::Requirement
|
139
139
|
requirements:
|
140
140
|
- - ~>
|
141
141
|
- !ruby/object:Gem::Version
|
142
|
-
version: 0.14.0
|
142
|
+
version: 0.14.0
|
143
143
|
description: Ruby-based microframework for robotics
|
144
144
|
email:
|
145
145
|
- artoo@hybridgroup.com
|
@@ -192,6 +192,7 @@ files:
|
|
192
192
|
- bin/retry.sh
|
193
193
|
- bin/robi
|
194
194
|
- examples/ardrone.rb
|
195
|
+
- examples/ardrone_multi.rb
|
195
196
|
- examples/ardrone_nav.rb
|
196
197
|
- examples/ardrone_nav_video.rb
|
197
198
|
- examples/ardrone_nav_video_wii.rb
|
@@ -213,6 +214,7 @@ files:
|
|
213
214
|
- examples/roomba_wiichuck.rb
|
214
215
|
- examples/sphero.rb
|
215
216
|
- examples/sphero_color.rb
|
217
|
+
- examples/sphero_color_wiichuck.rb
|
216
218
|
- examples/sphero_cycle.rb
|
217
219
|
- examples/sphero_firmata.rb
|
218
220
|
- examples/sphero_messages.rb
|
@@ -224,13 +226,13 @@ files:
|
|
224
226
|
- lib/artoo/adaptors/adaptor.rb
|
225
227
|
- lib/artoo/adaptors/loopback.rb
|
226
228
|
- lib/artoo/adaptors/test.rb
|
227
|
-
- lib/artoo/api.rb
|
228
|
-
- lib/artoo/
|
229
|
+
- lib/artoo/api/api.rb
|
230
|
+
- lib/artoo/api/device_event_client.rb
|
231
|
+
- lib/artoo/api/route_helpers.rb
|
229
232
|
- lib/artoo/basic.rb
|
230
233
|
- lib/artoo/connection.rb
|
231
234
|
- lib/artoo/delegator.rb
|
232
235
|
- lib/artoo/device.rb
|
233
|
-
- lib/artoo/device_event_client.rb
|
234
236
|
- lib/artoo/drivers/counter.rb
|
235
237
|
- lib/artoo/drivers/driver.rb
|
236
238
|
- lib/artoo/drivers/passthru.rb
|
@@ -249,7 +251,7 @@ files:
|
|
249
251
|
- lib/artoo/version.rb
|
250
252
|
- test/adaptors/adaptor_test.rb
|
251
253
|
- test/adaptors/loopback_test.rb
|
252
|
-
- test/api_test.rb
|
254
|
+
- test/api/api_test.rb
|
253
255
|
- test/artoo_test.rb
|
254
256
|
- test/connection_test.rb
|
255
257
|
- test/delegator_test.rb
|
data/lib/artoo/api.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
require 'reel'
|
2
|
-
require 'artoo/api_route_helpers'
|
3
|
-
require 'artoo/device_event_client'
|
4
|
-
|
5
|
-
module Artoo
|
6
|
-
# Artoo API Server provides an interface to communicate with
|
7
|
-
# master class and retrieve information about robots being
|
8
|
-
# controlled
|
9
|
-
class Api < Reel::Server
|
10
|
-
include Artoo::ApiRouteHelpers
|
11
|
-
|
12
|
-
# Create new API server
|
13
|
-
# @param [String] host
|
14
|
-
# @param [Int] port
|
15
|
-
def initialize(host = "127.0.0.1", port = 3000)
|
16
|
-
super(host, port, &method(:on_connection))
|
17
|
-
end
|
18
|
-
|
19
|
-
# Dispatches connection requests
|
20
|
-
def on_connection(connection)
|
21
|
-
while request = connection.request
|
22
|
-
dispatch!(connection, request)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Retrieve list of robots
|
27
|
-
# @return [JSON] robots
|
28
|
-
get '/robots' do
|
29
|
-
MultiJson.dump(master.robots.collect {|r|r.to_hash})
|
30
|
-
end
|
31
|
-
|
32
|
-
# Retrieve robot by id
|
33
|
-
# @return [JSON] robot
|
34
|
-
get '/robots/:robotid' do
|
35
|
-
master.robot(@params['robotid']).as_json
|
36
|
-
end
|
37
|
-
|
38
|
-
# Retrieve robot devices
|
39
|
-
# @return [JSON] devices
|
40
|
-
get '/robots/:robotid/devices' do
|
41
|
-
MultiJson.dump(master.robot_devices(@params['robotid']).each_value.collect {|d| d.to_hash})
|
42
|
-
end
|
43
|
-
|
44
|
-
# Retrieve robot device
|
45
|
-
# @return [JSON] device
|
46
|
-
get '/robots/:robotid/devices/:deviceid' do
|
47
|
-
device(@params['robotid'], @params['deviceid']).as_json
|
48
|
-
end
|
49
|
-
|
50
|
-
# Retrieve robot commands
|
51
|
-
# @return [JSON] commands
|
52
|
-
get '/robots/:robotid/devices/:deviceid/commands' do
|
53
|
-
MultiJson.dump(device(@params['robotid'], @params['deviceid']).commands)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Execute robot command
|
57
|
-
# @return [JSON] command
|
58
|
-
post '/robots/:robotid/devices/:deviceid/commands/:commandid' do
|
59
|
-
result = device(@params['robotid'], @params['deviceid']).command(@params['commandid'], command_params)
|
60
|
-
return MultiJson.dump({'result' => result})
|
61
|
-
end
|
62
|
-
|
63
|
-
# Subscribte to robot device events
|
64
|
-
# @return [nil]
|
65
|
-
get_ws '/robots/:robotid/devices/:deviceid/events' do
|
66
|
-
DeviceEventClient.new(@req, device(@params['robotid'], @params['deviceid']).event_topic_name('update'))
|
67
|
-
return nil
|
68
|
-
end
|
69
|
-
|
70
|
-
# Retrieve robot connections
|
71
|
-
# @return [JSON] connections
|
72
|
-
get '/robots/:robotid/connections' do
|
73
|
-
MultiJson.dump(master.robot_connections(@params['robotid']).each_value.collect {|c| c.to_hash})
|
74
|
-
end
|
75
|
-
|
76
|
-
# Retrieve robot connection
|
77
|
-
# @return [JSON] connection
|
78
|
-
get '/robots/:robotid/connections/:connectionid' do
|
79
|
-
master.robot_connection(@params['robotid'], @params['connectionid']).as_json
|
80
|
-
end
|
81
|
-
|
82
|
-
protected
|
83
|
-
|
84
|
-
def master
|
85
|
-
Actor[:master]
|
86
|
-
end
|
87
|
-
|
88
|
-
def device(robot_id, device_id)
|
89
|
-
master.robot_device(robot_id, device_id)
|
90
|
-
end
|
91
|
-
|
92
|
-
def command_params
|
93
|
-
data = MultiJson.load(@req.body, :symbolize_keys => true)
|
94
|
-
if data && params = data[:params]
|
95
|
-
params.size == 1 ? params.first : params
|
96
|
-
else
|
97
|
-
nil
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
@@ -1,220 +0,0 @@
|
|
1
|
-
module Artoo
|
2
|
-
# Route helpers used within the Artoo::Api class
|
3
|
-
module ApiRouteHelpers
|
4
|
-
class ResponseHandled < StandardError; end
|
5
|
-
module ClassMethods
|
6
|
-
|
7
|
-
# Path to api/public directory
|
8
|
-
# @return [String] static path
|
9
|
-
def static_path(default=File.join(File.dirname(__FILE__), "..", "..", "api/public"))
|
10
|
-
@static_path ||= default
|
11
|
-
end
|
12
|
-
|
13
|
-
# @return [Hash] routes
|
14
|
-
def routes
|
15
|
-
@routes ||= {}
|
16
|
-
end
|
17
|
-
|
18
|
-
# Adds compiled signature to routes hash
|
19
|
-
# @return [Array] signature
|
20
|
-
def route(verb, path, &block)
|
21
|
-
signature = compile!(verb, path, block, {})
|
22
|
-
(routes[verb] ||= []) << signature
|
23
|
-
end
|
24
|
-
|
25
|
-
# Creates method from block, ripped from Sinatra
|
26
|
-
# 'cause it's so sexy in there
|
27
|
-
# @return [Method] generated method
|
28
|
-
def generate_method(method_name, &block)
|
29
|
-
define_method(method_name, &block)
|
30
|
-
method = instance_method method_name
|
31
|
-
remove_method method_name
|
32
|
-
method
|
33
|
-
end
|
34
|
-
|
35
|
-
#@todo Add documentation
|
36
|
-
def compile!(verb, path, block, options = {})
|
37
|
-
options.each_pair { |option, args| send(option, *args) }
|
38
|
-
method_name = "#{verb} #{path}"
|
39
|
-
unbound_method = generate_method(method_name, &block)
|
40
|
-
pattern, keys = compile path
|
41
|
-
conditions, @conditions = @conditions, []
|
42
|
-
|
43
|
-
[ pattern, keys, conditions, block.arity != 0 ?
|
44
|
-
proc { |a,p| unbound_method.bind(a).call(*p) } :
|
45
|
-
proc { |a,p| unbound_method.bind(a).call } ]
|
46
|
-
end
|
47
|
-
|
48
|
-
#@todo Add documentation
|
49
|
-
def compile(path)
|
50
|
-
keys = []
|
51
|
-
if path.respond_to? :to_str
|
52
|
-
ignore = ""
|
53
|
-
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
|
54
|
-
ignore << escaped(c).join if c.match(/[\.@]/)
|
55
|
-
patt = encoded(c)
|
56
|
-
patt.gsub(/%[\da-fA-F]{2}/) do |match|
|
57
|
-
match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
|
58
|
-
end
|
59
|
-
end
|
60
|
-
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
61
|
-
if match == "*"
|
62
|
-
keys << 'splat'
|
63
|
-
"(.*?)"
|
64
|
-
else
|
65
|
-
keys << $2[1..-1]
|
66
|
-
ignore_pattern = safe_ignore(ignore)
|
67
|
-
|
68
|
-
ignore_pattern
|
69
|
-
end
|
70
|
-
end
|
71
|
-
[/\A#{pattern}\z/, keys]
|
72
|
-
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
73
|
-
[path, path.keys]
|
74
|
-
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
75
|
-
[path, path.names]
|
76
|
-
elsif path.respond_to? :match
|
77
|
-
[path, keys]
|
78
|
-
else
|
79
|
-
raise TypeError, path
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
#@todo Add documentation
|
84
|
-
def safe_ignore(ignore)
|
85
|
-
unsafe_ignore = []
|
86
|
-
ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
|
87
|
-
unsafe_ignore << hex[1..2]
|
88
|
-
''
|
89
|
-
end
|
90
|
-
unsafe_patterns = unsafe_ignore.map do |unsafe|
|
91
|
-
chars = unsafe.split(//).map do |char|
|
92
|
-
if char =~ /[A-Z]/
|
93
|
-
char <<= char.tr('A-Z', 'a-z')
|
94
|
-
end
|
95
|
-
char
|
96
|
-
end
|
97
|
-
|
98
|
-
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
99
|
-
end
|
100
|
-
if unsafe_patterns.length > 0
|
101
|
-
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
102
|
-
else
|
103
|
-
"([^#{ignore}/?#]+)"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Route function for get
|
108
|
-
def get(path, &block)
|
109
|
-
route 'GET', path, &block
|
110
|
-
end
|
111
|
-
|
112
|
-
# Route function for get_ws
|
113
|
-
def get_ws(path, &block)
|
114
|
-
route 'GET', path, &block
|
115
|
-
end
|
116
|
-
|
117
|
-
# Route function for post
|
118
|
-
def post(path, &block)
|
119
|
-
route 'POST', path, &block
|
120
|
-
end
|
121
|
-
|
122
|
-
# Route function for put
|
123
|
-
def put(path, &block)
|
124
|
-
route 'PUT', path, &block
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
module InstanceMethods
|
129
|
-
## Handle the request
|
130
|
-
def dispatch!(connection, req)
|
131
|
-
resp = catch(:halt) do
|
132
|
-
try_static! connection, req
|
133
|
-
route! connection, req
|
134
|
-
end
|
135
|
-
if resp && !resp.nil?
|
136
|
-
return if req.is_a?(Reel::WebSocket)
|
137
|
-
status, body = resp
|
138
|
-
begin
|
139
|
-
req.respond status, body
|
140
|
-
rescue Errno::EAGAIN
|
141
|
-
retry
|
142
|
-
end
|
143
|
-
else
|
144
|
-
req.respond :not_found, "NOT FOUND"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Exit the current block, halts any further processing
|
149
|
-
# of the request, and returns the specified response.
|
150
|
-
def halt(*response)
|
151
|
-
response = response.first if response.length == 1
|
152
|
-
throw :halt, response
|
153
|
-
end
|
154
|
-
|
155
|
-
def try_static!(connection, req)
|
156
|
-
fpath = req.url == '/' ? 'index.html' : req.url[1..-1]
|
157
|
-
filepath = File.expand_path(fpath, self.class.static_path)
|
158
|
-
if File.file?(filepath)
|
159
|
-
# TODO: stream this?
|
160
|
-
data = open(filepath).read
|
161
|
-
halt :ok, data
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def route!(connection, req)
|
166
|
-
if routes = self.class.routes[req.method]
|
167
|
-
routes.each do |pattern, keys, conditions, block|
|
168
|
-
route = req.url
|
169
|
-
next unless match = pattern.match(route)
|
170
|
-
values = match.captures.to_a.map { |v| URI.decode_www_form_component(v) if v }
|
171
|
-
if values.any?
|
172
|
-
params = {}
|
173
|
-
keys.zip(values) { |k,v| Array === params[k] ? params[k] << v : params[k] = v if v }
|
174
|
-
@params = params
|
175
|
-
end
|
176
|
-
|
177
|
-
@connection = connection
|
178
|
-
@req = req
|
179
|
-
|
180
|
-
begin
|
181
|
-
body = block ? block[self, values] : yield(self, values)
|
182
|
-
halt [:ok, body]
|
183
|
-
rescue Exception => e
|
184
|
-
p [:e, e]
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
nil
|
189
|
-
end
|
190
|
-
|
191
|
-
# Fixes encoding issues by
|
192
|
-
# * defaulting to UTF-8
|
193
|
-
# * casting params to Encoding.default_external
|
194
|
-
#
|
195
|
-
# The latter might not be necessary if Rack handles it one day.
|
196
|
-
# Keep an eye on Rack's LH #100.
|
197
|
-
def force_encoding(*args) settings.force_encoding(*args) end
|
198
|
-
if defined? Encoding
|
199
|
-
def self.force_encoding(data, encoding = default_encoding)
|
200
|
-
return if data == settings || data.is_a?(Tempfile)
|
201
|
-
if data.respond_to? :force_encoding
|
202
|
-
data.force_encoding(encoding).encode!
|
203
|
-
elsif data.respond_to? :each_value
|
204
|
-
data.each_value { |v| force_encoding(v, encoding) }
|
205
|
-
elsif data.respond_to? :each
|
206
|
-
data.each { |v| force_encoding(v, encoding) }
|
207
|
-
end
|
208
|
-
data
|
209
|
-
end
|
210
|
-
else
|
211
|
-
def self.force_encoding(data, *) data end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def self.included(receiver)
|
216
|
-
receiver.extend ClassMethods
|
217
|
-
receiver.send :include, InstanceMethods
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Artoo
|
2
|
-
# The Artoo::DeviceEventClient class is how a websocket client can subscribe
|
3
|
-
# to event notifications for a specific device.
|
4
|
-
# Example: ardrone nav data
|
5
|
-
class DeviceEventClient
|
6
|
-
include Celluloid
|
7
|
-
include Celluloid::Notifications
|
8
|
-
include Celluloid::Logger
|
9
|
-
|
10
|
-
attr_reader :topic
|
11
|
-
|
12
|
-
# Create new event client
|
13
|
-
# @param [Socket] websocket
|
14
|
-
# @param [String] topic
|
15
|
-
def initialize(websocket, topic)
|
16
|
-
@topic = topic
|
17
|
-
info "Streaming #{@topic} to websocket..."
|
18
|
-
@socket = websocket
|
19
|
-
subscribe(@topic, :notify_event)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Event notification
|
23
|
-
# @param [String] topic
|
24
|
-
# @param [Object] data
|
25
|
-
def notify_event(topic, *data)
|
26
|
-
# TODO: send which topic sent the notification
|
27
|
-
@socket << data.last.to_s
|
28
|
-
rescue Reel::SocketError, Errno::EPIPE
|
29
|
-
info "Device event notification #{topic} websocket disconnected"
|
30
|
-
terminate
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|