artoo 0.1.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 (112) hide show
  1. data/.gitignore +25 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +144 -0
  4. data/Guardfile +15 -0
  5. data/LICENSE +13 -0
  6. data/README.md +97 -0
  7. data/Rakefile +11 -0
  8. data/api/assets/compass.rb +25 -0
  9. data/api/assets/javascripts/artoo/controllers/robot.js.coffee +27 -0
  10. data/api/assets/javascripts/artoo/routes.js.coffee +23 -0
  11. data/api/assets/javascripts/core.js.coffee +6 -0
  12. data/api/assets/javascripts/vendor/angular.min.js +161 -0
  13. data/api/assets/javascripts/vendor/bootstrap.min.js +6 -0
  14. data/api/assets/javascripts/vendor/jquery.min.js +5 -0
  15. data/api/assets/stylesheets/artoo/_core.css.scss +3 -0
  16. data/api/assets/stylesheets/artoo/_font-awesome.scss +534 -0
  17. data/api/assets/stylesheets/artoo/_variables.scss +8 -0
  18. data/api/assets/stylesheets/core.scss +70 -0
  19. data/api/public/core.css +9848 -0
  20. data/api/public/core.js +259 -0
  21. data/api/public/favicon.ico +0 -0
  22. data/api/public/font/FontAwesome.otf +0 -0
  23. data/api/public/font/fontawesome-webfont.eot +0 -0
  24. data/api/public/font/fontawesome-webfont.svg +284 -0
  25. data/api/public/font/fontawesome-webfont.ttf +0 -0
  26. data/api/public/font/fontawesome-webfont.woff +0 -0
  27. data/api/public/html5shiv.js +8 -0
  28. data/api/public/images/devices/ardrone.jpg +0 -0
  29. data/api/public/images/devices/arduino.jpg +0 -0
  30. data/api/public/images/devices/sphero.png +0 -0
  31. data/api/public/images/glyphicons-halflings-white.png +0 -0
  32. data/api/public/images/glyphicons-halflings.png +0 -0
  33. data/api/public/index.html +36 -0
  34. data/api/public/partials/robot-detail.html +111 -0
  35. data/api/public/partials/robot-device-detail.html +0 -0
  36. data/api/public/partials/robot-index.html +26 -0
  37. data/artoo.gemspec +21 -0
  38. data/bin/retry.sh +8 -0
  39. data/bin/sphero.sh +8 -0
  40. data/examples/ardrone.rb +12 -0
  41. data/examples/ardrone_nav.rb +22 -0
  42. data/examples/ardrone_nav_video.rb +33 -0
  43. data/examples/ardrone_video.rb +22 -0
  44. data/examples/conway_sphero.rb +65 -0
  45. data/examples/firmata.rb +13 -0
  46. data/examples/firmata_button.rb +9 -0
  47. data/examples/hello.rb +12 -0
  48. data/examples/hello_api.rb +9 -0
  49. data/examples/hello_api_multiple.rb +25 -0
  50. data/examples/hello_modular.rb +16 -0
  51. data/examples/hello_multiple.rb +22 -0
  52. data/examples/notifications.rb +9 -0
  53. data/examples/sphero.rb +11 -0
  54. data/examples/sphero_color.rb +13 -0
  55. data/examples/sphero_firmata.rb +17 -0
  56. data/examples/sphero_messages.rb +22 -0
  57. data/examples/sphero_multiple.rb +33 -0
  58. data/examples/wiiclassic.rb +94 -0
  59. data/lib/artoo.rb +3 -0
  60. data/lib/artoo/adaptors/adaptor.rb +54 -0
  61. data/lib/artoo/adaptors/ardrone.rb +32 -0
  62. data/lib/artoo/adaptors/ardrone_navigation.rb +26 -0
  63. data/lib/artoo/adaptors/ardrone_video.rb +27 -0
  64. data/lib/artoo/adaptors/firmata.rb +25 -0
  65. data/lib/artoo/adaptors/loopback.rb +8 -0
  66. data/lib/artoo/adaptors/sphero.rb +46 -0
  67. data/lib/artoo/api.rb +48 -0
  68. data/lib/artoo/api_route_helpers.rb +197 -0
  69. data/lib/artoo/connection.rb +70 -0
  70. data/lib/artoo/delegator.rb +49 -0
  71. data/lib/artoo/device.rb +61 -0
  72. data/lib/artoo/device_event_client.rb +27 -0
  73. data/lib/artoo/drivers/ardrone.rb +9 -0
  74. data/lib/artoo/drivers/ardrone_navigation.rb +21 -0
  75. data/lib/artoo/drivers/ardrone_video.rb +22 -0
  76. data/lib/artoo/drivers/button.rb +40 -0
  77. data/lib/artoo/drivers/driver.rb +48 -0
  78. data/lib/artoo/drivers/led.rb +37 -0
  79. data/lib/artoo/drivers/passthru.rb +9 -0
  80. data/lib/artoo/drivers/pinger.rb +19 -0
  81. data/lib/artoo/drivers/pinger2.rb +18 -0
  82. data/lib/artoo/drivers/sphero.rb +57 -0
  83. data/lib/artoo/drivers/wiichuck.rb +29 -0
  84. data/lib/artoo/drivers/wiiclassic.rb +137 -0
  85. data/lib/artoo/main.rb +32 -0
  86. data/lib/artoo/master.rb +16 -0
  87. data/lib/artoo/port.rb +51 -0
  88. data/lib/artoo/robot.rb +299 -0
  89. data/lib/artoo/utility.rb +39 -0
  90. data/lib/artoo/version.rb +5 -0
  91. data/test/adaptors/adaptor_test.rb +18 -0
  92. data/test/adaptors/ardrone_test.rb +24 -0
  93. data/test/adaptors/firmata_test.rb +25 -0
  94. data/test/adaptors/loopback_test.rb +18 -0
  95. data/test/adaptors/sphero_test.rb +24 -0
  96. data/test/api_test.rb +61 -0
  97. data/test/artoo_test.rb +12 -0
  98. data/test/connection_test.rb +28 -0
  99. data/test/delegator_test.rb +71 -0
  100. data/test/device_test.rb +41 -0
  101. data/test/drivers/ardrone_navigation_test.rb +11 -0
  102. data/test/drivers/ardrone_test.rb +11 -0
  103. data/test/drivers/ardrone_video_test.rb +11 -0
  104. data/test/drivers/driver_test.rb +21 -0
  105. data/test/drivers/led_test.rb +52 -0
  106. data/test/drivers/sphero_test.rb +54 -0
  107. data/test/drivers/wiichuck_test.rb +11 -0
  108. data/test/port_test.rb +33 -0
  109. data/test/robot_test.rb +96 -0
  110. data/test/test_helper.rb +8 -0
  111. data/test/utility_test.rb +27 -0
  112. metadata +185 -0
@@ -0,0 +1,32 @@
1
+ require 'artoo/delegator'
2
+ require 'artoo/robot'
3
+
4
+ module Artoo
5
+ # Execution context for top-level robots
6
+ # DSL methods executed on main are delegated to this class like Sinatra
7
+ class MainRobot < Artoo::Robot
8
+
9
+ # we assume that the first file that requires 'artoo' is the
10
+ # app_file. all other path related options are calculated based
11
+ # on this path by default.
12
+ set :app_file, caller_files.first || $0
13
+ set :start_work, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
14
+
15
+ # if run? && ARGV.any?
16
+ # require 'optparse'
17
+ # OptionParser.new { |op|
18
+ # op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) }
19
+ # op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :bind, val }
20
+ # op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym }
21
+ # op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val }
22
+ # op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true }
23
+ # }.parse!(ARGV.dup)
24
+ # end
25
+ end
26
+
27
+ at_exit { MainRobot.work! if $!.nil? && MainRobot.start_work? }
28
+ end
29
+
30
+ # include would include the module in Object
31
+ # extend only extends the `main` object
32
+ extend Artoo::Delegator
@@ -0,0 +1,16 @@
1
+ module Artoo
2
+ # The Artoo::Master class is a registered supervisor class to keep track
3
+ # of all the running robots
4
+ class Master
5
+ include Celluloid
6
+ attr_reader :robots
7
+
8
+ def initialize(bots)
9
+ @robots = bots
10
+ end
11
+
12
+ def get_robot_by_name(name)
13
+ robots.find_all {|r| r.name == name}.first
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ module Artoo
2
+ # The Artoo::Port class represents port and/or host to be used to connect
3
+ # tp a specific individual hardware device.
4
+ class Port
5
+ attr_reader :port, :host
6
+
7
+ def initialize(data)
8
+ @is_tcp, @is_serial = false
9
+ parse(data)
10
+ end
11
+
12
+ def is_serial?
13
+ @is_serial == true
14
+ end
15
+
16
+ def is_tcp?
17
+ @is_tcp == true
18
+ end
19
+
20
+ def to_s
21
+ if is_serial?
22
+ port
23
+ else
24
+ "#{host}:#{port}"
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def parse(data)
31
+ # is TCP host/port?
32
+ if m = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})/.match(data)
33
+ @port = m[2]
34
+ @host = m[1]
35
+ @is_tcp = true
36
+
37
+ # is it a numeric port for localhost tcp?
38
+ elsif /^[0-9]{1,5}$/.match(data)
39
+ @port = data
40
+ @host = "localhost"
41
+ @is_tcp = true
42
+
43
+ # must be a serial port
44
+ else
45
+ @port = data
46
+ @host = nil
47
+ @is_serial = true
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,299 @@
1
+ require 'celluloid/io'
2
+ require 'multi_json'
3
+
4
+ require 'artoo/connection'
5
+ require 'artoo/device'
6
+ require 'artoo/api'
7
+ require 'artoo/master'
8
+ require 'artoo/port'
9
+ require 'artoo/utility'
10
+
11
+ module Artoo
12
+ # The most important class used by Artoo is Robot. This represents the primary
13
+ # interface for interacting with a collection of physical computing capabilities.
14
+ class Robot
15
+ include Celluloid
16
+ include Celluloid::Notifications
17
+ include Artoo::Utility
18
+
19
+ attr_reader :connections, :devices, :name
20
+
21
+ def initialize(params={})
22
+ @name = params[:name] || "Robot #{random_string}"
23
+ initialize_connections(params[:connections] || {})
24
+ initialize_devices(params[:devices] || {})
25
+ end
26
+
27
+ class << self
28
+ attr_accessor :connection_types, :device_types, :working_code,
29
+ :use_api, :api_host, :api_port
30
+
31
+ # connection to some hardware that has one or more devices via some specific protocol
32
+ # Example:
33
+ # connection :arduino, :adaptor => :firmata, :port => '/dev/tty.usbmodemxxxxx'
34
+ def connection(name, params = {})
35
+ Celluloid::Logger.info "Registering connection '#{name}'..."
36
+ self.connection_types ||= []
37
+ self.connection_types << {:name => name}.merge(params)
38
+ end
39
+
40
+ # device that uses a connection to communicate
41
+ # Example:
42
+ # device :collision_detect, :driver => :switch, :pin => 3
43
+ def device(name, params = {})
44
+ Celluloid::Logger.info "Registering device '#{name}'..."
45
+ self.device_types ||= []
46
+ self.device_types << {:name => name}.merge(params)
47
+ end
48
+
49
+ # the work that needs to be performed
50
+ # Example:
51
+ # work do
52
+ # every(10.seconds) do
53
+ # puts "hello, world"
54
+ # end
55
+ # end
56
+ def work(&block)
57
+ Celluloid::Logger.info "Preparing work..."
58
+ self.working_code = block if block_given?
59
+ end
60
+
61
+ # activate RESTful api
62
+ # Example:
63
+ # api :host => '127.0.0.1', :port => '1234'
64
+ def api(params = {})
65
+ Celluloid::Logger.info "Registering api..."
66
+ self.use_api = true
67
+ self.api_host = params[:host] || '127.0.0.1'
68
+ self.api_port = params[:port] || '4321'
69
+ end
70
+
71
+ # work can be performed by either:
72
+ # an existing instance
73
+ # an array of existing instances
74
+ # or, a new instance can be created
75
+ def work!(robot=nil)
76
+ if robot.respond_to?(:work)
77
+ robots = [robot]
78
+ elsif robot.kind_of?(Array)
79
+ robots = robot
80
+ else
81
+ robots = [self.new]
82
+ end
83
+
84
+ robots.each {|r| r.async.work}
85
+
86
+ Celluloid::Actor[:master] = Master.new(robots)
87
+ Celluloid::Actor[:api] = Api.new(self.api_host, self.api_port) if self.use_api
88
+
89
+ sleep # sleep main thread, and let the work commence!
90
+ end
91
+
92
+ def test?
93
+ ENV["ARTOO_TEST"] == 'true'
94
+ end
95
+
96
+ # Taken from Sinatra codebase
97
+ # Sets an option to the given value. If the value is a proc,
98
+ # the proc will be called every time the option is accessed.
99
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
100
+ raise ArgumentError if block and !not_set
101
+ value, not_set = block, false if block
102
+
103
+ if not_set
104
+ raise ArgumentError unless option.respond_to?(:each)
105
+ option.each { |k,v| set(k, v) }
106
+ return self
107
+ end
108
+
109
+ if respond_to?("#{option}=") and not ignore_setter
110
+ return __send__("#{option}=", value)
111
+ end
112
+
113
+ setter = proc { |val| set option, val, true }
114
+ getter = proc { value }
115
+
116
+ case value
117
+ when Proc
118
+ getter = value
119
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
120
+ getter = value.inspect
121
+ when Hash
122
+ setter = proc do |val|
123
+ val = value.merge val if Hash === val
124
+ set option, val, true
125
+ end
126
+ end
127
+
128
+ define_singleton_method("#{option}=", setter) if setter
129
+ define_singleton_method(option, getter) if getter
130
+ define_singleton_method("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
131
+ self
132
+ end
133
+
134
+ # Taken from Sinatra codebase
135
+ CALLERS_TO_IGNORE = [ # :nodoc:
136
+ /lib\/artoo.*\.rb$/, # artoo code
137
+ /^\(.*\)$/, # generated code
138
+ /rubygems\/custom_require\.rb$/, # rubygems require hacks
139
+ /active_support/, # active_support require hacks
140
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
141
+ /<internal:/, # internal in ruby >= 1.9.2
142
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
143
+ ]
144
+
145
+ # Taken from Sinatra codebase
146
+ # Like Kernel#caller but excluding certain magic entries and without
147
+ # line / method information; the resulting array contains filenames only.
148
+ def caller_files
149
+ cleaned_caller(1).flatten
150
+ end
151
+
152
+ private
153
+
154
+ # Taken from Sinatra codebase
155
+ def define_singleton_method(name, content = Proc.new)
156
+ # replace with call to singleton_class once we're 1.9 only
157
+ (class << self; self; end).class_eval do
158
+ undef_method(name) if method_defined? name
159
+ String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
160
+ end
161
+ end
162
+
163
+ # Taken from Sinatra codebase
164
+ # Like Kernel#caller but excluding certain magic entries
165
+ def cleaned_caller(keep = 3)
166
+ caller(1).
167
+ map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
168
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
169
+ end
170
+ end
171
+
172
+ def safe_name
173
+ name.gsub(' ', '_').downcase
174
+ end
175
+
176
+ def api_host
177
+ self.class.api_host
178
+ end
179
+
180
+ def api_port
181
+ self.class.api_port
182
+ end
183
+
184
+ # start doing the work
185
+ def work
186
+ Logger.info "Starting work..."
187
+ make_connections
188
+ start_devices
189
+ current_instance.instance_eval(&working_code)
190
+ rescue Exception => e
191
+ Logger.error e.message
192
+ Logger.error e.backtrace.inspect
193
+ end
194
+
195
+ def make_connections
196
+ result = false
197
+ future_connections = []
198
+ # block until all connections done
199
+ connections.each {|k, c| future_connections << c.future.connect}
200
+ future_connections.each {|v| result = v.value}
201
+ result
202
+ end
203
+
204
+ def start_devices
205
+ result = false
206
+ future_devices = []
207
+ # block until all devices done
208
+ devices.each {|k, d| future_devices << d.future.start_device}
209
+ future_devices.each {|v| result = v.value}
210
+ result
211
+ end
212
+
213
+ def disconnect
214
+ connections.each {|k, c| c.async.disconnect}
215
+ end
216
+
217
+ def default_connection
218
+ connections[connections.keys.first]
219
+ end
220
+
221
+ def connection_types
222
+ current_class.connection_types ||= []
223
+ current_class.connection_types
224
+ end
225
+
226
+ def device_types
227
+ current_class.device_types ||= []
228
+ current_class.device_types
229
+ end
230
+
231
+ def working_code
232
+ current_class.working_code ||= proc {puts "No work defined."}
233
+ end
234
+
235
+ # Subscribe to an event from a device
236
+ def on(device, events={})
237
+ events.each do |k, v|
238
+ subscribe("#{safe_name}_#{device.name}_#{k}", create_proxy_method(k, v))
239
+ end
240
+ end
241
+
242
+ # Create an anonymous subscription method so we can wrap the
243
+ # subscription method fire into a valid method regardless
244
+ # of where it is defined
245
+ def create_proxy_method(k, v)
246
+ proxy_method_name(k).tap do |name|
247
+ self.class.send :define_method, name do |*args|
248
+ case v
249
+ when Symbol
250
+ self.send v.to_sym, *args
251
+ when Proc
252
+ v.call(*args)
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ # A simple loop to create a 'fake' anonymous method
259
+ def proxy_method_name(k)
260
+ begin
261
+ meth = "#{k}_#{Random.rand(999)}"
262
+ end while respond_to?(meth)
263
+ meth
264
+ end
265
+
266
+ def to_hash
267
+ {:name => name,
268
+ :connections => connections.each_value.collect {|c|c.to_hash},
269
+ :devices => devices.each_value.collect {|d|d.to_hash}
270
+ }
271
+ end
272
+
273
+ def as_json
274
+ MultiJson.dump(to_hash)
275
+ end
276
+
277
+ private
278
+
279
+ def initialize_connections(params={})
280
+ @connections = {}
281
+ connection_types.each {|ct|
282
+ Logger.info "Initializing connection #{ct[:name].to_s}..."
283
+ cp = params[ct[:name]] || {}
284
+ c = Connection.new(ct.merge(cp).merge(:parent => current_instance))
285
+ @connections[ct[:name]] = c
286
+ }
287
+ end
288
+
289
+ def initialize_devices(params={})
290
+ @devices = {}
291
+ device_types.each {|d|
292
+ Logger.info "Initializing device #{d[:name].to_s}..."
293
+ d = Device.new(d.merge(:parent => current_instance))
294
+ instance_eval("def #{d.name}; return devices[:#{d.name}]; end")
295
+ @devices[d.name.intern] = d
296
+ }
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Artoo
4
+ module Utility
5
+ def constantize(camel_cased_word)
6
+ ActiveSupport::Inflector.constantize(camel_cased_word)
7
+ end
8
+
9
+ def classify(underscored)
10
+ ActiveSupport::Inflector.camelize(underscored.to_s.sub(/.*\./, ''))
11
+ end
12
+
13
+ def random_string
14
+ (0...8).map{65.+(rand(26)).chr}.join
15
+ end
16
+
17
+ def current_instance
18
+ Celluloid::Actor.current
19
+ end
20
+
21
+ def current_class
22
+ Celluloid::Actor.current.class
23
+ end
24
+ end
25
+ end
26
+
27
+ # just a bit of syntactic sugar, actually does nothing
28
+ # Example:
29
+ # 20.seconds => 20
30
+ # 1.second => 1
31
+ class Integer < Numeric
32
+ def seconds
33
+ return self
34
+ end
35
+
36
+ def second
37
+ return self
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Artoo
2
+ unless const_defined?('VERSION')
3
+ VERSION = "0.1.0"
4
+ end
5
+ end