hybridgroup-sphero 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +13 -0
- data/Manifest.txt +10 -0
- data/README.markdown +66 -0
- data/Rakefile +23 -0
- data/bin/sphero +3 -0
- data/lib/sphero/request.rb +117 -0
- data/lib/sphero/response.rb +88 -0
- data/lib/sphero.rb +154 -0
- data/test/test_sphero.rb +14 -0
- metadata +110 -0
data/.autotest
ADDED
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
=== 1.0.0 / 2012-07-20
|
2
|
+
|
3
|
+
* 1 major enhancement
|
4
|
+
|
5
|
+
* Birthday!
|
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
|
data/Manifest.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# sphero
|
2
|
+
|
3
|
+
* http://github.com/tenderlove/sphero
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
A ruby gem for controlling your Sphero ball. Sends commands over the TTY
|
8
|
+
provided by the bluetooth connection.
|
9
|
+
|
10
|
+
## FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
* You need a Sphero
|
13
|
+
|
14
|
+
## SYNOPSIS:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
s = Sphero.new "/dev/tty.Sphero-PRG-RN-SPP"
|
18
|
+
s.ping
|
19
|
+
|
20
|
+
# Roll 0 degrees, speed 125
|
21
|
+
s.roll(125, 0)
|
22
|
+
|
23
|
+
# Turn 360 degrees, 30 degrees at a time
|
24
|
+
0.step(360, 30) { |h|
|
25
|
+
h = 0 if h == 360
|
26
|
+
|
27
|
+
# Set the heading to h degrees
|
28
|
+
s.heading = h
|
29
|
+
sleep 1
|
30
|
+
}
|
31
|
+
sleep 1
|
32
|
+
s.stop
|
33
|
+
```
|
34
|
+
|
35
|
+
## REQUIREMENTS:
|
36
|
+
|
37
|
+
* A Sphero ball connected to your computer
|
38
|
+
|
39
|
+
## INSTALL:
|
40
|
+
|
41
|
+
* gem install sphero
|
42
|
+
|
43
|
+
## LICENSE:
|
44
|
+
|
45
|
+
(The MIT License)
|
46
|
+
|
47
|
+
Copyright (c) 2012 Aaron Patterson
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.plugins.delete :rubyforge
|
7
|
+
Hoe.plugin :minitest
|
8
|
+
Hoe.plugin :gemspec # `gem install hoe-gemspec`
|
9
|
+
Hoe.plugin :git # `gem install hoe-git`
|
10
|
+
|
11
|
+
Hoe.spec 'hybridgroup-sphero' do
|
12
|
+
developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
|
13
|
+
self.readme_file = 'README.markdown'
|
14
|
+
self.history_file = 'CHANGELOG.rdoc'
|
15
|
+
self.extra_rdoc_files = FileList['*.{rdoc,markdown}']
|
16
|
+
self.extra_deps << ['serialport']
|
17
|
+
|
18
|
+
self.spec_extras = {
|
19
|
+
:required_ruby_version => '>= 1.9.2'
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
# vim: syntax=ruby
|
data/bin/sphero
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
class Sphero
|
2
|
+
class Request
|
3
|
+
SOP1 = 0xFF
|
4
|
+
SOP2 = 0xFF
|
5
|
+
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize seq, data = []
|
9
|
+
@seq = seq
|
10
|
+
@data = data
|
11
|
+
@did = 0x00
|
12
|
+
end
|
13
|
+
|
14
|
+
def header
|
15
|
+
[SOP1, SOP2, @did, @cid, @seq, dlen]
|
16
|
+
end
|
17
|
+
|
18
|
+
# The data to write to the socket
|
19
|
+
def to_str
|
20
|
+
bytes
|
21
|
+
end
|
22
|
+
|
23
|
+
def response header, body
|
24
|
+
name = self.class.name.split('::').last
|
25
|
+
klass = if Response.const_defined?(name)
|
26
|
+
Response.const_get(name).new header, body
|
27
|
+
else
|
28
|
+
Response.new header, body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def packet_header
|
33
|
+
header.pack 'CCCCCC'
|
34
|
+
end
|
35
|
+
|
36
|
+
def packet_body
|
37
|
+
@data.pack 'C*'
|
38
|
+
end
|
39
|
+
|
40
|
+
def checksum
|
41
|
+
~((packet_header + packet_body).unpack('C*').drop(2).reduce(:+) % 256) & 0xFF
|
42
|
+
end
|
43
|
+
|
44
|
+
def bytes
|
45
|
+
packet_header + packet_body + checksum.chr
|
46
|
+
end
|
47
|
+
|
48
|
+
def dlen
|
49
|
+
packet_body.bytesize + 1
|
50
|
+
end
|
51
|
+
|
52
|
+
class Sphero < Request
|
53
|
+
def initialize seq, data = []
|
54
|
+
super
|
55
|
+
@did = 0x02
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
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
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
SetBackLEDOutput = make_command Sphero, 0x21
|
69
|
+
SetRotationRate = make_command Sphero, 0x03
|
70
|
+
SetRGB = make_command Sphero, 0x20
|
71
|
+
GetRGB = make_command Sphero, 0x22
|
72
|
+
|
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
|
79
|
+
|
80
|
+
class Roll < Sphero
|
81
|
+
def initialize seq, speed, heading, delay
|
82
|
+
super(seq, [speed, heading, delay])
|
83
|
+
@cid = 0x30
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def packet_body
|
88
|
+
@data.pack 'CnC'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Heading < Request
|
93
|
+
def initialize seq, heading
|
94
|
+
super(seq, [heading])
|
95
|
+
@cid = 0x01
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def packet_body
|
100
|
+
@data.pack 'n'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Sleep < Request
|
105
|
+
def initialize seq, wakeup, macro
|
106
|
+
super(seq, [wakeup, macro])
|
107
|
+
@cid = 0x22
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def packet_body
|
113
|
+
@data.pack 'nC'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Sphero
|
2
|
+
class Response
|
3
|
+
SOP1 = 0
|
4
|
+
SOP2 = 1
|
5
|
+
MRSP = 2
|
6
|
+
SEQ = 3
|
7
|
+
DLEN = 4
|
8
|
+
|
9
|
+
CODE_OK = 0
|
10
|
+
|
11
|
+
def initialize header, body
|
12
|
+
@header = header
|
13
|
+
@body = body
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
@header[DLEN] == 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def success?
|
21
|
+
@header[MRSP] == CODE_OK
|
22
|
+
end
|
23
|
+
|
24
|
+
def seq
|
25
|
+
@header[SEQ]
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
@body.unpack 'C*'
|
30
|
+
end
|
31
|
+
|
32
|
+
class GetAutoReconnect < Response
|
33
|
+
def time
|
34
|
+
body[1]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class GetPowerState < Response
|
39
|
+
# constants for power_state
|
40
|
+
CHARGING = 0x01
|
41
|
+
OK = 0x02
|
42
|
+
LOW = 0x03
|
43
|
+
CRITICAL = 0x04
|
44
|
+
|
45
|
+
def body
|
46
|
+
@body.unpack 'CCnnnC'
|
47
|
+
end
|
48
|
+
|
49
|
+
def rec_ver
|
50
|
+
body[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
def power_state
|
54
|
+
body[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Voltage * 100
|
58
|
+
def batt_voltage
|
59
|
+
body[2]
|
60
|
+
end
|
61
|
+
|
62
|
+
def num_charges
|
63
|
+
body[3]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Time since awakened in seconds
|
67
|
+
def time_since_charge
|
68
|
+
body[4]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class GetBluetoothInfo < Response
|
73
|
+
def name
|
74
|
+
body.take(16).slice_before(0x00).first.pack 'C*'
|
75
|
+
end
|
76
|
+
|
77
|
+
def bta
|
78
|
+
body.drop(16).slice_before(0x00).first.pack 'C*'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class GetRGB < Response
|
83
|
+
def r; body[0]; end
|
84
|
+
def g; body[1]; end
|
85
|
+
def b; body[2]; end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/sphero.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'serialport'
|
2
|
+
require 'sphero/request'
|
3
|
+
require 'sphero/response'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
class Sphero
|
7
|
+
VERSION = '1.0.1'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def start(dev)
|
11
|
+
self.new dev
|
12
|
+
rescue Errno::EBUSY
|
13
|
+
retry
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize dev
|
18
|
+
@sp = SerialPort.new dev, 115200, 8, 1, SerialPort::NONE
|
19
|
+
@dev = 0x00
|
20
|
+
@seq = 0x00
|
21
|
+
@lock = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def ping
|
25
|
+
write Request::Ping.new(@seq)
|
26
|
+
end
|
27
|
+
|
28
|
+
def version
|
29
|
+
write Request::GetVersioning.new(@seq)
|
30
|
+
end
|
31
|
+
|
32
|
+
def bluetooth_info
|
33
|
+
write Request::GetBluetoothInfo.new(@seq)
|
34
|
+
end
|
35
|
+
|
36
|
+
def auto_reconnect= time_s
|
37
|
+
write Request::SetAutoReconnect.new(@seq, time_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def auto_reconnect
|
41
|
+
write(Request::GetAutoReconnect.new(@seq)).time
|
42
|
+
end
|
43
|
+
|
44
|
+
def disable_auto_reconnect
|
45
|
+
write Request::SetAutoReconnect.new(@seq, 0, 0x00)
|
46
|
+
end
|
47
|
+
|
48
|
+
def power_state
|
49
|
+
write Request::GetPowerState.new(@seq)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sleep wakeup = 0, macro = 0
|
53
|
+
write Request::Sleep.new(@seq, wakeup, macro)
|
54
|
+
end
|
55
|
+
|
56
|
+
def roll speed, heading, state = true
|
57
|
+
write Request::Roll.new(@seq, speed, heading, state ? 0x01 : 0x00)
|
58
|
+
end
|
59
|
+
|
60
|
+
def stop
|
61
|
+
roll 0, 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def heading= h
|
65
|
+
write Request::Heading.new(@seq, h)
|
66
|
+
end
|
67
|
+
|
68
|
+
def rgb r, g, b, persistant = false
|
69
|
+
write Request::SetRGB.new(@seq, r, g, b, persistant ? 0x01 : 0x00)
|
70
|
+
end
|
71
|
+
|
72
|
+
# This retrieves the "user LED color" which is stored in the config block
|
73
|
+
# (which may or may not be actively driven to the RGB LED).
|
74
|
+
def user_led
|
75
|
+
write Request::GetRGB.new(@seq)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Brightness 0x00 - 0xFF
|
79
|
+
def back_led_output= h
|
80
|
+
write Request::SetBackLEDOutput.new(@seq, h)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Rotation Rate 0x00 - 0xFF
|
84
|
+
def rotation_rate= h
|
85
|
+
write Request::SetRotationRate.new(@seq, h)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def write packet
|
91
|
+
header = nil
|
92
|
+
body = nil
|
93
|
+
|
94
|
+
@lock.synchronize do
|
95
|
+
@sp.write packet.to_str
|
96
|
+
@seq += 1
|
97
|
+
|
98
|
+
header = @sp.read(5).unpack 'C5'
|
99
|
+
body = @sp.read header.last
|
100
|
+
end
|
101
|
+
|
102
|
+
response = packet.response header, body
|
103
|
+
|
104
|
+
if response.success?
|
105
|
+
response
|
106
|
+
else
|
107
|
+
raise response
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if $0 == __FILE__
|
113
|
+
begin
|
114
|
+
s = Sphero.new "/dev/tty.Sphero-BRR-RN-SPP"
|
115
|
+
rescue Errno::EBUSY
|
116
|
+
retry
|
117
|
+
end
|
118
|
+
|
119
|
+
10.times {
|
120
|
+
p s.ping
|
121
|
+
}
|
122
|
+
|
123
|
+
trap(:INT) {
|
124
|
+
s.stop
|
125
|
+
exit!
|
126
|
+
}
|
127
|
+
|
128
|
+
#s.roll 100, 0
|
129
|
+
|
130
|
+
p s.user_led
|
131
|
+
exit
|
132
|
+
loop do
|
133
|
+
[0, 180].each do |dir|
|
134
|
+
s.heading = dir
|
135
|
+
sleep 10
|
136
|
+
end
|
137
|
+
|
138
|
+
#[
|
139
|
+
# [0, 0, 0xFF],
|
140
|
+
# [0xFF, 0, 0],
|
141
|
+
# [0, 0xFF, 0],
|
142
|
+
#].each do |color|
|
143
|
+
# s.rgb(*color)
|
144
|
+
# sleep 5
|
145
|
+
#end
|
146
|
+
end
|
147
|
+
|
148
|
+
#36.times {
|
149
|
+
# i = 10
|
150
|
+
# p :step => i
|
151
|
+
# s.heading = i
|
152
|
+
# sleep 0.5
|
153
|
+
#}
|
154
|
+
end
|
data/test/test_sphero.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'sphero'
|
3
|
+
|
4
|
+
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
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_sleep_dlen
|
11
|
+
sleep = Sphero::Request::Sleep.new 0, 0, 0
|
12
|
+
assert_equal 0x04, sleep.dlen
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hybridgroup-sphero
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Aaron Patterson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: serialport
|
16
|
+
requirement: &2152670780 !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: *2152670780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: minitest
|
27
|
+
requirement: &2152670180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.11'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2152670180
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rdoc
|
38
|
+
requirement: &2152669620 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.10'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2152669620
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: hoe
|
49
|
+
requirement: &2152669060 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.1'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2152669060
|
58
|
+
description: ! 'A ruby gem for controlling your Sphero ball. Sends commands over
|
59
|
+
the TTY
|
60
|
+
|
61
|
+
provided by the bluetooth connection.'
|
62
|
+
email:
|
63
|
+
- aaron@tenderlovemaking.com
|
64
|
+
executables:
|
65
|
+
- sphero
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files:
|
68
|
+
- CHANGELOG.rdoc
|
69
|
+
- Manifest.txt
|
70
|
+
- README.markdown
|
71
|
+
files:
|
72
|
+
- .autotest
|
73
|
+
- CHANGELOG.rdoc
|
74
|
+
- Manifest.txt
|
75
|
+
- README.markdown
|
76
|
+
- Rakefile
|
77
|
+
- bin/sphero
|
78
|
+
- lib/sphero.rb
|
79
|
+
- lib/sphero/request.rb
|
80
|
+
- lib/sphero/response.rb
|
81
|
+
- test/test_sphero.rb
|
82
|
+
- .gemtest
|
83
|
+
homepage: http://github.com/tenderlove/sphero
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options:
|
87
|
+
- --main
|
88
|
+
- README.markdown
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.9.2
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project: hybridgroup-sphero
|
105
|
+
rubygems_version: 1.8.6
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: A ruby gem for controlling your Sphero ball
|
109
|
+
test_files:
|
110
|
+
- test/test_sphero.rb
|