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