gobot.io/x/gobot@v1.16.0/platforms/sphero/sphero_driver.go (about) 1 package sphero 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "sync" 8 "time" 9 10 "gobot.io/x/gobot" 11 ) 12 13 const ( 14 // Error event when error encountered 15 Error = "error" 16 17 // SensorData event when sensor data is received 18 SensorData = "sensordata" 19 20 // Collision event when collision is detected 21 Collision = "collision" 22 ) 23 24 type packet struct { 25 header []uint8 26 body []uint8 27 checksum uint8 28 } 29 30 // SpheroDriver Represents a Sphero 2.0 31 type SpheroDriver struct { 32 name string 33 connection gobot.Connection 34 mtx sync.Mutex 35 seq uint8 36 asyncResponse [][]uint8 37 syncResponse [][]uint8 38 packetChannel chan *packet 39 responseChannel chan []uint8 40 gobot.Eventer 41 gobot.Commander 42 } 43 44 // NewSpheroDriver returns a new SpheroDriver given a Sphero Adaptor. 45 // 46 // Adds the following API Commands: 47 // "ConfigureLocator" - See SpheroDriver.ConfigureLocator 48 // "Roll" - See SpheroDriver.Roll 49 // "Stop" - See SpheroDriver.Stop 50 // "GetRGB" - See SpheroDriver.GetRGB 51 // "ReadLocator" - See SpheroDriver.ReadLocator 52 // "SetBackLED" - See SpheroDriver.SetBackLED 53 // "SetHeading" - See SpheroDriver.SetHeading 54 // "SetStabilization" - See SpheroDriver.SetStabilization 55 // "SetDataStreaming" - See SpheroDriver.SetDataStreaming 56 // "SetRotationRate" - See SpheroDriver.SetRotationRate 57 func NewSpheroDriver(a *Adaptor) *SpheroDriver { 58 s := &SpheroDriver{ 59 name: gobot.DefaultName("Sphero"), 60 connection: a, 61 Eventer: gobot.NewEventer(), 62 Commander: gobot.NewCommander(), 63 packetChannel: make(chan *packet, 1024), 64 responseChannel: make(chan []uint8, 1024), 65 } 66 67 s.AddEvent(Error) 68 s.AddEvent(Collision) 69 s.AddEvent(SensorData) 70 71 s.AddCommand("SetRGB", func(params map[string]interface{}) interface{} { 72 r := uint8(params["r"].(float64)) 73 g := uint8(params["g"].(float64)) 74 b := uint8(params["b"].(float64)) 75 s.SetRGB(r, g, b) 76 return nil 77 }) 78 79 s.AddCommand("Roll", func(params map[string]interface{}) interface{} { 80 speed := uint8(params["speed"].(float64)) 81 heading := uint16(params["heading"].(float64)) 82 s.Roll(speed, heading) 83 return nil 84 }) 85 86 s.AddCommand("Stop", func(params map[string]interface{}) interface{} { 87 s.Stop() 88 return nil 89 }) 90 91 s.AddCommand("GetRGB", func(params map[string]interface{}) interface{} { 92 return s.GetRGB() 93 }) 94 95 s.AddCommand("ReadLocator", func(params map[string]interface{}) interface{} { 96 return s.ReadLocator() 97 }) 98 99 s.AddCommand("SetBackLED", func(params map[string]interface{}) interface{} { 100 level := uint8(params["level"].(float64)) 101 s.SetBackLED(level) 102 return nil 103 }) 104 105 s.AddCommand("SetRotationRate", func(params map[string]interface{}) interface{} { 106 level := uint8(params["level"].(float64)) 107 s.SetRotationRate(level) 108 return nil 109 }) 110 111 s.AddCommand("SetHeading", func(params map[string]interface{}) interface{} { 112 heading := uint16(params["heading"].(float64)) 113 s.SetHeading(heading) 114 return nil 115 }) 116 117 s.AddCommand("SetStabilization", func(params map[string]interface{}) interface{} { 118 on := params["enable"].(bool) 119 s.SetStabilization(on) 120 return nil 121 }) 122 123 s.AddCommand("SetDataStreaming", func(params map[string]interface{}) interface{} { 124 N := uint16(params["N"].(float64)) 125 M := uint16(params["M"].(float64)) 126 Mask := uint32(params["Mask"].(float64)) 127 Pcnt := uint8(params["Pcnt"].(float64)) 128 Mask2 := uint32(params["Mask2"].(float64)) 129 130 s.SetDataStreaming(DataStreamingConfig{N: N, M: M, Mask2: Mask2, Pcnt: Pcnt, Mask: Mask}) 131 return nil 132 }) 133 134 s.AddCommand("ConfigureLocator", func(params map[string]interface{}) interface{} { 135 Flags := uint8(params["Flags"].(float64)) 136 X := int16(params["X"].(float64)) 137 Y := int16(params["Y"].(float64)) 138 YawTare := int16(params["YawTare"].(float64)) 139 140 s.ConfigureLocator(LocatorConfig{Flags: Flags, X: X, Y: Y, YawTare: YawTare}) 141 return nil 142 }) 143 144 return s 145 } 146 147 // Name returns the Driver Name 148 func (s *SpheroDriver) Name() string { return s.name } 149 150 // SetName sets the Driver Name 151 func (s *SpheroDriver) SetName(n string) { s.name = n } 152 153 // Connection returns the Driver's Connection 154 func (s *SpheroDriver) Connection() gobot.Connection { return s.connection } 155 156 func (s *SpheroDriver) adaptor() *Adaptor { 157 return s.Connection().(*Adaptor) 158 } 159 160 // Start starts the SpheroDriver and enables Collision Detection. 161 // Returns true on successful start. 162 // 163 // Emits the Events: 164 // Collision sphero.CollisionPacket - On Collision Detected 165 // SensorData sphero.DataStreamingPacket - On Data Streaming event 166 // Error error- On error while processing asynchronous response 167 func (s *SpheroDriver) Start() (err error) { 168 go func() { 169 for { 170 packet := <-s.packetChannel 171 err := s.write(packet) 172 if err != nil { 173 s.Publish(Error, err) 174 } 175 } 176 }() 177 178 go func() { 179 for { 180 response := <-s.responseChannel 181 s.syncResponse = append(s.syncResponse, response) 182 } 183 }() 184 185 go func() { 186 for { 187 header := s.readHeader() 188 if len(header) > 0 { 189 body := s.readBody(header[4]) 190 data := append(header, body...) 191 checksum := data[len(data)-1] 192 if checksum != calculateChecksum(data[2:len(data)-1]) { 193 continue 194 } 195 switch header[1] { 196 case 0xFE: 197 s.asyncResponse = append(s.asyncResponse, data) 198 case 0xFF: 199 s.responseChannel <- data 200 } 201 } 202 } 203 }() 204 205 go func() { 206 for { 207 var evt []uint8 208 for len(s.asyncResponse) != 0 { 209 evt, s.asyncResponse = s.asyncResponse[len(s.asyncResponse)-1], s.asyncResponse[:len(s.asyncResponse)-1] 210 if evt[2] == 0x07 { 211 s.handleCollisionDetected(evt) 212 } else if evt[2] == 0x03 { 213 s.handleDataStreaming(evt) 214 } 215 } 216 time.Sleep(100 * time.Millisecond) 217 } 218 }() 219 220 s.ConfigureCollisionDetection(DefaultCollisionConfig()) 221 s.enableStopOnDisconnect() 222 223 return 224 } 225 226 // Halt halts the SpheroDriver and sends a SpheroDriver.Stop command to the Sphero. 227 // Returns true on successful halt. 228 func (s *SpheroDriver) Halt() (err error) { 229 if s.adaptor().connected { 230 gobot.Every(10*time.Millisecond, func() { 231 s.Stop() 232 }) 233 time.Sleep(1 * time.Second) 234 } 235 return 236 } 237 238 // SetRGB sets the Sphero to the given r, g, and b values 239 func (s *SpheroDriver) SetRGB(r uint8, g uint8, b uint8) { 240 s.packetChannel <- s.craftPacket([]uint8{r, g, b, 0x01}, 0x02, 0x20) 241 } 242 243 // GetRGB returns the current r, g, b value of the Sphero 244 func (s *SpheroDriver) GetRGB() []uint8 { 245 buf := s.getSyncResponse(s.craftPacket([]uint8{}, 0x02, 0x22)) 246 if len(buf) == 9 { 247 return []uint8{buf[5], buf[6], buf[7]} 248 } 249 return []uint8{} 250 } 251 252 // ReadLocator reads Sphero's current position (X,Y), component velocities and SOG (speed over ground). 253 func (s *SpheroDriver) ReadLocator() []int16 { 254 buf := s.getSyncResponse(s.craftPacket([]uint8{}, 0x02, 0x15)) 255 if len(buf) == 16 { 256 vals := make([]int16, 5) 257 _ = binary.Read(bytes.NewReader(buf[5:15]), binary.BigEndian, &vals) 258 return vals 259 } 260 return []int16{} 261 } 262 263 // SetBackLED sets the Sphero Back LED to the specified brightness 264 func (s *SpheroDriver) SetBackLED(level uint8) { 265 s.packetChannel <- s.craftPacket([]uint8{level}, 0x02, 0x21) 266 } 267 268 // SetRotationRate sets the Sphero rotation rate 269 // A value of 255 jumps to the maximum (currently 400 degrees/sec). 270 func (s *SpheroDriver) SetRotationRate(level uint8) { 271 s.packetChannel <- s.craftPacket([]uint8{level}, 0x02, 0x03) 272 } 273 274 // SetHeading sets the heading of the Sphero 275 func (s *SpheroDriver) SetHeading(heading uint16) { 276 s.packetChannel <- s.craftPacket([]uint8{uint8(heading >> 8), uint8(heading & 0xFF)}, 0x02, 0x01) 277 } 278 279 // SetStabilization enables or disables the built-in auto stabilizing features of the Sphero 280 func (s *SpheroDriver) SetStabilization(on bool) { 281 b := uint8(0x01) 282 if !on { 283 b = 0x00 284 } 285 s.packetChannel <- s.craftPacket([]uint8{b}, 0x02, 0x02) 286 } 287 288 // Roll sends a roll command to the Sphero gives a speed and heading 289 func (s *SpheroDriver) Roll(speed uint8, heading uint16) { 290 s.packetChannel <- s.craftPacket([]uint8{speed, uint8(heading >> 8), uint8(heading & 0xFF), 0x01}, 0x02, 0x30) 291 } 292 293 // ConfigureLocator configures and enables the Locator 294 func (s *SpheroDriver) ConfigureLocator(d LocatorConfig) { 295 buf := new(bytes.Buffer) 296 binary.Write(buf, binary.BigEndian, d) 297 298 s.packetChannel <- s.craftPacket(buf.Bytes(), 0x02, 0x13) 299 } 300 301 // SetDataStreaming enables sensor data streaming 302 func (s *SpheroDriver) SetDataStreaming(d DataStreamingConfig) { 303 buf := new(bytes.Buffer) 304 binary.Write(buf, binary.BigEndian, d) 305 306 s.packetChannel <- s.craftPacket(buf.Bytes(), 0x02, 0x11) 307 } 308 309 // Stop sets the Sphero to a roll speed of 0 310 func (s *SpheroDriver) Stop() { 311 s.Roll(0, 0) 312 } 313 314 // ConfigureCollisionDetection configures the sensitivity of the detection. 315 func (s *SpheroDriver) ConfigureCollisionDetection(cc CollisionConfig) { 316 s.packetChannel <- s.craftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12) 317 } 318 319 func (s *SpheroDriver) enableStopOnDisconnect() { 320 s.packetChannel <- s.craftPacket([]uint8{0x00, 0x00, 0x00, 0x01}, 0x02, 0x37) 321 } 322 323 func (s *SpheroDriver) handleCollisionDetected(data []uint8) { 324 // ensure data is the right length: 325 if len(data) != 22 || data[4] != 17 { 326 return 327 } 328 var collision CollisionPacket 329 buffer := bytes.NewBuffer(data[5:]) // skip header 330 binary.Read(buffer, binary.BigEndian, &collision) 331 s.Publish(Collision, collision) 332 } 333 334 func (s *SpheroDriver) handleDataStreaming(data []uint8) { 335 // ensure data is the right length: 336 if len(data) != 90 { 337 return 338 } 339 var dataPacket DataStreamingPacket 340 buffer := bytes.NewBuffer(data[5:]) // skip header 341 binary.Read(buffer, binary.BigEndian, &dataPacket) 342 s.Publish(SensorData, dataPacket) 343 } 344 345 func (s *SpheroDriver) getSyncResponse(packet *packet) []byte { 346 s.packetChannel <- packet 347 for i := 0; i < 500; i++ { 348 for key := range s.syncResponse { 349 if s.syncResponse[key][3] == packet.header[4] && len(s.syncResponse[key]) > 6 { 350 var response []byte 351 response, s.syncResponse = s.syncResponse[len(s.syncResponse)-1], s.syncResponse[:len(s.syncResponse)-1] 352 return response 353 } 354 } 355 time.Sleep(100 * time.Microsecond) 356 } 357 358 return []byte{} 359 } 360 361 func (s *SpheroDriver) craftPacket(body []uint8, did byte, cid byte) *packet { 362 s.mtx.Lock() 363 defer s.mtx.Unlock() 364 packet := new(packet) 365 packet.body = body 366 dlen := len(packet.body) + 1 367 packet.header = []uint8{0xFF, 0xFF, did, cid, s.seq, uint8(dlen)} 368 packet.checksum = s.calculateChecksum(packet) 369 return packet 370 } 371 372 func (s *SpheroDriver) write(packet *packet) (err error) { 373 s.mtx.Lock() 374 defer s.mtx.Unlock() 375 buf := append(packet.header, packet.body...) 376 buf = append(buf, packet.checksum) 377 length, err := s.adaptor().sp.Write(buf) 378 if err != nil { 379 return err 380 } else if length != len(buf) { 381 return errors.New("Not enough bytes written") 382 } 383 s.seq++ 384 return 385 } 386 387 func (s *SpheroDriver) calculateChecksum(packet *packet) uint8 { 388 buf := append(packet.header, packet.body...) 389 return calculateChecksum(buf[2:]) 390 } 391 392 func calculateChecksum(buf []byte) byte { 393 var calculatedChecksum uint16 394 for i := range buf { 395 calculatedChecksum += uint16(buf[i]) 396 } 397 return uint8(^(calculatedChecksum % 256)) 398 } 399 400 func (s *SpheroDriver) readHeader() []uint8 { 401 return s.readNextChunk(5) 402 } 403 404 func (s *SpheroDriver) readBody(length uint8) []uint8 { 405 return s.readNextChunk(int(length)) 406 } 407 408 func (s *SpheroDriver) readNextChunk(length int) []uint8 { 409 read := make([]uint8, length) 410 bytesRead := 0 411 412 for bytesRead < length { 413 time.Sleep(1 * time.Millisecond) 414 n, err := s.adaptor().sp.Read(read[bytesRead:]) 415 if err != nil { 416 return nil 417 } 418 bytesRead += n 419 } 420 return read 421 }