artoo 1.6.7 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +6 -20
  4. data/artoo.gemspec +7 -7
  5. data/bin/artoo +0 -8
  6. data/examples/ardrone_nav_video_wii.rb +1 -1
  7. data/examples/ardrone_nav_wiiclassic.rb +1 -1
  8. data/examples/ardrone_wiiclassic.rb +1 -1
  9. data/examples/firmata.rb +1 -1
  10. data/examples/firmata_button.rb +1 -1
  11. data/examples/hello_api_multiple.rb +1 -1
  12. data/examples/roomba_wiichuck.rb +1 -1
  13. data/examples/sphero.rb +2 -2
  14. data/examples/sphero_color.rb +1 -1
  15. data/examples/sphero_color_wiichuck.rb +2 -2
  16. data/examples/sphero_cycle.rb +1 -1
  17. data/examples/sphero_messages.rb +1 -1
  18. data/examples/sphero_pebble.rb +42 -0
  19. data/examples/sphero_wiichuck.rb +3 -3
  20. data/examples/test_bot.rb +23 -0
  21. data/examples/wiichuck.rb +1 -1
  22. data/examples/wiiclassic.rb +1 -1
  23. data/lib/artoo/api/api.rb +93 -30
  24. data/lib/artoo/api/device_event_client.rb +15 -10
  25. data/lib/artoo/api/route_helpers.rb +14 -3
  26. data/lib/artoo/connection.rb +8 -5
  27. data/lib/artoo/device.rb +29 -11
  28. data/lib/artoo/drivers/driver.rb +4 -0
  29. data/lib/artoo/drivers/{pinger.rb → ping.rb} +6 -6
  30. data/lib/artoo/interfaces/interface.rb +37 -0
  31. data/lib/artoo/interfaces/ping.rb +17 -0
  32. data/lib/artoo/interfaces/rover.rb +35 -0
  33. data/lib/artoo/master.rb +45 -3
  34. data/lib/artoo/robot.rb +33 -8
  35. data/lib/artoo/utility.rb +10 -0
  36. data/lib/artoo/version.rb +1 -1
  37. data/test/api/api_routes_test.rb +46 -0
  38. data/test/connection_test.rb +2 -2
  39. data/test/device_test.rb +8 -1
  40. data/test/interfaces/interface_test.rb +29 -0
  41. data/test/master_test.rb +13 -7
  42. metadata +32 -28
  43. data/Gemfile.lock +0 -88
  44. data/lib/artoo/commands/bluetooth.rb +0 -74
  45. data/lib/artoo/commands/scan.rb +0 -42
  46. data/lib/artoo/ext/actor.rb +0 -18
  47. data/lib/artoo/ext/timers.rb +0 -41
@@ -9,7 +9,7 @@ module Artoo
9
9
  include Artoo::Utility
10
10
  include Comparable
11
11
 
12
- attr_reader :parent, :name, :port, :adaptor, :connection_id
12
+ attr_reader :parent, :name, :port, :adaptor, :connection_id, :details
13
13
 
14
14
  # Create new connection
15
15
  # @param [Hash] params
@@ -23,6 +23,7 @@ module Artoo
23
23
  @name = params[:name].to_s
24
24
  @port = Port.new(params[:port])
25
25
  @parent = params[:parent]
26
+ @details = remove_keys(params, :name, :parent, :id, :loopback)
26
27
 
27
28
  require_adaptor(params[:adaptor] || :loopback, params)
28
29
  end
@@ -58,10 +59,8 @@ module Artoo
58
59
  def to_hash
59
60
  {
60
61
  :name => name,
61
- :connection_id => connection_id,
62
- :port => port.to_s,
63
62
  :adaptor => adaptor_name.to_s.gsub(/^.*::/, ''),
64
- :connected => connected?
63
+ :details => @details
65
64
  }
66
65
  end
67
66
 
@@ -90,11 +89,15 @@ module Artoo
90
89
  return nil
91
90
  end
92
91
 
92
+ def respond_to_missing?(method_name, include_private = false)
93
+ # TODO: verify that the adaptor supprts the method we're calling
94
+ true
95
+ end
96
+
93
97
  private
94
98
 
95
99
  def require_adaptor(type, params)
96
100
  if Artoo::Robot.test?
97
- original_type = type
98
101
  type = :test
99
102
  end
100
103
 
data/lib/artoo/device.rb CHANGED
@@ -6,7 +6,7 @@ module Artoo
6
6
  include Celluloid
7
7
  include Artoo::Utility
8
8
 
9
- attr_reader :parent, :name, :driver, :pin, :connection, :interval
9
+ attr_reader :parent, :name, :driver, :pin, :connection, :interval, :interface, :details
10
10
 
11
11
  # Create new device
12
12
  # @param [Hash] params
@@ -17,11 +17,15 @@ module Artoo
17
17
  # @option params :interval [String]
18
18
  # @option params :driver [String]
19
19
  def initialize(params={})
20
- @name = params[:name].to_s
21
- @pin = params[:pin]
22
- @parent = params[:parent]
23
- @connection = determine_connection(params[:connection]) || default_connection
24
- @interval = params[:interval] || 0.5
20
+ @name = params[:name].to_s
21
+ @pin = params[:pin]
22
+ @parent = params[:parent]
23
+ @connection = determine_connection(params[:connection]) ||
24
+ default_connection
25
+ @interval = params[:interval] || 0.5
26
+
27
+ @details = remove_keys(params, :name, :parent, :connection,
28
+ :passthru, :driver)
25
29
 
26
30
  require_driver(params[:driver] || :passthru, params)
27
31
  end
@@ -60,10 +64,9 @@ module Artoo
60
64
  {
61
65
  :name => name,
62
66
  :driver => driver.class.name.to_s.gsub(/^.*::/, ''),
63
- :pin => pin.to_s,
64
- :connection => connection.to_hash,
65
- :interval => interval,
66
- :commands => driver.commands
67
+ :connection => connection.name,
68
+ :commands => driver.commands,
69
+ :details => @details
67
70
  }
68
71
  end
69
72
 
@@ -87,9 +90,24 @@ module Artoo
87
90
  command(method_name, *arguments, &block)
88
91
  end
89
92
 
93
+ def respond_to_missing?(method_name, include_private = false)
94
+ commands.include?(method_name) || super
95
+ end
96
+
90
97
  # @return [String] pretty inspect
91
98
  def inspect
92
- "#<Device @id=#{object_id}, @name='name', @driver='driver'>"
99
+ "#<Device @id=#{object_id}, @name='name', @driver='#{driver.class.name}'>"
100
+ end
101
+
102
+ def add_interface(i)
103
+ @parent.add_interface(i)
104
+ end
105
+
106
+ def require_interface(i)
107
+ Logger.info "Require interface #{i}"
108
+ require "artoo/interfaces/#{i.to_s}"
109
+ @interface = constantize("Artoo::Interfaces::#{classify(i.to_s)}").new(:name => i.to_s, :robot => parent, :device => current_instance)
110
+ add_interface(@interface)
93
111
  end
94
112
 
95
113
  private
@@ -77,6 +77,10 @@ module Artoo
77
77
  return false
78
78
  end
79
79
 
80
+ def require_interface(i)
81
+ parent.require_interface(i)
82
+ end
83
+
80
84
  # Sends missing methods to connection
81
85
  def method_missing(method_name, *arguments, &block)
82
86
  connection.send(method_name, *arguments, &block)
@@ -3,22 +3,22 @@ require 'artoo/drivers/driver'
3
3
  module Artoo
4
4
  module Drivers
5
5
  # Test driver that can be pinged itself
6
- class Pinger < Driver
6
+ class Ping < Driver
7
7
 
8
8
  COMMANDS = [:ping].freeze
9
9
 
10
10
  def start_driver
11
- @count = 0
12
11
  super
13
12
  end
14
13
 
15
14
  # Publishes events to update event topic
16
15
  # when pinged
17
16
  def ping
18
- @count += 1
19
- publish(event_topic_name("update"), "ping", @count)
20
- publish(event_topic_name("ping"), @count)
21
- "ping #{@count}"
17
+ data = 'pong'
18
+ publish(event_topic_name("update"), "ping", data)
19
+ publish(event_topic_name("ping"), data)
20
+
21
+ data
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,37 @@
1
+ module Artoo
2
+ module Interfaces
3
+ # The Interface class is the base class used to
4
+ # implement behavior for a specific category of robot. Examples
5
+ # would be a Rover or Copter.
6
+ #
7
+ # Derive a class from this class, in order to implement higher-order
8
+ # behavior for a new category of robot.
9
+ class Interface
10
+ include Celluloid
11
+ include Celluloid::Notifications
12
+
13
+ attr_accessor :name, :robot, :device
14
+
15
+ def interface_type
16
+ :raw
17
+ end
18
+
19
+ COMMANDS = [].freeze
20
+
21
+ # Create new interface
22
+ # @param [Hash] params
23
+ # @option params [Object] :robot
24
+ # @option params [Object] :device
25
+ def initialize(params={})
26
+ @name = params[:name]
27
+ @robot = params[:robot]
28
+ @device = params[:device]
29
+ end
30
+
31
+ # @return [Collection] commands
32
+ def commands
33
+ self.class.const_get('COMMANDS')
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ require 'artoo/interfaces/interface'
2
+
3
+ module Artoo
4
+ module Interfaces
5
+ # The Ping interface.
6
+ class Ping < Interface
7
+ def interface_type
8
+ :ping
9
+ end
10
+
11
+ COMMANDS = [:ping]
12
+
13
+ def ping
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ require 'artoo/interfaces/interface'
2
+
3
+ module Artoo
4
+ module Interfaces
5
+ # The Rover interface.
6
+ class Rover < Interface
7
+ def interface_type
8
+ :rover
9
+ end
10
+
11
+ COMMANDS = [:forward, :backward, :left, :right, :turn_left, :turn_right, :stop]
12
+
13
+ def forward(speed)
14
+ end
15
+
16
+ def backward(speed)
17
+ end
18
+
19
+ def left(speed)
20
+ end
21
+
22
+ def right(speed)
23
+ end
24
+
25
+ def turn_left(degrees)
26
+ end
27
+
28
+ def turn_right(degrees)
29
+ end
30
+
31
+ def stop
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/artoo/master.rb CHANGED
@@ -22,6 +22,10 @@ module Artoo
22
22
  current.robot(name)
23
23
  end
24
24
 
25
+ def robot?(name)
26
+ current.robot?(name)
27
+ end
28
+
25
29
  def start_work
26
30
  current.start_work
27
31
  end
@@ -37,12 +41,26 @@ module Artoo
37
41
  def continue_work
38
42
  current.continue_work
39
43
  end
44
+
45
+ def command(name, params)
46
+ current.command(name, params)
47
+ end
48
+
49
+ def add_command(name, behaviour)
50
+ current.add_command(name, behaviour)
51
+ end
52
+
53
+ def commands
54
+ current.commands
55
+ end
56
+
40
57
  end
41
58
 
42
59
  # Create new master
43
60
  # @param [Collection] robots
44
61
  def initialize(bots=[])
45
62
  @robots = []
63
+ @commands = []
46
64
  assign(bots)
47
65
  end
48
66
 
@@ -56,9 +74,11 @@ module Artoo
56
74
  # @param [String] name
57
75
  # @return [Robot] robot
58
76
  def robot(name)
59
- r = robots.find {|r| r.name == name}
60
- raise RobotNotFound if r.nil?
61
- r
77
+ robots.find {|r| r.name == name}
78
+ end
79
+
80
+ def robot?(name)
81
+ robots.find {|r| r.name == name}
62
82
  end
63
83
 
64
84
  # @param [String] name
@@ -111,5 +131,27 @@ module Artoo
111
131
  robots.each {|r| r.terminate} unless !Artoo::Robot.is_running?
112
132
  Artoo::Robot.stopped!
113
133
  end
134
+
135
+ # return list of master command names
136
+ def commands
137
+ @commands.map{ |c| c[:name] }
138
+ end
139
+
140
+ # execute master command
141
+ def command(name, params)
142
+ command = @commands.find{ |c| c[:name] == name.to_sym }
143
+ if command
144
+ if params.nil?
145
+ command[:behaviour].call
146
+ else
147
+ command[:behaviour].call(params)
148
+ end
149
+ end
150
+ end
151
+
152
+ # add command to master
153
+ def add_command(name, behaviour)
154
+ @commands << { name: name.to_sym, behaviour: behaviour }
155
+ end
114
156
  end
115
157
  end
data/lib/artoo/robot.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'celluloid/autostart'
2
2
  require 'celluloid/io'
3
3
  require 'multi_json'
4
- require 'artoo/ext/timers'
5
- require 'artoo/ext/actor'
6
4
 
7
5
  require 'artoo/robot_class_methods'
8
6
  require 'artoo/basic'
@@ -16,6 +14,9 @@ require 'artoo/api/api'
16
14
  require 'artoo/master'
17
15
  require 'artoo/port'
18
16
  require 'artoo/utility'
17
+ require 'artoo/interfaces/interface'
18
+ require 'artoo/interfaces/ping'
19
+ require 'artoo/interfaces/rover'
19
20
 
20
21
  module Artoo
21
22
  # The most important class used by Artoo is Robot. This represents the primary
@@ -30,7 +31,7 @@ module Artoo
30
31
  include Artoo::Utility
31
32
  include Artoo::Events
32
33
 
33
- attr_reader :connections, :devices, :name, :commands
34
+ attr_reader :connections, :devices, :name, :commands, :interfaces
34
35
 
35
36
  exclusive :execute_startup
36
37
 
@@ -42,6 +43,7 @@ module Artoo
42
43
  def initialize(params={})
43
44
  @name = params[:name] || current_class.name || "Robot #{random_string}"
44
45
  @commands = params[:commands] || []
46
+ @interfaces = {}
45
47
  initialize_connections(params[:connections] || {})
46
48
  initialize_devices(params[:devices] || {})
47
49
  end
@@ -134,12 +136,14 @@ module Artoo
134
136
  "#<Robot #{object_id}>"
135
137
  end
136
138
 
137
- def command(method_name, *arguments)
138
- if known_command?(method_name)
139
+ # @return [Object] whatever result is passed back from the wrapped robot
140
+ def command(method_name, *arguments, &block)
141
+ t = interface_for_command(method_name)
142
+ if t
139
143
  if arguments.first
140
- self.send(method_name, *arguments)
144
+ t.send(method_name, *arguments)
141
145
  else
142
- self.send(method_name)
146
+ t.send(method_name)
143
147
  end
144
148
  else
145
149
  "Unknown Command"
@@ -151,10 +155,31 @@ module Artoo
151
155
  end
152
156
 
153
157
  # @return [Boolean] True if command exists
154
- def known_command?(method_name)
158
+ def own_command?(method_name)
155
159
  return commands.include?(method_name.intern)
156
160
  end
157
161
 
162
+ def add_interface(i)
163
+ @interfaces[i.interface_type.intern] = i
164
+ end
165
+
166
+ # @return [Boolean] True if command exists in any of the robot's interfaces
167
+ def interface_for_command(method_name)
168
+ return self if own_command?(method_name)
169
+ @interfaces.each_value {|i|
170
+ return i if i.commands.include?(method_name.intern)
171
+ }
172
+ return nil
173
+ end
174
+
175
+ # Sends missing methods to command
176
+ def method_missing(method_name, *arguments, &block)
177
+ command(method_name, *arguments, &block)
178
+ end
179
+
180
+ def respond_to_missing?(method_name, include_private = false)
181
+ own_command?(method_name)|| interface_for_command(method_name)
182
+ end
158
183
 
159
184
  private
160
185
 
data/lib/artoo/utility.rb CHANGED
@@ -85,5 +85,15 @@ module Artoo
85
85
  end
86
86
  )
87
87
  end
88
+
89
+ # Removes selected keys from hash
90
+ # @example {one: 'one', two: 'two'} => {two: 'two'}
91
+ # @return [Hash] new object without selected keys
92
+ def remove_keys(h, *keys)
93
+ hash = h.dup
94
+ keys.each { |key| hash.delete(key) }
95
+ hash
96
+ end
97
+
88
98
  end
89
99
  end
data/lib/artoo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Artoo
2
2
  unless const_defined?('VERSION')
3
- VERSION = "1.6.7"
3
+ VERSION = "1.8.0"
4
4
  end
5
5
  end
@@ -0,0 +1,46 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
2
+
3
+ describe "API routes" do
4
+
5
+ let(:base) { 'http://localhost:8080' }
6
+
7
+ def validate_route(relative_path, status=200)
8
+ res = HTTP.get(base + relative_path)
9
+ res.status.must_equal status
10
+ end
11
+
12
+ def validate_form(relative_path, params)
13
+ res = HTTP.post base + '/api/commands/echo', :body => JSON.dump(params)
14
+
15
+ res.status.must_equal 200
16
+ end
17
+
18
+ before :all do
19
+ @pid = fork { require_relative '../../examples/test_bot' }
20
+ sleep 1
21
+ end
22
+
23
+ after (:all) { system("kill -9 #{@pid}") }
24
+
25
+ it 'must respond to expected routes' do
26
+ validate_route('/api')
27
+ validate_route('/api/commands')
28
+ validate_route('/api/robots')
29
+ validate_route('/api/robots/TestBot')
30
+ validate_route('/api/robots/NonExistentBot', 404)
31
+ validate_route('/api/robots/TestBot/commands')
32
+ validate_route('/api/robots/TestBot/commands/hello')
33
+ validate_route('/api/robots/TestBot/devices')
34
+ validate_route('/api/robots/TestBot/devices/ping')
35
+ validate_route('/api/robots/TestBot/devices/ping/commands')
36
+ validate_route('/api/robots/TestBot/connections')
37
+ validate_route('/api/robots/TestBot/connections/loopback')
38
+ end
39
+
40
+ it 'must respond to command calls' do
41
+ validate_form('/api/commands/echo', { param: 'pong' })
42
+ validate_form('/api/robots/TestBot/devices/ping/commands/ping',
43
+ { name: 'bot' })
44
+ end
45
+
46
+ end
@@ -23,10 +23,10 @@ describe Artoo::Connection do
23
23
 
24
24
  it 'Artoo::Connection#as_json' do
25
25
  MultiJson.load(@connection.as_json, :symbolize_keys => true)[:name].must_equal "my_test_connection"
26
- MultiJson.load(@connection.as_json, :symbolize_keys => true)[:connected].must_equal false
26
+ MultiJson.load(@connection.as_json, :symbolize_keys => true)[:adaptor].must_equal "Loopback"
27
27
  end
28
28
 
29
29
  it 'Artoo::Connection#additional_params' do
30
30
  @robot.default_connection.adaptor.additional_params[:awesomeness].must_equal :high
31
31
  end
32
- end
32
+ end
data/test/device_test.rb CHANGED
@@ -43,4 +43,11 @@ describe Artoo::Device do
43
43
  @device = @robot.devices[:test_device_2]
44
44
  @device.driver.additional_params[:cool_factor].must_equal 11
45
45
  end
46
- end
46
+
47
+ it 'Artoo::Device#require_interface' do
48
+ @device = @robot.devices[:test_device_1]
49
+ @device.require_interface(:ping)
50
+ @device.interface.name.must_equal 'ping'
51
+ @robot.interfaces[:ping].name.must_equal 'ping'
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
2
+
3
+ class Awesomeness < Artoo::Interfaces::Interface
4
+ COMMANDS = [:awesome].freeze
5
+
6
+ def interface_type
7
+ :awesomeness
8
+ end
9
+
10
+ def awesome
11
+ true
12
+ end
13
+ end
14
+
15
+ describe Artoo::Interfaces::Interface do
16
+ before do
17
+ @robot = mock('robot')
18
+ @device = mock('device')
19
+ @interface = Awesomeness.new(:robot => @robot, :device => @device)
20
+ end
21
+
22
+ it 'Interface#interface_type' do
23
+ @interface.interface_type.must_equal :awesomeness
24
+ end
25
+
26
+ it 'Interface#commands' do
27
+ @interface.commands.first.must_equal :awesome
28
+ end
29
+ end
data/test/master_test.rb CHANGED
@@ -23,7 +23,7 @@ describe Artoo::Master do
23
23
  @robot1 = MockRobot.new("robot1")
24
24
  @robot2 = MockRobot.new("robot2")
25
25
  @robot3 = MockRobot.new("robot3")
26
-
26
+
27
27
  @robots << @robot1
28
28
  @robots << @robot2
29
29
  @robots << @robot3
@@ -36,14 +36,20 @@ describe Artoo::Master do
36
36
  end
37
37
 
38
38
  it 'Artoo::Master#robot with invalid robot name' do
39
- proc {@master.robot("robotno")}.must_raise(Artoo::RobotNotFound)
39
+ @master.robot("robotno").must_equal(nil)
40
+ end
41
+
42
+ it 'Artoo::Master::#commands' do
43
+ @master.commands.must_equal []
40
44
  end
41
45
 
42
- it 'Artoo::Master#robot_devices' do
43
- @master.robot_devices("robot2").first.must_equal "robot2-device1"
46
+ it 'Artoo::Master::#add_command' do
47
+ @master.add_command :test, lambda{}
48
+ @master.commands.must_equal [:test]
44
49
  end
45
50
 
46
- it 'Artoo::Master#robot_connections' do
47
- @master.robot_connections("robot2").last.must_equal "robot2-connection3"
51
+ it 'Artoo::Master::#command' do
52
+ @master.add_command :test, lambda{'test'}
53
+ @master.command(:test, nil).must_equal 'test'
48
54
  end
49
- end
55
+ end