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  }