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,27 @@
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
+ def initialize(websocket, topic)
13
+ @topic = topic
14
+ info "Streaming #{@topic} to websocket..."
15
+ @socket = websocket
16
+ subscribe(@topic, :notify_event)
17
+ end
18
+
19
+ def notify_event(topic, *data)
20
+ # TODO: send which topic sent the notification
21
+ @socket << data.last.to_s
22
+ rescue Reel::SocketError, Errno::EPIPE
23
+ info "Device event notification #{topic} websocket disconnected"
24
+ terminate
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Ardrone driver behaviors
6
+ class Ardrone < Driver
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Ardrone navigation driver behaviors
6
+ class ArdroneNavigation < Driver
7
+ def start_driver
8
+ every(interval) do
9
+ handle_update
10
+ end
11
+
12
+ super
13
+ end
14
+
15
+ def handle_update
16
+ navdata = connection.receive_data
17
+ publish(event_topic_name("update"), navdata)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Ardrone video driver behaviors
6
+ class ArdroneVideo < Driver
7
+ def start_driver
8
+ every(interval) do
9
+ handle_frame
10
+ end
11
+
12
+ super
13
+ end
14
+
15
+ def handle_frame(*params)
16
+ frame = connection.video_parser.get_frame
17
+ publish(event_topic_name("update"), "frame", frame)
18
+ publish(event_topic_name("frame"), frame)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Button driver behaviors for Firmata
6
+ class Button < Driver
7
+ DOWN = 1
8
+ UP = 0
9
+
10
+ def is_pressed?
11
+ (@is_pressed ||= false) == true
12
+ end
13
+
14
+ def start_driver
15
+ listener = ->(value) { update(value) }
16
+ connection.on("digital-read-#{pin}", listener)
17
+ connection.set_pin_mode(pin, Firmata::Board::INPUT)
18
+ connection.toggle_pin_reporting(pin)
19
+
20
+ every(interval) do
21
+ connection.read_and_process
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ def update(value)
28
+ if value == DOWN
29
+ @is_pressed = true
30
+ publish(event_topic_name("update"), "push", value)
31
+ publish(event_topic_name("push"), value)
32
+ else
33
+ @is_pressed = false
34
+ publish(event_topic_name("update"), "release", value)
35
+ publish(event_topic_name("release"), value)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ module Artoo
2
+ module Drivers
3
+ # The Driver class is the base class used to
4
+ # implement behavior for a specific kind of hardware devices. Examples
5
+ # would be an Arduino, a Sphero, or an ARDrone.
6
+ #
7
+ # Derive a class from this class, in order to implement behavior
8
+ # for a new type of hardware device.
9
+ class Driver
10
+ include Celluloid
11
+ include Celluloid::Notifications
12
+
13
+ attr_reader :parent
14
+
15
+ def initialize(params={})
16
+ @parent = params[:parent]
17
+ end
18
+
19
+ def connection
20
+ parent.connection
21
+ end
22
+
23
+ def pin
24
+ parent.pin
25
+ end
26
+
27
+ def interval
28
+ parent.interval
29
+ end
30
+
31
+ def start_driver
32
+ Logger.info "Starting driver '#{self.class.name}'..."
33
+ end
34
+
35
+ def event_topic_name(event)
36
+ parent.event_topic_name(event)
37
+ end
38
+
39
+ def method_missing(method_name, *arguments, &block)
40
+ connection.send(method_name, *arguments, &block)
41
+ rescue Exception => e
42
+ Logger.error e.message
43
+ Logger.error e.backtrace.inspect
44
+ return nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # The LED driver behaviors
6
+ class Led < Driver
7
+ def is_on?
8
+ (@is_on ||= false) == true
9
+ end
10
+
11
+ def is_off?
12
+ (@is_on ||= false) == false
13
+ end
14
+
15
+ def on
16
+ @is_on = true
17
+ connection.set_pin_mode(pin, Firmata::Board::OUTPUT)
18
+ connection.digital_write(pin, Firmata::Board::HIGH)
19
+ end
20
+
21
+ def off
22
+ @is_on = false
23
+ connection.set_pin_mode(pin, Firmata::Board::OUTPUT)
24
+ connection.digital_write(pin, Firmata::Board::LOW)
25
+ end
26
+
27
+ def toggle
28
+ is_off? ? on : off
29
+ end
30
+
31
+ def brightness(level=0)
32
+ connection.set_pin_mode(pin, Firmata::Board::PWM)
33
+ connection.analog_write(pin, level)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # The Passthru driver just passes calls along to the parent's connection.
6
+ class Passthru < Driver
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Pings itself
6
+ class Pinger < Driver
7
+ def start_driver
8
+ @count = 1
9
+ every(interval) do
10
+ publish(event_topic_name("update"), "alive", @count)
11
+ publish(event_topic_name("alive"), @count)
12
+ @count += 1
13
+ end
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Pings itself
6
+ class Pinger2 < Driver
7
+ def start_driver
8
+ every(interval) do
9
+ @count = rand(100000)
10
+ publish(event_topic_name("update"), "alive", @count)
11
+ publish(event_topic_name("alive"), @count)
12
+ end
13
+
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # The Sphero driver behaviors
6
+ class Sphero < Driver
7
+ def detect_collisions(params={})
8
+ connection.configure_collision_detection 0x01, 0x20, 0x20, 0x20, 0x20, 0x50
9
+ end
10
+
11
+ def clear_collisions
12
+ messages = connection.async_messages
13
+ messages.clear if messages
14
+ end
15
+
16
+ def collisions
17
+ messages = connection.async_messages
18
+ return nil unless messages
19
+ messages.select {|m| m.is_a?(::Sphero::Response::CollisionDetected)}
20
+ end
21
+
22
+ def power_notifications
23
+ messages = connection.async_messages
24
+ return nil unless messages
25
+ messages.select {|m| m.is_a?(::Sphero::Response::PowerNotification)}
26
+ end
27
+
28
+ def sensor_data
29
+ messages = connection.async_messages
30
+ return nil unless messages
31
+ messages.select {|m| m.is_a?(::Sphero::Response::SensorData)}
32
+ end
33
+
34
+ def set_color(r, g=nil, b=nil)
35
+ r, g, b = color(r, g, b)
36
+ connection.rgb(r, g, b)
37
+ end
38
+
39
+ def color(r, g=nil, b=nil)
40
+ case r
41
+ when :red
42
+ return 255, 0, 0
43
+ when :green
44
+ return 0, 255, 0
45
+ when :yellow
46
+ return 255, 255, 0
47
+ when :blue
48
+ return 0, 0, 255
49
+ when :white
50
+ return 255, 255, 255
51
+ else
52
+ return r, g, b
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Wiichuck driver behaviors for Firmata
6
+ class Wiichuck < Driver
7
+ def address; 0x52; end
8
+
9
+ def start_driver
10
+ listener = ->(value) { update(value) }
11
+ connection.on("i2c_reply", listener)
12
+
13
+ connection.i2c_config(10)
14
+ connection.i2c_request(address, 0x40, 0x00)
15
+
16
+ every(interval) do
17
+ connection.i2c_request(address, 0x00)
18
+ connection.read_and_process
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ def update(value)
25
+ publish("#{parent.name}_update", value)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,137 @@
1
+ require 'artoo/drivers/driver'
2
+
3
+ module Artoo
4
+ module Drivers
5
+ # Wiichuck driver behaviors for Firmata
6
+ class Wiiclassic < Driver
7
+ def address; 0x52; end
8
+
9
+ def start_driver
10
+ begin
11
+ @joystick = {
12
+ :ry_offset => 8,
13
+ :ry_origin => nil,
14
+ :ly_offset => 20,
15
+ :lx_offset => 20,
16
+ :ly_origin => nil,
17
+ :lx_origin => nil,
18
+ :rt_origin => nil,
19
+ :lt_origin => nil,
20
+ :rt_offset => 5,
21
+ :lt_offset => 5
22
+ }
23
+ listener = ->(value) { update(value) }
24
+ connection.on("i2c_reply", listener)
25
+
26
+ connection.i2c_config(0)
27
+ every(interval) do
28
+ connection.i2c_write_request(address, 0x40, 0x00)
29
+ p
30
+ connection.i2c_write_request(address, 0x00, 0x00)
31
+ p
32
+ connection.i2c_read_request(address, 6)
33
+ p
34
+ connection.read_and_process
35
+ end
36
+
37
+ super
38
+ rescue Exception => e
39
+ p "start driver"
40
+ p e.message
41
+ p e.backtrace.inspect
42
+ end
43
+
44
+ end
45
+
46
+ def update(value)
47
+ begin
48
+ #Logger.info "value[:data] #{value[:data].inspect}"
49
+ if value[:data][0] == value[:data][1] && value[:data][2] == value[:data][3] && value[:data][4] == value[:data][5]
50
+ # Logger.error "Encrypted bytes from wiiclassic"
51
+ break
52
+ end
53
+ data = parse_wiiclassic(value)
54
+ #publish(event_topic_name("_a_button") if data[:a] == 0
55
+ publish(event_topic_name("a_button")) if data[:a] == 0
56
+ publish(event_topic_name("b_button")) if data[:b] == 0
57
+ publish(event_topic_name("x_button")) if data[:x] == 0
58
+ publish(event_topic_name("y_button")) if data[:y] == 0
59
+ publish(event_topic_name("home_button")) if data[:h] == 0
60
+ publish(event_topic_name("start_button")) if data[:+] == 0
61
+ publish(event_topic_name("select_button")) if data[:-] == 0
62
+
63
+ @joystick[:ly_origin] = data[:ly] if @joystick[:ly_origin].nil?
64
+ @joystick[:lx_origin] = data[:lx] if @joystick[:lx_origin].nil?
65
+
66
+ @joystick[:ry_origin] = data[:ry] if @joystick[:ry_origin].nil?
67
+
68
+ @joystick[:rt_origin] = data[:rt] if @joystick[:rt_origin].nil?
69
+ @joystick[:lt_origin] = data[:lt] if @joystick[:lt_origin].nil?
70
+
71
+ if data[:ly] > (@joystick[:ly_origin] + @joystick[:ly_offset])
72
+ publish(event_topic_name("ly_up"))
73
+ elsif data[:ly] < (@joystick[:ly_origin] - @joystick[:ly_offset])
74
+ publish(event_topic_name("ly_down"))
75
+ elsif data[:lx] > (@joystick[:lx_origin] + @joystick[:lx_offset])
76
+ publish(event_topic_name("lx_right"))
77
+ elsif data[:lx] < (@joystick[:lx_origin] - @joystick[:lx_offset])
78
+ publish(event_topic_name("lx_left"))
79
+ else
80
+ publish(event_topic_name("reset_pitch_roll"))
81
+ end
82
+
83
+ if data[:ry] > (@joystick[:ry_origin] + @joystick[:ry_offset])
84
+ publish(event_topic_name("ry_up"))
85
+ elsif data[:ry] < (@joystick[:ry_origin] - @joystick[:ry_offset])
86
+ publish(event_topic_name("ry_down"))
87
+ else
88
+ publish(event_topic_name("reset_altitude"))
89
+ end
90
+
91
+ if data[:rt] > (@joystick[:rt_origin] + @joystick[:rt_offset])
92
+ publish(event_topic_name("rotate_right"))
93
+ elsif data[:lt] > (@joystick[:lt_origin] + @joystick[:lt_offset])
94
+ publish(event_topic_name("rotate_left"))
95
+ else
96
+ publish(event_topic_name("reset_rotate"))
97
+ end
98
+
99
+ rescue Exception => e
100
+ p "wiiclassic update exception!"
101
+ p e.message
102
+ p e.backtrace.inspect
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def decode( x )
109
+ return ( x ^ 0x17 ) + 0x17
110
+ end
111
+
112
+ def parse_wiiclassic(value)
113
+ return {
114
+ :lx => decode(value[:data][0]) & 0x3f,
115
+ :ly => decode(value[:data][1]) & 0x3f,
116
+ :rx => ((decode(value[:data][0]) & 0xC0) >> 2) | ((decode(value[:data][1]) & 0xC0) >> 4) | (decode(value[:data][2])[7]),
117
+ :ry => decode(value[:data][2]) & 0x1f,
118
+ :lt => ((decode(value[:data][2]) & 0x60) >> 3) | ((decode(value[:data][3]) & 0xE0) >> 6),
119
+ :rt => decode(value[:data][3]) & 0x1f,
120
+ :d_up => decode(value[:data][5])[0],
121
+ :d_down => decode(value[:data][4])[6],
122
+ :D_left => decode(value[:data][5])[1],
123
+ :D_right => decode(value[:data][4])[7],
124
+ :zr => decode(value[:data][5])[2],
125
+ :zl => decode(value[:data][5])[7],
126
+ :a => decode(value[:data][5])[4],
127
+ :b => decode(value[:data][5])[6],
128
+ :x => decode(value[:data][5])[3],
129
+ :y => decode(value[:data][5])[5],
130
+ :+ => decode(value[:data][4])[2],
131
+ :- => decode(value[:data][4])[4],
132
+ :h => decode(value[:data][4])[3],
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end