tello_rust/
lib.rs

1// Rust wrapper to interact with the Ryze Tello drone using the official Tello api
2// By Ryan Thomas: https://github.com/RThom6
3// Essentially a translation of DJITelloPy into Rust: https://github.com/damiafuentes/DJITelloPy/tree/master#
4// Note: This wrapper only supports one drone at a time
5//
6// Tello API documentation:
7// [1.3](https://dl-cdn.ryzerobotics.com/downloads/tello/20180910/Tello%20SDK%20Documentation%20EN_1.3.pdf),
8// This Wrapper does not support EDU-only commands as of 01/08/2024, this may change in future
9// [2.0 with EDU-only commands](https://dl-cdn.ryzerobotics.com/downloads/Tello/Tello%20SDK%202.0%20User%20Guide.pdf)
10
11use log::{debug, error};
12use std::{
13    collections::HashMap,
14    net::UdpSocket,
15    sync::{Arc, Mutex},
16    thread,
17    time::{Duration, Instant},
18};
19
20const TELLO_ADDR: &'static str = "192.168.10.1:8889";
21const TELLO_STATE_ADDR: &'static str = "0.0.0.0:8890";
22const RESPONSE_TIMEOUT: u64 = 7; // Seconds
23const TAKEOFF_TIMEOUT: u64 = 20; // Seconds
24const TIME_BTW_COMMANDS: f64 = 0.1; // Seconds
25
26// State field types: states not named here are all strings
27const INT_STATE_FIELDS: &[&str] = &[
28    "mid", "x", "y", "z", "pitch", "roll", "yaw", "vgx", "vgy", "vgz", "templ", "temph", "tof",
29    "h", "bat", "time",
30];
31const FLOAT_STATE_FIELDS: &[&str] = &["baro", "agx", "agy", "agz"];
32
33pub struct Drone {
34    socket: UdpSocket,
35    is_flying: bool,
36    stream_on: bool,
37    retry_count: i32,
38    last_command_time: Instant,
39    shared_response: Arc<Mutex<Option<String>>>,
40    shared_state: Arc<Mutex<HashMap<String, StateValue>>>, //State hashmap, closest thing to python dict
41    read_state: HashMap<String, StateValue>,
42}
43
44#[derive(Clone)]
45#[allow(dead_code)]
46enum StateValue {
47    Int(i32),
48    Float(f64),
49    Str(String),
50}
51
52fn parse_state(state_str: &str) -> Option<HashMap<String, StateValue>> {
53    let state_str = state_str.trim();
54
55    if state_str.eq("ok") {
56        return None;
57    }
58
59    let mut state_map: HashMap<String, StateValue> = HashMap::new();
60
61    for field in state_str.split(';') {
62        let split: Vec<&str> = field.split(':').collect();
63
64        if split.len() < 2 {
65            continue;
66        }
67
68        let key = split[0].to_string();
69        let value_str = split[1];
70        let value: StateValue = match state_field_converter(&key, value_str) {
71            Ok(v) => v,
72            Err(e) => {
73                debug!(
74                    "Error parsing state value for {}: {} to {}",
75                    key, value_str, e
76                );
77                error!("{}", e);
78                continue;
79            }
80        };
81
82        state_map.insert(key, value);
83    }
84
85    return Some(state_map);
86}
87
88/// Converts fields to the correct type based on field key
89fn state_field_converter(key: &str, value_str: &str) -> Result<StateValue, String> {
90    if INT_STATE_FIELDS.contains(&key) {
91        value_str
92            .parse::<i32>()
93            .map(StateValue::Int)
94            .map_err(|e| e.to_string())
95    } else if FLOAT_STATE_FIELDS.contains(&key) {
96        value_str
97            .parse::<f64>()
98            .map(StateValue::Float)
99            .map_err(|e| e.to_string())
100    } else {
101        Ok(StateValue::Str(value_str.to_string()))
102    }
103}
104
105/// State receiver thread to run in background\n
106/// Private method ran when connected to drone, should kill program
107/// before trying connect again or will end up with many background threads
108fn start_state_receiver_thread(response_receiver: Arc<Mutex<HashMap<String, StateValue>>>) {
109    thread::spawn(move || {
110        let socket = UdpSocket::bind(TELLO_STATE_ADDR).expect("Couldn't bind receiver socket");
111        let mut buf = [0; 1024];
112
113        loop {
114            let (amt, _src) = socket.recv_from(&mut buf).expect("Didn't receive message");
115            let received = String::from_utf8_lossy(&buf[..amt]);
116
117            let state_map = match parse_state(&received) {
118                Some(map) => map,
119                None => continue,
120            };
121
122            let mut value = response_receiver.lock().unwrap();
123            *value = state_map;
124        }
125    });
126}
127
128impl Drone {
129    /// Instantiate a new Drone object
130    pub fn new() -> Self {
131        let socket = UdpSocket::bind("0.0.0.0:8889")
132            .expect(format!("couldn't bind to address {}", TELLO_ADDR).as_str());
133
134        socket
135            .set_read_timeout(Some(Duration::from_secs(RESPONSE_TIMEOUT)))
136            .expect("set_read_timeout call failed");
137
138        let socket_recv = socket.try_clone().unwrap(); // Cloned socket to have multiple of same socket
139
140        // Shared response variable protected by mutex
141        let shared_response = Arc::new(Mutex::new(None::<String>));
142        let response_receiver = Arc::clone(&shared_response);
143
144        // Receiver thread, listens for response from drone on command port
145        thread::spawn(move || {
146            let socket = socket_recv;
147            let mut buf = [0; 1024];
148
149            loop {
150                match socket.recv_from(&mut buf) {
151                    Ok((amt, src)) => {
152                        if src.to_string() != TELLO_ADDR {
153                            println!("{}", src.to_string());
154                            continue;
155                        }
156                        let received = String::from_utf8_lossy(&buf[..amt]);
157                        if let Ok(mut value) = response_receiver.lock() {
158                            *value = Some(received.to_string()); // Save value to shared variable
159                        }
160                    }
161                    Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
162                        // Ignore the WouldBlock error and continue the loop
163                        continue;
164                    }
165                    Err(e) => {
166                        eprintln!("Error receiving message: {:?}", e);
167                        break;
168                    }
169                }
170            }
171        });
172
173        Drone {
174            socket,
175            is_flying: false,
176            stream_on: false,
177            retry_count: 3,
178            last_command_time: Instant::now(),
179            shared_response,
180            shared_state: Arc::new(Mutex::new(HashMap::new())),
181            read_state: HashMap::new(),
182        }
183    }
184}
185
186/**
187 * Private command methods for API
188 */
189impl Drone {
190    /// Send a command without waiting for a return\n
191    /// Private Method called by library
192    fn send_command_without_return(&self, command: &str) {
193        self.socket
194            .send_to(command.as_bytes(), TELLO_ADDR)
195            .expect("Sending command failed");
196
197        println!("Send Command {}", command);
198    }
199
200    /// Send command and wait for a return\n
201    /// Private method called by library
202    fn send_command_with_return(&mut self, command: &str, timeout: u64) -> Option<String> {
203        let time_since_last_command = Instant::now().duration_since(self.last_command_time);
204        if TIME_BTW_COMMANDS.min(time_since_last_command.as_secs_f64()) != TIME_BTW_COMMANDS {
205            println!(
206                "Command {} executed too soon, waiting {} seconds",
207                command, TIME_BTW_COMMANDS
208            );
209            thread::sleep(Duration::from_secs_f64(TIME_BTW_COMMANDS));
210        }
211
212        let timestamp = Instant::now();
213
214        self.socket
215            .send_to(command.as_bytes(), TELLO_ADDR)
216            .expect("Sending command failed");
217
218        loop {
219            let mut value = self.shared_response.lock().unwrap();
220
221            if !value.is_none() {
222                self.last_command_time = Instant::now();
223                let temp = value.clone();
224                let mut temp = temp.unwrap();
225                temp = String::from(temp.trim_end_matches("\r\n"));
226                *value = None;
227                return Some(temp);
228            }
229
230            if Instant::now().duration_since(timestamp).as_secs() >= timeout {
231                println!("CONFUSED");
232                // Timeout handling
233                let temp = format!(
234                    "Aborting command '{}'. Did not receive a response after {} seconds",
235                    command, timeout
236                );
237                *value = None;
238                return Some(temp);
239            }
240
241            println!("{}", Instant::now().duration_since(timestamp).as_millis());
242        }
243    }
244
245    /// Sends control command to Tello and waits for a response
246    fn send_control_command(&mut self, command: &str, timeout: u64) -> bool {
247        for i in 0..self.retry_count {
248            let response = self
249                .send_command_with_return(command, timeout)
250                .unwrap_or_else(|| String::from("Attempt failed, retrying"));
251
252            if response.to_lowercase().contains("ok") {
253                println!("{}", response);
254                return true;
255            } else {
256                println!("{}", response);
257            }
258
259            println!("tried {} times: {}", i, response);
260        }
261
262        return false;
263    }
264
265    /// Private function to handle sending and parsing read commands
266    fn send_read_command(&mut self, command: &str) -> String {
267        let response = self
268            .send_command_with_return(command, RESPONSE_TIMEOUT)
269            .unwrap();
270
271        let error_words = ["error", "ERROR", "False"];
272        if error_words.iter().any(|&word| response.contains(word)) {
273            debug!("uh oh");
274            error!("ruh roh");
275        }
276
277        return response;
278    }
279
280    /// Send command to tello and wait for response, parses response into an integer
281    fn send_read_command_int(&mut self, command: &str) -> i32 {
282        self.send_read_command(command).parse::<i32>().unwrap()
283    }
284
285    /// Send command to tello and wait for response, parses response into float
286    fn send_read_command_float(&mut self, command: &str) -> f64 {
287        self.send_read_command(command).parse::<f64>().unwrap()
288    }
289}
290
291// State field methods
292impl Drone {
293    /// Get a specific state field by name
294    fn get_state_field(&mut self, key: &str) -> &StateValue {
295        // This may be sacreligious
296        // Clones the value of the r/w state variable then the lock is released
297        self.read_state = self.shared_state.lock().unwrap().clone();
298
299        // Checks whether our new state has the value sought after
300        if !self.read_state.contains_key(&key.to_string()) {
301            error!("Could not get state property: {}", key);
302        }
303        // Returns a reference to this value stored in read state
304        self.read_state.get(&key.to_string()).unwrap()
305    }
306
307    /// Returns pitch in degree
308    pub fn get_pitch(&mut self) -> i32 {
309        match self.get_state_field("pitch") {
310            StateValue::Int(i) => *i,
311            _ => panic!("'pitch' state returned the incorrect Type"),
312        }
313    }
314
315    /// Returns roll in degree
316    pub fn get_roll(&mut self) -> i32 {
317        match self.get_state_field("roll") {
318            StateValue::Int(i) => *i,
319            _ => panic!("'roll' state returned the incorrect Type"),
320        }
321    }
322
323    /// Returns yaw in degree
324    pub fn get_yaw(&mut self) -> i32 {
325        match self.get_state_field("yaw") {
326            StateValue::Int(i) => *i,
327            _ => panic!("'yaw' state returned the incorrect Type"),
328        }
329    }
330
331    /// X axis speed
332    pub fn get_speed_x(&mut self) -> i32 {
333        match self.get_state_field("vgx") {
334            StateValue::Int(i) => *i,
335            _ => panic!("'soeed_x' state returned the incorrect Type"),
336        }
337    }
338
339    /// Y axis speed
340    pub fn get_speed_y(&mut self) -> i32 {
341        match self.get_state_field("vgy") {
342            StateValue::Int(i) => *i,
343            _ => panic!("'speed_y' state returned the incorrect Type"),
344        }
345    }
346
347    /// Z axis speed
348    pub fn get_speed_z(&mut self) -> i32 {
349        match self.get_state_field("vgz") {
350            StateValue::Int(i) => *i,
351            _ => panic!("'speed_z' state returned the incorrect Type"),
352        }
353    }
354
355    /// X axis acceleration
356    pub fn get_acceleration_x(&mut self) -> f64 {
357        match self.get_state_field("agx") {
358            StateValue::Float(i) => *i,
359            _ => panic!("'accel_x' state returned the incorrect Type"),
360        }
361    }
362
363    /// Y axis acceleration
364    pub fn get_acceleration_y(&mut self) -> f64 {
365        match self.get_state_field("agy") {
366            StateValue::Float(i) => *i,
367            _ => panic!("'accel_y' state returned the incorrect Type"),
368        }
369    }
370
371    /// Z axis acceleration
372    pub fn get_acceleration_z(&mut self) -> f64 {
373        match self.get_state_field("agz") {
374            StateValue::Float(i) => *i,
375            _ => panic!("'accel_z' state returned the incorrect Type"),
376        }
377    }
378
379    /// Get lowest temperature
380    pub fn get_lowest_temperature(&mut self) -> i32 {
381        match self.get_state_field("templ") {
382            StateValue::Int(i) => *i,
383            _ => panic!("'lowest_temperature' state returned the incorrect Type"),
384        }
385    }
386
387    /// Get highest temperature
388    pub fn get_highest_temperature(&mut self) -> i32 {
389        match self.get_state_field("temph") {
390            StateValue::Int(i) => *i,
391            _ => panic!("'highest_temperature' state returned the incorrect Type"),
392        }
393    }
394
395    /// Get average temperature
396    pub fn get_temperature(&mut self) -> i32 {
397        let templ = self.get_lowest_temperature();
398        let temph = self.get_highest_temperature();
399
400        templ + temph
401    }
402
403    /// Get current height in cm
404    pub fn get_height(&mut self) -> i32 {
405        match self.get_state_field("h") {
406            StateValue::Int(i) => *i,
407            _ => panic!("'height' state returned the incorrect Type"),
408        }
409    }
410
411    /// Get current distance value from TOF in cm
412    pub fn get_distance_tof(&mut self) -> i32 {
413        match self.get_state_field("tof") {
414            StateValue::Int(i) => *i,
415            _ => panic!("'distance_tof' state returned the incorrect Type"),
416        }
417    }
418
419    /// Get current barometer measurement in cm -> absolute height
420    pub fn get_barometer(&mut self) -> f64 {
421        match self.get_state_field("baro") {
422            StateValue::Float(i) => *i * 100.0,
423            _ => panic!("'baro' state returned the incorrect Type"),
424        }
425    }
426
427    /// Get the time the motors have been active in seconds
428    pub fn get_flight_time(&mut self) -> i32 {
429        match self.get_state_field("time") {
430            StateValue::Int(i) => *i,
431            _ => panic!("'time' state returned the incorrect Type"),
432        }
433    }
434
435    pub fn get_battery(&mut self) -> i32 {
436        match self.get_state_field("bat") {
437            StateValue::Int(i) => *i,
438            _ => panic!("'bat' state returned the incorrect Type"),
439        }
440    }
441}
442
443// Public user command methods
444impl Drone {
445    pub fn connect(&mut self) {
446        // Shared state response variable protected by mutex
447        let state_response = Arc::new(Mutex::new(HashMap::new()));
448        let state_receiver = Arc::clone(&state_response);
449        start_state_receiver_thread(state_receiver);
450        self.send_control_command("command", RESPONSE_TIMEOUT);
451
452        let reps = 20;
453        for _ in 0..reps {
454            // Make a method to clone shared state into read state?
455            {
456                self.read_state = self.shared_state.lock().unwrap().clone();
457            }
458
459            if !self.read_state.is_empty() {
460                // let t = i / reps;
461                println!("trying");
462                // Debug message
463                break;
464            }
465
466            thread::sleep(Duration::from_secs_f64(1.0 / reps as f64));
467        }
468
469        if self.read_state.is_empty() {
470            // raise error
471        }
472    }
473
474    // Send a keepalive packet to keep the drone from landing after 15s
475    pub fn send_keepalive(&mut self) {
476        self.send_control_command("keepalive", RESPONSE_TIMEOUT);
477    }
478
479    pub fn turn_motor_on(&mut self) {
480        self.send_control_command("motoron", RESPONSE_TIMEOUT);
481    }
482
483    pub fn turn_motor_off(&mut self) {
484        self.send_control_command("motoroff", RESPONSE_TIMEOUT);
485    }
486
487    pub fn initiate_throw_takeoff(&mut self) {
488        self.send_control_command("throwfly", RESPONSE_TIMEOUT);
489    }
490
491    pub fn takeoff(&mut self) {
492        self.send_control_command("takeoff", TAKEOFF_TIMEOUT);
493        self.is_flying = true;
494    }
495
496    pub fn land(&mut self) {
497        self.send_control_command("land", RESPONSE_TIMEOUT);
498        self.is_flying = false;
499    }
500
501    // Turn on video streaming
502    pub fn streamon(&mut self) {
503        // UDP port magic need to implement
504        self.send_control_command("streamon", RESPONSE_TIMEOUT);
505        self.stream_on = true;
506    }
507
508    // Turn off video streaming
509    pub fn streamoff(&mut self) {
510        self.send_control_command("streamoff", RESPONSE_TIMEOUT);
511        self.stream_on = false;
512
513        // NEED TO IMPLEMENT BACKGROUND FRAME READ
514        // if self.background_frame_read != None {
515        //     self.background_frame_read.stop();
516        //     self.background_frame_read = None;
517        // }
518    }
519
520    // Stop all motors immediately
521    pub fn emergency(&mut self) {
522        self.send_command_without_return("emergency");
523        self.is_flying = false;
524    }
525
526    /// Move in any chosen direction\n
527    /// direction: up, down, left, right, forward or back\n
528    /// x: 20-500
529    pub fn move_any(&mut self, direction: &str, x: i32) {
530        self.send_control_command(format!("{} {}", direction, x).as_str(), RESPONSE_TIMEOUT);
531    }
532
533    /// Move up
534    /// x: 20-500 in cm
535    pub fn move_up(&mut self, x: i32) {
536        self.move_any("up", x);
537    }
538
539    /// Move down
540    /// x: 20-500 in cm
541    pub fn move_down(&mut self, x: i32) {
542        self.move_any("down", x);
543    }
544
545    /// Move left
546    /// x: 20-500 in cm
547    pub fn move_left(&mut self, x: i32) {
548        self.move_any("left", x);
549    }
550
551    /// Move right
552    /// x: 20-500 in cm
553    pub fn move_right(&mut self, x: i32) {
554        self.move_any("right", x);
555    }
556
557    /// Move forward
558    /// x: 20-500 in cm
559    pub fn move_forward(&mut self, x: i32) {
560        self.move_any("forward", x);
561    }
562
563    /// Move back
564    /// x: 20-500 in cm
565    pub fn move_back(&mut self, x: i32) {
566        self.move_any("back", x);
567    }
568
569    /// Rotate x degree clockwise
570    pub fn rotate_clockwise(&mut self, x: i32) {
571        self.send_control_command(&format!("cw {}", x), RESPONSE_TIMEOUT);
572    }
573
574    /// Rotate x degree counter-clockwise
575    pub fn rotate_counter_clockwise(&mut self, x: i32) {
576        self.send_control_command(&format!("ccw {}", x), RESPONSE_TIMEOUT);
577    }
578
579    /// Do a flip maneuver, helper for flip_x functions
580    pub fn flip(&mut self, direction: &str) {
581        self.send_control_command(&format!("flip {}", direction), RESPONSE_TIMEOUT);
582    }
583
584    /// Flip left
585    pub fn flip_left(&mut self) {
586        self.flip("l");
587    }
588
589    /// Flip right
590    pub fn flip_right(&mut self) {
591        self.flip("r");
592    }
593
594    /// Flip forward
595    pub fn flip_forward(&mut self) {
596        self.flip("f");
597    }
598
599    /// Flip backwards
600    pub fn flip_back(&mut self) {
601        self.flip("b");
602    }
603
604    /// Fly to xyz relative to current position\n
605    /// Speed in cm/s\n
606    /// Argument ranges:\n
607    /// x: -500 - 500\n
608    /// y: -500 - 500\n
609    /// z: -500 - 500\n
610    /// speed 10 - 100
611    pub fn go_xyz_speed(&mut self, x: i32, y: i32, z: i32, speed: i32) {
612        let cmd = format!("go {} {} {} {}", x, y, z, speed);
613        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
614    }
615
616    /// Fly to x2 y2 z2 in a curve via x1 y1 z1, speed is travelling speed in cm/s\n
617    /// Both points relative to current position\n
618    /// Argument value ranges:\n
619    /// x1: -500 - 500\n
620    /// y1: -500 - 500\n
621    /// z1: -500 - 500\n
622    /// x2: -500 - 500\n
623    /// y2: -500 - 500\n
624    /// z2: -500 - 500\n
625    /// speed: 10 - 60
626    pub fn curve_xyz_speed(
627        &mut self,
628        x1: i32,
629        y1: i32,
630        z1: i32,
631        x2: i32,
632        y2: i32,
633        z2: i32,
634        speed: i32,
635    ) {
636        let cmd = format!("curve {} {} {} {} {} {} {}", x1, y1, z1, x2, y2, z2, speed);
637        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
638    }
639
640    /// Set speed in cm/s
641    pub fn set_speed(&mut self, speed: i32) {
642        let cmd = format!("speed {}", speed);
643        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
644    }
645
646    /// Send RC control via four channels. Command is sent every self.TIME_BTW_RC_CONTROL_COMMANDS seconds.
647    /// Argument parameters:
648    /// left_right_velocity: -100~100 (left/right)
649    /// forward_backward_velocity: -100~100 (forward/backward)
650    /// up_down_velocity: -100~100 (up/down)
651    /// yaw_velocity: -100~100 (yaw)
652    pub fn send_rc_control(
653        &mut self,
654        left_right_velocity: i32,
655        forward_backward_velocity: i32,
656        up_down_velocity: i32,
657        yaw_velocity: i32,
658    ) {
659        let clamp100 = |x: i32| -> i32 { x.max(-100).min(100) };
660
661        if Instant::now()
662            .duration_since(self.last_command_time)
663            .as_secs_f64()
664            > TIME_BTW_COMMANDS
665        {
666            self.last_command_time = Instant::now();
667            let cmd = format!(
668                "rc {} {} {} {}",
669                clamp100(left_right_velocity),
670                clamp100(forward_backward_velocity),
671                clamp100(up_down_velocity),
672                clamp100(yaw_velocity)
673            );
674            self.send_command_without_return(&cmd);
675        }
676    }
677
678    /// Set the WiFi SSID and Password for the Tello, will cause a reboot
679    pub fn set_wifi_credentials(&mut self, ssid: &str, password: &str) {
680        let cmd = format!("wifi {} {}", ssid, password);
681        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
682    }
683
684    /// Only works on Tello-EDU
685    /// Connects to a WiFi with the SSID and Password
686    pub fn connect_to_wifi(&mut self, ssid: &str, password: &str) {
687        let cmd = format!("ap {} {}", ssid, password);
688        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
689    }
690
691    /// Can change the ports of the Tello drone's state and video packets
692    /// Not supported in this library
693    pub fn set_network_ports(&mut self, state_packet_port: i32, video_stream_port: i32) {
694        let cmd = format!("port {} {}", state_packet_port, video_stream_port);
695        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
696    }
697
698    /// Reboots the drone
699    pub fn reboot(&mut self) {
700        self.send_command_without_return("reboot");
701    }
702
703    /// Sets the bitrate of the video stream\n
704    /// Only supports MBPS as i32 in the range of [0, 5] where 0 is auto
705    pub fn set_video_bitrate(&mut self, bitrate: i32) {
706        let cmd = format!("setbitrate {}", bitrate);
707        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
708    }
709
710    /// Sets the resolution of the video stream\n
711    /// 'low' for 480P\n
712    /// 'high' for 720P\n
713    pub fn set_video_resolution(&mut self, resolution: &str) {
714        let cmd = format!("setresolution {}", resolution);
715        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
716    }
717
718    /// Set the frames per second of the video stream\n
719    /// 'low' for 5 fps\n
720    /// 'middle' for 15 fps\n
721    /// 'high' for 30 fps\n
722    pub fn set_video_fps(&mut self, fps: &str) {
723        let cmd = format!("setfps {}", fps);
724        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
725    }
726
727    /// Selects one of 2 cameras for streaming\n
728    /// Forward camera is the regular 1080x720 colour camera\n
729    /// Downward camera is a grey-only 320x240 IR-sensitive camera\n
730    pub fn set_video_direction(&mut self, direction: i32) {
731        let cmd = format!("downvision {}", direction);
732        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
733    }
734}
735
736// MISSION PAD METHODS
737impl Drone {
738    /// Fly to xyz relative to mission pad with id: mid\n
739    /// Speed in cm/s\n
740    /// Argument value ranges:\n
741    /// x: -500 - 500\n
742    /// y: -500 - 500\n
743    /// z: -500 - 500\n
744    /// mid: 1 - 8
745    pub fn go_xyz_speed_mid(&mut self, x: i32, y: i32, z: i32, mid: i32) {
746        let cmd = format!("go {} {} {} m{}", x, y, z, mid);
747        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
748    }
749
750    /// Fly to x2 y2 z2 in a curve via x1 y1 z1, speed is travelling speed in cm/s\n
751    /// Both points relative to mission pad with id: mid\n
752    /// Argument value ranges:\n
753    /// x1: -500 - 500\n
754    /// y1: -500 - 500\n
755    /// z1: -500 - 500\n
756    /// x2: -500 - 500\n
757    /// y2: -500 - 500\n
758    /// z2: -500 - 500\n
759    /// speed: 10 - 60
760    pub fn curve_xyz_speed_mid(
761        &mut self,
762        x1: i32,
763        y1: i32,
764        z1: i32,
765        x2: i32,
766        y2: i32,
767        z2: i32,
768        speed: i32,
769        mid: i32,
770    ) {
771        let cmd = format!(
772            "curve {} {} {} {} {} {} {} m{}",
773            x1, y1, z1, x2, y2, z2, speed, mid
774        );
775        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
776    }
777
778    pub fn enable_mission_pads(&mut self) {
779        self.send_control_command("mon", RESPONSE_TIMEOUT);
780    }
781
782    pub fn disable_mission_pads(&mut self) {
783        self.send_control_command("moff", RESPONSE_TIMEOUT);
784    }
785
786    pub fn set_mission_pad_detection_direction(&mut self, direction: i32) {
787        let cmd = format!("mdirection {}", direction);
788        self.send_control_command(&cmd, RESPONSE_TIMEOUT);
789    }
790}
791
792// QUERY COMMANDS, USUALLY SLOWER THAN GETTERS
793impl Drone {
794    pub fn query_speed(&mut self) -> i32 {
795        self.send_read_command_int("speed?")
796    }
797
798    pub fn query_battery(&mut self) -> i32 {
799        self.send_read_command_int("battery?")
800    }
801
802    pub fn query_flight_time(&mut self) -> i32 {
803        self.send_read_command_int("time?")
804    }
805
806    pub fn query_height(&mut self) -> i32 {
807        self.send_read_command_int("height?")
808    }
809
810    pub fn query_temperature(&mut self) -> i32 {
811        self.send_read_command_int("temp?")
812    }
813
814    /// Query IMU attitude data\n
815    /// Using get_pitch, get_roll and get_yaw is usually faster\n
816    /// Returns Map with {'pitch': i32, 'roll': int, 'yaw': i32}
817    pub fn query_attitude(&mut self) -> HashMap<String, i32> {
818        let response = self.send_read_command("attitude?");
819
820        let mut attitude: HashMap<String, i32> = HashMap::new();
821
822        for field in response.split(';') {
823            let split: Vec<&str> = field.split(':').collect();
824
825            if split.len() < 2 {
826                continue;
827            }
828
829            let key = split[0].to_string();
830            let value_str = split[1];
831            let value: StateValue = match state_field_converter(&key, value_str) {
832                Ok(v) => v,
833                Err(e) => {
834                    debug!(
835                        "Error parsing state value for {}: {} to {}",
836                        key, value_str, e
837                    );
838                    error!("{}", e);
839                    continue;
840                }
841            };
842
843            let value_i32 = match value {
844                StateValue::Int(i) => i,
845                _ => panic!("'bat' state returned the incorrect Type"),
846            };
847
848            attitude.insert(key, value_i32);
849        }
850
851        return attitude;
852    }
853
854    pub fn query_barometer(&mut self) -> f64 {
855        self.send_read_command_float("baro?") * 100.0
856    }
857
858    pub fn query_distance_tof(&mut self) -> f64 {
859        let tof = self.send_read_command("tof?");
860        tof.trim_end_matches("mm").parse::<f64>().unwrap_or(0.0) / 10.0
861    }
862
863    pub fn query_wifi_signal_noise_ratio(&mut self) -> String {
864        self.send_read_command("wifi?")
865    }
866
867    pub fn query_sdk_version(&mut self) -> String {
868        self.send_read_command("sdk?")
869    }
870
871    pub fn query_serial_number(&mut self) -> String {
872        self.send_read_command("sn?")
873    }
874
875    pub fn query_active(&mut self) -> String {
876        self.send_read_command("active?")
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883
884    #[test]
885    fn system_time_test() {
886        let socket = UdpSocket::bind(TELLO_ADDR).expect("couldn't bind to address");
887        let shared_response = Arc::new(Mutex::new(None::<String>));
888
889        let state_response = Arc::new(Mutex::new(HashMap::new()));
890        let shared_state = Arc::clone(&state_response);
891
892        let mut d = Drone {
893            socket,
894            is_flying: false,
895            stream_on: false,
896            retry_count: 3,
897            last_command_time: Instant::now(),
898            shared_response,
899            shared_state,
900            read_state: HashMap::new(),
901        };
902
903        d.send_command_with_return("a", 6);
904    }
905}