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