2018-04-03 20:42:16 +02:00
|
|
|
package tello
|
|
|
|
|
|
|
|
import (
|
2018-04-07 21:23:57 +02:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2018-04-03 20:42:16 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2018-04-13 13:49:12 +02:00
|
|
|
"sync"
|
2018-04-03 20:42:16 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"gobot.io/x/gobot"
|
|
|
|
)
|
|
|
|
|
2018-04-16 23:08:22 +02:00
|
|
|
const (
|
2018-04-17 15:01:34 +02:00
|
|
|
// ConnectedEvent event
|
|
|
|
ConnectedEvent = "connected"
|
2018-04-16 23:08:22 +02:00
|
|
|
|
2018-04-17 15:01:34 +02:00
|
|
|
// FlightDataEvent event
|
|
|
|
FlightDataEvent = "flightdata"
|
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
// TakeoffEvent event
|
|
|
|
TakeoffEvent = "takeoff"
|
|
|
|
|
|
|
|
// LandingEvent event
|
|
|
|
LandingEvent = "landing"
|
|
|
|
|
|
|
|
// FlipEvent event
|
|
|
|
FlipEvent = "flip"
|
|
|
|
|
|
|
|
// TimeEvent event
|
|
|
|
TimeEvent = "time"
|
|
|
|
|
|
|
|
// LogEvent event
|
|
|
|
LogEvent = "log"
|
|
|
|
|
|
|
|
// WifiDataEvent event
|
|
|
|
WifiDataEvent = "wifidata"
|
|
|
|
|
|
|
|
// LightStrengthEvent event
|
|
|
|
LightStrengthEvent = "lightstrength"
|
2018-04-17 15:01:34 +02:00
|
|
|
|
2018-04-19 15:22:05 +02:00
|
|
|
// SetExposureEvent event
|
|
|
|
SetExposureEvent = "setexposure"
|
|
|
|
|
2018-04-17 15:01:34 +02:00
|
|
|
// VideoFrameEvent event
|
|
|
|
VideoFrameEvent = "videoframe"
|
2018-04-22 18:15:38 +02:00
|
|
|
|
|
|
|
// SetVideoEncoderRateEvent event
|
|
|
|
SetVideoEncoderRateEvent = "setvideoencoder"
|
2018-04-16 23:08:22 +02:00
|
|
|
)
|
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
const (
|
2018-04-22 18:15:38 +02:00
|
|
|
messageStart = 0xcc
|
|
|
|
wifiMessage = 26
|
|
|
|
videoRateQuery = 40
|
|
|
|
lightMessage = 53
|
|
|
|
flightMessage = 86
|
2018-04-18 12:15:18 +02:00
|
|
|
|
|
|
|
logMessage = 0x50
|
|
|
|
|
2018-04-19 15:22:05 +02:00
|
|
|
videoEncoderRateCommand = 0x20
|
|
|
|
videoStartCommand = 0x25
|
|
|
|
exposureCommand = 0x34
|
2018-04-23 20:43:22 +02:00
|
|
|
timeCommand = 70
|
|
|
|
stickCommand = 80
|
2018-04-19 15:22:05 +02:00
|
|
|
takeoffCommand = 0x54
|
|
|
|
landCommand = 0x55
|
|
|
|
flipCommand = 0x5c
|
2018-04-22 18:15:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2018-04-18 12:15:18 +02:00
|
|
|
|
2018-04-22 18:15:38 +02:00
|
|
|
// 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
|
2018-04-18 12:15:18 +02:00
|
|
|
)
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// FlightData packet returned by the Tello
|
2018-04-07 21:23:57 +02:00
|
|
|
type FlightData struct {
|
2018-05-15 15:27:24 +01:00
|
|
|
BatteryLow bool
|
|
|
|
BatteryLower bool
|
2018-05-15 11:47:53 +01:00
|
|
|
BatteryPercentage int8
|
2018-05-15 15:27:24 +01:00
|
|
|
BatteryState bool
|
2018-05-15 11:47:53 +01:00
|
|
|
CameraState int8
|
2018-05-15 15:27:24 +01:00
|
|
|
DownVisualState bool
|
2018-05-15 11:47:53 +01:00
|
|
|
DroneBatteryLeft int16
|
|
|
|
DroneFlyTimeLeft int16
|
2018-05-15 15:27:24 +01:00
|
|
|
DroneHover bool
|
|
|
|
EmOpen bool
|
|
|
|
EmSky bool
|
|
|
|
EmGround bool
|
2018-05-15 11:47:53 +01:00
|
|
|
EastSpeed int16
|
|
|
|
ElectricalMachineryState int16
|
2018-05-15 15:27:24 +01:00
|
|
|
FactoryMode bool
|
2018-05-15 11:47:53 +01:00
|
|
|
FlyMode int8
|
|
|
|
FlySpeed int16
|
|
|
|
FlyTime int16
|
2018-05-15 15:27:24 +01:00
|
|
|
FrontIn bool
|
|
|
|
FrontLSC bool
|
|
|
|
FrontOut bool
|
|
|
|
GravityState bool
|
2018-05-15 11:47:53 +01:00
|
|
|
GroundSpeed int16
|
|
|
|
Height int16
|
|
|
|
ImuCalibrationState int8
|
2018-05-15 15:27:24 +01:00
|
|
|
ImuState bool
|
2018-05-15 11:47:53 +01:00
|
|
|
LightStrength int16
|
|
|
|
NorthSpeed int16
|
2018-05-15 15:27:24 +01:00
|
|
|
OutageRecording bool
|
|
|
|
PowerState bool
|
|
|
|
PressureState bool
|
2018-05-15 11:47:53 +01:00
|
|
|
SmartVideoExitMode int16
|
2018-05-15 15:27:24 +01:00
|
|
|
TemperatureHeight bool
|
2018-05-15 11:47:53 +01:00
|
|
|
ThrowFlyTimer int8
|
|
|
|
WifiDisturb int16
|
|
|
|
WifiStrength int16
|
2018-05-15 15:27:24 +01:00
|
|
|
WindState bool
|
2018-04-07 21:23:57 +02:00
|
|
|
}
|
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
// WifiData packet returned by the Tello
|
|
|
|
type WifiData struct {
|
|
|
|
Disturb int16
|
|
|
|
Strength int16
|
|
|
|
}
|
|
|
|
|
2018-04-03 20:42:16 +02:00
|
|
|
// Driver represents the DJI Tello drone
|
|
|
|
type Driver struct {
|
2018-04-13 13:49:12 +02:00
|
|
|
name string
|
|
|
|
reqAddr string
|
|
|
|
reqConn *net.UDPConn // UDP connection to send/receive drone commands
|
2018-04-16 23:08:22 +02:00
|
|
|
videoConn *net.UDPConn // UDP connection for drone video
|
2018-04-13 13:49:12 +02:00
|
|
|
respPort string
|
|
|
|
cmdMutex sync.Mutex
|
2018-04-24 10:11:10 +02:00
|
|
|
seq int16
|
2018-04-13 13:49:12 +02:00
|
|
|
rx, ry, lx, ly, throttle float32
|
2018-04-16 23:08:22 +02:00
|
|
|
gobot.Eventer
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
2018-04-04 13:37:44 +02:00
|
|
|
// 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 {
|
2018-04-16 23:08:22 +02:00
|
|
|
d := &Driver{name: gobot.DefaultName("Tello"),
|
2018-04-18 12:15:18 +02:00
|
|
|
reqAddr: "192.168.10.1:8889",
|
|
|
|
respPort: port,
|
|
|
|
Eventer: gobot.NewEventer(),
|
2018-04-16 23:08:22 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 15:01:34 +02:00
|
|
|
d.AddEvent(ConnectedEvent)
|
|
|
|
d.AddEvent(FlightDataEvent)
|
2018-04-22 18:15:38 +02:00
|
|
|
d.AddEvent(TakeoffEvent)
|
|
|
|
d.AddEvent(LandingEvent)
|
|
|
|
d.AddEvent(FlipEvent)
|
|
|
|
d.AddEvent(TimeEvent)
|
|
|
|
d.AddEvent(LogEvent)
|
2018-04-18 12:15:18 +02:00
|
|
|
d.AddEvent(WifiDataEvent)
|
|
|
|
d.AddEvent(LightStrengthEvent)
|
2018-04-19 15:22:05 +02:00
|
|
|
d.AddEvent(SetExposureEvent)
|
2018-04-22 18:15:38 +02:00
|
|
|
d.AddEvent(VideoFrameEvent)
|
|
|
|
d.AddEvent(SetVideoEncoderRateEvent)
|
2018-04-16 23:08:22 +02:00
|
|
|
|
|
|
|
return d
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2018-04-04 13:37:44 +02:00
|
|
|
reqAddr, err := net.ResolveUDPAddr("udp", d.reqAddr)
|
2018-04-03 20:42:16 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
2018-04-04 13:37:44 +02:00
|
|
|
respPort, err := net.ResolveUDPAddr("udp", ":"+d.respPort)
|
2018-04-03 20:42:16 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
2018-04-04 13:37:44 +02:00
|
|
|
d.reqConn, err = net.DialUDP("udp", respPort, reqAddr)
|
2018-04-03 21:33:42 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// handle responses
|
2018-04-04 13:37:44 +02:00
|
|
|
go func() {
|
|
|
|
for {
|
2018-04-04 16:13:37 +02:00
|
|
|
err := d.handleResponse()
|
|
|
|
if err != nil {
|
2018-04-13 13:49:12 +02:00
|
|
|
fmt.Println("response parse error:", err)
|
2018-04-04 16:13:37 +02:00
|
|
|
}
|
2018-04-04 13:37:44 +02:00
|
|
|
}
|
|
|
|
}()
|
2018-04-03 21:33:42 +02:00
|
|
|
|
2018-04-16 23:08:22 +02:00
|
|
|
// starts notifications coming from drone to port 6038 aka 0x9617 when encoded low-endian.
|
2018-04-18 12:15:18 +02:00
|
|
|
// TODO: allow setting a specific video port.
|
2018-04-13 13:49:12 +02:00
|
|
|
d.SendCommand("conn_req:\x96\x17")
|
2018-04-07 21:23:57 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// send stick commands
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
err := d.SendStickCommand()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("stick command error:", err)
|
|
|
|
}
|
2018-04-19 18:28:14 +02:00
|
|
|
time.Sleep(20 * time.Millisecond)
|
2018-04-13 13:49:12 +02:00
|
|
|
}
|
|
|
|
}()
|
2018-04-03 20:42:16 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Halt stops the driver.
|
|
|
|
func (d *Driver) Halt() (err error) {
|
|
|
|
d.reqConn.Close()
|
2018-04-18 12:15:18 +02:00
|
|
|
d.videoConn.Close()
|
2018-04-03 20:42:16 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TakeOff tells drones to liftoff and start flying.
|
2018-04-13 13:49:12 +02:00
|
|
|
func (d *Driver) TakeOff() (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(takeoffCommand, 0x68, 0)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
|
|
|
|
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-13 13:49:12 +02:00
|
|
|
return
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Land tells drone to come in for landing.
|
2018-04-13 13:49:12 +02:00
|
|
|
func (d *Driver) Land() (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(landCommand, 0x68, 1)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(0x00))
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
|
|
|
|
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-13 13:49:12 +02:00
|
|
|
return
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
2018-04-22 18:15:38 +02:00
|
|
|
// StartVideo tells Tello to send start info (SPS/PPS) for video stream.
|
2018-04-16 23:08:22 +02:00
|
|
|
func (d *Driver) StartVideo() (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(videoStartCommand, 0x60, 0)
|
|
|
|
binary.Write(buf, binary.LittleEndian, int16(0x00)) // seq = 0
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
|
|
|
|
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-16 23:08:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-19 15:22:05 +02:00
|
|
|
// SetExposure sets the drone camera exposure level. Valid levels are 0, 1, and 2.
|
|
|
|
func (d *Driver) SetExposure(level int) (err error) {
|
|
|
|
if level < 0 || level > 2 {
|
|
|
|
return errors.New("Invalid exposure level")
|
|
|
|
}
|
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(exposureCommand, 0x48, 1)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(level))
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
2018-04-19 15:22:05 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-19 15:22:05 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetVideoEncoderRate sets the drone video encoder rate.
|
2018-04-22 18:15:38 +02:00
|
|
|
func (d *Driver) SetVideoEncoderRate(rate VideoBitRate) (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(videoEncoderRateCommand, 0x68, 1)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(rate))
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
2018-04-19 18:28:14 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-19 18:28:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-22 18:15:38 +02:00
|
|
|
// Rate queries the current video bit rate.
|
2018-04-19 18:28:14 +02:00
|
|
|
func (d *Driver) Rate() (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(videoRateQuery, 0x48, 0)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
2018-04-19 15:22:05 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-19 15:22:05 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-03 20:42:16 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.ly = float32(val) / 100.0
|
|
|
|
return nil
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-03 20:42:16 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.ly = float32(val) / 100.0 * -1
|
|
|
|
return nil
|
2018-04-03 20:42:16 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-03 20:42:16 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.ry = float32(val) / 100.0
|
|
|
|
return nil
|
2018-04-04 16:13:37 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-04 16:13:37 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.ry = float32(val) / 100.0 * -1
|
|
|
|
return nil
|
2018-04-04 16:13:37 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-04 16:13:37 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.rx = float32(val) / 100.0
|
2018-04-04 16:13:37 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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
|
2018-04-04 16:13:37 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-07 21:23:57 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.lx = float32(val) / 100.0
|
|
|
|
return nil
|
2018-04-04 16:13:37 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// 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()
|
2018-04-04 16:13:37 +02:00
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
d.lx = float32(val) / 100.0 * -1
|
|
|
|
return nil
|
2018-04-04 16:13:37 +02:00
|
|
|
}
|
2018-04-07 21:23:57 +02:00
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
// Flip tells drone to flip
|
2018-04-22 18:15:38 +02:00
|
|
|
func (d *Driver) Flip(direction FlipType) (err error) {
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(flipCommand, 0x70, 1)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(direction))
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
2018-04-18 12:15:18 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-18 12:15:18 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// FrontFlip tells the drone to perform a front flip.
|
|
|
|
func (d *Driver) FrontFlip() (err error) {
|
2018-04-22 18:15:38 +02:00
|
|
|
return d.Flip(FlipFront)
|
2018-04-18 12:15:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// BackFlip tells the drone to perform a back flip.
|
|
|
|
func (d *Driver) BackFlip() (err error) {
|
2018-04-22 18:15:38 +02:00
|
|
|
return d.Flip(FlipBack)
|
2018-04-18 12:15:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// RightFlip tells the drone to perform a flip to the right.
|
|
|
|
func (d *Driver) RightFlip() (err error) {
|
2018-04-22 18:15:38 +02:00
|
|
|
return d.Flip(FlipRight)
|
2018-04-18 12:15:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// LeftFlip tells the drone to perform a flip to the left.
|
|
|
|
func (d *Driver) LeftFlip() (err error) {
|
2018-04-22 18:15:38 +02:00
|
|
|
return d.Flip(FlipLeft)
|
2018-04-18 12:15:18 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 13:49:12 +02:00
|
|
|
// ParseFlightData from drone
|
|
|
|
func (d *Driver) ParseFlightData(b []byte) (fd *FlightData, err error) {
|
2018-04-07 21:23:57 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.Height)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.NorthSpeed)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.EastSpeed)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.GroundSpeed)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.FlyTime)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &data)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
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
|
2018-05-15 11:47:53 +01:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.ImuCalibrationState)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.BatteryPercentage)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.DroneFlyTimeLeft)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.DroneBatteryLeft)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &data)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fd.EmSky = (data >> 0 & 0x1) == 1
|
|
|
|
fd.EmGround = (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
|
2018-05-15 11:47:53 +01:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.FlyMode)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.ThrowFlyTimer)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
err = binary.Read(buf, binary.LittleEndian, &fd.CameraState)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &data)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-05-15 11:47:53 +01:00
|
|
|
fd.ElectricalMachineryState = int16(data & 0xff)
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &data)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fd.FrontIn = (data >> 0 & 0x1) == 1
|
|
|
|
fd.FrontOut = (data >> 1 & 0x1) == 1
|
|
|
|
fd.FrontLSC = (data >> 2 & 0x1) == 1
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
err = binary.Read(buf, binary.LittleEndian, &data)
|
2018-05-15 15:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fd.TemperatureHeight = (data >> 0 & 0x1) == 1
|
2018-04-07 21:23:57 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2018-04-13 13:49:12 +02:00
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
// SendStickCommand sends the joystick command packet to the drone.
|
2018-04-13 13:49:12 +02:00
|
|
|
func (d *Driver) SendStickCommand() (err error) {
|
|
|
|
d.cmdMutex.Lock()
|
|
|
|
defer d.cmdMutex.Unlock()
|
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(stickCommand, 0x60, 11)
|
|
|
|
binary.Write(buf, binary.LittleEndian, int16(0x00)) // seq = 0
|
2018-04-13 13:49:12 +02:00
|
|
|
|
|
|
|
// RightX center=1024 left =364 right =-364
|
|
|
|
axis1 := int16(660.0*d.rx + 1024.0)
|
|
|
|
|
2018-04-24 10:57:04 +02:00
|
|
|
// RightY down =364 up =-364
|
2018-04-13 13:49:12 +02:00
|
|
|
axis2 := int16(660.0*d.ry + 1024.0)
|
|
|
|
|
2018-04-24 10:57:04 +02:00
|
|
|
// LeftY down =364 up =-364
|
2018-04-13 13:49:12 +02:00
|
|
|
axis3 := int16(660.0*d.ly + 1024.0)
|
|
|
|
|
2018-04-24 10:57:04 +02:00
|
|
|
// LeftX left =364 right =-364
|
2018-04-13 13:49:12 +02:00
|
|
|
axis4 := int16(660.0*d.lx + 1024.0)
|
|
|
|
|
|
|
|
// speed control
|
|
|
|
axis5 := int16(660.0*d.throttle + 1024.0)
|
|
|
|
|
|
|
|
packedAxis := int64(axis1)&0x7FF | int64(axis2&0x7FF)<<11 | 0x7FF&int64(axis3)<<22 | 0x7FF&int64(axis4)<<33 | int64(axis5)<<44
|
2018-04-24 10:11:10 +02:00
|
|
|
binary.Write(buf, binary.LittleEndian, byte(0xFF&packedAxis))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(packedAxis>>8&0xFF))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(packedAxis>>16&0xFF))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(packedAxis>>24&0xFF))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(packedAxis>>32&0xFF))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(packedAxis>>40&0xFF))
|
2018-04-13 13:49:12 +02:00
|
|
|
|
|
|
|
now := time.Now()
|
2018-04-24 10:11:10 +02:00
|
|
|
binary.Write(buf, binary.LittleEndian, byte(now.Hour()))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(now.Minute()))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(now.Second()))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(now.UnixNano()/int64(time.Millisecond)&0xff))
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(now.UnixNano()/int64(time.Millisecond)>>8))
|
2018-04-13 13:49:12 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
|
|
|
|
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-23 20:43:22 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendDateTime sends the current date/time to the drone.
|
|
|
|
func (d *Driver) SendDateTime() (err error) {
|
|
|
|
d.cmdMutex.Lock()
|
|
|
|
defer d.cmdMutex.Unlock()
|
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
buf, _ := d.createPacket(timeCommand, 0x50, 11)
|
|
|
|
d.seq++
|
|
|
|
binary.Write(buf, binary.LittleEndian, d.seq)
|
2018-04-23 20:43:22 +02:00
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(0x00))
|
2018-05-16 09:28:06 +01:00
|
|
|
binary.Write(buf, binary.LittleEndian, int16(now.Hour()))
|
|
|
|
binary.Write(buf, binary.LittleEndian, int16(now.Minute()))
|
|
|
|
binary.Write(buf, binary.LittleEndian, int16(now.Second()))
|
2018-04-23 20:43:22 +02:00
|
|
|
binary.Write(buf, binary.LittleEndian, int16(now.UnixNano()/int64(time.Millisecond)&0xff))
|
|
|
|
binary.Write(buf, binary.LittleEndian, int16(now.UnixNano()/int64(time.Millisecond)>>8))
|
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC16(buf.Bytes()))
|
2018-04-13 13:49:12 +02:00
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
_, err = d.reqConn.Write(buf.Bytes())
|
2018-04-13 13:49:12 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-18 12:15:18 +02:00
|
|
|
// SendCommand is used to send a text command such as the initial connection request to the drone.
|
2018-04-13 13:49:12 +02:00
|
|
|
func (d *Driver) SendCommand(cmd string) (err error) {
|
|
|
|
_, err = d.reqConn.Write([]byte(cmd))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-24 10:57:04 +02:00
|
|
|
func (d *Driver) handleResponse() error {
|
|
|
|
var buf [2048]byte
|
|
|
|
n, err := d.reqConn.Read(buf[0:])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse binary packet
|
|
|
|
if buf[0] == messageStart {
|
|
|
|
if buf[6] == 0x10 {
|
|
|
|
switch buf[5] {
|
|
|
|
case logMessage:
|
|
|
|
d.Publish(d.Event(LogEvent), buf[9:])
|
|
|
|
default:
|
|
|
|
fmt.Printf("Unknown message: %+v\n", buf[0:n])
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch buf[5] {
|
|
|
|
case wifiMessage:
|
|
|
|
buf := bytes.NewReader(buf[9:12])
|
|
|
|
wd := &WifiData{}
|
2018-05-17 10:06:11 +01:00
|
|
|
binary.Read(buf, binary.LittleEndian, &wd.Disturb)
|
|
|
|
binary.Read(buf, binary.LittleEndian, &wd.Strength)
|
2018-04-24 10:57:04 +02:00
|
|
|
d.Publish(d.Event(WifiDataEvent), wd)
|
|
|
|
case lightMessage:
|
|
|
|
buf := bytes.NewReader(buf[9:10])
|
|
|
|
var ld int16
|
2018-05-17 10:06:11 +01:00
|
|
|
binary.Read(buf, binary.LittleEndian, &ld)
|
2018-04-24 10:57:04 +02:00
|
|
|
d.Publish(d.Event(LightStrengthEvent), ld)
|
|
|
|
case timeCommand:
|
|
|
|
d.Publish(d.Event(TimeEvent), buf[7:8])
|
|
|
|
case takeoffCommand:
|
|
|
|
d.Publish(d.Event(TakeoffEvent), buf[7:8])
|
|
|
|
case landCommand:
|
|
|
|
d.Publish(d.Event(LandingEvent), 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)
|
|
|
|
d.SendDateTime()
|
|
|
|
d.processVideo()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Driver) processVideo() error {
|
|
|
|
videoPort, err := net.ResolveUDPAddr("udp", ":6038")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.videoConn, err = net.ListenUDP("udp", videoPort)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
buf := make([]byte, 2048)
|
|
|
|
n, _, err := d.videoConn.ReadFromUDP(buf)
|
|
|
|
d.Publish(d.Event(VideoFrameEvent), buf[2:n])
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-24 10:11:10 +02:00
|
|
|
func (d *Driver) createPacket(cmd int16, pktType byte, len int16) (buf *bytes.Buffer, err error) {
|
|
|
|
l := len + 11
|
|
|
|
buf = &bytes.Buffer{}
|
|
|
|
|
|
|
|
binary.Write(buf, binary.LittleEndian, byte(messageStart))
|
|
|
|
binary.Write(buf, binary.LittleEndian, l<<3)
|
|
|
|
binary.Write(buf, binary.LittleEndian, CalculateCRC8(buf.Bytes()[0:3]))
|
|
|
|
binary.Write(buf, binary.LittleEndian, pktType)
|
|
|
|
binary.Write(buf, binary.LittleEndian, cmd)
|
|
|
|
|
|
|
|
return buf, nil
|
|
|
|
}
|