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 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