2014-04-26 18:07:04 -07:00
|
|
|
package sphero
|
2014-04-26 03:11:51 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2014-07-09 18:32:27 -07:00
|
|
|
|
|
|
|
"github.com/hybridgroup/gobot"
|
2014-04-26 03:11:51 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type packet struct {
|
|
|
|
header []uint8
|
|
|
|
body []uint8
|
|
|
|
checksum uint8
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
type SpheroDriver struct {
|
2014-04-26 03:11:51 -07:00
|
|
|
gobot.Driver
|
2014-06-10 15:16:11 -07:00
|
|
|
seq uint8
|
|
|
|
asyncResponse [][]uint8
|
|
|
|
syncResponse [][]uint8
|
|
|
|
packetChannel chan *packet
|
|
|
|
responseChannel chan []uint8
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-05-22 21:20:16 -07:00
|
|
|
func NewSpheroDriver(a *SpheroAdaptor, name string) *SpheroDriver {
|
2014-06-11 18:59:30 -07:00
|
|
|
s := &SpheroDriver{
|
2014-07-07 17:42:19 -07:00
|
|
|
Driver: *gobot.NewDriver(
|
|
|
|
name,
|
|
|
|
"SpheroDriver",
|
|
|
|
a,
|
|
|
|
),
|
2014-06-10 15:16:11 -07:00
|
|
|
packetChannel: make(chan *packet, 1024),
|
|
|
|
responseChannel: make(chan []uint8, 1024),
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2014-06-11 18:59:30 -07:00
|
|
|
|
2014-07-07 17:42:19 -07:00
|
|
|
s.AddEvent("collision")
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("SetRGB", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
r := uint8(params["r"].(float64))
|
|
|
|
g := uint8(params["g"].(float64))
|
|
|
|
b := uint8(params["b"].(float64))
|
|
|
|
s.SetRGB(r, g, b)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("Roll", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
speed := uint8(params["speed"].(float64))
|
|
|
|
heading := uint16(params["heading"].(float64))
|
|
|
|
s.Roll(speed, heading)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("Stop", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
s.Stop()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("GetRGB", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
return s.GetRGB()
|
|
|
|
})
|
|
|
|
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("SetBackLED", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
level := uint8(params["level"].(float64))
|
|
|
|
s.SetBackLED(level)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("SetHeading", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
heading := uint16(params["heading"].(float64))
|
|
|
|
s.SetHeading(heading)
|
|
|
|
return nil
|
|
|
|
})
|
2014-07-09 18:32:27 -07:00
|
|
|
s.AddCommand("SetStabilization", func(params map[string]interface{}) interface{} {
|
2014-06-11 18:59:30 -07:00
|
|
|
on := params["heading"].(bool)
|
|
|
|
s.SetStabilization(on)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return s
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2014-06-15 17:22:50 -07:00
|
|
|
|
|
|
|
func (s *SpheroDriver) adaptor() *SpheroAdaptor {
|
2014-07-09 18:32:27 -07:00
|
|
|
return s.Adaptor().(*SpheroAdaptor)
|
2014-06-15 17:22:50 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) Init() bool {
|
2014-04-26 03:11:51 -07:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) Start() bool {
|
2014-04-26 03:11:51 -07:00
|
|
|
go func() {
|
|
|
|
for {
|
2014-06-10 15:16:11 -07:00
|
|
|
packet := <-s.packetChannel
|
2014-04-26 18:07:04 -07:00
|
|
|
s.write(packet)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
2014-06-10 15:16:11 -07:00
|
|
|
response := <-s.responseChannel
|
|
|
|
s.syncResponse = append(s.syncResponse, response)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
2014-04-26 18:07:04 -07:00
|
|
|
header := s.readHeader()
|
2014-04-26 03:11:51 -07:00
|
|
|
if header != nil && len(header) != 0 {
|
2014-04-26 18:07:04 -07:00
|
|
|
body := s.readBody(header[4])
|
2014-04-26 03:11:51 -07:00
|
|
|
if header[1] == 0xFE {
|
|
|
|
async := append(header, body...)
|
2014-06-10 15:16:11 -07:00
|
|
|
s.asyncResponse = append(s.asyncResponse, async)
|
2014-04-26 03:11:51 -07:00
|
|
|
} else {
|
2014-06-10 15:16:11 -07:00
|
|
|
s.responseChannel <- append(header, body...)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
var evt []uint8
|
2014-06-10 15:16:11 -07:00
|
|
|
for len(s.asyncResponse) != 0 {
|
|
|
|
evt, s.asyncResponse = s.asyncResponse[len(s.asyncResponse)-1], s.asyncResponse[:len(s.asyncResponse)-1]
|
2014-04-26 03:11:51 -07:00
|
|
|
if evt[2] == 0x07 {
|
2014-04-26 18:07:04 -07:00
|
|
|
s.handleCollisionDetected(evt)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2014-04-26 18:07:04 -07:00
|
|
|
s.configureCollisionDetection()
|
2014-07-14 16:21:03 -05:00
|
|
|
s.enableStopOnDisconnect()
|
2014-04-26 03:11:51 -07:00
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) Halt() bool {
|
2014-07-10 19:24:57 -07:00
|
|
|
gobot.Every(10*time.Millisecond, func() {
|
|
|
|
s.Stop()
|
|
|
|
})
|
2014-04-26 03:11:51 -07:00
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) SetRGB(r uint8, g uint8, b uint8) {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{r, g, b, 0x01}, 0x02, 0x20)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) GetRGB() []uint8 {
|
2014-08-06 01:06:34 +08:00
|
|
|
return s.getSyncResponse(s.craftPacket([]uint8{}, 0x02, 0x22))
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) SetBackLED(level uint8) {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{level}, 0x02, 0x21)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) SetHeading(heading uint16) {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{uint8(heading >> 8), uint8(heading & 0xFF)}, 0x02, 0x01)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) SetStabilization(on bool) {
|
2014-04-26 03:11:51 -07:00
|
|
|
b := uint8(0x01)
|
|
|
|
if on == false {
|
|
|
|
b = 0x00
|
|
|
|
}
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{b}, 0x02, 0x02)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) Roll(speed uint8, heading uint16) {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{speed, uint8(heading >> 8), uint8(heading & 0xFF), 0x01}, 0x02, 0x30)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) Stop() {
|
2014-04-26 18:07:04 -07:00
|
|
|
s.Roll(0, 0)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) configureCollisionDetection() {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{0x01, 0x40, 0x40, 0x50, 0x50, 0x60}, 0x02, 0x12)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-07-14 16:21:03 -05:00
|
|
|
func (s *SpheroDriver) enableStopOnDisconnect() {
|
2014-08-06 01:06:34 +08:00
|
|
|
s.packetChannel <- s.craftPacket([]uint8{0x00, 0x00, 0x00, 0x01}, 0x02, 0x37)
|
2014-07-14 16:21:03 -05:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) handleCollisionDetected(data []uint8) {
|
2014-07-07 17:42:19 -07:00
|
|
|
gobot.Publish(s.Event("collision"), data)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-06-10 15:16:11 -07:00
|
|
|
func (s *SpheroDriver) getSyncResponse(packet *packet) []byte {
|
|
|
|
s.packetChannel <- packet
|
2014-04-26 03:11:51 -07:00
|
|
|
for i := 0; i < 500; i++ {
|
2014-06-10 15:16:11 -07:00
|
|
|
for key := range s.syncResponse {
|
|
|
|
if s.syncResponse[key][3] == packet.header[4] && len(s.syncResponse[key]) > 6 {
|
2014-04-26 03:11:51 -07:00
|
|
|
var response []byte
|
2014-06-10 15:16:11 -07:00
|
|
|
response, s.syncResponse = s.syncResponse[len(s.syncResponse)-1], s.syncResponse[:len(s.syncResponse)-1]
|
2014-04-26 03:11:51 -07:00
|
|
|
return response
|
|
|
|
}
|
|
|
|
}
|
|
|
|
time.Sleep(10 * time.Microsecond)
|
|
|
|
}
|
|
|
|
|
2014-06-10 15:16:11 -07:00
|
|
|
return []byte{}
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-08-06 01:06:34 +08:00
|
|
|
func (s *SpheroDriver) craftPacket(body []uint8, did byte, cid byte) *packet {
|
2014-04-26 03:11:51 -07:00
|
|
|
packet := new(packet)
|
|
|
|
packet.body = body
|
|
|
|
dlen := len(packet.body) + 1
|
2014-08-06 01:06:34 +08:00
|
|
|
packet.header = []uint8{0xFF, 0xFF, did, cid, s.seq, uint8(dlen)}
|
2014-04-26 18:07:04 -07:00
|
|
|
packet.checksum = s.calculateChecksum(packet)
|
2014-04-26 03:11:51 -07:00
|
|
|
return packet
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) write(packet *packet) {
|
2014-04-26 03:11:51 -07:00
|
|
|
buf := append(packet.header, packet.body...)
|
|
|
|
buf = append(buf, packet.checksum)
|
2014-06-15 17:22:50 -07:00
|
|
|
length, err := s.adaptor().sp.Write(buf)
|
2014-04-26 03:11:51 -07:00
|
|
|
if err != nil {
|
2014-04-26 18:07:04 -07:00
|
|
|
fmt.Println(s.Name, err)
|
2014-06-15 17:22:50 -07:00
|
|
|
s.adaptor().Disconnect()
|
2014-04-28 11:40:20 -07:00
|
|
|
fmt.Println("Reconnecting to SpheroDriver...")
|
2014-06-15 17:22:50 -07:00
|
|
|
s.adaptor().Connect()
|
2014-04-26 03:11:51 -07:00
|
|
|
return
|
|
|
|
} else if length != len(buf) {
|
2014-04-26 18:07:04 -07:00
|
|
|
fmt.Println("Not enough bytes written", s.Name)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2014-06-10 15:16:11 -07:00
|
|
|
s.seq++
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) calculateChecksum(packet *packet) uint8 {
|
2014-04-26 03:11:51 -07:00
|
|
|
buf := append(packet.header, packet.body...)
|
|
|
|
buf = buf[2:]
|
|
|
|
var calculatedChecksum uint16
|
|
|
|
for i := range buf {
|
|
|
|
calculatedChecksum += uint16(buf[i])
|
|
|
|
}
|
|
|
|
return uint8(^(calculatedChecksum % 256))
|
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) readHeader() []uint8 {
|
2014-08-26 21:20:29 -06:00
|
|
|
return s.readNextChunk(5)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:40:20 -07:00
|
|
|
func (s *SpheroDriver) readBody(length uint8) []uint8 {
|
2014-08-26 21:20:29 -06:00
|
|
|
return s.readNextChunk(int(length))
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-08-26 21:20:29 -06:00
|
|
|
func (s *SpheroDriver) readNextChunk(length int) []uint8 {
|
|
|
|
read := make([]uint8, length)
|
|
|
|
bytesRead := 0
|
|
|
|
|
|
|
|
for bytesRead < length {
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
n, err := s.adaptor().sp.Read(read[bytesRead:])
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
bytesRead += n
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2014-06-10 15:16:11 -07:00
|
|
|
return read
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|