artoo 1.6.7 → 1.8.0

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