gobot.io/x/gobot/v2@v2.1.0/platforms/parrot/minidrone/minidrone_driver.go (about)

     1  package minidrone
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"gobot.io/x/gobot/v2"
    11  	"gobot.io/x/gobot/v2/platforms/ble"
    12  )
    13  
    14  // Driver is the Gobot interface to the Parrot Minidrone
    15  type Driver struct {
    16  	name       string
    17  	connection gobot.Connection
    18  	stepsfa0a  uint16
    19  	stepsfa0b  uint16
    20  	pcmdMutex  sync.Mutex
    21  	flying     bool
    22  	Pcmd       Pcmd
    23  	gobot.Eventer
    24  }
    25  
    26  const (
    27  	// BLE services
    28  	droneCommandService      = "9a66fa000800919111e4012d1540cb8e"
    29  	droneNotificationService = "9a66fb000800919111e4012d1540cb8e"
    30  
    31  	// send characteristics
    32  	pcmdCharacteristic     = "9a66fa0a0800919111e4012d1540cb8e"
    33  	commandCharacteristic  = "9a66fa0b0800919111e4012d1540cb8e"
    34  	priorityCharacteristic = "9a66fa0c0800919111e4012d1540cb8e"
    35  
    36  	// receive characteristics
    37  	flightStatusCharacteristic = "9a66fb0e0800919111e4012d1540cb8e"
    38  	batteryCharacteristic      = "9a66fb0f0800919111e4012d1540cb8e"
    39  
    40  	// piloting states
    41  	flatTrimChanged    = 0
    42  	flyingStateChanged = 1
    43  
    44  	// flying states
    45  	flyingStateLanded    = 0
    46  	flyingStateTakeoff   = 1
    47  	flyingStateHovering  = 2
    48  	flyingStateFlying    = 3
    49  	flyingStateLanding   = 4
    50  	flyingStateEmergency = 5
    51  	flyingStateRolling   = 6
    52  
    53  	// Battery event
    54  	Battery = "battery"
    55  
    56  	// FlightStatus event
    57  	FlightStatus = "flightstatus"
    58  
    59  	// Takeoff event
    60  	Takeoff = "takeoff"
    61  
    62  	// Hovering event
    63  	Hovering = "hovering"
    64  
    65  	// Flying event
    66  	Flying = "flying"
    67  
    68  	// Landing event
    69  	Landing = "landing"
    70  
    71  	// Landed event
    72  	Landed = "landed"
    73  
    74  	// Emergency event
    75  	Emergency = "emergency"
    76  
    77  	// Rolling event
    78  	Rolling = "rolling"
    79  
    80  	// FlatTrimChange event
    81  	FlatTrimChange = "flattrimchange"
    82  
    83  	// LightFixed mode for LightControl
    84  	LightFixed = 0
    85  
    86  	// LightBlinked mode for LightControl
    87  	LightBlinked = 1
    88  
    89  	// LightOscillated mode for LightControl
    90  	LightOscillated = 3
    91  
    92  	// ClawOpen mode for ClawControl
    93  	ClawOpen = 0
    94  
    95  	// ClawClosed mode for ClawControl
    96  	ClawClosed = 1
    97  )
    98  
    99  // Pcmd is the Parrot Command structure for flight control
   100  type Pcmd struct {
   101  	Flag  int
   102  	Roll  int
   103  	Pitch int
   104  	Yaw   int
   105  	Gaz   int
   106  	Psi   float32
   107  }
   108  
   109  // NewDriver creates a Parrot Minidrone Driver
   110  func NewDriver(a ble.BLEConnector) *Driver {
   111  	n := &Driver{
   112  		name:       gobot.DefaultName("Minidrone"),
   113  		connection: a,
   114  		Pcmd: Pcmd{
   115  			Flag:  0,
   116  			Roll:  0,
   117  			Pitch: 0,
   118  			Yaw:   0,
   119  			Gaz:   0,
   120  			Psi:   0,
   121  		},
   122  		Eventer: gobot.NewEventer(),
   123  	}
   124  
   125  	n.AddEvent(Battery)
   126  	n.AddEvent(FlightStatus)
   127  
   128  	n.AddEvent(Takeoff)
   129  	n.AddEvent(Flying)
   130  	n.AddEvent(Hovering)
   131  	n.AddEvent(Landing)
   132  	n.AddEvent(Landed)
   133  	n.AddEvent(Emergency)
   134  	n.AddEvent(Rolling)
   135  
   136  	return n
   137  }
   138  
   139  // Connection returns the BLE connection
   140  func (b *Driver) Connection() gobot.Connection { return b.connection }
   141  
   142  // Name returns the Driver Name
   143  func (b *Driver) Name() string { return b.name }
   144  
   145  // SetName sets the Driver Name
   146  func (b *Driver) SetName(n string) { b.name = n }
   147  
   148  // adaptor returns BLE adaptor
   149  func (b *Driver) adaptor() ble.BLEConnector {
   150  	return b.Connection().(ble.BLEConnector)
   151  }
   152  
   153  // Start tells driver to get ready to do work
   154  func (b *Driver) Start() (err error) {
   155  	b.adaptor().WithoutResponses(true)
   156  	b.Init()
   157  	b.FlatTrim()
   158  	b.StartPcmd()
   159  	b.FlatTrim()
   160  
   161  	return
   162  }
   163  
   164  // Halt stops minidrone driver (void)
   165  func (b *Driver) Halt() (err error) {
   166  	b.Land()
   167  
   168  	time.Sleep(500 * time.Millisecond)
   169  	return
   170  }
   171  
   172  // Init initializes the BLE insterfaces used by the Minidrone
   173  func (b *Driver) Init() (err error) {
   174  	b.GenerateAllStates()
   175  
   176  	// subscribe to battery notifications
   177  	b.adaptor().Subscribe(batteryCharacteristic, func(data []byte, e error) {
   178  		b.Publish(b.Event(Battery), data[len(data)-1])
   179  	})
   180  
   181  	// subscribe to flying status notifications
   182  	b.adaptor().Subscribe(flightStatusCharacteristic, func(data []byte, e error) {
   183  		b.processFlightStatus(data)
   184  	})
   185  
   186  	return
   187  }
   188  
   189  // GenerateAllStates sets up all the default states aka settings on the drone
   190  func (b *Driver) GenerateAllStates() (err error) {
   191  	b.stepsfa0b++
   192  	buf := []byte{0x04, byte(b.stepsfa0b), 0x00, 0x04, 0x01, 0x00, 0x32, 0x30, 0x31, 0x34, 0x2D, 0x31, 0x30, 0x2D, 0x32, 0x38, 0x00}
   193  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   194  
   195  	return
   196  }
   197  
   198  // TakeOff tells the Minidrone to takeoff
   199  func (b *Driver) TakeOff() (err error) {
   200  	b.stepsfa0b++
   201  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x01, 0x00}
   202  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   203  
   204  	return
   205  }
   206  
   207  // Land tells the Minidrone to land
   208  func (b *Driver) Land() (err error) {
   209  	b.stepsfa0b++
   210  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x03, 0x00}
   211  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   212  
   213  	return err
   214  }
   215  
   216  // FlatTrim calibrates the Minidrone to use its current position as being level
   217  func (b *Driver) FlatTrim() (err error) {
   218  	b.stepsfa0b++
   219  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x00, 0x00}
   220  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   221  
   222  	return err
   223  }
   224  
   225  // Emergency sets the Minidrone into emergency mode
   226  func (b *Driver) Emergency() (err error) {
   227  	b.stepsfa0b++
   228  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x04, 0x00}
   229  	err = b.adaptor().WriteCharacteristic(priorityCharacteristic, buf)
   230  
   231  	return err
   232  }
   233  
   234  // TakePicture tells the Minidrone to take a picture
   235  func (b *Driver) TakePicture() (err error) {
   236  	b.stepsfa0b++
   237  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x06, 0x01, 0x00}
   238  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   239  
   240  	return err
   241  }
   242  
   243  // StartPcmd starts the continuous Pcmd communication with the Minidrone
   244  func (b *Driver) StartPcmd() {
   245  	go func() {
   246  		// wait a little bit so that there is enough time to get some ACKs
   247  		time.Sleep(500 * time.Millisecond)
   248  		for {
   249  			err := b.adaptor().WriteCharacteristic(pcmdCharacteristic, b.generatePcmd().Bytes())
   250  			if err != nil {
   251  				fmt.Println("pcmd write error:", err)
   252  			}
   253  			time.Sleep(50 * time.Millisecond)
   254  		}
   255  	}()
   256  }
   257  
   258  // Up tells the drone to ascend. Pass in an int from 0-100.
   259  func (b *Driver) Up(val int) error {
   260  	b.pcmdMutex.Lock()
   261  	defer b.pcmdMutex.Unlock()
   262  
   263  	b.Pcmd.Flag = 1
   264  	b.Pcmd.Gaz = validatePitch(val)
   265  	return nil
   266  }
   267  
   268  // Down tells the drone to descend. Pass in an int from 0-100.
   269  func (b *Driver) Down(val int) error {
   270  	b.pcmdMutex.Lock()
   271  	defer b.pcmdMutex.Unlock()
   272  
   273  	b.Pcmd.Flag = 1
   274  	b.Pcmd.Gaz = validatePitch(val) * -1
   275  	return nil
   276  }
   277  
   278  // Forward tells the drone to go forward. Pass in an int from 0-100.
   279  func (b *Driver) Forward(val int) error {
   280  	b.pcmdMutex.Lock()
   281  	defer b.pcmdMutex.Unlock()
   282  
   283  	b.Pcmd.Flag = 1
   284  	b.Pcmd.Pitch = validatePitch(val)
   285  	return nil
   286  }
   287  
   288  // Backward tells drone to go in reverse. Pass in an int from 0-100.
   289  func (b *Driver) Backward(val int) error {
   290  	b.pcmdMutex.Lock()
   291  	defer b.pcmdMutex.Unlock()
   292  
   293  	b.Pcmd.Flag = 1
   294  	b.Pcmd.Pitch = validatePitch(val) * -1
   295  	return nil
   296  }
   297  
   298  // Right tells drone to go right. Pass in an int from 0-100.
   299  func (b *Driver) Right(val int) error {
   300  	b.pcmdMutex.Lock()
   301  	defer b.pcmdMutex.Unlock()
   302  
   303  	b.Pcmd.Flag = 1
   304  	b.Pcmd.Roll = validatePitch(val)
   305  	return nil
   306  }
   307  
   308  // Left tells drone to go left. Pass in an int from 0-100.
   309  func (b *Driver) Left(val int) error {
   310  	b.pcmdMutex.Lock()
   311  	defer b.pcmdMutex.Unlock()
   312  
   313  	b.Pcmd.Flag = 1
   314  	b.Pcmd.Roll = validatePitch(val) * -1
   315  	return nil
   316  }
   317  
   318  // Clockwise tells drone to rotate in a clockwise direction. Pass in an int from 0-100.
   319  func (b *Driver) Clockwise(val int) error {
   320  	b.pcmdMutex.Lock()
   321  	defer b.pcmdMutex.Unlock()
   322  
   323  	b.Pcmd.Flag = 1
   324  	b.Pcmd.Yaw = validatePitch(val)
   325  	return nil
   326  }
   327  
   328  // CounterClockwise tells drone to rotate in a counter-clockwise direction.
   329  // Pass in an int from 0-100.
   330  func (b *Driver) CounterClockwise(val int) error {
   331  	b.pcmdMutex.Lock()
   332  	defer b.pcmdMutex.Unlock()
   333  
   334  	b.Pcmd.Flag = 1
   335  	b.Pcmd.Yaw = validatePitch(val) * -1
   336  	return nil
   337  }
   338  
   339  // Stop tells the drone to stop moving in any direction and simply hover in place
   340  func (b *Driver) Stop() error {
   341  	b.pcmdMutex.Lock()
   342  	defer b.pcmdMutex.Unlock()
   343  
   344  	b.Pcmd = Pcmd{
   345  		Flag:  0,
   346  		Roll:  0,
   347  		Pitch: 0,
   348  		Yaw:   0,
   349  		Gaz:   0,
   350  		Psi:   0,
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  // StartRecording is not supported by the Parrot Minidrone
   357  func (b *Driver) StartRecording() error {
   358  	return nil
   359  }
   360  
   361  // StopRecording is not supported by the Parrot Minidrone
   362  func (b *Driver) StopRecording() error {
   363  	return nil
   364  }
   365  
   366  // HullProtection is not supported by the Parrot Minidrone
   367  func (b *Driver) HullProtection(protect bool) error {
   368  	return nil
   369  }
   370  
   371  // Outdoor mode is not supported by the Parrot Minidrone
   372  func (b *Driver) Outdoor(outdoor bool) error {
   373  	return nil
   374  }
   375  
   376  // FrontFlip tells the drone to perform a front flip
   377  func (b *Driver) FrontFlip() (err error) {
   378  	return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(0).Bytes())
   379  }
   380  
   381  // BackFlip tells the drone to perform a backflip
   382  func (b *Driver) BackFlip() (err error) {
   383  	return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(1).Bytes())
   384  }
   385  
   386  // RightFlip tells the drone to perform a flip to the right
   387  func (b *Driver) RightFlip() (err error) {
   388  	return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(2).Bytes())
   389  }
   390  
   391  // LeftFlip tells the drone to perform a flip to the left
   392  func (b *Driver) LeftFlip() (err error) {
   393  	return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(3).Bytes())
   394  }
   395  
   396  // LightControl controls lights on those Minidrone models which
   397  // have the correct hardware, such as the Maclane, Blaze, & Swat.
   398  // Params:
   399  //
   400  //	id - always 0
   401  //	mode - either LightFixed, LightBlinked, or LightOscillated
   402  //	intensity - Light intensity from 0 (OFF) to 100 (Max intensity).
   403  //				Only used in LightFixed mode.
   404  func (b *Driver) LightControl(id uint8, mode uint8, intensity uint8) (err error) {
   405  	b.stepsfa0b++
   406  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x00, id, mode, intensity, 0x00}
   407  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   408  	return
   409  }
   410  
   411  // ClawControl controls the claw on the Parrot Mambo
   412  // Params:
   413  //
   414  //	id - always 0
   415  //	mode - either ClawOpen or ClawClosed
   416  func (b *Driver) ClawControl(id uint8, mode uint8) (err error) {
   417  	b.stepsfa0b++
   418  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x01, id, mode, 0x00}
   419  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   420  	return
   421  }
   422  
   423  // GunControl fires the gun on the Parrot Mambo
   424  // Params:
   425  //
   426  //	id - always 0
   427  func (b *Driver) GunControl(id uint8) (err error) {
   428  	b.stepsfa0b++
   429  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x02, id, 0x00}
   430  	err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf)
   431  	return
   432  }
   433  
   434  func (b *Driver) generateAnimation(direction int8) *bytes.Buffer {
   435  	b.stepsfa0b++
   436  	buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x04, 0x00, 0x00, byte(direction), 0x00, 0x00, 0x00}
   437  	return bytes.NewBuffer(buf)
   438  }
   439  
   440  func (b *Driver) generatePcmd() *bytes.Buffer {
   441  	b.pcmdMutex.Lock()
   442  	defer b.pcmdMutex.Unlock()
   443  	b.stepsfa0a++
   444  	pcmd := b.Pcmd
   445  
   446  	cmd := &bytes.Buffer{}
   447  	binary.Write(cmd, binary.LittleEndian, int8(2))
   448  	binary.Write(cmd, binary.LittleEndian, int8(b.stepsfa0a))
   449  	binary.Write(cmd, binary.LittleEndian, int8(2))
   450  	binary.Write(cmd, binary.LittleEndian, int8(0))
   451  	binary.Write(cmd, binary.LittleEndian, int8(2))
   452  	binary.Write(cmd, binary.LittleEndian, int8(0))
   453  	binary.Write(cmd, binary.LittleEndian, int8(pcmd.Flag))
   454  	binary.Write(cmd, binary.LittleEndian, int8(pcmd.Roll))
   455  	binary.Write(cmd, binary.LittleEndian, int8(pcmd.Pitch))
   456  	binary.Write(cmd, binary.LittleEndian, int8(pcmd.Yaw))
   457  	binary.Write(cmd, binary.LittleEndian, int8(pcmd.Gaz))
   458  	binary.Write(cmd, binary.LittleEndian, float32(pcmd.Psi))
   459  	binary.Write(cmd, binary.LittleEndian, int16(0))
   460  	binary.Write(cmd, binary.LittleEndian, int16(0))
   461  
   462  	return cmd
   463  }
   464  
   465  func (b *Driver) processFlightStatus(data []byte) {
   466  	if len(data) < 5 {
   467  		// ignore, just a sync
   468  		return
   469  	}
   470  
   471  	b.Publish(FlightStatus, data[4])
   472  
   473  	switch data[4] {
   474  	case flatTrimChanged:
   475  		b.Publish(FlatTrimChange, true)
   476  
   477  	case flyingStateChanged:
   478  		switch data[6] {
   479  		case flyingStateLanded:
   480  			if b.flying {
   481  				b.flying = false
   482  				b.Publish(Landed, true)
   483  			}
   484  		case flyingStateTakeoff:
   485  			b.Publish(Takeoff, true)
   486  		case flyingStateHovering:
   487  			if !b.flying {
   488  				b.flying = true
   489  				b.Publish(Hovering, true)
   490  			}
   491  		case flyingStateFlying:
   492  			if !b.flying {
   493  				b.flying = true
   494  				b.Publish(Flying, true)
   495  			}
   496  		case flyingStateLanding:
   497  			b.Publish(Landing, true)
   498  		case flyingStateEmergency:
   499  			b.Publish(Emergency, true)
   500  		case flyingStateRolling:
   501  			b.Publish(Rolling, true)
   502  		}
   503  	}
   504  }
   505  
   506  func validatePitch(val int) int {
   507  	if val > 100 {
   508  		return 100
   509  	} else if val < 0 {
   510  		return 0
   511  	}
   512  
   513  	return val
   514  }