hybridgroup-sphero 1.1.6 → 1.2.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.
data/README.markdown CHANGED
@@ -77,6 +77,23 @@ sleep 1
77
77
  s.stop
78
78
  ```
79
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
+
80
97
  ## REQUIREMENTS:
81
98
 
82
99
  * A Sphero ball connected to your computer
data/Rakefile CHANGED
@@ -14,7 +14,6 @@ Hoe.spec 'hybridgroup-sphero' do
14
14
  self.readme_file = 'README.markdown'
15
15
  self.history_file = 'CHANGELOG.rdoc'
16
16
  self.extra_rdoc_files = FileList['*.{rdoc,markdown}']
17
- self.extra_deps << ['hybridgroup-serialport']
18
17
 
19
18
  self.spec_extras = {
20
19
  :required_ruby_version => '>= 1.9.2'
@@ -113,5 +113,73 @@ class Sphero
113
113
  @data.pack 'nC'
114
114
  end
115
115
  end
116
+
117
+ class SetPowerNotification < Sphero
118
+ def initialize seq, enable
119
+ super(seq, [enable])
120
+ @cid = 0x21
121
+ end
122
+ end
123
+
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
169
+ end
170
+
171
+ private
172
+
173
+ def packet_body
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
182
+ end
183
+ end
116
184
  end
117
185
  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
data/lib/sphero.rb CHANGED
@@ -1,18 +1,22 @@
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.1.6'
6
+ VERSION = '1.2.0'
8
7
 
9
8
  FORWARD = 0
10
9
  RIGHT = 90
11
10
  BACKWARD = 180
12
11
  LEFT = 270
13
12
 
13
+ DEFAULT_RETRIES = 3
14
+
15
+ attr_accessor :connection_types, :async_messages
16
+
14
17
  class << self
15
18
  def start(dev, &block)
19
+ retries_left = DEFAULT_RETRIES
16
20
  sphero = self.new dev
17
21
  if (block_given?)
18
22
  begin
@@ -24,15 +28,23 @@ class Sphero
24
28
  end
25
29
  return sphero
26
30
  rescue Errno::EBUSY
27
- retry
31
+ puts retries_left
32
+ retries_left = retries_left - 1
33
+ retry unless retries_left > 0
28
34
  end
29
35
  end
30
36
 
31
37
  def initialize dev
32
- initialize_serialport dev
38
+ if dev.is_a?(String)
39
+ initialize_serialport dev
40
+ else
41
+ @sp = dev
42
+ end
43
+
33
44
  @dev = 0x00
34
45
  @seq = 0x00
35
46
  @lock = Mutex.new
47
+ @async_messages = []
36
48
  end
37
49
 
38
50
  def close
@@ -110,6 +122,42 @@ class Sphero
110
122
  Kernel::sleep duration
111
123
  end
112
124
 
125
+ ## async messages
126
+
127
+ # configure power notification messages
128
+ def set_power_notification enable=true
129
+ write Request::SetPowerNotification.new(@seq, enable ? 0x01 : 0x00)
130
+ end
131
+
132
+ # configure data streaming notification messages
133
+ def set_data_streaming n, m, mask, pcnt, mask2
134
+ write Request::SetDataStreaming.new(@seq, n, m, mask, pcnt, mask2)
135
+ end
136
+
137
+ # configure collision detection messages
138
+ def configure_collision_detection meth, x_t, y_t, x_spd, y_spd, dead
139
+ write Request::ConfigureCollisionDetection.new(@seq, meth, x_t, y_t, x_spd, y_spd, dead)
140
+ end
141
+
142
+ # read all outstanding async packets and store in async_responses
143
+ # would not do well to receive simple responses this way...
144
+ def read_async_messages
145
+ header, body = nil
146
+ new_responses = []
147
+
148
+ @lock.synchronize do
149
+ header, body = read_next_response
150
+
151
+ while header && Response.async?(header)
152
+ new_responses << Response::AsyncResponse.response(header, body)
153
+ header, body = read_next_response
154
+ end
155
+ end
156
+
157
+ async_messages.concat(new_responses) unless new_responses.empty?
158
+ return !new_responses.empty?
159
+ end
160
+
113
161
  private
114
162
 
115
163
  def is_windows?
@@ -122,24 +170,39 @@ class Sphero
122
170
  end
123
171
 
124
172
  def initialize_serialport dev
173
+ require 'serialport'
125
174
  @sp = SerialPort.new dev, 115200, 8, 1, SerialPort::NONE
126
175
  if is_windows?
127
176
  @sp.read_timeout=1000
128
177
  @sp.write_timeout=0
129
178
  @sp.initial_byte_offset=5
130
179
  end
180
+ rescue LoadError
181
+ puts "Please 'gem install hybridgroup-serialport' for serial port support."
131
182
  end
132
183
 
133
184
  def write packet
134
- header = nil
135
- body = nil
185
+ header, body = nil
136
186
 
137
187
  @lock.synchronize do
188
+ rs, ws = IO.select([], [@sp], [], 20)
138
189
  @sp.write packet.to_str
139
190
  @seq += 1
140
191
 
141
- header = @sp.read(5).unpack 'C5'
142
- body = @sp.read header.last
192
+ header = nil
193
+ loop do
194
+ header = read_header(true)
195
+ break if header
196
+ end
197
+
198
+ body = read_body(header.last, true) if header
199
+
200
+ # pick off asynch packets and store, till we get to the message response
201
+ while header && Response.async?(header)
202
+ async_messages << Response::AsyncResponse.response(header, body)
203
+ header = read_header(true)
204
+ body = read_body(header.last, true) if header
205
+ end
143
206
  end
144
207
 
145
208
  response = packet.response header, body
@@ -147,8 +210,58 @@ class Sphero
147
210
  if response.success?
148
211
  response
149
212
  else
150
- raise response
213
+ raise "Unable to write to Sphero!"
151
214
  end
152
215
  end
216
+
217
+ def read_header(blocking=false)
218
+ begin
219
+ data = read_next_chunk(5, blocking)
220
+ return nil unless data && data.length == 5
221
+ header = data.unpack 'C5'
222
+ rescue Errno::EBUSY
223
+ retry
224
+ rescue Exception => e
225
+ puts e.message
226
+ puts e.backtrace.inspect
227
+ retry
228
+ end
229
+
230
+ header
231
+ end
232
+
233
+ def read_body(len, blocking=false)
234
+ begin
235
+ data = read_next_chunk(len, blocking)
236
+ return nil unless data && data.length == len
237
+ rescue Errno::EBUSY
238
+ retry
239
+ rescue Exception => e
240
+ puts e.message
241
+ puts e.backtrace.inspect
242
+ retry
243
+ end
244
+
245
+ data
246
+ end
247
+
248
+ def read_next_chunk(len, blocking=false)
249
+ begin
250
+ if blocking || is_windows?
251
+ rs, ws, = IO.select([@sp], [], [], 20)
252
+ data = @sp.read(len)
253
+ return nil unless data && data.length == len
254
+ else
255
+ data = @sp.read_nonblock(len)
256
+ end
257
+ rescue Errno::EBUSY
258
+ retry
259
+ rescue Exception => e
260
+ puts e.message
261
+ puts e.backtrace.inspect
262
+ return nil
263
+ end
264
+ data
265
+ end
153
266
  end
154
267
 
data/test/test_sphero.rb CHANGED
@@ -3,11 +3,12 @@ require 'sphero'
3
3
  require 'mocha/setup'
4
4
 
5
5
  class TestSphero < MiniTest::Unit::TestCase
6
- def setup
7
- Sphero.any_instance.stubs(:initialize_serialport)
8
- @sphero = Sphero.new 'port123'
9
- @seq = 0x00
10
- end
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
11
+ end
11
12
 
12
13
  def test_start_returns_new_sphero
13
14
  assert_kind_of Sphero, Sphero.start('someport')
@@ -22,77 +23,77 @@ class TestSphero < MiniTest::Unit::TestCase
22
23
  end
23
24
 
24
25
  def test_ping
25
- Sphero::Request::Ping.expects(:new).with(@seq)
26
- @sphero.expects(:write)
27
- @sphero.ping
26
+ Sphero::Request::Ping.expects(:new).with(@seq)
27
+ @sphero.expects(:write)
28
+ @sphero.ping
28
29
  end
29
30
 
30
31
  def test_version
31
- Sphero::Request::GetVersioning.expects(:new).with(@seq)
32
- @sphero.expects(:write)
33
- @sphero.version
32
+ Sphero::Request::GetVersioning.expects(:new).with(@seq)
33
+ @sphero.expects(:write)
34
+ @sphero.version
34
35
  end
35
36
 
36
37
  def test_bluetooth_info
37
- Sphero::Request::GetBluetoothInfo.expects(:new).with(@seq)
38
- @sphero.expects(:write)
39
- @sphero.bluetooth_info
38
+ Sphero::Request::GetBluetoothInfo.expects(:new).with(@seq)
39
+ @sphero.expects(:write)
40
+ @sphero.bluetooth_info
40
41
  end
41
42
 
42
43
  def test_auto_reconnect=
43
- @time_s = "10"
44
- Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, @time_s)
45
- @sphero.expects(:write)
46
- @sphero.auto_reconnect = @time_s
44
+ @time_s = "10"
45
+ Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, @time_s)
46
+ @sphero.expects(:write)
47
+ @sphero.auto_reconnect = @time_s
47
48
  end
48
49
 
49
50
  def test_auto_reconnect
50
- @time_s = "10"
51
- packet = mock 'packet'
52
- packet.stubs(:time).returns(@time_s)
51
+ @time_s = "10"
52
+ packet = mock 'packet'
53
+ packet.stubs(:time).returns(@time_s)
53
54
 
54
- Sphero::Request::GetAutoReconnect.expects(:new).with(@seq)
55
- @sphero.expects(:write).returns(packet)
56
- assert_equal @sphero.auto_reconnect, @time_s
55
+ Sphero::Request::GetAutoReconnect.expects(:new).with(@seq)
56
+ @sphero.expects(:write).returns(packet)
57
+ assert_equal @sphero.auto_reconnect, @time_s
57
58
  end
58
59
 
59
60
  def test_disable_auto_reconnect
60
- Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, 0, 0x00)
61
- @sphero.expects(:write)
62
- @sphero.disable_auto_reconnect
61
+ Sphero::Request::SetAutoReconnect.expects(:new).with(@seq, 0, 0x00)
62
+ @sphero.expects(:write)
63
+ @sphero.disable_auto_reconnect
63
64
  end
64
65
 
65
66
  def test_power_state
66
- Sphero::Request::GetPowerState.expects(:new).with(@seq)
67
- @sphero.expects(:write)
68
- @sphero.power_state
67
+ Sphero::Request::GetPowerState.expects(:new).with(@seq)
68
+ @sphero.expects(:write)
69
+ @sphero.power_state
69
70
  end
70
71
 
71
72
  def test_sphero_sleep
72
- wakeup = 1
73
- macro = 2
74
- Sphero::Request::Sleep.expects(:new).with(@seq, wakeup, macro)
75
- @sphero.expects(:write)
76
- @sphero.sphero_sleep wakeup, macro
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
77
78
  end
78
79
 
79
80
  def test_roll
80
- speed = 1
81
- heading = 2
82
- state = 1
83
- Sphero::Request::Roll.expects(:new).with(@seq, speed, heading, state)
84
- @sphero.expects(:write)
85
- @sphero.roll speed, heading, true
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
86
87
  end
87
88
 
88
89
  def test_stop
89
- @sphero.expects(:roll).with(0, 0)
90
- @sphero.stop
90
+ @sphero.expects(:roll).with(0, 0)
91
+ @sphero.stop
91
92
  end
92
93
 
93
94
  def test_keepgoing
94
- Kernel.expects(:sleep).with(3)
95
- @sphero.keep_going 3
95
+ Kernel.expects(:sleep).with(3)
96
+ @sphero.keep_going 3
96
97
  end
97
98
 
98
99
  def test_directions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hybridgroup-sphero
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.6
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-14 00:00:00.000000000 Z
12
+ date: 2013-02-23 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: hybridgroup-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
14
  - !ruby/object:Gem::Dependency
31
15
  name: minitest
32
16
  requirement: !ruby/object:Gem::Requirement