Skip to main content

tello/
lib.rs

1//! # Tello drone
2//!
3//! There are two interfaces for the tello drone. The text based and a
4//! non-public interface, used by the native app. The guys from the
5//! [tellopilots forum](https://tellopilots.com/) did an awesome job by
6//! reverse engineer this interface and support other public repositories
7//! for go, python...
8//!
9//! This library combines the network protocol to communicate with the drone and get
10//! available meta data additionally and a remote-control framework is available to
11//! simplify the wiring to the keyboard or an joystick.
12//!
13//! In the sources you will find an example, how to create a SDL-Ui and use
14//! the keyboard to control the drone. You can run it with `cargo run --example fly`
15//!
16//! **Please keep in mind, advanced maneuvers require a bright environment. (Flip, Bounce, ...)**
17//!
18//! ## Communication
19//!
20//! When the drone gets an enable package (`drone.connect(11111);`), the Tello drone
21//! send data on two UDP channels. A the command channel (port: 8889) and B the
22//! video channel (default: port: 11111). In the AP mode, the drone will appear with
23//! the default ip 192.168.10.1. All send calls are done synchronously.
24//! To receive the data, you have to poll the drone. Here is an example:
25//!
26//!
27//! ### Example
28//!
29//! ```
30//! use tello::{Drone, Message, Package, PackageData, ResponseMsg};
31//! use std::time::Duration;
32//!
33//! fn main() -> Result<(), String> {
34//!     let mut drone = Drone::new("192.168.10.1:8889");
35//!     drone.connect(11111);
36//!     loop {
37//!         if let Some(msg) = drone.poll() {
38//!             match msg {
39//!                 Message::Data(Package {data: PackageData::FlightData(d), ..}) => {
40//!                     println!("battery {}", d.battery_percentage);
41//!                 }
42//!                 Message::Frame(frame_id, data) => {
43//!                     println!("frame {} {:?}", frame_id, data);
44//!                 }
45//!                 Message::Response(ResponseMsg::Connected(_)) => {
46//!                     println!("connected");
47//!                     drone.throw_and_go().unwrap();
48//!                 }
49//!                 _ => ()
50//!             }
51//!         }
52//!         ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
53//!     }
54//! }
55//! ```
56//!
57//! ## Remote control
58//!
59//! The poll is not only receiving messages from the drone, it will also send some default-settings,
60//! replies with acknowledgements, triggers the key frames or send the remote-control state for the
61//! live move commands.
62//!
63//! The Drone contains a rc_state to manipulate the movement. e.g.: `drone.rc_state.go_down()`,
64//! `drone.rc_state.go_forward_back(-0.7)`
65//!
66//! The following example is opening a window with SDL, handles the keyboard inputs and shows how to connect a
67//! game pad or joystick.
68//!
69//!
70//! ### Examples
71//!
72//! ```
73//! use sdl2::event::Event;
74//! use sdl2::keyboard::Keycode;
75//! use tello::{Drone, Message, Package, PackageData, ResponseMsg};
76//! use std::time::Duration;
77//!
78//! fn main() -> Result<(), String> {
79//!     let mut drone = Drone::new("192.168.10.1:8889");
80//!     drone.connect(11111);
81//!
82//!     let sdl_context = sdl2::init()?;
83//!     let video_subsystem = sdl_context.video()?;
84//!     let window = video_subsystem.window("TELLO drone", 1280, 720).build().unwrap();
85//!     let mut canvas = window.into_canvas().build().unwrap();
86//!
87//!     let mut event_pump = sdl_context.event_pump()?;
88//!     'running: loop {
89//!         // draw some stuff
90//!         canvas.clear();
91//!         // [...]
92//!
93//!         // handle input from a keyboard or something like a game-pad
94//!         // ue the keyboard events
95//!         for event in event_pump.poll_iter() {
96//!             match event {
97//!                 Event::Quit { .. }
98//!                 | Event::KeyDown { keycode: Some(Keycode::Escape), .. } =>
99//!                     break 'running,
100//!                 Event::KeyDown { keycode: Some(Keycode::K), .. } =>
101//!                     drone.take_off().unwrap(),
102//!                 Event::KeyDown { keycode: Some(Keycode::L), .. } =>
103//!                     drone.land().unwrap(),
104//!                 Event::KeyDown { keycode: Some(Keycode::A), .. } =>
105//!                     drone.rc_state.go_left(),
106//!                 Event::KeyDown { keycode: Some(Keycode::D), .. } =>
107//!                     drone.rc_state.go_right(),
108//!                 Event::KeyUp { keycode: Some(Keycode::A), .. }
109//!                 | Event::KeyUp { keycode: Some(Keycode::D), .. } =>
110//!                     drone.rc_state.stop_left_right(),
111//!                 //...
112//!             }
113//!         }
114//!
115//!         // or use a game pad (range from -1 to 1)
116//!         // drone.rc_state.go_left_right(dummy_joystick.axis.1);
117//!         // drone.rc_state.go_forward_back(dummy_joystick.axis.2);
118//!         // drone.rc_state.go_up_down(dummy_joystick.axis.3);
119//!         // drone.rc_state.turn(dummy_joystick.axis.4);
120//!
121//!         // the poll will send the move command to the drone
122//!         drone.poll();
123//!
124//!         canvas.present();
125//!         ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
126//!     }
127//! }
128//! ```
129
130use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
131use chrono::prelude::*;
132use crc::{crc16, crc8};
133use drone_state::{FlightData, LightInfo, LogMessage, WifiInfo};
134use std::convert::TryFrom;
135use std::io::{Cursor, Read, Seek, SeekFrom, Write};
136use std::net::{SocketAddr, UdpSocket};
137use std::sync::atomic::{AtomicU16, Ordering};
138use std::time::SystemTime;
139
140pub mod command_mode;
141mod crc;
142pub mod drone_state;
143pub mod odometry;
144mod rc_state;
145
146pub use command_mode::CommandMode;
147pub use drone_state::DroneMeta;
148pub use rc_state::RCState;
149
150static SEQ_NO: AtomicU16 = AtomicU16::new(1);
151
152type Result = std::result::Result<(), ()>;
153
154/// The video data itself is just H264 encoded YUV420p
155#[derive(Debug, Clone)]
156struct VideoSettings {
157    pub port: u16,
158    pub enabled: bool,
159    pub mode: VideoMode,
160    pub level: u8,
161    pub encoding_rate: u8,
162    pub last_video_poll: SystemTime,
163}
164
165/// Main connection and controller for the drone
166#[derive(Debug)]
167pub struct Drone {
168    peer_ip: String,
169
170    socket: UdpSocket,
171    video_socket: Option<UdpSocket>,
172    video: VideoSettings,
173    last_stick_command: SystemTime,
174
175    /// remote control values to control the drone
176    pub rc_state: RCState,
177
178    /// current meta data from the drone
179    pub drone_meta: DroneMeta,
180
181    /// used to query some metadata delayed after connecting
182    status_counter: u32,
183}
184
185const START_OF_PACKET: u8 = 0xcc;
186
187/// known Command ids. Not all of them are implemented.
188#[derive(Debug, Clone, Copy, PartialEq)]
189#[repr(u16)]
190pub enum CommandIds {
191    Undefined = 0x0000,
192    SsidMsg = 0x0011,
193    SsidCmd = 0x0012,
194    SsidPasswordMsg = 0x0013,
195    SsidPasswordCmd = 0x0014,
196    WifiRegionMsg = 0x0015,
197    WifiRegionCmd = 0x0016,
198    WifiMsg = 0x001a,
199    VideoEncoderRateCmd = 0x0020,
200    VideoDynAdjRateCmd = 0x0021,
201    EisCmd = 0x0024,
202    VideoStartCmd = 0x0025,
203    VideoRateQuery = 0x0028,
204    TakePictureCommand = 0x0030,
205    VideoModeCmd = 0x0031,
206    VideoRecordCmd = 0x0032,
207    ExposureCmd = 0x0034,
208    LightMsg = 0x0035,
209    JpegQualityMsg = 0x0037,
210    Error1Msg = 0x0043,
211    Error2Msg = 0x0044,
212    VersionMsg = 0x0045,
213    TimeCmd = 0x0046,
214    ActivationTimeMsg = 0x0047,
215    LoaderVersionMsg = 0x0049,
216    StickCmd = 0x0050,
217    TakeoffCmd = 0x0054,
218    LandCmd = 0x0055,
219    FlightMsg = 0x0056,
220    AltLimitCmd = 0x0058,
221    FlipCmd = 0x005c,
222    ThrowAndGoCmd = 0x005d,
223    PalmLandCmd = 0x005e,
224    TelloCmdFileSize = 0x0062,
225    TelloCmdFileData = 0x0063,
226    TelloCmdFileComplete = 0x0064,
227    SmartVideoCmd = 0x0080,
228    SmartVideoStatusMsg = 0x0081,
229    LogHeaderMsg = 0x1050,
230    LogDataMsg = 0x1051,
231    LogConfigMsg = 0x1052,
232    BounceCmd = 0x1053,
233    CalibrateCmd = 0x1054,
234    LowBatThresholdCmd = 0x1055,
235    AltLimitMsg = 0x1056,
236    LowBatThresholdMsg = 0x1057,
237    AttLimitCmd = 0x1058,
238    AttLimitMsg = 0x1059,
239}
240
241impl From<u16> for CommandIds {
242    fn from(value: u16) -> CommandIds {
243        match value {
244            0x0011 => CommandIds::SsidMsg,
245            0x0012 => CommandIds::SsidCmd,
246            0x0013 => CommandIds::SsidPasswordMsg,
247            0x0014 => CommandIds::SsidPasswordCmd,
248            0x0015 => CommandIds::WifiRegionMsg,
249            0x0016 => CommandIds::WifiRegionCmd,
250            0x001a => CommandIds::WifiMsg,
251            0x0020 => CommandIds::VideoEncoderRateCmd,
252            0x0021 => CommandIds::VideoDynAdjRateCmd,
253            0x0024 => CommandIds::EisCmd,
254            0x0025 => CommandIds::VideoStartCmd,
255            0x0028 => CommandIds::VideoRateQuery,
256            0x0030 => CommandIds::TakePictureCommand,
257            0x0031 => CommandIds::VideoModeCmd,
258            0x0032 => CommandIds::VideoRecordCmd,
259            0x0034 => CommandIds::ExposureCmd,
260            0x0035 => CommandIds::LightMsg,
261            0x0037 => CommandIds::JpegQualityMsg,
262            0x0043 => CommandIds::Error1Msg,
263            0x0044 => CommandIds::Error2Msg,
264            0x0045 => CommandIds::VersionMsg,
265            0x0046 => CommandIds::TimeCmd,
266            0x0047 => CommandIds::ActivationTimeMsg,
267            0x0049 => CommandIds::LoaderVersionMsg,
268            0x0050 => CommandIds::StickCmd,
269            0x0054 => CommandIds::TakeoffCmd,
270            0x0055 => CommandIds::LandCmd,
271            0x0056 => CommandIds::FlightMsg,
272            0x0058 => CommandIds::AltLimitCmd,
273            0x005c => CommandIds::FlipCmd,
274            0x005d => CommandIds::ThrowAndGoCmd,
275            0x005e => CommandIds::PalmLandCmd,
276            0x0062 => CommandIds::TelloCmdFileSize,
277            0x0063 => CommandIds::TelloCmdFileData,
278            0x0064 => CommandIds::TelloCmdFileComplete,
279            0x0080 => CommandIds::SmartVideoCmd,
280            0x0081 => CommandIds::SmartVideoStatusMsg,
281            0x1050 => CommandIds::LogHeaderMsg,
282            0x1051 => CommandIds::LogDataMsg,
283            0x1052 => CommandIds::LogConfigMsg,
284            0x1053 => CommandIds::BounceCmd,
285            0x1054 => CommandIds::CalibrateCmd,
286            0x1055 => CommandIds::LowBatThresholdCmd,
287            0x1056 => CommandIds::AltLimitMsg,
288            0x1057 => CommandIds::LowBatThresholdMsg,
289            0x1058 => CommandIds::AttLimitCmd,
290            0x1059 => CommandIds::AttLimitMsg,
291            _ => CommandIds::Undefined,
292        }
293    }
294}
295/// unformatted response from the drone.
296#[derive(Debug, Clone)]
297pub enum ResponseMsg {
298    Connected(String),
299    UnknownCommand(CommandIds),
300}
301
302/// The package type bitmask discripe the payload and how the drone should behave.
303/// More info are available on the https://tellopilots.com webpage
304#[derive(Debug, Clone, Copy)]
305#[repr(u8)]
306pub enum PackageTypes {
307    X48 = 0x48,
308    X50 = 0x50,
309    X60 = 0x60,
310    X70 = 0x70,
311    X68 = 0x68,
312}
313
314/// Flip commands taken from Go version of code
315pub enum Flip {
316    /// flips forward.
317    Forward = 0,
318    /// flips left.
319    Left = 1,
320    /// flips backwards.
321    Back = 2,
322    /// flips to the right.
323    Right = 3,
324    /// flips forwards and to the left.
325    ForwardLeft = 4,
326    /// flips backwards and to the left.
327    BackLeft = 5,
328    /// flips backwards and to the right.
329    BackRight = 6,
330    /// flips forwards and to the right.
331    ForwardRight = 7,
332}
333
334/// available modes for the tello drone
335#[derive(Debug, Clone)]
336pub enum VideoMode {
337    M960x720 = 0,
338    M1280x720 = 1,
339}
340
341impl Drone {
342    /// create a new drone and and listen to the Response port 8889
343    /// this struct implements a number of commands to control the drone
344    ///
345    /// After connection to the drone it is very important to poll the drone at least with 20Hz.
346    /// This will acknowledge some messages and parse the state of the drone.
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// let mut drone = Drone::new("192.168.10.1:8889");
352    /// drone.connect(11111);
353    /// // wait for the connection
354    /// drone.take_off();
355    /// ```
356    pub fn new(ip: &str) -> Drone {
357        let peer_ip = ip.to_string();
358        let socket = UdpSocket::bind(&SocketAddr::from(([0, 0, 0, 0], 8889)))
359            .expect("couldn't bind to command address");
360        socket.set_nonblocking(true).unwrap();
361        socket.connect(ip).expect("connect command socket failed");
362
363        let video = VideoSettings {
364            port: 0,
365            enabled: false,
366            mode: VideoMode::M960x720,
367            level: 1,
368            encoding_rate: 4,
369            last_video_poll: SystemTime::now(),
370        };
371
372        let rc_state = RCState::default();
373        let drone_meta = DroneMeta::default();
374
375        Drone {
376            peer_ip,
377            socket,
378            video_socket: None,
379            video,
380            status_counter: 0,
381            last_stick_command: SystemTime::now(),
382            rc_state,
383            drone_meta,
384        }
385    }
386
387    /// Connect to the drone and inform the drone on with port you are ready to receive the video-stream
388    ///
389    /// The Video stream do not start automatically. You have to start it with
390    /// `drone.start_video()` and pool every key-frame with an additional `drone.start_video()` call.
391    pub fn connect(&mut self, video_port: u16) -> usize {
392        let mut data = b"conn_req:  ".to_vec();
393        let mut cur = Cursor::new(&mut data);
394        cur.set_position(9);
395        cur.write_u16::<LittleEndian>(video_port).unwrap();
396        self.video.port = video_port;
397        self.start_video().unwrap();
398
399        let video_socket = UdpSocket::bind(&SocketAddr::from(([0, 0, 0, 0], self.video.port)))
400            .expect("couldn't bind to video address");
401        video_socket.set_nonblocking(true).unwrap();
402        self.video_socket = Some(video_socket);
403
404        self.socket.send(&data).expect("network should be usable")
405    }
406
407    /// convert the command into a Vec<u8> and send it to the drone.
408    /// this is mostly for internal purposes, but you can implement missing commands your self
409    pub fn send(&self, command: UdpCommand) -> Result {
410        let data: Vec<u8> = command.into();
411
412        if self.socket.send(&data).is_ok() {
413            Ok(())
414        } else {
415            Err(())
416        }
417    }
418
419    /// when the drone send the current log stats, it is required to ack this.
420    /// The logic is implemented in the poll function.
421    fn send_ack_log(&self, id: u16) -> Result {
422        let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::LogHeaderMsg, PackageTypes::X50);
423        cmd.write_u16(id);
424        self.send(cmd)
425    }
426
427    /// if there are some data in the udp-socket, all of one frame are collected and returned as UDP-Package
428    fn receive_video_frame(&self, socket: &UdpSocket) -> Option<Message> {
429        let mut read_buf = [0; 1440];
430
431        socket.set_nonblocking(true).unwrap();
432        if let Ok(received) = socket.recv(&mut read_buf) {
433            let active_frame_id = read_buf[0];
434            let mut sqn = read_buf[1];
435            let mut frame_buffer = read_buf[2..received].to_owned();
436
437            // should start with 0. otherwise delete frame package
438            if sqn != 0 {
439                return None;
440            }
441
442            socket.set_nonblocking(false).unwrap();
443            'recVideo: loop {
444                if sqn >= 120 {
445                    break 'recVideo Some(Message::Frame(active_frame_id, frame_buffer));
446                }
447                if let Ok(received) = socket.recv(&mut read_buf) {
448                    let frame_id = read_buf[0];
449                    if frame_id != active_frame_id {
450                        // drop frame to stop data mess
451                        break 'recVideo None;
452                    }
453
454                    sqn = read_buf[1];
455                    let mut data = read_buf[2..received].to_owned();
456
457                    frame_buffer.append(&mut data);
458                } else {
459                    break 'recVideo None;
460                }
461            }
462        } else {
463            None
464        }
465    }
466
467    /// poll data from drone and send common data to the drone
468    /// - every 33 millis, the sick command is send to the drone
469    /// - every 1 sec, a key-frame is requested from the drone
470    /// - logMessage packages are replied immediately with an ack package
471    /// - dateTime packages are replied immediately with the local SystemTime
472    /// - after the third status message some default data are send to the drone
473    ///
474    /// To receive a smooth video stream, you should poll at least 35 times per second
475    pub fn poll(&mut self) -> Option<Message> {
476        let now = SystemTime::now();
477
478        let delta = now.duration_since(self.last_stick_command).unwrap();
479        if delta.as_millis() > 1000 / 30 {
480            let (pitch, nick, roll, yaw, fast) = self.rc_state.get_stick_parameter();
481            self.send_stick(pitch, nick, roll, yaw, fast).unwrap();
482            self.last_stick_command = now.clone();
483        }
484
485        // poll I-Frame every second and receive udp frame data
486        if self.video.enabled {
487            let delta = now.duration_since(self.video.last_video_poll).unwrap();
488            if delta.as_secs() > 1 {
489                self.video.last_video_poll = now;
490                self.poll_key_frame().unwrap();
491            }
492            if let Some(socket) = self.video_socket.as_ref() {
493                let frame = self.receive_video_frame(&socket);
494                if frame.is_some() {
495                    return frame;
496                }
497            }
498        }
499
500        // receive and process data on command socket
501        let mut read_buf = [0; 1440];
502        if let Ok(received) = self.socket.recv(&mut read_buf) {
503            let data = read_buf[..received].to_vec();
504            match Message::try_from(data) {
505                Ok(msg) => {
506                    match &msg {
507                        Message::Response(ResponseMsg::Connected(_)) => self.status_counter = 0,
508                        Message::Data(Package {
509                            data: PackageData::LogMessage(log),
510                            ..
511                        }) => self.send_ack_log(log.id).unwrap(),
512                        Message::Data(Package { cmd, .. }) if *cmd == CommandIds::TimeCmd => {
513                            self.send_date_time().unwrap()
514                        }
515                        Message::Data(Package { cmd, data, .. })
516                            if *cmd == CommandIds::FlightMsg =>
517                        {
518                            self.drone_meta.update(&data);
519
520                            self.status_counter += 1;
521                            if self.status_counter == 3 {
522                                self.get_version().unwrap();
523                                self.set_video_bitrate(4).unwrap();
524                                self.get_alt_limit().unwrap();
525                                self.get_battery_threshold().unwrap();
526                                self.get_att_angle().unwrap();
527                                self.get_region().unwrap();
528                                self.set_exposure(2).unwrap();
529                            };
530                        }
531                        Message::Data(Package { data, .. }) => {
532                            self.drone_meta.update(&data);
533                        }
534                        _ => (),
535                    };
536
537                    Some(msg)
538                }
539                Err(_e) => None,
540            }
541        } else {
542            None
543        }
544    }
545}
546
547impl Drone {
548    /// You can switch the drone to the command mode.
549    /// To get back to the "Free-Flight-Mode" you have to reboot the drone.
550    ///
551    /// In the CommandMode you can move the drone by cm in the 3D space
552    /// If you are using tokio as executer, use the `tokio_async` feature
553    /// to prevent the executer from being blocked.
554    pub fn command_mode(self) -> CommandMode {
555        CommandMode::from(self.peer_ip.parse::<SocketAddr>().unwrap())
556    }
557}
558
559impl Drone {
560    pub fn take_off(&self) -> Result {
561        self.send(UdpCommand::new(CommandIds::TakeoffCmd, PackageTypes::X68))
562    }
563    pub fn throw_and_go(&self) -> Result {
564        let mut cmd = UdpCommand::new(CommandIds::ThrowAndGoCmd, PackageTypes::X48);
565        cmd.write_u8(0);
566        self.send(cmd)
567    }
568    pub fn land(&self) -> Result {
569        let mut command = UdpCommand::new(CommandIds::LandCmd, PackageTypes::X68);
570        command.write_u8(0x00);
571        self.send(command)
572    }
573    pub fn stop_land(&self) -> Result {
574        let mut command = UdpCommand::new(CommandIds::LandCmd, PackageTypes::X68);
575        command.write_u8(0x00);
576        self.send(command)
577    }
578    pub fn palm_land(&self) -> Result {
579        let mut cmd = UdpCommand::new(CommandIds::PalmLandCmd, PackageTypes::X68);
580        cmd.write_u8(0);
581        self.send(cmd)
582    }
583
584    pub fn flip(&self, direction: Flip) -> Result {
585        let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::FlipCmd, PackageTypes::X70);
586        cmd.write_u8(direction as u8);
587        self.send(cmd)
588    }
589    pub fn bounce(&self) -> Result {
590        let mut cmd = UdpCommand::new(CommandIds::BounceCmd, PackageTypes::X68);
591        cmd.write_u8(0x30);
592        self.send(cmd)
593    }
594    pub fn bounce_stop(&self) -> Result {
595        let mut cmd = UdpCommand::new(CommandIds::BounceCmd, PackageTypes::X68);
596        cmd.write_u8(0x31);
597        self.send(cmd)
598    }
599
600    pub fn get_version(&self) -> Result {
601        self.send(UdpCommand::new(CommandIds::VersionMsg, PackageTypes::X48))
602    }
603    pub fn get_alt_limit(&self) -> Result {
604        self.send(UdpCommand::new(CommandIds::AltLimitMsg, PackageTypes::X68))
605    }
606    pub fn set_alt_limit(&self, limit: u8) -> Result {
607        let mut cmd = UdpCommand::new(CommandIds::AltLimitCmd, PackageTypes::X68);
608        cmd.write_u8(limit);
609        cmd.write_u8(0);
610        self.send(cmd)
611    }
612    pub fn get_att_angle(&self) -> Result {
613        self.send(UdpCommand::new(CommandIds::AttLimitMsg, PackageTypes::X68))
614    }
615    pub fn set_att_angle(&self) -> Result {
616        let mut cmd = UdpCommand::new(CommandIds::AttLimitCmd, PackageTypes::X68);
617        cmd.write_u8(0);
618        cmd.write_u8(0);
619        // TODO set angle correct
620        // pkt.add_byte( int(float_to_hex(float(limit))[4:6], 16) ) # 'attitude limit' formatted in float of 4 bytes
621        cmd.write_u8(10);
622        cmd.write_u8(0x41);
623        self.send(cmd)
624    }
625
626    pub fn get_battery_threshold(&self) -> Result {
627        self.send(UdpCommand::new(
628            CommandIds::LowBatThresholdMsg,
629            PackageTypes::X68,
630        ))
631    }
632    pub fn set_battery_threshold(&self, threshold: u8) -> Result {
633        let mut cmd = UdpCommand::new(CommandIds::LowBatThresholdCmd, PackageTypes::X68);
634        cmd.write_u8(threshold);
635        self.send(cmd)
636    }
637
638    pub fn get_region(&self) -> Result {
639        self.send(UdpCommand::new(
640            CommandIds::WifiRegionCmd,
641            PackageTypes::X48,
642        ))
643    }
644
645    /// send the stick command via udp to the drone
646    ///
647    /// pitch up/down -1 -> 1
648    /// nick forward/backward -1 -> 1
649    /// roll right/left -1 -> 1
650    /// yaw cw/ccw -1 -> 1
651    pub fn send_stick(&self, pitch: f32, nick: f32, roll: f32, yaw: f32, fast: bool) -> Result {
652        let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::StickCmd, PackageTypes::X60);
653
654        // RightX center=1024 left =364 right =-364
655        let pitch_u = (1024.0 + 660.0 * pitch) as i64;
656
657        // RightY down =364 up =-364
658        let nick_u = (1024.0 + 660.0 * nick) as i64;
659
660        // LeftY down =364 up =-364
661        let roll_u = (1024.0 + 660.0 * roll) as i64;
662
663        // LeftX left =364 right =-364
664        let yaw_u = (1024.0 + 660.0 * yaw) as i64;
665
666        // speed control
667        let throttle_u = if fast { 1i64 } else { 0i64 };
668
669        // create axis package
670        let packed_axis: i64 = (roll_u & 0x7FF)
671            | (nick_u & 0x7FF) << 11
672            | (pitch_u & 0x7FF) << 22
673            | (yaw_u & 0x7FF) << 33
674            | throttle_u << 44;
675
676        // println!("p {:} n {:} r {:} y {:} t {:} => {:x}", pitch_u & 0x7FF, nick_u& 0x7FF, roll_u& 0x7FF, yaw_u& 0x7FF, throttle_u, packed_axis);
677
678        cmd.write_u8(((packed_axis) & 0xFF) as u8);
679        cmd.write_u8(((packed_axis >> 8) & 0xFF) as u8);
680        cmd.write_u8(((packed_axis >> 16) & 0xFF) as u8);
681        cmd.write_u8(((packed_axis >> 24) & 0xFF) as u8);
682        cmd.write_u8(((packed_axis >> 32) & 0xFF) as u8);
683        cmd.write_u8(((packed_axis >> 40) & 0xFF) as u8);
684
685        self.send(Drone::add_time(cmd))
686    }
687
688    /// SendDateTime sends the current date/time to the drone.
689    pub fn send_date_time(&self) -> Result {
690        let command = UdpCommand::new(CommandIds::TimeCmd, PackageTypes::X50);
691        self.send(Drone::add_date_time(command))
692    }
693
694    pub fn add_time(mut command: UdpCommand) -> UdpCommand {
695        let now = Local::now();
696        let millis = now.nanosecond() / 1_000_000;
697        command.write_u8(now.hour() as u8);
698        command.write_u8(now.minute() as u8);
699        command.write_u8(now.second() as u8);
700        command.write_u16(millis as u16);
701        command
702    }
703
704    pub fn add_date_time(mut command: UdpCommand) -> UdpCommand {
705        let now = Local::now();
706        let millis = now.nanosecond() / 1_000_000;
707        command.write_u8(0);
708        command.write_u16(now.year() as u16);
709        command.write_u16(now.month() as u16);
710        command.write_u16(now.day() as u16);
711        command.write_u16(now.hour() as u16);
712        command.write_u16(now.minute() as u16);
713        command.write_u16(now.second() as u16);
714        command.write_u16(millis as u16);
715        command
716    }
717}
718
719impl Drone {
720    /// start_video starts the streaming and requests the info (SPS/PPS) for the video stream.
721    ///
722    /// Video-metadata:
723    /// e.g.: caps = video/x-h264, stream-format=(string)avc, width=(int)960, height=(int)720, framerate=(fraction)0/1, interlace-mode=(string)progressive, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, parsed=(boolean)true, alignment=(string)au, profile=(string)main, level=(string)4, codec_data=(buffer)014d4028ffe10009674d402895a03c05b901000468ee3880
724    ///
725    /// # Examples
726    /// ```no_run
727    /// let mut drone = Drone::new("192.168.10.1:8889");
728    /// drone.connect(11111);
729    /// // ...
730    /// drone.start_video().unwrap();
731    /// ```
732    pub fn start_video(&mut self) -> Result {
733        self.video.enabled = true;
734        self.video.last_video_poll = SystemTime::now();
735        self.send(UdpCommand::new_with_zero_sqn(
736            CommandIds::VideoStartCmd,
737            PackageTypes::X60,
738        ))
739    }
740
741    /// Same as start_video(), but a better name to poll the (SPS/PPS) for the video stream.
742    ///
743    /// This is automatically called in the poll function every second.
744    pub fn poll_key_frame(&mut self) -> Result {
745        self.start_video()
746    }
747
748    /// Set the video mode to 960x720 4:3 video, or 1280x720 16:9 zoomed video.
749    /// 4:3 has a wider field of view (both vertically and horizontally), 16:9 is crisper.
750    ///
751    /// # Examples
752    /// ```no_run
753    /// let mut drone = Drone::new("192.168.10.1:8889");
754    /// drone.connect(11111);
755    /// // ...
756    /// drone.set_video_mode(VideoMode::M960x720).unwrap();
757    /// ```
758    pub fn set_video_mode(&mut self, mode: VideoMode) -> Result {
759        self.video.mode = mode.clone();
760        let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::VideoStartCmd, PackageTypes::X68);
761        cmd.write_u8(mode as u8);
762        self.send(cmd)
763    }
764
765    /// Set the camera exposure level.
766    /// param level: it can be 0, 1 or 2
767    ///
768    /// # Examples
769    /// ```no_run
770    /// let mut drone = Drone::new("192.168.10.1:8889");
771    /// drone.connect(11111);
772    /// // ...
773    /// drone.set_exposure(2).unwrap();
774    /// ```
775    pub fn set_exposure(&mut self, level: u8) -> Result {
776        let mut cmd = UdpCommand::new(CommandIds::ExposureCmd, PackageTypes::X48);
777        cmd.write_u8(level);
778        self.send(cmd)
779    }
780
781    /// set the video encoder rate for the camera.
782    /// param rate: TODO: unknown
783    ///
784    /// # Examples
785    /// ```no_run
786    /// let mut drone = Drone::new("192.168.10.1:8889");
787    /// drone.connect(11111);
788    /// // ...
789    /// drone.set_video_bitrate(3).unwrap();
790    /// ```
791    pub fn set_video_bitrate(&mut self, rate: u8) -> Result {
792        self.video.encoding_rate = rate;
793        let mut cmd = UdpCommand::new(CommandIds::VideoEncoderRateCmd, PackageTypes::X68);
794        cmd.write_u8(rate);
795        self.send(cmd)
796    }
797
798    /// take a single picture and provide it to download it.
799    ///
800    /// # Examples
801    /// ```no_run
802    /// let mut drone = Drone::new("192.168.10.1:8889");
803    /// drone.connect(11111);
804    /// // ...
805    /// drone.take_picture(3).unwrap();
806    ///
807    /// @TODO: download image
808    /// ```
809    pub fn take_picture(&self) -> Result {
810        self.send(UdpCommand::new(
811            CommandIds::TakePictureCommand,
812            PackageTypes::X68,
813        ))
814    }
815}
816
817/// wrapper to generate Udp Commands to send them to the drone.
818///
819/// It is public, to enable users to implement missing commands
820#[derive(Debug, Clone)]
821pub struct UdpCommand {
822    cmd: CommandIds,
823    pkt_type: PackageTypes,
824    zero_sqn: bool,
825    inner: Vec<u8>,
826}
827
828impl UdpCommand {
829    /// create a new command, prepare the header to send out the command
830    pub fn new(cmd: CommandIds, pkt_type: PackageTypes) -> UdpCommand {
831        UdpCommand {
832            cmd,
833            pkt_type,
834            zero_sqn: false,
835            inner: Vec::new(),
836        }
837    }
838    pub fn new_with_zero_sqn(cmd: CommandIds, pkt_type: PackageTypes) -> UdpCommand {
839        UdpCommand {
840            cmd,
841            pkt_type,
842            zero_sqn: true,
843            inner: Vec::new(),
844        }
845    }
846}
847
848impl UdpCommand {
849    pub fn write(&mut self, bytes: &[u8]) {
850        self.inner.append(&mut bytes.to_owned())
851    }
852    pub fn write_u8(&mut self, byte: u8) {
853        self.inner.push(byte)
854    }
855    pub fn write_u16(&mut self, value: u16) {
856        let mut cur = Cursor::new(&mut self.inner);
857        cur.seek(SeekFrom::End(0)).expect("");
858        cur.write_u16::<LittleEndian>(value).expect("");
859    }
860    pub fn write_u64(&mut self, value: u64) {
861        let mut cur = Cursor::new(&mut self.inner);
862        cur.seek(SeekFrom::End(0)).expect("");
863        cur.write_u64::<LittleEndian>(value).expect("");
864    }
865}
866
867impl Into<Vec<u8>> for UdpCommand {
868    fn into(self) -> Vec<u8> {
869        let mut data = {
870            let lng = self.inner.len();
871            let data: &[u8] = &self.inner;
872
873            let mut cur = Cursor::new(Vec::new());
874            cur.write_u8(START_OF_PACKET).expect("");
875            cur.write_u16::<LittleEndian>((lng as u16 + 11) << 3)
876                .expect("");
877            cur.write_u8(crc8(cur.clone().into_inner())).expect("");
878            cur.write_u8(self.pkt_type as u8).expect("");
879            cur.write_u16::<LittleEndian>(self.cmd as u16).expect("");
880
881            if self.zero_sqn {
882                cur.write_u16::<LittleEndian>(0).expect("");
883            } else {
884                let nr = SEQ_NO.fetch_add(1, Ordering::SeqCst);
885                cur.write_u16::<LittleEndian>(nr).expect("");
886            }
887
888            if lng > 0 {
889                cur.write_all(&data).unwrap();
890            }
891
892            cur.into_inner()
893        };
894
895        data.write_u16::<LittleEndian>(crc16(data.clone()))
896            .expect("");
897
898        data
899    }
900}
901
902/// Data / command package received from the drone with parsed data (if supported and known)
903#[derive(Debug, Clone)]
904pub struct Package {
905    pub cmd: CommandIds,
906    pub size: u16,
907    pub sq_nr: u16,
908    pub data: PackageData,
909}
910
911/// Incoming message can be Data, a response from the drone or a VideoFrame
912#[derive(Debug, Clone)]
913pub enum Message {
914    Data(Package),
915    Response(ResponseMsg),
916    Frame(u8, Vec<u8>),
917}
918
919impl TryFrom<Vec<u8>> for Message {
920    type Error = String;
921
922    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
923        let mut cur = Cursor::new(data);
924        if let Ok(START_OF_PACKET) = cur.read_u8() {
925            let size = (cur.read_u16::<LittleEndian>().unwrap() >> 3) - 11;
926            let _crc8 = cur.read_u8().unwrap();
927            let _pkt_type = cur.read_u8().unwrap();
928            let cmd = CommandIds::from(cur.read_u16::<LittleEndian>().unwrap());
929            let sq_nr = cur.read_u16::<LittleEndian>().unwrap();
930            let data = if size > 0 {
931                let mut data: Vec<u8> = Vec::with_capacity(size as usize);
932                cur.read_to_end(&mut data).unwrap();
933                if data.len() >= 2 {
934                    let _crc16: u16 =
935                        (data.pop().unwrap() as u16) + ((data.pop().unwrap() as u16) << 8);
936                }
937                match cmd {
938                    CommandIds::FlightMsg => PackageData::FlightData(FlightData::from(data)),
939                    CommandIds::WifiMsg => PackageData::WifiInfo(WifiInfo::from(data)),
940                    CommandIds::LightMsg => PackageData::LightInfo(LightInfo::from(data)),
941                    CommandIds::VersionMsg => PackageData::Version(
942                        String::from_utf8(data[1..].to_vec())
943                            .expect("version is not valid")
944                            .trim_matches(char::from(0))
945                            .to_string(),
946                    ),
947                    CommandIds::AltLimitMsg => {
948                        let mut c = Cursor::new(data);
949                        let _ = c.read_u8().unwrap();
950                        let h = c.read_u16::<LittleEndian>().unwrap();
951                        PackageData::AtlInfo(h)
952                    }
953
954                    CommandIds::LogHeaderMsg => PackageData::LogMessage(LogMessage::from(data)),
955                    _ => PackageData::Unknown(data),
956                }
957            } else {
958                PackageData::NoData()
959            };
960
961            Ok(Message::Data(Package {
962                cmd,
963                size,
964                sq_nr,
965                data,
966            }))
967        } else {
968            let data = cur.into_inner();
969            if data[0..9].to_vec() == b"conn_ack:" {
970                return Ok(Message::Response(ResponseMsg::Connected(
971                    String::from_utf8(data).unwrap(),
972                )));
973            } else if data[0..16].to_vec() == b"unknown command:" {
974                let mut cur = Cursor::new(data[17..].to_owned());
975                let command = CommandIds::from(cur.read_u16::<LittleEndian>().unwrap());
976                return Ok(Message::Response(ResponseMsg::UnknownCommand(command)));
977            }
978
979            let msg = String::from_utf8(data.clone()[0..5].to_vec()).unwrap_or_default();
980            Err(format!("invalid package {:x?}", msg))
981        }
982    }
983}
984
985/// Parsed data from the drone.
986#[derive(Debug, Clone)]
987pub enum PackageData {
988    NoData(),
989    AtlInfo(u16),
990    FlightData(FlightData),
991    LightInfo(LightInfo),
992    LogMessage(LogMessage),
993    Version(String),
994    WifiInfo(WifiInfo),
995    Unknown(Vec<u8>),
996}