Skip to content

Commit

Permalink
Merge pull request hybridgroup#168 from hybridgroup/sphero_enhancements
Browse files Browse the repository at this point in the history
Sphero enhancements
  • Loading branch information
deadprogram committed Dec 25, 2014
2 parents 3e8e451 + 7c836d6 commit 21b411a
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 24 deletions.
10 changes: 8 additions & 2 deletions examples/sphero.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ func main() {
spheroDriver := sphero.NewSpheroDriver(adaptor, "sphero")

work := func() {
gobot.On(spheroDriver.Event("collision"), func(data interface{}) {
fmt.Printf("Collision Detected! %+v\n", data)
spheroDriver.SetDataStreaming(sphero.DefaultDataStreamingConfig())

gobot.On(spheroDriver.Event(sphero.Collision), func(data interface{}) {
fmt.Printf("Collision! %+v\n", data)
})

gobot.On(spheroDriver.Event(sphero.SensorData), func(data interface{}) {
fmt.Printf("Streaming Data! %+v\n", data)
})

gobot.Every(3*time.Second, func() {
Expand Down
76 changes: 54 additions & 22 deletions platforms/sphero/sphero_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import (

var _ gobot.Driver = (*SpheroDriver)(nil)

const (
SensorData = "sensordata"
Collision = "collision"
Error = "error"
)

type packet struct {
header []uint8
body []uint8
Expand All @@ -30,19 +36,6 @@ type SpheroDriver struct {
gobot.Commander
}

type Collision struct {
// Normalized impact components (direction of the collision event):
X, Y, Z int16
// Thresholds exceeded by X (1h) and/or Y (2h) axis (bitmask):
Axis byte
// Power that cross threshold Xt + Xs:
XMagnitude, YMagnitude int16
// Sphero's speed when impact detected:
Speed uint8
// Millisecond timer
Timestamp uint32
}

// NewSpheroDriver returns a new SpheroDriver given a SpheroAdaptor and name.
//
// Adds the following API Commands:
Expand All @@ -52,6 +45,7 @@ type Collision struct {
// "SetBackLED" - See SpheroDriver.SetBackLED
// "SetHeading" - See SpheroDriver.SetHeading
// "SetStabilization" - See SpheroDriver.SetStabilization
// "SetDataStreaming" - See SpheroDriver.SetDataStreaming
func NewSpheroDriver(a *SpheroAdaptor, name string) *SpheroDriver {
s := &SpheroDriver{
name: name,
Expand All @@ -62,8 +56,10 @@ func NewSpheroDriver(a *SpheroAdaptor, name string) *SpheroDriver {
responseChannel: make(chan []uint8, 1024),
}

s.AddEvent("error")
s.AddEvent("collision")
s.AddEvent(Error)
s.AddEvent(Collision)
s.AddEvent(SensorData)

s.AddCommand("SetRGB", func(params map[string]interface{}) interface{} {
r := uint8(params["r"].(float64))
g := uint8(params["g"].(float64))
Expand Down Expand Up @@ -99,12 +95,24 @@ func NewSpheroDriver(a *SpheroAdaptor, name string) *SpheroDriver {
s.SetHeading(heading)
return nil
})

s.AddCommand("SetStabilization", func(params map[string]interface{}) interface{} {
on := params["enable"].(bool)
s.SetStabilization(on)
return nil
})

s.AddCommand("SetDataStreaming", func(params map[string]interface{}) interface{} {
N := uint16(params["N"].(float64))
M := uint16(params["M"].(float64))
Mask := uint32(params["Mask"].(float64))
Pcnt := uint8(params["Pcnt"].(float64))
Mask2 := uint32(params["Mask2"].(float64))

s.SetDataStreaming(DataStreamingConfig{N: N, M: M, Mask2: Mask2, Pcnt: Pcnt, Mask: Mask})
return nil
})

return s
}

Expand All @@ -119,14 +127,16 @@ func (s *SpheroDriver) adaptor() *SpheroAdaptor {
// Returns true on successful start.
//
// Emits the Events:
// "collision" SpheroDriver.Collision - On Collision Detected
// Collision sphero.CollisionPacket - On Collision Detected
// SensorData sphero.DataStreamingPacket - On Data Streaming event
// Error error- On error while processing asynchronous response
func (s *SpheroDriver) Start() (errs []error) {
go func() {
for {
packet := <-s.packetChannel
err := s.write(packet)
if err != nil {
gobot.Publish(s.Event("error"), err)
gobot.Publish(s.Event(Error), err)
}
}
}()
Expand Down Expand Up @@ -165,13 +175,15 @@ func (s *SpheroDriver) Start() (errs []error) {
evt, s.asyncResponse = s.asyncResponse[len(s.asyncResponse)-1], s.asyncResponse[:len(s.asyncResponse)-1]
if evt[2] == 0x07 {
s.handleCollisionDetected(evt)
} else if evt[2] == 0x03 {
s.handleDataStreaming(evt)
}
}
time.Sleep(100 * time.Millisecond)
}
}()

s.configureCollisionDetection()
s.ConfigureCollisionDetection(DefaultCollisionConfig())
s.enableStopOnDisconnect()

return
Expand Down Expand Up @@ -227,13 +239,22 @@ func (s *SpheroDriver) Roll(speed uint8, heading uint16) {
s.packetChannel <- s.craftPacket([]uint8{speed, uint8(heading >> 8), uint8(heading & 0xFF), 0x01}, 0x02, 0x30)
}

// Enables sensor data streaming
func (s *SpheroDriver) SetDataStreaming(d DataStreamingConfig) {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, d)

s.packetChannel <- s.craftPacket(buf.Bytes(), 0x02, 0x11)
}

// Stop sets the Sphero to a roll speed of 0
func (s *SpheroDriver) Stop() {
s.Roll(0, 0)
}

func (s *SpheroDriver) configureCollisionDetection() {
s.packetChannel <- s.craftPacket([]uint8{0x01, 0x40, 0x40, 0x50, 0x50, 0x60}, 0x02, 0x12)
// ConfigureCollisionDetection configures the sensitivity of the detection.
func (s *SpheroDriver) ConfigureCollisionDetection(cc CollisionConfig) {
s.packetChannel <- s.craftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12)
}

func (s *SpheroDriver) enableStopOnDisconnect() {
Expand All @@ -245,10 +266,21 @@ func (s *SpheroDriver) handleCollisionDetected(data []uint8) {
if len(data) != 22 || data[4] != 17 {
return
}
var collision Collision
var collision CollisionPacket
buffer := bytes.NewBuffer(data[5:]) // skip header
binary.Read(buffer, binary.BigEndian, &collision)
gobot.Publish(s.Event("collision"), collision)
gobot.Publish(s.Event(Collision), collision)
}

func (s *SpheroDriver) handleDataStreaming(data []uint8) {
// ensure data is the right length:
if len(data) != 90 {
return
}
var dataPacket DataStreamingPacket
buffer := bytes.NewBuffer(data[5:]) // skip header
binary.Read(buffer, binary.BigEndian, &dataPacket)
gobot.Publish(s.Event(SensorData), dataPacket)
}

func (s *SpheroDriver) getSyncResponse(packet *packet) []byte {
Expand Down
32 changes: 32 additions & 0 deletions platforms/sphero/sphero_driver_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sphero

import (
"bytes"
"encoding/binary"
"testing"

"github.com/hybridgroup/gobot"
Expand Down Expand Up @@ -62,6 +64,36 @@ func TestSpheroDriverHalt(t *testing.T) {
gobot.Assert(t, len(d.Halt()), 0)
}

func TestSpheroDriverSetDataStreaming(t *testing.T) {
d := initTestSpheroDriver()
d.SetDataStreaming(DefaultDataStreamingConfig())

data := <-d.packetChannel

buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, DefaultDataStreamingConfig())

gobot.Assert(t, data.body, buf.Bytes())

ret := d.Command("SetDataStreaming")(
map[string]interface{}{
"N": 100.0,
"M": 200.0,
"Mask": 300.0,
"Pcnt": 255.0,
"Mask2": 400.0,
},
)
gobot.Assert(t, ret, nil)
data = <-d.packetChannel

dconfig := DataStreamingConfig{N: 100, M: 200, Mask: 300, Pcnt: 255, Mask2: 400}
buf = new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, dconfig)

gobot.Assert(t, data.body, buf.Bytes())
}

func TestCalculateChecksum(t *testing.T) {
tests := []struct {
data []byte
Expand Down
162 changes: 162 additions & 0 deletions platforms/sphero/sphero_packets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package sphero

// DefaultCollisionConfig returns a CollisionConfig with sensible collision defaults
func DefaultCollisionConfig() CollisionConfig {
return CollisionConfig{
Method: 0x01,
Xt: 0x80,
Yt: 0x80,
Xs: 0x80,
Ys: 0x80,
Dead: 0x60,
}
}

// CollisionConfig provides configuration for the collision detection alogorithm.
// For more information refer to the offical api specification https://github.com/orbotix/DeveloperResources/blob/master/docs/Collision%20detection%201.2.pdf.
type CollisionConfig struct {
// Detection method type to use. Methods 01h and 02h are supported as
// of FW ver 1.42. Use 00h to completely disable this service.
Method uint8
// An 8-bit settable threshold for the X (left/right) axes of Sphero.
// A value of 00h disables the contribution of that axis.
Xt uint8
// An 8-bit settable threshold for the Y (front/back) axes of Sphero.
// A value of 00h disables the contribution of that axis.
Yt uint8
// An 8-bit settable speed value for the X axes. This setting is ranged
// by the speed, then added to Xt to generate the final threshold value.
Xs uint8
// An 8-bit settable speed value for the Y axes. This setting is ranged
// by the speed, then added to Yt to generate the final threshold value.
Ys uint8
// An 8-bit post-collision dead time to prevent retriggering; specified
// in 10ms increments.
Dead uint8
}

// CollisionPacket represents the response from a Collision event
type CollisionPacket struct {
// Normalized impact components (direction of the collision event):
X, Y, Z int16
// Thresholds exceeded by X (1h) and/or Y (2h) axis (bitmask):
Axis byte
// Power that cross threshold Xt + Xs:
XMagnitude, YMagnitude int16
// Sphero's speed when impact detected:
Speed uint8
// Millisecond timer
Timestamp uint32
}

// DefaultDataStreamingConfig returns a DataStreamingConfig with a sampling rate of 40hz, 1 sample frame per package, unlimited streaming, and will stream all available sensor information
func DefaultDataStreamingConfig() DataStreamingConfig {
return DataStreamingConfig{
N: 10,
M: 1,
Mask: 4294967295,
Pcnt: 0,
Mask2: 4294967295,
}
}

// DataStreamingConfig provides configuration for Sensor Data Streaming.
// For more information refer to the offical api specification https://github.com/orbotix/DeveloperResources/blob/master/docs/Sphero_API_1.50.pdf page 28
type DataStreamingConfig struct {
// Divisor of the maximum sensor sampling rate
N uint16
// Number of sample frames emitted per packet
M uint16
// Bitwise selector of data sources to stream
Mask uint32
// Packet count 1-255 (or 0 for unlimited streaming)
Pcnt uint8
// Bitwise selector of more data sources to stream (optional)
Mask2 uint32
}

// DataStreamingPacket represents the response from a Data Streaming event
type DataStreamingPacket struct {
// 8000 0000h accelerometer axis X, raw -2048 to 2047 4mG
RawAccX int16
// 4000 0000h accelerometer axis Y, raw -2048 to 2047 4mG
RawAccY int16
// 2000 0000h accelerometer axis Z, raw -2048 to 2047 4mG
RawAccZ int16
// 1000 0000h gyro axis X, raw -32768 to 32767 0.068 degrees
RawGyroX int16
// 0800 0000h gyro axis Y, raw -32768 to 32767 0.068 degrees
RawGyroY int16
// 0400 0000h gyro axis Z, raw -32768 to 32767 0.068 degrees
RawGyroZ int16
// 0200 0000h Reserved
Rsrv1 int16
// 0100 0000h Reserved
Rsrv2 int16
// 0080 0000h Reserved
Rsrv3 int16
// 0040 0000h right motor back EMF, raw -32768 to 32767 22.5 cm
RawRMotorBack int16
// 0020 0000h left motor back EMF, raw -32768 to 32767 22.5 cm
RawLMotorBack int16
// 0010 0000h left motor, PWM, raw -2048 to 2047 duty cycle
RawLMotor int16
// 0008 0000h right motor, PWM raw -2048 to 2047 duty cycle
RawRMotor int16
// 0004 0000h IMU pitch angle, filtered -179 to 180 degrees
FiltPitch int16
// 0002 0000h IMU roll angle, filtered -179 to 180 degrees
FiltRoll int16
// 0001 0000h IMU yaw angle, filtered -179 to 180 degrees
FiltYaw int16
// 0000 8000h accelerometer axis X, filtered -32768 to 32767 1/4096 G
FiltAccX int16
// 0000 4000h accelerometer axis Y, filtered -32768 to 32767 1/4096 G
FiltAccY int16
// 0000 2000h accelerometer axis Z, filtered -32768 to 32767 1/4096 G
FiltAccZ int16
// 0000 1000h gyro axis X, filtered -20000 to 20000 0.1 dps
FiltGyroX int16
// 0000 0800h gyro axis Y, filtered -20000 to 20000 0.1 dps
FiltGyroY int16
// 0000 0400h gyro axis Z, filtered -20000 to 20000 0.1 dps
FiltGyroZ int16
// 0000 0200h Reserved
Rsrv4 int16
// 0000 0100h Reserved
Rsrv5 int16
// 0000 0080h Reserved
Rsrv6 int16
// 0000 0040h right motor back EMF, filtered -32768 to 32767 22.5 cm
FiltRMotorBack int16
// 0000 0020h left motor back EMF, filtered -32768 to 32767 22.5 cm
FiltLMotorBack int16
// 0000 0010h Reserved 1
Rsrv7 int16
// 0000 0008h Reserved 2
Rsrv8 int16
// 0000 0004h Reserved 3
Rsrv9 int16
// 0000 0002h Reserved 4
Rsrv10 int16
// 0000 0001h Reserved 5
Rsrv11 int16
// 8000 0000h Quaternion Q0 -10000 to 10000 1/10000 Q
Quat0 int16
// 4000 0000h Quaternion Q1 -10000 to 10000 1/10000 Q
Quat1 int16
// 2000 0000h Quaternion Q2 -10000 to 10000 1/10000 Q
Quat2 int16
// 1000 0000h Quaternion Q3 -10000 to 10000 1/10000 Q
Quat3 int16
// 0800 0000h Odometer X -32768 to 32767 cm
OdomX int16
// 0400 0000h Odometer Y -32768 to 32767 cm
OdomY int16
// 0200 0000h AccelOne 0 to 8000 1 mG
AccelOne int16
// 0100 0000h Velocity X -32768 to 32767 mm/s
VeloX int16
// 0080 0000h Velocity Y -32768 to 32767 mm/s
VeloY int16
}

0 comments on commit 21b411a

Please sign in to comment.