sphero 1.0.0 → 1.4.1

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.
@@ -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