artoo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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