sphero 1.0.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,3 +4,36 @@
4
4
 
5
5
  * Birthday!
6
6
 
7
+ === 1.0.1 / 2012-10-28
8
+
9
+ * 3 minor fixes/enhancements
10
+
11
+ * Works with Ruby 1.9.2 for us regular folks
12
+ * Sphero.start method to endlessly retry when connecting
13
+ * I think heading might work again, not too sure
14
+
15
+ === 1.1.0 / 2013-01-04
16
+
17
+ * 3 enhancements
18
+
19
+ * Pass a block to Sphero.start for DSL-awesomeness
20
+ * Use Sphero#keep_going as a nicer alias for Ruby's sleep command
21
+ * Add some additional test coverage
22
+
23
+ === 1.1.2 / 2013-01-07
24
+
25
+ * 1 bugfix
26
+
27
+ * Correct Sphero#keep_going to call Kernel sleep method correctly
28
+
29
+ === 1.1.3 / 2013-01-07
30
+
31
+ * 1 enhancement/bugfix depending on how you look at it
32
+
33
+ * Change Sphero#sleep to Sphero#sphero_sleep to distingish naive call to Kernel sleep
34
+
35
+ === 1.1.4 / 2013-01-10
36
+
37
+ * 1 enhancement
38
+
39
+ * Add constants FORWARD, BACKWARD, RIGHT, & LEFT for quick directionality
@@ -8,3 +8,4 @@ lib/sphero.rb
8
8
  lib/sphero/request.rb
9
9
  lib/sphero/response.rb
10
10
  test/test_sphero.rb
11
+ test/test_sphero_request.rb
@@ -1,6 +1,6 @@
1
1
  # sphero
2
2
 
3
- * http://github.com/tenderlove/sphero
3
+ * http://github.com/hybridgroup/sphero
4
4
 
5
5
  ## DESCRIPTION:
6
6
 
@@ -13,6 +13,51 @@ provided by the bluetooth connection.
13
13
 
14
14
  ## SYNOPSIS:
15
15
 
16
+ You can easily start your Sphero and send it commands like this:
17
+
18
+ ```ruby
19
+ Sphero.start '/dev/tty.Sphero-YBW-RN-SPP' do
20
+ roll 60, FORWARD
21
+ keep_going 3
22
+
23
+ roll 60, RIGHT
24
+ keep_going 3
25
+
26
+ roll 60, BACKWARD
27
+ keep_going 3
28
+
29
+ roll 60, LEFT
30
+ keep_going 3
31
+
32
+ stop
33
+ end
34
+ ```
35
+
36
+ Here is another example:
37
+
38
+ ```ruby
39
+ Sphero.start "/dev/tty.Sphero-PRG-RN-SPP" do
40
+ ping
41
+
42
+ # Roll 0 degrees, speed 125
43
+ roll(125, 0)
44
+
45
+ # Turn 360 degrees, 30 degrees at a time
46
+ 0.step(360, 30) { |h|
47
+ h = 0 if h == 360
48
+
49
+ # Set the heading to h degrees
50
+ heading = h
51
+ keep_going 1
52
+ }
53
+
54
+ keep_going 1
55
+ stop
56
+ end
57
+ ```
58
+
59
+ Here is a another way to do the same thing as the previos example, via just normal method calls instead of the DSL:
60
+
16
61
  ```ruby
17
62
  s = Sphero.new "/dev/tty.Sphero-PRG-RN-SPP"
18
63
  s.ping
@@ -32,19 +77,38 @@ sleep 1
32
77
  s.stop
33
78
  ```
34
79
 
80
+ ## Pairing sphero with ubuntu
81
+ Add your user to the `dialout` group
82
+ ```
83
+ $ sudo usermod -a -G dialout <user>
84
+ ```
85
+ Then logout or restart your computer. Once your user is logged back in, pair the sphero with the ubuntu bluetooth manager.
86
+
87
+ Once paired, you may now bind your sphero to a rfcomm port
88
+ ```
89
+ $ sudo hcitool scan
90
+ Scanning ...
91
+ <address> Sphero
92
+ $ sudo rfcomm bind /dev/rfcomm0 <address> 1
93
+ ```
94
+
95
+ You may now access the sphero from `/dev/rfcomm0`
96
+
35
97
  ## REQUIREMENTS:
36
98
 
37
99
  * A Sphero ball connected to your computer
100
+ * Supports MRI 1.9.2/1.9.3 and Rubinius 2.0rc1 for sure...
38
101
 
39
102
  ## INSTALL:
40
103
 
41
- * gem install sphero
104
+ * gem install hybridgroup-sphero
42
105
 
43
106
  ## LICENSE:
44
107
 
45
108
  (The MIT License)
46
109
 
47
110
  Copyright (c) 2012 Aaron Patterson
111
+ Copyright (c) 2012-2013 The Hybrid Group
48
112
 
49
113
  Permission is hereby granted, free of charge, to any person obtaining
50
114
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -4,19 +4,19 @@ require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
6
  Hoe.plugins.delete :rubyforge
7
+ Hoe.plugins.delete :flog
7
8
  Hoe.plugin :minitest
8
9
  Hoe.plugin :gemspec # `gem install hoe-gemspec`
9
10
  Hoe.plugin :git # `gem install hoe-git`
10
11
 
11
12
  Hoe.spec 'sphero' do
12
- developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
13
+ developer('Hybrid Group', 'sphero@hybridgroup.com')
13
14
  self.readme_file = 'README.markdown'
14
15
  self.history_file = 'CHANGELOG.rdoc'
15
16
  self.extra_rdoc_files = FileList['*.{rdoc,markdown}']
16
- self.extra_deps << ['serialport']
17
17
 
18
18
  self.spec_extras = {
19
- :required_ruby_version => '>= 1.9.3'
19
+ :required_ruby_version => '>= 1.9.2'
20
20
  }
21
21
  end
22
22
 
@@ -1,16 +1,62 @@
1
- require 'serialport'
2
1
  require 'sphero/request'
3
2
  require 'sphero/response'
4
3
  require 'thread'
5
4
 
6
5
  class Sphero
7
- VERSION = '1.0.0'
6
+ VERSION = '1.4.1'
7
+
8
+ FORWARD = 0
9
+ RIGHT = 90
10
+ BACKWARD = 180
11
+ LEFT = 270
12
+
13
+ DEFAULT_RETRIES = 3
14
+
15
+ attr_accessor :connection_types, :messages
16
+
17
+ class << self
18
+ def start(dev, &block)
19
+ retries_left = DEFAULT_RETRIES
20
+ begin
21
+ sphero = self.new dev
22
+ if (block_given?)
23
+ begin
24
+ sphero.instance_eval(&block)
25
+ ensure
26
+ sphero.close
27
+ end
28
+ return nil
29
+ end
30
+ return sphero
31
+ rescue Errno::EBUSY
32
+ puts retries_left
33
+ retries_left = retries_left - 1
34
+ retry unless retries_left < 0
35
+ end
36
+ end
37
+ end
8
38
 
9
39
  def initialize dev
10
- @sp = SerialPort.new dev, 115200, 8, 1, SerialPort::NONE
40
+ if dev.is_a?(String)
41
+ initialize_serialport dev
42
+ else
43
+ @sp = dev
44
+ end
45
+
11
46
  @dev = 0x00
12
47
  @seq = 0x00
13
48
  @lock = Mutex.new
49
+ @messages = Queue.new
50
+ end
51
+
52
+ def close
53
+ begin
54
+ stop
55
+ rescue Exception => e
56
+ puts e.message
57
+ ensure
58
+ @sp.close
59
+ end
14
60
  end
15
61
 
16
62
  def ping
@@ -41,7 +87,7 @@ class Sphero
41
87
  write Request::GetPowerState.new(@seq)
42
88
  end
43
89
 
44
- def sleep wakeup = 0, macro = 0
90
+ def sphero_sleep wakeup = 0, macro = 0
45
91
  write Request::Sleep.new(@seq, wakeup, macro)
46
92
  end
47
93
 
@@ -57,6 +103,11 @@ class Sphero
57
103
  write Request::Heading.new(@seq, h)
58
104
  end
59
105
 
106
+ def color colorname, persistant = false
107
+ color = COLORS[colorname]
108
+ rgb color[:r], color[:g], color[:b], persistant
109
+ end
110
+
60
111
  def rgb r, g, b, persistant = false
61
112
  write Request::SetRGB.new(@seq, r, g, b, persistant ? 0x01 : 0x00)
62
113
  end
@@ -77,18 +128,76 @@ class Sphero
77
128
  write Request::SetRotationRate.new(@seq, h)
78
129
  end
79
130
 
131
+ # just a nicer alias for Ruby's own sleep
132
+ def keep_going(duration)
133
+ Kernel::sleep duration
134
+ end
135
+
136
+ ## async messages
137
+
138
+ # configure power notification messages
139
+ def set_power_notification enable=true
140
+ write Request::SetPowerNotification.new(@seq, enable ? 0x01 : 0x00)
141
+ end
142
+
143
+ # configure data streaming notification messages
144
+ def set_data_streaming n, m, mask, pcnt, mask2
145
+ write Request::SetDataStreaming.new(@seq, n, m, mask, pcnt, mask2)
146
+ end
147
+
148
+ # configure collision detection messages
149
+ def configure_collision_detection meth, x_t, y_t, x_spd, y_spd, dead
150
+ write Request::ConfigureCollisionDetection.new(@seq, meth, x_t, y_t, x_spd, y_spd, dead)
151
+ end
152
+
80
153
  private
154
+
155
+ def is_windows?
156
+ os = RUBY_PLATFORM.split("-")[1]
157
+ if (os == 'mswin' or os == 'bccwin' or os == 'mingw' or os == 'mingw32')
158
+ true
159
+ else
160
+ false
161
+ end
162
+ end
163
+
164
+ def initialize_serialport dev
165
+ require 'serialport'
166
+ @sp = SerialPort.new dev, 115200, 8, 1, SerialPort::NONE
167
+ if is_windows?
168
+ @sp.read_timeout=1000
169
+ @sp.write_timeout=0
170
+ @sp.initial_byte_offset=5
171
+ end
172
+ rescue LoadError
173
+ puts "Please 'gem install hybridgroup-serialport' for serial port support."
174
+ end
81
175
 
82
176
  def write packet
83
- header = nil
84
- body = nil
177
+ header, body = nil
178
+
179
+ IO.select([], [@sp], [], 20)
85
180
 
86
181
  @lock.synchronize do
87
182
  @sp.write packet.to_str
88
183
  @seq += 1
184
+ end
185
+
186
+ IO.select([@sp], [], [], 20)
187
+ header = read_header(true)
188
+ body = read_body(header.last, true) if header
89
189
 
90
- header = @sp.read(5).unpack 'C5'
91
- body = @sp.read header.last
190
+ # pick off asynch packets and store, till we get to the message response
191
+ while header && Response.async?(header)
192
+ messages << Response::AsyncResponse.response(header, body)
193
+
194
+ IO.select([@sp], [], [], 20)
195
+ header = read_header(true)
196
+ if header
197
+ body = read_body(header.last, true)
198
+ else
199
+ body = nil
200
+ end
92
201
  end
93
202
 
94
203
  response = packet.response header, body
@@ -96,38 +205,203 @@ class Sphero
96
205
  if response.success?
97
206
  response
98
207
  else
99
- raise response
208
+ raise "Unable to write to Sphero!"
100
209
  end
101
210
  end
102
- end
103
211
 
104
- if $0 == __FILE__
105
- begin
106
- s = Sphero.new "/dev/tty.Sphero-BRR-RN-SPP"
107
- rescue Errno::EBUSY
108
- retry
109
- end
212
+ def read_header(blocking=false)
213
+ header = nil
214
+ begin
215
+ data = read_next_chunk(5, blocking)
216
+ return nil unless data
217
+ header = data.unpack 'C5'
218
+ rescue Errno::EBUSY
219
+ retry
220
+ rescue Exception => e
221
+ puts e.message
222
+ puts e.backtrace.inspect
223
+ retry
224
+ end
110
225
 
111
- 10.times {
112
- p s.ping
113
- }
226
+ header
227
+ end
114
228
 
115
- trap(:INT) {
116
- s.stop
117
- }
229
+ def read_body(len, blocking=false)
230
+ data = nil
231
+ begin
232
+ data = read_next_chunk(len, blocking)
233
+ return nil unless data && data.length == len
234
+ rescue Errno::EBUSY
235
+ retry
236
+ rescue Exception => e
237
+ puts e.message
238
+ puts e.backtrace.inspect
239
+ retry
240
+ end
118
241
 
119
- s.roll 100, 0
242
+ data
243
+ end
120
244
 
121
- loop do
122
- [0, 180].each do |dir|
123
- s.heading = dir
245
+ def read_next_chunk(len, blocking=false)
246
+ data = nil
247
+ begin
248
+ @lock.synchronize do
249
+ if blocking || is_windows?
250
+ data = @sp.read(len)
251
+ else
252
+ data = @sp.read_nonblock(len)
253
+ end
254
+ end
255
+ rescue Errno::EBUSY
256
+ retry
257
+ rescue Exception => e
258
+ puts e.message
259
+ puts e.backtrace.inspect
260
+ return nil
124
261
  end
125
- end
262
+ data
263
+ end
126
264
 
127
- #36.times {
128
- # i = 10
129
- # p :step => i
130
- # s.heading = i
131
- # sleep 0.5
132
- #}
265
+ COLORS = {
266
+ 'aliceblue' => {:r => 240, :g => 248, :b => 255},
267
+ 'antiquewhite' => {:r => 250, :g => 235, :b => 215},
268
+ 'aqua' => {:r => 0, :g => 255, :b => 255},
269
+ 'aquamarine' => {:r => 127, :g => 255, :b => 212},
270
+ 'azure' => {:r => 240, :g => 255, :b => 255},
271
+ 'beige' => {:r => 245, :g => 245, :b => 220},
272
+ 'bisque' => {:r => 255, :g => 228, :b => 196},
273
+ 'black' => {:r => 0, :g => 0, :b => 0},
274
+ 'blanchedalmond' => {:r => 255, :g => 235, :b => 205},
275
+ 'blue' => {:r => 0, :g => 0, :b => 255},
276
+ 'blueviolet' => {:r => 138, :g => 43, :b => 226},
277
+ 'brown' => {:r => 165, :g => 42, :b => 42},
278
+ 'burlywood' => {:r => 222, :g => 184, :b => 135},
279
+ 'cadetblue' => {:r => 95, :g => 158, :b => 160},
280
+ 'chartreuse' => {:r => 127, :g => 255, :b => 0},
281
+ 'chocolate' => {:r => 210, :g => 105, :b => 30},
282
+ 'coral' => {:r => 255, :g => 127, :b => 80},
283
+ 'cornflowerblue' => {:r => 100, :g => 149, :b => 237},
284
+ 'cornsilk' => {:r => 255, :g => 248, :b => 220},
285
+ 'crimson' => {:r => 220, :g => 20, :b => 60},
286
+ 'cyan' => {:r => 0, :g => 255, :b => 255},
287
+ 'darkblue' => {:r => 0, :g => 0, :b => 139},
288
+ 'darkcyan' => {:r => 0, :g => 139, :b => 139},
289
+ 'darkgoldenrod' => {:r => 184, :g => 134, :b => 11},
290
+ 'darkgray' => {:r => 169, :g => 169, :b => 169},
291
+ 'darkgreen' => {:r => 0, :g => 100, :b => 0},
292
+ 'darkkhaki' => {:r => 189, :g => 183, :b => 107},
293
+ 'darkmagenta' => {:r => 139, :g => 0, :b => 139},
294
+ 'darkolivegreen' => {:r => 85, :g => 107, :b => 47},
295
+ 'darkorange' => {:r => 255, :g => 140, :b => 0},
296
+ 'darkorchid' => {:r => 153, :g => 50, :b => 204},
297
+ 'darkred' => {:r => 139, :g => 0, :b => 0},
298
+ 'darksalmon' => {:r => 233, :g => 150, :b => 122},
299
+ 'darkseagreen' => {:r => 143, :g => 188, :b => 143},
300
+ 'darkslateblue' => {:r => 72, :g => 61, :b => 139},
301
+ 'darkslategray' => {:r => 47, :g => 79, :b => 79},
302
+ 'darkturquoise' => {:r => 0, :g => 206, :b => 209},
303
+ 'darkviolet' => {:r => 148, :g => 0, :b => 211},
304
+ 'deeppink' => {:r => 255, :g => 20, :b => 147},
305
+ 'deepskyblue' => {:r => 0, :g => 191, :b => 255},
306
+ 'dimgray' => {:r => 105, :g => 105, :b => 105},
307
+ 'dodgerblue' => {:r => 30, :g => 144, :b => 255},
308
+ 'firebrick' => {:r => 178, :g => 34, :b => 34},
309
+ 'floralwhite' => {:r => 255, :g => 250, :b => 240},
310
+ 'forestgreen' => {:r => 34, :g => 139, :b => 34},
311
+ 'fuchsia' => {:r => 255, :g => 0, :b => 255},
312
+ 'gainsboro' => {:r => 220, :g => 220, :b => 220},
313
+ 'ghostwhite' => {:r => 248, :g => 248, :b => 255},
314
+ 'gold' => {:r => 255, :g => 215, :b => 0},
315
+ 'goldenrod' => {:r => 218, :g => 165, :b => 32},
316
+ 'gray' => {:r => 128, :g => 128, :b => 128},
317
+ 'green' => {:r => 0, :g => 128, :b => 0},
318
+ 'greenyellow' => {:r => 173, :g => 255, :b => 47},
319
+ 'honeydew' => {:r => 240, :g => 255, :b => 240},
320
+ 'hotpink' => {:r => 255, :g => 105, :b => 180},
321
+ 'indianred' => {:r => 205, :g => 92, :b => 92},
322
+ 'indigo' => {:r => 75, :g => 0, :b => 130},
323
+ 'ivory' => {:r => 255, :g => 255, :b => 240},
324
+ 'khaki' => {:r => 240, :g => 230, :b => 140},
325
+ 'lavender' => {:r => 230, :g => 230, :b => 250},
326
+ 'lavenderblush' => {:r => 255, :g => 240, :b => 245},
327
+ 'lawngreen' => {:r => 124, :g => 252, :b => 0},
328
+ 'lemonchiffon' => {:r => 255, :g => 250, :b => 205},
329
+ 'lightblue' => {:r => 173, :g => 216, :b => 230},
330
+ 'lightcoral' => {:r => 240, :g => 128, :b => 128},
331
+ 'lightcyan' => {:r => 224, :g => 255, :b => 255},
332
+ 'lightgoldenrodyellow' => {:r => 250, :g => 250, :b => 210},
333
+ 'lightgreen' => {:r => 144, :g => 238, :b => 144},
334
+ 'lightgrey' => {:r => 211, :g => 211, :b => 211},
335
+ 'lightpink' => {:r => 255, :g => 182, :b => 193},
336
+ 'lightsalmon' => {:r => 255, :g => 160, :b => 122},
337
+ 'lightseagreen' => {:r => 32, :g => 178, :b => 170},
338
+ 'lightskyblue' => {:r => 135, :g => 206, :b => 250},
339
+ 'lightslategray' => {:r => 119, :g => 136, :b => 153},
340
+ 'lightsteelblue' => {:r => 176, :g => 196, :b => 222},
341
+ 'lightyellow' => {:r => 255, :g => 255, :b => 224},
342
+ 'lime' => {:r => 0, :g => 255, :b => 0},
343
+ 'limegreen' => {:r => 50, :g => 205, :b => 50},
344
+ 'linen' => {:r => 250, :g => 240, :b => 230},
345
+ 'magenta' => {:r => 255, :g => 0, :b => 255},
346
+ 'maroon' => {:r => 128, :g => 0, :b => 0},
347
+ 'mediumaquamarine' => {:r => 102, :g => 205, :b => 170},
348
+ 'mediumblue' => {:r => 0, :g => 0, :b => 205},
349
+ 'mediumorchid' => {:r => 186, :g => 85, :b => 211},
350
+ 'mediumpurple' => {:r => 147, :g => 112, :b => 219},
351
+ 'mediumseagreen' => {:r => 60, :g => 179, :b => 113},
352
+ 'mediumslateblue' => {:r => 123, :g => 104, :b => 238},
353
+ 'mediumspringgreen' => {:r => 0, :g => 250, :b => 154},
354
+ 'mediumturquoise' => {:r => 72, :g => 209, :b => 204},
355
+ 'mediumvioletred' => {:r => 199, :g => 21, :b => 133},
356
+ 'midnightblue' => {:r => 25, :g => 25, :b => 112},
357
+ 'mintcream' => {:r => 245, :g => 255, :b => 250},
358
+ 'mistyrose' => {:r => 255, :g => 228, :b => 225},
359
+ 'moccasin' => {:r => 255, :g => 228, :b => 181},
360
+ 'navajowhite' => {:r => 255, :g => 222, :b => 173},
361
+ 'navy' => {:r => 0, :g => 0, :b => 128},
362
+ 'oldlace' => {:r => 253, :g => 245, :b => 230},
363
+ 'olive' => {:r => 128, :g => 128, :b => 0},
364
+ 'olivedrab' => {:r => 107, :g => 142, :b => 35},
365
+ 'orange' => {:r => 255, :g => 165, :b => 0},
366
+ 'orangered' => {:r => 255, :g => 69, :b => 0},
367
+ 'orchid' => {:r => 218, :g => 112, :b => 214},
368
+ 'palegoldenrod' => {:r => 238, :g => 232, :b => 170},
369
+ 'palegreen' => {:r => 152, :g => 251, :b => 152},
370
+ 'paleturquoise' => {:r => 175, :g => 238, :b => 238},
371
+ 'palevioletred' => {:r => 219, :g => 112, :b => 147},
372
+ 'papayawhip' => {:r => 255, :g => 239, :b => 213},
373
+ 'peachpuff' => {:r => 255, :g => 218, :b => 185},
374
+ 'peru' => {:r => 205, :g => 133, :b => 63},
375
+ 'pink' => {:r => 255, :g => 192, :b => 203},
376
+ 'plum' => {:r => 221, :g => 160, :b => 221},
377
+ 'powderblue' => {:r => 176, :g => 224, :b => 230},
378
+ 'purple' => {:r => 128, :g => 0, :b => 128},
379
+ 'red' => {:r => 255, :g => 0, :b => 0},
380
+ 'rosybrown' => {:r => 188, :g => 143, :b => 143},
381
+ 'royalblue' => {:r => 65, :g => 105, :b => 225},
382
+ 'saddlebrown' => {:r => 139, :g => 69, :b => 19},
383
+ 'salmon' => {:r => 250, :g => 128, :b => 114},
384
+ 'sandybrown' => {:r => 244, :g => 164, :b => 96},
385
+ 'seagreen' => {:r => 46, :g => 139, :b => 87},
386
+ 'seashell' => {:r => 255, :g => 245, :b => 238},
387
+ 'sienna' => {:r => 160, :g => 82, :b => 45},
388
+ 'silver' => {:r => 192, :g => 192, :b => 192},
389
+ 'skyblue' => {:r => 135, :g => 206, :b => 235},
390
+ 'slateblue' => {:r => 106, :g => 90, :b => 205},
391
+ 'slategray' => {:r => 112, :g => 128, :b => 144},
392
+ 'snow' => {:r => 255, :g => 250, :b => 250},
393
+ 'springgreen' => {:r => 0, :g => 255, :b => 127},
394
+ 'steelblue' => {:r => 70, :g => 130, :b => 180},
395
+ 'tan' => {:r => 210, :g => 180, :b => 140},
396
+ 'teal' => {:r => 0, :g => 128, :b => 128},
397
+ 'thistle' => {:r => 216, :g => 191, :b => 216},
398
+ 'tomato' => {:r => 255, :g => 99, :b => 71},
399
+ 'turquoise' => {:r => 64, :g => 224, :b => 208},
400
+ 'violet' => {:r => 238, :g => 130, :b => 238},
401
+ 'wheat' => {:r => 245, :g => 222, :b => 179},
402
+ 'white' => {:r => 255, :g => 255, :b => 255},
403
+ 'whitesmoke' => {:r => 245, :g => 245, :b => 245},
404
+ 'yellow' => {:r => 255, :g => 255, :b => 0},
405
+ 'yellowgreen' => {:r => 154, :g => 205, :b => 50}
406
+ }
133
407
  end
@@ -3,6 +3,8 @@ class Sphero
3
3
  SOP1 = 0xFF
4
4
  SOP2 = 0xFF
5
5
 
6
+ attr_reader :data
7
+
6
8
  def initialize seq, data = []
7
9
  @seq = seq
8
10
  @data = data
@@ -19,7 +21,12 @@ class Sphero
19
21
  end
20
22
 
21
23
  def response header, body
22
- Response.new header, body
24
+ name = self.class.name.split('::').last
25
+ if Response.const_defined?(name)
26
+ Response.const_get(name).new header, body
27
+ else
28
+ Response.new header, body
29
+ end
23
30
  end
24
31
 
25
32
  def packet_header
@@ -49,49 +56,26 @@ class Sphero
49
56
  end
50
57
  end
51
58
 
52
- class Heading < Sphero
53
- def initialize seq, heading
54
- super(seq, [heading])
55
- @cid = 0x01
56
- end
57
-
58
- private
59
- def packet_body
60
- @data.pack 'n'
61
- end
59
+ def self.make_command klass, cid, &block
60
+ Class.new(klass) {
61
+ define_method(:initialize) do |seq, *args|
62
+ super(seq, args)
63
+ @cid = cid
64
+ end
65
+ }
62
66
  end
63
67
 
64
- class SetBackLEDOutput < Sphero
65
- def initialize seq, brightness
66
- super(seq, [brightness])
67
- @cid = 0x21
68
- end
69
- end
68
+ SetBackLEDOutput = make_command Sphero, 0x21
69
+ SetRotationRate = make_command Sphero, 0x03
70
+ SetRGB = make_command Sphero, 0x20
71
+ GetRGB = make_command Sphero, 0x22
70
72
 
71
- class SetRotationRate < Sphero
72
- def initialize seq, rate
73
- super(seq, [rate])
74
- @cid = 0x03
75
- end
76
- end
77
-
78
- class SetRGB < Sphero
79
- def initialize seq, r, g, b, persistant
80
- super(seq, [r, g, b, persistant])
81
- @cid = 0x20
82
- end
83
- end
84
-
85
- class GetRGB < Sphero
86
- def initialize seq
87
- super(seq, [])
88
- @cid = 0x22
89
- end
90
-
91
- def response header, body
92
- Response::GetRGB.new header, body
93
- end
94
- end
73
+ Ping = make_command Request, 0x01
74
+ GetVersioning = make_command Request, 0x02
75
+ GetBluetoothInfo = make_command Request, 0x11
76
+ SetAutoReconnect = make_command Request, 0x12
77
+ GetAutoReconnect = make_command Request, 0x13
78
+ GetPowerState = make_command Request, 0x20
95
79
 
96
80
  class Roll < Sphero
97
81
  def initialize seq, speed, heading, delay
@@ -105,70 +89,96 @@ class Sphero
105
89
  end
106
90
  end
107
91
 
108
- class Ping < Request
109
- def initialize seq
110
- super(seq, [])
111
- @cid = 0x01
112
- end
113
- end
114
-
115
- class GetVersioning < Request
116
- def initialize seq
117
- super(seq, [])
118
- @cid = 0x02
119
- end
120
- end
121
-
122
- class GetBluetoothInfo < Request
123
- def initialize seq
124
- super(seq, [])
125
- @cid = 0x11
92
+ class Heading < Request
93
+ def initialize seq, heading
94
+ super(seq, [heading])
95
+ @cid = 0x01
126
96
  end
127
97
 
128
- def response header, body
129
- Response::GetBluetoothInfo.new header, body
98
+ private
99
+ def packet_body
100
+ @data.pack 'n'
130
101
  end
131
102
  end
132
103
 
133
- class SetAutoReconnect < Request
134
- def initialize seq, time = 7, enabled = 0x01
135
- super(seq, [enabled, time])
136
- @cid = 0x12
104
+ class Sleep < Request
105
+ def initialize seq, wakeup, macro
106
+ super(seq, [wakeup, macro])
107
+ @cid = 0x22
137
108
  end
138
- end
139
109
 
140
- class GetAutoReconnect < Request
141
- def initialize seq
142
- super(seq, [])
143
- @cid = 0x13
144
- end
110
+ private
145
111
 
146
- def response header, body
147
- Response::GetAutoReconnect.new header, body
112
+ def packet_body
113
+ @data.pack 'nC'
148
114
  end
149
115
  end
150
116
 
151
- class GetPowerState < Request
152
- def initialize seq
153
- super(seq, [])
154
- @cid = 0x20
155
- end
156
-
157
- def response header, body
158
- Response::GetPowerState.new header, body
117
+ class SetPowerNotification < Sphero
118
+ def initialize seq, enable
119
+ super(seq, [enable])
120
+ @cid = 0x21
159
121
  end
160
122
  end
161
123
 
162
- class Sleep < Request
163
- def initialize seq, wakeup, macro
164
- super(seq, [wakeup, macro])
165
- @cid = 0x22
124
+ GYRO_AXIS_H_FILTERED = 0x0000_0001
125
+ GYRO_AXIS_M_FILTERED = 0x0000_0002
126
+ GYRO_AXIS_L_FILTERED = 0x0000_0004
127
+ LEFT_MOTOR_BACK_EMF_FILTERED = 0x0000_0020
128
+ RIGHT_MOTOR_BACK_EMF_FILTERED = 0x0000_0040
129
+ MAGNETOMETER_AXIS_Z_FILTERED = 0x0000_0080
130
+ MAGNETOMETER_AXIS_Y_FILTERED = 0x0000_0100
131
+ MAGNETOMETER_AXIS_X_FILTERED = 0x0000_0200
132
+ GYRO_AXIS_Z_FILTERED = 0x0000_0400
133
+ GYRO_AXIS_Y_FILTERED = 0x0000_0800
134
+ GYRO_AXIS_X_FILTERED = 0x0000_1000
135
+ ACCELEROMETER_AXIS_Z_FILTERED = 0x0000_2000
136
+ ACCELEROMETER_AXIS_Y_FILTERED = 0x0000_4000
137
+ ACCELEROMETER_AXIS_X_FILTERED = 0x0000_8000
138
+ IMU_YAW_ANGLE_FILTERED = 0x0001_0000
139
+ IMU_ROLL_ANGLE_FILTERED = 0x0002_0000
140
+ IMU_PITCH_ANGLE_FILTERED = 0x0004_0000
141
+ LEFT_MOTOR_BACK_EMF_RAW = 0x0020_0000
142
+ RIGHT_MOTOR_BACK_EMF_RAW = 0x0040_0000
143
+ MAGNETOMETER_AXIS_Z_RAW = 0x0080_0000
144
+ MAGNETOMETER_AXIS_Y_RAW = 0x0100_0000
145
+ MAGNETOMETER_AXIS_X_RAW = 0x0200_0000
146
+ GYRO_AXIS_Z_RAW = 0x0400_0000
147
+ GYRO_AXIS_Y_RAW = 0x0800_0000
148
+ GYRO_AXIS_X_RAW = 0x1000_0000
149
+ ACCELEROMETER_AXIS_Z_RAW = 0x2000_0000
150
+ ACCELEROMETER_AXIS_Y_RAW = 0x4000_0000
151
+ ACCELEROMETER_AXIS_X_RAW = 0x8000_0000
152
+
153
+ QUATERNION_Q0 = 0x0000_0001
154
+ QUATERNION_Q1 = 0x0000_0002
155
+ QUATERNION_Q2 = 0x0000_0004
156
+ QUATERNION_Q3 = 0x0000_0008
157
+ ODOMETER_X = 0x0000_0010
158
+ ODOMETER_Y = 0x0000_0020
159
+ ACCELONE = 0x0000_0040
160
+ VELOCITY_X = 0x0000_0080
161
+ VELOCITY_Y = 0x0000_0100
162
+
163
+ class SetDataStreaming < Sphero
164
+ def initialize seq, n, m, mask, pcnt, mask2
165
+ super(seq, [n, m, mask, pcnt, mask2])
166
+ @cid = 0x12
167
+ @mask = mask
168
+ @mask2 = mask2
166
169
  end
167
170
 
168
171
  private
169
172
 
170
173
  def packet_body
171
- @data.pack 'nC'
174
+ @data.pack 'nnNCN'
175
+ end
176
+ end
177
+
178
+ class ConfigureCollisionDetection < Sphero
179
+ def initialize seq, meth, x_t, y_t, x_spd, y_spd, dead
180
+ super(seq, [meth, x_t, y_t, x_spd, y_spd, dead])
181
+ @cid = 0x12
172
182
  end
173
183
  end
174
184
  end
@@ -7,22 +7,36 @@ class Sphero
7
7
  DLEN = 4
8
8
 
9
9
  CODE_OK = 0
10
+ SIMPLE_RESPONSE = 0xFF
11
+ ASYNC_RESPONSE = 0xFE
12
+
13
+ def self.simple?(header)
14
+ header[SOP2] == SIMPLE_RESPONSE
15
+ end
16
+
17
+ def self.async?(header)
18
+ header[SOP2] == ASYNC_RESPONSE
19
+ end
10
20
 
11
21
  def initialize header, body
12
22
  @header = header
13
23
  @body = body
14
24
  end
15
25
 
26
+ def valid?
27
+ @header && @body
28
+ end
29
+
16
30
  def empty?
17
- @header[DLEN] == 1
31
+ valid? && @header[DLEN] == 1
18
32
  end
19
33
 
20
34
  def success?
21
- @header[MRSP] == CODE_OK
35
+ valid? && @header[MRSP] == CODE_OK
22
36
  end
23
37
 
24
38
  def seq
25
- @header[SEQ]
39
+ valid? && @header[SEQ]
26
40
  end
27
41
 
28
42
  def body
@@ -84,5 +98,106 @@ class Sphero
84
98
  def g; body[1]; end
85
99
  def b; body[2]; end
86
100
  end
101
+
102
+ class AsyncResponse < Response
103
+ ID_CODE = 2
104
+ DLEN_MSB = 3
105
+ DLEN_LSB = 4
106
+
107
+ POWER_NOTIFICATION = 0x01
108
+ LEVEL_1_DIAGNOSTIC = 0x02
109
+ SENSOR_DATA = 0x03
110
+ CONFIG_BLOCK = 0x04
111
+ PRESLEEP_WARNING = 0x05
112
+ MACRO_MARKERS = 0x06
113
+ COLLISION_DETECTED = 0x07
114
+
115
+ VALID_REPONSES = {POWER_NOTIFICATION => 'Sphero::Response::PowerNotification',
116
+ #LEVEL_1_DIAGNOSTIC => 'AsyncResponse',
117
+ SENSOR_DATA => 'Sphero::Response::SensorData',
118
+ #CONFIG_BLOCK => 'AsyncResponse',
119
+ #PRESLEEP_WARNING => 'AsyncResponse',
120
+ #MACRO_MARKERS => 'AsyncResponse',
121
+ COLLISION_DETECTED => 'Sphero::Response::CollisionDetected'}
122
+
123
+ def self.valid?(header)
124
+ VALID_REPONSES.keys.include?(header[ID_CODE])
125
+ end
126
+
127
+ def self.response header, body
128
+ raise "no good" unless self.valid?(header)
129
+ constantize(VALID_REPONSES[header[ID_CODE]]).new(header, body)
130
+ end
131
+
132
+ def self.constantize(camel_cased_word)
133
+ names = camel_cased_word.split('::')
134
+ names.shift if names.empty? || names.first.empty?
135
+
136
+ constant = Object
137
+ names.each do |name|
138
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
139
+ end
140
+ constant
141
+ end
142
+
143
+ def empty?
144
+ @header[DLEN_LSB] == 1
145
+ end
146
+
147
+ def success?
148
+ AsyncResponse.valid?(@header)
149
+ end
150
+
151
+ def seq
152
+ 1
153
+ end
154
+ end
155
+
156
+ class PowerNotification < GetPowerState
157
+ end
158
+
159
+ class SensorData < AsyncResponse
160
+ def body
161
+ @body.unpack 's*'
162
+ end
163
+ end
164
+
165
+ class CollisionDetected < AsyncResponse
166
+ def body
167
+ @body.unpack 'nnnCnnCN'
168
+ end
169
+
170
+ def x
171
+ body[0]
172
+ end
173
+
174
+ def y
175
+ body[1]
176
+ end
177
+
178
+ def z
179
+ body[2]
180
+ end
181
+
182
+ def axis
183
+ body[3]
184
+ end
185
+
186
+ def x_magnitude
187
+ body[4]
188
+ end
189
+
190
+ def y_magnitude
191
+ body[5]
192
+ end
193
+
194
+ def speed
195
+ body[6]
196
+ end
197
+
198
+ def timestamp
199
+ body[7]
200
+ end
201
+ end
87
202
  end
88
203
  end
@@ -1,14 +1,105 @@
1
1
  require 'minitest/autorun'
2
2
  require 'sphero'
3
+ require 'mocha/setup'
3
4
 
4
5
  class TestSphero < MiniTest::Unit::TestCase
5
- def test_ping_checksum
6
- ping = Sphero::Request::Ping.new 0
7
- assert_equal "\xFF\xFF\x00\x01\x00\x01\xFD", ping.to_str
6
+ def setup
7
+ Sphero.any_instance.stubs(:initialize_serialport)
8
+ Sphero.any_instance.stubs(:initialize_socket)
9
+ @sphero = Sphero.new 'port123'
10
+ @seq = 0x00
8
11
  end
9
12
 
10
- def test_sleep_dlen
11
- sleep = Sphero::Request::Sleep.new 0, 0, 0
12
- assert_equal 0x04, sleep.dlen
13
+ def test_start_returns_new_sphero
14
+ assert_kind_of Sphero, Sphero.start('someport')
15
+ end
16
+
17
+ def test_start_sphero_executes_block
18
+ Sphero.any_instance.expects(:ping)
19
+ Sphero.any_instance.expects(:close)
20
+ Sphero.start('someport') do
21
+ ping
22
+ end
23
+ end
24
+
25
+ def test_ping
26
+ Sphero::Request::Ping.expects(:new).with(@seq)
27
+ @sphero.expects(:write)
28
+ @sphero.ping
29
+ end
30
+
31
+ def test_version
32
+ Sphero::Request::GetVersioning.expects(:new).with(@seq)
33
+ @sphero.expects(:write)
34
+ @sphero.version
35
+ end
36
+
37
+ def test_bluetooth_info
38
+ Sphero::Request::GetBluetoothInfo.expects(:new).with(@seq)
39
+ @sphero.expects(:write)
40
+ @sphero.bluetooth_info
41
+ end
42
+
43
+ def test_auto_reconnect=
44
+ @time_s = "10"
45
+ Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, @time_s)
46
+ @sphero.expects(:write)
47
+ @sphero.auto_reconnect = @time_s
48
+ end
49
+
50
+ def test_auto_reconnect
51
+ @time_s = "10"
52
+ packet = mock 'packet'
53
+ packet.stubs(:time).returns(@time_s)
54
+
55
+ Sphero::Request::GetAutoReconnect.expects(:new).with(@seq)
56
+ @sphero.expects(:write).returns(packet)
57
+ assert_equal @sphero.auto_reconnect, @time_s
58
+ end
59
+
60
+ def test_disable_auto_reconnect
61
+ Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, 0, 0x00)
62
+ @sphero.expects(:write)
63
+ @sphero.disable_auto_reconnect
64
+ end
65
+
66
+ def test_power_state
67
+ Sphero::Request::GetPowerState.expects(:new).with(@seq)
68
+ @sphero.expects(:write)
69
+ @sphero.power_state
70
+ end
71
+
72
+ def test_sphero_sleep
73
+ wakeup = 1
74
+ macro = 2
75
+ Sphero::Request::Sleep.expects(:new).with(@seq, wakeup, macro)
76
+ @sphero.expects(:write)
77
+ @sphero.sphero_sleep wakeup, macro
78
+ end
79
+
80
+ def test_roll
81
+ speed = 1
82
+ heading = 2
83
+ state = 1
84
+ Sphero::Request::Roll.expects(:new).with(@seq, speed, heading, state)
85
+ @sphero.expects(:write)
86
+ @sphero.roll speed, heading, true
87
+ end
88
+
89
+ def test_stop
90
+ @sphero.expects(:roll).with(0, 0)
91
+ @sphero.stop
92
+ end
93
+
94
+ def test_keepgoing
95
+ Kernel.expects(:sleep).with(3)
96
+ @sphero.keep_going 3
97
+ end
98
+
99
+ def test_directions
100
+ assert_equal 0, Sphero::FORWARD
101
+ assert_equal 90, Sphero::RIGHT
102
+ assert_equal 180, Sphero::BACKWARD
103
+ assert_equal 270, Sphero::LEFT
13
104
  end
14
105
  end
@@ -0,0 +1,15 @@
1
+ require 'minitest/autorun'
2
+ require 'sphero'
3
+
4
+ class TestSpheroRequest < MiniTest::Unit::TestCase
5
+ def test_ping_checksum
6
+ ping = Sphero::Request::Ping.new 0
7
+ expected_bytes = "\xFF\xFF\x00\x01\x00\x01\xFD".force_encoding(Encoding::ASCII_8BIT)
8
+ assert_equal expected_bytes, ping.to_str
9
+ end
10
+
11
+ def test_sleep_dlen
12
+ sleep = Sphero::Request::Sleep.new 0, 0, 0
13
+ assert_equal 0x04, sleep.dlen
14
+ end
15
+ end
metadata CHANGED
@@ -1,48 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sphero
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Aaron Patterson
8
+ - Hybrid Group
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-25 00:00:00.000000000 Z
12
+ date: 2013-07-25 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: serialport
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: minitest
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: '3.2'
38
- type: :development
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: '3.2'
46
14
  - !ruby/object:Gem::Dependency
47
15
  name: rdoc
48
16
  requirement: !ruby/object:Gem::Requirement
@@ -50,7 +18,7 @@ dependencies:
50
18
  requirements:
51
19
  - - ~>
52
20
  - !ruby/object:Gem::Version
53
- version: '3.10'
21
+ version: '4.0'
54
22
  type: :development
55
23
  prerelease: false
56
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +26,7 @@ dependencies:
58
26
  requirements:
59
27
  - - ~>
60
28
  - !ruby/object:Gem::Version
61
- version: '3.10'
29
+ version: '4.0'
62
30
  - !ruby/object:Gem::Dependency
63
31
  name: hoe
64
32
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +34,7 @@ dependencies:
66
34
  requirements:
67
35
  - - ~>
68
36
  - !ruby/object:Gem::Version
69
- version: '3.0'
37
+ version: '3.6'
70
38
  type: :development
71
39
  prerelease: false
72
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -74,13 +42,13 @@ dependencies:
74
42
  requirements:
75
43
  - - ~>
76
44
  - !ruby/object:Gem::Version
77
- version: '3.0'
45
+ version: '3.6'
78
46
  description: ! 'A ruby gem for controlling your Sphero ball. Sends commands over
79
47
  the TTY
80
48
 
81
49
  provided by the bluetooth connection.'
82
50
  email:
83
- - aaron@tenderlovemaking.com
51
+ - sphero@hybridgroup.com
84
52
  executables:
85
53
  - sphero
86
54
  extensions: []
@@ -99,8 +67,9 @@ files:
99
67
  - lib/sphero/request.rb
100
68
  - lib/sphero/response.rb
101
69
  - test/test_sphero.rb
70
+ - test/test_sphero_request.rb
102
71
  - .gemtest
103
- homepage: http://github.com/tenderlove/sphero
72
+ homepage: http://github.com/hybridgroup/sphero
104
73
  licenses: []
105
74
  post_install_message:
106
75
  rdoc_options:
@@ -113,7 +82,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
82
  requirements:
114
83
  - - ! '>='
115
84
  - !ruby/object:Gem::Version
116
- version: 1.9.3
85
+ version: 1.9.2
117
86
  required_rubygems_version: !ruby/object:Gem::Requirement
118
87
  none: false
119
88
  requirements:
@@ -122,9 +91,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
91
  version: '0'
123
92
  requirements: []
124
93
  rubyforge_project: sphero
125
- rubygems_version: 1.8.23
94
+ rubygems_version: 1.8.24
126
95
  signing_key:
127
96
  specification_version: 3
128
97
  summary: A ruby gem for controlling your Sphero ball
129
98
  test_files:
130
99
  - test/test_sphero.rb
100
+ - test/test_sphero_request.rb