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  }