artoo 1.0.0.pre → 1.0.0.rc1
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.
- 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
|