1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

1129 lines
28 KiB
Go

package tello
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
"gobot.io/x/gobot/v2"
)
const (
// BounceEvent event
BounceEvent = "bounce"
// ConnectedEvent event
ConnectedEvent = "connected"
// FlightDataEvent event
FlightDataEvent = "flightdata"
// TakeoffEvent event
TakeoffEvent = "takeoff"
// LandingEvent event
LandingEvent = "landing"
// PalmLandingEvent event
PalmLandingEvent = "palm-landing"
// FlipEvent event
FlipEvent = "flip"
// TimeEvent event
TimeEvent = "time"
// LogEvent event
LogEvent = "log"
// WifiDataEvent event
WifiDataEvent = "wifidata"
// LightStrengthEvent event
LightStrengthEvent = "lightstrength"
// SetExposureEvent event
SetExposureEvent = "setexposure"
// VideoFrameEvent event
VideoFrameEvent = "videoframe"
// SetVideoEncoderRateEvent event
SetVideoEncoderRateEvent = "setvideoencoder"
)
// the 16-bit messages and commands stored in bytes 6 & 5 of the packet
const (
messageStart = 0x00cc // 204
wifiMessage = 0x001a // 26
videoRateQuery = 0x0028 // 40
lightMessage = 0x0035 // 53
flightMessage = 0x0056 // 86
logMessage = 0x1050 // 4176
videoEncoderRateCommand = 0x0020 // 32
videoStartCommand = 0x0025 // 37
exposureCommand = 0x0034 // 52
timeCommand = 0x0046 // 70
stickCommand = 0x0050 // 80
takeoffCommand = 0x0054 // 84
landCommand = 0x0055 // 85
flipCommand = 0x005c // 92
throwtakeoffCommand = 0x005d // 93
palmLandCommand = 0x005e // 94
bounceCommand = 0x1053 // 4179
)
// FlipType is used for the various flips supported by the Tello.
type FlipType int
const (
// FlipFront flips forward.
FlipFront FlipType = 0
// FlipLeft flips left.
FlipLeft FlipType = 1
// FlipBack flips backwards.
FlipBack FlipType = 2
// FlipRight flips to the right.
FlipRight FlipType = 3
// FlipForwardLeft flips forwards and to the left.
FlipForwardLeft FlipType = 4
// FlipBackLeft flips backwards and to the left.
FlipBackLeft FlipType = 5
// FlipBackRight flips backwards and to the right.
FlipBackRight FlipType = 6
// FlipForwardRight flips forewards and to the right.
FlipForwardRight FlipType = 7
)
// VideoBitRate is used to set the bit rate for the streaming video returned by the Tello.
type VideoBitRate int
const (
// VideoBitRateAuto sets the bitrate for streaming video to auto-adjust.
VideoBitRateAuto VideoBitRate = 0
// VideoBitRate1M sets the bitrate for streaming video to 1 Mb/s.
VideoBitRate1M VideoBitRate = 1
// VideoBitRate15M sets the bitrate for streaming video to 1.5 Mb/s
VideoBitRate15M VideoBitRate = 2
// VideoBitRate2M sets the bitrate for streaming video to 2 Mb/s.
VideoBitRate2M VideoBitRate = 3
// VideoBitRate3M sets the bitrate for streaming video to 3 Mb/s.
VideoBitRate3M VideoBitRate = 4
// VideoBitRate4M sets the bitrate for streaming video to 4 Mb/s.
VideoBitRate4M VideoBitRate = 5
)
// FlightData packet returned by the Tello.
//
// The meaning of some fields is not documented. If you learned more, please, contribute.
// See https://github.com/hybridgroup/gobot/issues/798.
type FlightData struct {
BatteryLow bool
BatteryLower bool
BatteryPercentage int8 // How much battery left [in %].
CameraState int8
DroneBatteryLeft int16
DroneFlyTimeLeft int16
DroneHover bool // If the drone is in the air and not moving.
EmOpen bool
Flying bool // If the drone is currently in the air.
OnGround bool // If the drone is currently on the ground.
EastSpeed int16 // Movement speed towards East [in cm/s]. Negative if moving west.
ElectricalMachineryState int16
FactoryMode bool
FlyMode int8
FlyTime int16 // How long since take off [in s/10].
FrontIn bool
FrontLSC bool
FrontOut bool
GravityState bool
VerticalSpeed int16 // Movement speed up [in cm/s].
Height int16 // The height [in decimeters].
ImuCalibrationState int8 // The IMU calibration step (when doing IMU calibration).
NorthSpeed int16 // Movement speed towards North [in cm/s]. Negative if moving South.
ThrowFlyTimer int8
// Warnings:
DownVisualState bool // If the ground is visible by the down camera.
BatteryState bool // If there is an issue with battery.
ImuState bool // If drone needs IMU (Inertial Measurement Unit) calibration.
OutageRecording bool // If there is an issue with video recording.
PowerState bool // If there is an issue with power supply.
PressureState bool // If there is an issue with air pressure.
TemperatureHigh bool // If drone is overheating.
WindState bool // If the wind is too strong.
}
// WifiData packet returned by the Tello
type WifiData struct {
Disturb int8
Strength int8
}
// Driver represents the DJI Tello drone
type Driver struct {
name string
reqAddr string
cmdConn io.WriteCloser // UDP connection to send/receive drone commands
videoConn *net.UDPConn // UDP connection for drone video
respPort string
videoPort string
cmdMutex sync.Mutex
seq int16
rx, ry, lx, ly float32
throttle int
bouncing bool
gobot.Eventer
doneCh chan struct{}
doneChReaderCount int32
}
// NewDriver creates a driver for the Tello drone. Pass in the UDP port to use for the responses
// from the drone.
func NewDriver(port string) *Driver {
return NewDriverWithIP("192.168.10.1", port)
}
// NewDriverWithIP creates a driver for the Tello EDU drone. Pass in the ip address and UDP port to use for
// the responses from the drone.
func NewDriverWithIP(ip string, port string) *Driver {
d := &Driver{
name: gobot.DefaultName("Tello"),
reqAddr: ip + ":8889",
respPort: port,
videoPort: "11111",
Eventer: gobot.NewEventer(),
doneCh: make(chan struct{}, 1),
}
d.AddEvent(ConnectedEvent)
d.AddEvent(FlightDataEvent)
d.AddEvent(TakeoffEvent)
d.AddEvent(LandingEvent)
d.AddEvent(PalmLandingEvent)
d.AddEvent(BounceEvent)
d.AddEvent(FlipEvent)
d.AddEvent(TimeEvent)
d.AddEvent(LogEvent)
d.AddEvent(WifiDataEvent)
d.AddEvent(LightStrengthEvent)
d.AddEvent(SetExposureEvent)
d.AddEvent(VideoFrameEvent)
d.AddEvent(SetVideoEncoderRateEvent)
return d
}
// Name returns the name of the device.
func (d *Driver) Name() string { return d.name }
// SetName sets the name of the device.
func (d *Driver) SetName(n string) { d.name = n }
// Connection returns the Connection of the device.
func (d *Driver) Connection() gobot.Connection { return nil }
// Start starts the driver.
func (d *Driver) Start() error {
reqAddr, err := net.ResolveUDPAddr("udp", d.reqAddr)
if err != nil {
fmt.Println(err)
return err
}
respPort, err := net.ResolveUDPAddr("udp", ":"+d.respPort)
if err != nil {
fmt.Println(err)
return err
}
cmdConn, err := net.DialUDP("udp", respPort, reqAddr)
if err != nil {
fmt.Println(err)
return err
}
d.cmdConn = cmdConn
// handle responses
d.addDoneChReaderCount(1)
go func() {
defer d.addDoneChReaderCount(-1)
err := d.On(d.Event(ConnectedEvent), func(interface{}) {
if err := d.SendDateTime(); err != nil {
panic(err)
}
if err := d.processVideo(); err != nil {
panic(err)
}
})
if err != nil {
panic(err)
}
cmdLoop:
for {
select {
case <-d.doneCh:
break cmdLoop
default:
err := d.handleResponse(cmdConn)
if err != nil {
fmt.Println("response parse error:", err)
}
}
}
}()
// starts notifications coming from drone to video port normally 11111
if err := d.SendCommand(d.connectionString()); err != nil {
return err
}
// send stick commands
d.addDoneChReaderCount(1)
go func() {
defer d.addDoneChReaderCount(-1)
stickCmdLoop:
for {
select {
case <-d.doneCh:
break stickCmdLoop
default:
if err := d.SendStickCommand(); err != nil {
fmt.Println("stick command error:", err)
}
time.Sleep(20 * time.Millisecond)
}
}
}()
return nil
}
// Halt stops the driver.
func (d *Driver) Halt() error {
// send a landing command when we disconnect, and give it 500ms to be received before we shutdown
if d.cmdConn != nil {
if err := d.Land(); err != nil {
return err
}
}
time.Sleep(500 * time.Millisecond)
if d.cmdConn != nil {
d.cmdConn.Close()
}
if d.videoConn != nil {
d.videoConn.Close()
}
readerCount := atomic.LoadInt32(&d.doneChReaderCount)
for i := 0; i < int(readerCount); i++ {
d.doneCh <- struct{}{}
}
return nil
}
// TakeOff tells drones to liftoff and start flying.
func (d *Driver) TakeOff() error {
buf, _ := d.createPacket(takeoffCommand, 0x68, 0)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// Throw & Go support
func (d *Driver) ThrowTakeOff() error {
buf, _ := d.createPacket(throwtakeoffCommand, 0x48, 0)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// Land tells drone to come in for landing.
func (d *Driver) Land() error {
buf, _ := d.createPacket(landCommand, 0x68, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(0x00)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// StopLanding tells drone to stop landing.
func (d *Driver) StopLanding() error {
buf, _ := d.createPacket(landCommand, 0x68, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(0x01)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// PalmLand tells drone to come in for a hand landing.
func (d *Driver) PalmLand() error {
buf, _ := d.createPacket(palmLandCommand, 0x68, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(0x00)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// StartVideo tells Tello to send start info (SPS/PPS) for video stream.
func (d *Driver) StartVideo() error {
buf, _ := d.createPacket(videoStartCommand, 0x60, 0)
// seq = 0
if err := binary.Write(buf, binary.LittleEndian, int16(0x00)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// SetExposure sets the drone camera exposure level. Valid levels are 0, 1, and 2.
func (d *Driver) SetExposure(level int) error {
if level < 0 || level > 2 {
return errors.New("Invalid exposure level")
}
buf, _ := d.createPacket(exposureCommand, 0x48, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(level)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// SetVideoEncoderRate sets the drone video encoder rate.
func (d *Driver) SetVideoEncoderRate(rate VideoBitRate) error {
buf, _ := d.createPacket(videoEncoderRateCommand, 0x68, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(rate)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// SetFastMode sets the drone throttle to 1.
func (d *Driver) SetFastMode() error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.throttle = 1
return nil
}
// SetSlowMode sets the drone throttle to 0.
func (d *Driver) SetSlowMode() error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.throttle = 0
return nil
}
// Rate queries the current video bit rate.
func (d *Driver) Rate() error {
buf, _ := d.createPacket(videoRateQuery, 0x48, 0)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// bound is a naive implementation that returns the smaller of x or y.
func bound(x, y float32) float32 { //nolint:unparam // keep y as parameter
if x < -y {
return -y
}
if x > y {
return y
}
return x
}
// Vector returns the current motion vector.
// Values are from 0 to 1.
// x, y, z denote forward, side and vertical translation,
// and psi yaw (rotation around the z-axis).
//
//nolint:nonamedreturns // sufficient here
func (d *Driver) Vector() (x, y, z, psi float32) {
return d.ry, d.rx, d.ly, d.lx
}
// AddVector adds to the current motion vector.
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) AddVector(x, y, z, psi float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ry = bound(d.ry+x, 1)
d.rx = bound(d.rx+y, 1)
d.ly = bound(d.ly+z, 1)
d.lx = bound(d.lx+psi, 1)
return nil
}
// SetVector sets the current motion vector.
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) SetVector(x, y, z, psi float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ry = x
d.rx = y
d.ly = z
d.lx = psi
return nil
}
// SetX sets the x component of the current motion vector
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) SetX(x float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ry = x
return nil
}
// SetY sets the y component of the current motion vector
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) SetY(y float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.rx = y
return nil
}
// SetZ sets the z component of the current motion vector
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) SetZ(z float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ly = z
return nil
}
// SetPsi sets the psi component (yaw) of the current motion vector
// Pass values from 0 to 1.
// See Vector() for the frame of reference.
func (d *Driver) SetPsi(psi float32) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.lx = psi
return nil
}
// Up tells the drone to ascend. Pass in an int from 0-100.
func (d *Driver) Up(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ly = float32(val) / 100.0
return nil
}
// Down tells the drone to descend. Pass in an int from 0-100.
func (d *Driver) Down(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ly = float32(val) / 100.0 * -1
return nil
}
// Forward tells the drone to go forward. Pass in an int from 0-100.
func (d *Driver) Forward(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ry = float32(val) / 100.0
return nil
}
// Backward tells drone to go in reverse. Pass in an int from 0-100.
func (d *Driver) Backward(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.ry = float32(val) / 100.0 * -1
return nil
}
// Right tells drone to go right. Pass in an int from 0-100.
func (d *Driver) Right(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.rx = float32(val) / 100.0
return nil
}
// Left tells drone to go left. Pass in an int from 0-100.
func (d *Driver) Left(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.rx = float32(val) / 100.0 * -1
return nil
}
// Clockwise tells drone to rotate in a clockwise direction. Pass in an int from 0-100.
func (d *Driver) Clockwise(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.lx = float32(val) / 100.0
return nil
}
// CounterClockwise tells drone to rotate in a counter-clockwise direction.
// Pass in an int from 0-100.
func (d *Driver) CounterClockwise(val int) error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.lx = float32(val) / 100.0 * -1
return nil
}
// Hover tells the drone to stop moving on the X, Y, and Z axes and stay in place
func (d *Driver) Hover() {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.rx = float32(0)
d.ry = float32(0)
d.lx = float32(0)
d.ly = float32(0)
}
// CeaseRotation stops any rotational motion
func (d *Driver) CeaseRotation() {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
d.lx = float32(0)
}
// Bounce tells drone to start/stop performing the bouncing action
func (d *Driver) Bounce() error {
buf, _ := d.createPacket(bounceCommand, 0x68, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if d.bouncing {
if err := binary.Write(buf, binary.LittleEndian, byte(0x31)); err != nil {
return err
}
} else {
if err := binary.Write(buf, binary.LittleEndian, byte(0x30)); err != nil {
return err
}
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
d.bouncing = !d.bouncing
return err
}
// Flip tells drone to flip
func (d *Driver) Flip(direction FlipType) error {
buf, _ := d.createPacket(flipCommand, 0x70, 1)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(direction)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// FrontFlip tells the drone to perform a front flip.
func (d *Driver) FrontFlip() error {
return d.Flip(FlipFront)
}
// BackFlip tells the drone to perform a back flip.
func (d *Driver) BackFlip() error {
return d.Flip(FlipBack)
}
// RightFlip tells the drone to perform a flip to the right.
func (d *Driver) RightFlip() error {
return d.Flip(FlipRight)
}
// LeftFlip tells the drone to perform a flip to the left.
func (d *Driver) LeftFlip() error {
return d.Flip(FlipLeft)
}
// ParseFlightData from drone
func (d *Driver) ParseFlightData(b []byte) (*FlightData, error) {
buf := bytes.NewReader(b)
fd := &FlightData{}
var data byte
if buf.Len() < 24 {
err := errors.New("Invalid buffer length for flight data packet")
fmt.Println(err)
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.Height); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.NorthSpeed); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.EastSpeed); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.VerticalSpeed); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.FlyTime); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &data); err != nil {
return fd, err
}
fd.ImuState = (data >> 0 & 0x1) == 1
fd.PressureState = (data >> 1 & 0x1) == 1
fd.DownVisualState = (data >> 2 & 0x1) == 1
fd.PowerState = (data >> 3 & 0x1) == 1
fd.BatteryState = (data >> 4 & 0x1) == 1
fd.GravityState = (data >> 5 & 0x1) == 1
fd.WindState = (data >> 7 & 0x1) == 1
if err := binary.Read(buf, binary.LittleEndian, &fd.ImuCalibrationState); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.BatteryPercentage); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.DroneFlyTimeLeft); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.DroneBatteryLeft); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &data); err != nil {
return fd, err
}
fd.Flying = (data >> 0 & 0x1) == 1
fd.OnGround = (data >> 1 & 0x1) == 1
fd.EmOpen = (data >> 2 & 0x1) == 1
fd.DroneHover = (data >> 3 & 0x1) == 1
fd.OutageRecording = (data >> 4 & 0x1) == 1
fd.BatteryLow = (data >> 5 & 0x1) == 1
fd.BatteryLower = (data >> 6 & 0x1) == 1
fd.FactoryMode = (data >> 7 & 0x1) == 1
if err := binary.Read(buf, binary.LittleEndian, &fd.FlyMode); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.ThrowFlyTimer); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &fd.CameraState); err != nil {
return fd, err
}
if err := binary.Read(buf, binary.LittleEndian, &data); err != nil {
return fd, err
}
fd.ElectricalMachineryState = int16(data & 0xff)
if err := binary.Read(buf, binary.LittleEndian, &data); err != nil {
return fd, err
}
fd.FrontIn = (data >> 0 & 0x1) == 1
fd.FrontOut = (data >> 1 & 0x1) == 1
fd.FrontLSC = (data >> 2 & 0x1) == 1
if err := binary.Read(buf, binary.LittleEndian, &data); err != nil {
return fd, err
}
fd.TemperatureHigh = (data >> 0 & 0x1) == 1
return fd, nil
}
// SendStickCommand sends the joystick command packet to the drone.
func (d *Driver) SendStickCommand() error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
buf, _ := d.createPacket(stickCommand, 0x60, 11)
// seq = 0
if err := binary.Write(buf, binary.LittleEndian, int16(0x00)); err != nil {
return err
}
// RightX center=1024 left =364 right =-364
axis1 := int16(660.0*d.rx + 1024.0)
// RightY down =364 up =-364
axis2 := int16(660.0*d.ry + 1024.0)
// LeftY down =364 up =-364
axis3 := int16(660.0*d.ly + 1024.0)
// LeftX left =364 right =-364
axis4 := int16(660.0*d.lx + 1024.0)
// speed control
axis5 := int16(d.throttle) //nolint:gosec // TODO: fix later
packedAxis := int64(axis1)&0x7FF | int64(axis2&0x7FF)<<11 | 0x7FF&int64(axis3)<<22 | 0x7FF&int64(axis4)<<33 |
int64(axis5)<<44
if err := binary.Write(buf, binary.LittleEndian, byte(0xFF&packedAxis)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(packedAxis>>8&0xFF)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(packedAxis>>16&0xFF)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(packedAxis>>24&0xFF)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(packedAxis>>32&0xFF)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(packedAxis>>40&0xFF)); err != nil {
return err
}
now := time.Now()
if err := binary.Write(buf, binary.LittleEndian, byte(now.Hour())); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(now.Minute())); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(now.Second())); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(now.UnixNano()/int64(time.Millisecond)&0xff)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, byte(now.UnixNano()/int64(time.Millisecond)>>8)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// SendDateTime sends the current date/time to the drone.
func (d *Driver) SendDateTime() error {
d.cmdMutex.Lock()
defer d.cmdMutex.Unlock()
buf, _ := d.createPacket(timeCommand, 0x50, 11)
d.seq++
if err := binary.Write(buf, binary.LittleEndian, d.seq); err != nil {
return err
}
now := time.Now()
if err := binary.Write(buf, binary.LittleEndian, byte(0x00)); err != nil {
return err
}
//nolint:gosec // TODO: fix later
if err := binary.Write(buf, binary.LittleEndian, int16(now.Hour())); err != nil {
return err
}
//nolint:gosec // TODO: fix later
if err := binary.Write(buf, binary.LittleEndian, int16(now.Minute())); err != nil {
return err
}
//nolint:gosec // TODO: fix later
if err := binary.Write(buf, binary.LittleEndian, int16(now.Second())); err != nil {
return err
}
//nolint:gosec // TODO: fix later
if err := binary.Write(buf, binary.LittleEndian, int16(now.UnixNano()/int64(time.Millisecond)&0xff)); err != nil {
return err
}
//nolint:gosec // TODO: fix later
if err := binary.Write(buf, binary.LittleEndian, int16(now.UnixNano()/int64(time.Millisecond)>>8)); err != nil {
return err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes())); err != nil {
return err
}
_, err := d.cmdConn.Write(buf.Bytes())
return err
}
// SendCommand is used to send a text command such as the initial connection request to the drone.
func (d *Driver) SendCommand(cmd string) error {
_, err := d.cmdConn.Write([]byte(cmd))
return err
}
func (d *Driver) handleResponse(r io.Reader) error {
var buf [2048]byte
var msgType uint16
n, err := r.Read(buf[0:])
if err != nil {
return err
}
// parse binary packet
if buf[0] == messageStart {
msgType = (uint16(buf[6]) << 8) | uint16(buf[5])
switch msgType {
case wifiMessage:
wd := &WifiData{
Strength: int8(buf[9:10][0]),
Disturb: int8(buf[10:11][0]),
}
d.Publish(d.Event(WifiDataEvent), wd)
case lightMessage:
d.Publish(d.Event(LightStrengthEvent), int8(buf[9:10][0]))
case logMessage:
d.Publish(d.Event(LogEvent), buf[9:])
case timeCommand:
d.Publish(d.Event(TimeEvent), buf[7:8])
case bounceCommand:
d.Publish(d.Event(BounceEvent), buf[7:8])
case takeoffCommand:
d.Publish(d.Event(TakeoffEvent), buf[7:8])
case landCommand:
d.Publish(d.Event(LandingEvent), buf[7:8])
case palmLandCommand:
d.Publish(d.Event(PalmLandingEvent), buf[7:8])
case flipCommand:
d.Publish(d.Event(FlipEvent), buf[7:8])
case flightMessage:
fd, _ := d.ParseFlightData(buf[9:])
d.Publish(d.Event(FlightDataEvent), fd)
case exposureCommand:
d.Publish(d.Event(SetExposureEvent), buf[7:8])
case videoEncoderRateCommand:
d.Publish(d.Event(SetVideoEncoderRateEvent), buf[7:8])
default:
fmt.Printf("Unknown message: %+v\n", buf[0:n])
}
return nil
}
// parse text packet
if buf[0] == 0x63 && buf[1] == 0x6f && buf[2] == 0x6e {
d.Publish(d.Event(ConnectedEvent), nil)
}
return nil
}
func (d *Driver) processVideo() error {
videoPort, err := net.ResolveUDPAddr("udp", ":11111")
if err != nil {
return err
}
d.videoConn, err = net.ListenUDP("udp", videoPort)
if err != nil {
return err
}
d.addDoneChReaderCount(1)
go func() {
defer d.addDoneChReaderCount(-1)
videoConnLoop:
for {
select {
case <-d.doneCh:
break videoConnLoop
default:
buf := make([]byte, 2048)
n, _, err := d.videoConn.ReadFromUDP(buf)
if err != nil {
fmt.Println("Error: ", err)
continue
}
d.Publish(d.Event(VideoFrameEvent), buf[2:n])
}
}
}()
return nil
}
func (d *Driver) createPacket(cmd int16, pktType byte, pktLen int16) (*bytes.Buffer, error) {
l := pktLen + 11
buf := &bytes.Buffer{}
if err := binary.Write(buf, binary.LittleEndian, byte(messageStart)); err != nil {
return buf, err
}
if err := binary.Write(buf, binary.LittleEndian, l<<3); err != nil {
return buf, err
}
if err := binary.Write(buf, binary.LittleEndian, CalculateCRC8(buf.Bytes()[0:3])); err != nil {
return buf, err
}
if err := binary.Write(buf, binary.LittleEndian, pktType); err != nil {
return buf, err
}
if err := binary.Write(buf, binary.LittleEndian, cmd); err != nil {
return buf, err
}
return buf, nil
}
func (d *Driver) connectionString() string {
x, _ := strconv.Atoi(d.videoPort)
b := [2]byte{}
binary.LittleEndian.PutUint16(b[:], uint16(x)) //nolint:gosec // TODO: fix later
res := fmt.Sprintf("conn_req:%s", b)
return res
}
func (d *Driver) addDoneChReaderCount(delta int32) {
atomic.AddInt32(&d.doneChReaderCount, delta)
}
func (f *FlightData) AirSpeed() float64 {
return math.Sqrt(
math.Pow(float64(f.NorthSpeed), 2) +
math.Pow(float64(f.EastSpeed), 2) +
math.Pow(float64(f.VerticalSpeed), 2))
}
func (f *FlightData) GroundSpeed() float64 {
return math.Sqrt(
math.Pow(float64(f.NorthSpeed), 2) +
math.Pow(float64(f.EastSpeed), 2))
}