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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 731043a8242e8887c4cbff414816cdf7eadae2af
4
- data.tar.gz: 6b0f65cd67cefd32098bea77d87adefcf7032d60
3
+ metadata.gz: bd71026c937b88a49c8386760572abc6744fb74c
4
+ data.tar.gz: 0d9006bcde225de347974ca52d3a961c45af4e9b
5
5
  SHA512:
6
- metadata.gz: eddbdcfe51452b114468046a6e2d0fd4e3fd160ec377ab93a903fab4ce01bd60f6081b390db66e72beac6129828c4710a3ff774abef272def8220ac2cb414566
7
- data.tar.gz: 6392e99e47cd6eaddcb69d98777afcf3cf4000a721632b42bfceb18242c2965c7fc150b3d32b87cf1d75fa19d39f2909d5f7b604c8694cefa827cdb01e08500d
6
+ metadata.gz: 28dad43c2ea5426571f389b3198777c13495794dde7ae09b2868614206889d2dfd295a69b041ea2b038b7d4b7a76dae8779cf773b0969153d5894c008a08004b
7
+ data.tar.gz: 63c0b5190bc20dc0508128aca8178f6a8f537e4a6ea522abb81a6f2c661c584b347b3c6af23b8edbaf957c06dd7f73bc839649fa015f64d9c8541086f521df0c
data/Gemfile CHANGED
@@ -10,7 +10,6 @@ gem 'sprockets'
10
10
  gem 'compass'
11
11
  gem 'bootstrap-sass'
12
12
  gem 'guard'
13
- gem 'guard-livereload'
14
13
  gem 'guard-sprockets'
15
14
  gem 'guard-compass'
16
15
  gem 'rb-inotify', '~> 0.8.8'
data/Gemfile.lock CHANGED
@@ -1,23 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- artoo (1.0.0.pre)
5
- celluloid (~> 0.14.0)
6
- celluloid-io (~> 0.14.0)
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.0)
17
+ celluloid (0.14.1)
18
18
  timers (>= 1.0.0)
19
- celluloid-io (0.14.0)
20
- celluloid (>= 0.13.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.5)
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.2)
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
@@ -8,8 +8,3 @@ end
8
8
  guard 'compass', :configuration_file => 'api/assets/compass.rb', :project_path => 'api/assets' do
9
9
  watch(%r{api(/assets/stylesheets/(.+\.(scss)))})
10
10
  end
11
-
12
- guard 'livereload' do
13
- watch(%r{api(/public/(.+\.(css|js|html)))})
14
- end
15
-
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.0'
23
- s.add_runtime_dependency 'celluloid-io', '~> 0.14.0'
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.alpha'
30
+ s.add_development_dependency 'mocha', '~> 0.14.0'
31
31
  end
data/examples/ardrone.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'artoo'
2
2
 
3
- connection :ardrone, :adaptor => :ardrone, :port => '192.168.1.1:5556'
3
+ connection :ardrone, :adaptor => :ardrone, :port => '192.168.0.43:5556'
4
4
  device :drone, :driver => :ardrone, :connection => :ardrone
5
5
 
6
6
  work do
@@ -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.1.1:5556'
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"
@@ -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(10.seconds) { birthday if alive? }
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 >= 3 && @contacts < 5
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 = {"127.0.0.1:4560" => "/dev/tty.Sphero-BRG-RN-SPP",
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:4560'
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(3.seconds) do
7
+ every(1.seconds) do
8
8
  puts "Rolling..."
9
9
  sphero.roll 90, rand(360)
10
10
  end
11
- end
11
+ end
@@ -1,13 +1,13 @@
1
1
  require 'artoo'
2
2
 
3
- connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4560'
3
+ connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4569'
4
4
  device :sphero, :driver => :sphero
5
-
5
+
6
6
  work do
7
- @count = 1
8
- every(3.seconds) do
9
- sphero.set_color(@count % 2 == 0 ? :green : :blue)
10
- @count += 1
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
@@ -1,6 +1,6 @@
1
1
  require 'artoo'
2
2
 
3
- connection :sphero, :adaptor => :sphero, :port => '127.0.0.1:4560'
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 20, @heading
18
+ sphero.roll 50, @heading
19
19
  end
20
20
  end
21
21
 
@@ -60,6 +60,7 @@ module Artoo
60
60
  def connect_to_tcp
61
61
  @socket ||= TCPSocket.new(port.host, port.port)
62
62
  end
63
+
63
64
  # @return [UDPSocket] UDP socket connection
64
65
  def connect_to_udp
65
66
  @udp_socket ||= UDPSocket.new
@@ -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
@@ -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
@@ -9,7 +9,7 @@ require 'artoo/basic'
9
9
  require 'artoo/connection'
10
10
  require 'artoo/device'
11
11
  require 'artoo/events'
12
- require 'artoo/api'
12
+ require 'artoo/api/api'
13
13
  require 'artoo/master'
14
14
  require 'artoo/port'
15
15
  require 'artoo/utility'
@@ -111,7 +111,7 @@ module Artoo
111
111
  end
112
112
 
113
113
  def start_api
114
- Celluloid::Actor[:api] = Api.new(self.api_host, self.api_port) if self.use_api
114
+ Celluloid::Actor[:api] = Api::Server.new(self.api_host, self.api_port) if self.use_api
115
115
  end
116
116
 
117
117
  # Master actor
data/lib/artoo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Artoo
2
2
  unless const_defined?('VERSION')
3
- VERSION = "1.0.0.pre"
3
+ VERSION = "1.0.0.rc1"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
- require File.expand_path(File.dirname(__FILE__) + "/test_helper")
2
- require File.expand_path(File.dirname(__FILE__) + "/../lib/artoo/api")
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::ApiRouteHelpers do
37
+ describe Artoo::Api::Server do
38
+ describe Artoo::Api::RouteHelpers do
39
39
 
40
40
  class DummyClass
41
- include Artoo::ApiRouteHelpers
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.pre
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-05-24 00:00:00.000000000 Z
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.0
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.0
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.0
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.0
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.alpha
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.alpha
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/api_route_helpers.rb
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