use log::{debug, error};
use std::{
collections::HashMap,
net::UdpSocket,
sync::{Arc, Mutex},
thread,
time::{Duration, Instant},
};
const TELLO_ADDR: &'static str = "192.168.10.1:8889";
const TELLO_STATE_ADDR: &'static str = "0.0.0.0:8890";
const RESPONSE_TIMEOUT: u64 = 7; const TAKEOFF_TIMEOUT: u64 = 20; const TIME_BTW_COMMANDS: f64 = 0.1;
const INT_STATE_FIELDS: &[&str] = &[
"mid", "x", "y", "z", "pitch", "roll", "yaw", "vgx", "vgy", "vgz", "templ", "temph", "tof",
"h", "bat", "time",
];
const FLOAT_STATE_FIELDS: &[&str] = &["baro", "agx", "agy", "agz"];
pub struct Drone {
socket: UdpSocket,
is_flying: bool,
stream_on: bool,
retry_count: i32,
last_command_time: Instant,
shared_response: Arc<Mutex<Option<String>>>,
shared_state: Arc<Mutex<HashMap<String, StateValue>>>, read_state: HashMap<String, StateValue>,
}
#[derive(Clone)]
#[allow(dead_code)]
enum StateValue {
Int(i32),
Float(f64),
Str(String),
}
fn parse_state(state_str: &str) -> Option<HashMap<String, StateValue>> {
let state_str = state_str.trim();
if state_str.eq("ok") {
return None;
}
let mut state_map: HashMap<String, StateValue> = HashMap::new();
for field in state_str.split(';') {
let split: Vec<&str> = field.split(':').collect();
if split.len() < 2 {
continue;
}
let key = split[0].to_string();
let value_str = split[1];
let value: StateValue = match state_field_converter(&key, value_str) {
Ok(v) => v,
Err(e) => {
debug!(
"Error parsing state value for {}: {} to {}",
key, value_str, e
);
error!("{}", e);
continue;
}
};
state_map.insert(key, value);
}
return Some(state_map);
}
fn state_field_converter(key: &str, value_str: &str) -> Result<StateValue, String> {
if INT_STATE_FIELDS.contains(&key) {
value_str
.parse::<i32>()
.map(StateValue::Int)
.map_err(|e| e.to_string())
} else if FLOAT_STATE_FIELDS.contains(&key) {
value_str
.parse::<f64>()
.map(StateValue::Float)
.map_err(|e| e.to_string())
} else {
Ok(StateValue::Str(value_str.to_string()))
}
}
fn start_state_receiver_thread(response_receiver: Arc<Mutex<HashMap<String, StateValue>>>) {
thread::spawn(move || {
let socket = UdpSocket::bind(TELLO_STATE_ADDR).expect("Couldn't bind receiver socket");
let mut buf = [0; 1024];
loop {
let (amt, _src) = socket.recv_from(&mut buf).expect("Didn't receive message");
let received = String::from_utf8_lossy(&buf[..amt]);
let state_map = match parse_state(&received) {
Some(map) => map,
None => continue,
};
let mut value = response_receiver.lock().unwrap();
*value = state_map;
}
});
}
impl Drone {
pub fn new() -> Self {
let socket = UdpSocket::bind("0.0.0.0:8889")
.expect(format!("couldn't bind to address {}", TELLO_ADDR).as_str());
socket
.set_read_timeout(Some(Duration::from_secs(RESPONSE_TIMEOUT)))
.expect("set_read_timeout call failed");
let socket_recv = socket.try_clone().unwrap();
let shared_response = Arc::new(Mutex::new(None::<String>));
let response_receiver = Arc::clone(&shared_response);
thread::spawn(move || {
let socket = socket_recv;
let mut buf = [0; 1024];
loop {
match socket.recv_from(&mut buf) {
Ok((amt, src)) => {
if src.to_string() != TELLO_ADDR {
println!("{}", src.to_string());
continue;
}
let received = String::from_utf8_lossy(&buf[..amt]);
if let Ok(mut value) = response_receiver.lock() {
*value = Some(received.to_string()); }
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
eprintln!("Error receiving message: {:?}", e);
break;
}
}
}
});
Drone {
socket,
is_flying: false,
stream_on: false,
retry_count: 3,
last_command_time: Instant::now(),
shared_response,
shared_state: Arc::new(Mutex::new(HashMap::new())),
read_state: HashMap::new(),
}
}
}
impl Drone {
fn send_command_without_return(&self, command: &str) {
self.socket
.send_to(command.as_bytes(), TELLO_ADDR)
.expect("Sending command failed");
println!("Send Command {}", command);
}
fn send_command_with_return(&mut self, command: &str, timeout: u64) -> Option<String> {
let time_since_last_command = Instant::now().duration_since(self.last_command_time);
if TIME_BTW_COMMANDS.min(time_since_last_command.as_secs_f64()) != TIME_BTW_COMMANDS {
println!(
"Command {} executed too soon, waiting {} seconds",
command, TIME_BTW_COMMANDS
);
thread::sleep(Duration::from_secs_f64(TIME_BTW_COMMANDS));
}
let timestamp = Instant::now();
self.socket
.send_to(command.as_bytes(), TELLO_ADDR)
.expect("Sending command failed");
loop {
let mut value = self.shared_response.lock().unwrap();
if !value.is_none() {
self.last_command_time = Instant::now();
let temp = value.clone();
let mut temp = temp.unwrap();
temp = String::from(temp.trim_end_matches("\r\n"));
*value = None;
return Some(temp);
}
if Instant::now().duration_since(timestamp).as_secs() >= timeout {
println!("CONFUSED");
let temp = format!(
"Aborting command '{}'. Did not receive a response after {} seconds",
command, timeout
);
*value = None;
return Some(temp);
}
println!("{}", Instant::now().duration_since(timestamp).as_millis());
}
}
fn send_control_command(&mut self, command: &str, timeout: u64) -> bool {
for i in 0..self.retry_count {
let response = self
.send_command_with_return(command, timeout)
.unwrap_or_else(|| String::from("Attempt failed, retrying"));
if response.to_lowercase().contains("ok") {
println!("{}", response);
return true;
} else {
println!("{}", response);
}
println!("tried {} times: {}", i, response);
}
return false;
}
fn send_read_command(&mut self, command: &str) -> String {
let response = self
.send_command_with_return(command, RESPONSE_TIMEOUT)
.unwrap();
let error_words = ["error", "ERROR", "False"];
if error_words.iter().any(|&word| response.contains(word)) {
debug!("uh oh");
error!("ruh roh");
}
return response;
}
fn send_read_command_int(&mut self, command: &str) -> i32 {
self.send_read_command(command).parse::<i32>().unwrap()
}
fn send_read_command_float(&mut self, command: &str) -> f64 {
self.send_read_command(command).parse::<f64>().unwrap()
}
}
impl Drone {
fn get_state_field(&mut self, key: &str) -> &StateValue {
self.read_state = self.shared_state.lock().unwrap().clone();
if !self.read_state.contains_key(&key.to_string()) {
error!("Could not get state property: {}", key);
}
self.read_state.get(&key.to_string()).unwrap()
}
pub fn get_pitch(&mut self) -> i32 {
match self.get_state_field("pitch") {
StateValue::Int(i) => *i,
_ => panic!("'pitch' state returned the incorrect Type"),
}
}
pub fn get_roll(&mut self) -> i32 {
match self.get_state_field("roll") {
StateValue::Int(i) => *i,
_ => panic!("'roll' state returned the incorrect Type"),
}
}
pub fn get_yaw(&mut self) -> i32 {
match self.get_state_field("yaw") {
StateValue::Int(i) => *i,
_ => panic!("'yaw' state returned the incorrect Type"),
}
}
pub fn get_speed_x(&mut self) -> i32 {
match self.get_state_field("vgx") {
StateValue::Int(i) => *i,
_ => panic!("'soeed_x' state returned the incorrect Type"),
}
}
pub fn get_speed_y(&mut self) -> i32 {
match self.get_state_field("vgy") {
StateValue::Int(i) => *i,
_ => panic!("'speed_y' state returned the incorrect Type"),
}
}
pub fn get_speed_z(&mut self) -> i32 {
match self.get_state_field("vgz") {
StateValue::Int(i) => *i,
_ => panic!("'speed_z' state returned the incorrect Type"),
}
}
pub fn get_acceleration_x(&mut self) -> f64 {
match self.get_state_field("agx") {
StateValue::Float(i) => *i,
_ => panic!("'accel_x' state returned the incorrect Type"),
}
}
pub fn get_acceleration_y(&mut self) -> f64 {
match self.get_state_field("agy") {
StateValue::Float(i) => *i,
_ => panic!("'accel_y' state returned the incorrect Type"),
}
}
pub fn get_acceleration_z(&mut self) -> f64 {
match self.get_state_field("agz") {
StateValue::Float(i) => *i,
_ => panic!("'accel_z' state returned the incorrect Type"),
}
}
pub fn get_lowest_temperature(&mut self) -> i32 {
match self.get_state_field("templ") {
StateValue::Int(i) => *i,
_ => panic!("'lowest_temperature' state returned the incorrect Type"),
}
}
pub fn get_highest_temperature(&mut self) -> i32 {
match self.get_state_field("temph") {
StateValue::Int(i) => *i,
_ => panic!("'highest_temperature' state returned the incorrect Type"),
}
}
pub fn get_temperature(&mut self) -> i32 {
let templ = self.get_lowest_temperature();
let temph = self.get_highest_temperature();
templ + temph
}
pub fn get_height(&mut self) -> i32 {
match self.get_state_field("h") {
StateValue::Int(i) => *i,
_ => panic!("'height' state returned the incorrect Type"),
}
}
pub fn get_distance_tof(&mut self) -> i32 {
match self.get_state_field("tof") {
StateValue::Int(i) => *i,
_ => panic!("'distance_tof' state returned the incorrect Type"),
}
}
pub fn get_barometer(&mut self) -> f64 {
match self.get_state_field("baro") {
StateValue::Float(i) => *i * 100.0,
_ => panic!("'baro' state returned the incorrect Type"),
}
}
pub fn get_flight_time(&mut self) -> i32 {
match self.get_state_field("time") {
StateValue::Int(i) => *i,
_ => panic!("'time' state returned the incorrect Type"),
}
}
pub fn get_battery(&mut self) -> i32 {
match self.get_state_field("bat") {
StateValue::Int(i) => *i,
_ => panic!("'bat' state returned the incorrect Type"),
}
}
}
impl Drone {
pub fn connect(&mut self) {
let state_response = Arc::new(Mutex::new(HashMap::new()));
let state_receiver = Arc::clone(&state_response);
start_state_receiver_thread(state_receiver);
self.send_control_command("command", RESPONSE_TIMEOUT);
let reps = 20;
for _ in 0..reps {
{
self.read_state = self.shared_state.lock().unwrap().clone();
}
if !self.read_state.is_empty() {
println!("trying");
break;
}
thread::sleep(Duration::from_secs_f64(1.0 / reps as f64));
}
if self.read_state.is_empty() {
}
}
pub fn send_keepalive(&mut self) {
self.send_control_command("keepalive", RESPONSE_TIMEOUT);
}
pub fn turn_motor_on(&mut self) {
self.send_control_command("motoron", RESPONSE_TIMEOUT);
}
pub fn turn_motor_off(&mut self) {
self.send_control_command("motoroff", RESPONSE_TIMEOUT);
}
pub fn initiate_throw_takeoff(&mut self) {
self.send_control_command("throwfly", RESPONSE_TIMEOUT);
}
pub fn takeoff(&mut self) {
self.send_control_command("takeoff", TAKEOFF_TIMEOUT);
self.is_flying = true;
}
pub fn land(&mut self) {
self.send_control_command("land", RESPONSE_TIMEOUT);
self.is_flying = false;
}
pub fn streamon(&mut self) {
self.send_control_command("streamon", RESPONSE_TIMEOUT);
self.stream_on = true;
}
pub fn streamoff(&mut self) {
self.send_control_command("streamoff", RESPONSE_TIMEOUT);
self.stream_on = false;
}
pub fn emergency(&mut self) {
self.send_command_without_return("emergency");
self.is_flying = false;
}
pub fn move_any(&mut self, direction: &str, x: i32) {
self.send_control_command(format!("{} {}", direction, x).as_str(), RESPONSE_TIMEOUT);
}
pub fn move_up(&mut self, x: i32) {
self.move_any("up", x);
}
pub fn move_down(&mut self, x: i32) {
self.move_any("down", x);
}
pub fn move_left(&mut self, x: i32) {
self.move_any("left", x);
}
pub fn move_right(&mut self, x: i32) {
self.move_any("right", x);
}
pub fn move_forward(&mut self, x: i32) {
self.move_any("forward", x);
}
pub fn move_back(&mut self, x: i32) {
self.move_any("back", x);
}
pub fn rotate_clockwise(&mut self, x: i32) {
self.send_control_command(&format!("cw {}", x), RESPONSE_TIMEOUT);
}
pub fn rotate_counter_clockwise(&mut self, x: i32) {
self.send_control_command(&format!("ccw {}", x), RESPONSE_TIMEOUT);
}
pub fn flip(&mut self, direction: &str) {
self.send_control_command(&format!("flip {}", direction), RESPONSE_TIMEOUT);
}
pub fn flip_left(&mut self) {
self.flip("l");
}
pub fn flip_right(&mut self) {
self.flip("r");
}
pub fn flip_forward(&mut self) {
self.flip("f");
}
pub fn flip_back(&mut self) {
self.flip("b");
}
pub fn go_xyz_speed(&mut self, x: i32, y: i32, z: i32, speed: i32) {
let cmd = format!("go {} {} {} {}", x, y, z, speed);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn curve_xyz_speed(
&mut self,
x1: i32,
y1: i32,
z1: i32,
x2: i32,
y2: i32,
z2: i32,
speed: i32,
) {
let cmd = format!("curve {} {} {} {} {} {} {}", x1, y1, z1, x2, y2, z2, speed);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn set_speed(&mut self, speed: i32) {
let cmd = format!("speed {}", speed);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn send_rc_control(
&mut self,
left_right_velocity: i32,
forward_backward_velocity: i32,
up_down_velocity: i32,
yaw_velocity: i32,
) {
let clamp100 = |x: i32| -> i32 { x.max(-100).min(100) };
if Instant::now()
.duration_since(self.last_command_time)
.as_secs_f64()
> TIME_BTW_COMMANDS
{
self.last_command_time = Instant::now();
let cmd = format!(
"rc {} {} {} {}",
clamp100(left_right_velocity),
clamp100(forward_backward_velocity),
clamp100(up_down_velocity),
clamp100(yaw_velocity)
);
self.send_command_without_return(&cmd);
}
}
pub fn set_wifi_credentials(&mut self, ssid: &str, password: &str) {
let cmd = format!("wifi {} {}", ssid, password);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn connect_to_wifi(&mut self, ssid: &str, password: &str) {
let cmd = format!("ap {} {}", ssid, password);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn set_network_ports(&mut self, state_packet_port: i32, video_stream_port: i32) {
let cmd = format!("port {} {}", state_packet_port, video_stream_port);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn reboot(&mut self) {
self.send_command_without_return("reboot");
}
pub fn set_video_bitrate(&mut self, bitrate: i32) {
let cmd = format!("setbitrate {}", bitrate);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn set_video_resolution(&mut self, resolution: &str) {
let cmd = format!("setresolution {}", resolution);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn set_video_fps(&mut self, fps: &str) {
let cmd = format!("setfps {}", fps);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn set_video_direction(&mut self, direction: i32) {
let cmd = format!("downvision {}", direction);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
}
impl Drone {
pub fn go_xyz_speed_mid(&mut self, x: i32, y: i32, z: i32, mid: i32) {
let cmd = format!("go {} {} {} m{}", x, y, z, mid);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn curve_xyz_speed_mid(
&mut self,
x1: i32,
y1: i32,
z1: i32,
x2: i32,
y2: i32,
z2: i32,
speed: i32,
mid: i32,
) {
let cmd = format!(
"curve {} {} {} {} {} {} {} m{}",
x1, y1, z1, x2, y2, z2, speed, mid
);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
pub fn enable_mission_pads(&mut self) {
self.send_control_command("mon", RESPONSE_TIMEOUT);
}
pub fn disable_mission_pads(&mut self) {
self.send_control_command("moff", RESPONSE_TIMEOUT);
}
pub fn set_mission_pad_detection_direction(&mut self, direction: i32) {
let cmd = format!("mdirection {}", direction);
self.send_control_command(&cmd, RESPONSE_TIMEOUT);
}
}
impl Drone {
pub fn query_speed(&mut self) -> i32 {
self.send_read_command_int("speed?")
}
pub fn query_battery(&mut self) -> i32 {
self.send_read_command_int("battery?")
}
pub fn query_flight_time(&mut self) -> i32 {
self.send_read_command_int("time?")
}
pub fn query_height(&mut self) -> i32 {
self.send_read_command_int("height?")
}
pub fn query_temperature(&mut self) -> i32 {
self.send_read_command_int("temp?")
}
pub fn query_attitude(&mut self) -> HashMap<String, i32> {
let response = self.send_read_command("attitude?");
let mut attitude: HashMap<String, i32> = HashMap::new();
for field in response.split(';') {
let split: Vec<&str> = field.split(':').collect();
if split.len() < 2 {
continue;
}
let key = split[0].to_string();
let value_str = split[1];
let value: StateValue = match state_field_converter(&key, value_str) {
Ok(v) => v,
Err(e) => {
debug!(
"Error parsing state value for {}: {} to {}",
key, value_str, e
);
error!("{}", e);
continue;
}
};
let value_i32 = match value {
StateValue::Int(i) => i,
_ => panic!("'bat' state returned the incorrect Type"),
};
attitude.insert(key, value_i32);
}
return attitude;
}
pub fn query_barometer(&mut self) -> f64 {
self.send_read_command_float("baro?") * 100.0
}
pub fn query_distance_tof(&mut self) -> f64 {
let tof = self.send_read_command("tof?");
tof.trim_end_matches("mm").parse::<f64>().unwrap_or(0.0) / 10.0
}
pub fn query_wifi_signal_noise_ratio(&mut self) -> String {
self.send_read_command("wifi?")
}
pub fn query_sdk_version(&mut self) -> String {
self.send_read_command("sdk?")
}
pub fn query_serial_number(&mut self) -> String {
self.send_read_command("sn?")
}
pub fn query_active(&mut self) -> String {
self.send_read_command("active?")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_time_test() {
let socket = UdpSocket::bind(TELLO_ADDR).expect("couldn't bind to address");
let shared_response = Arc::new(Mutex::new(None::<String>));
let state_response = Arc::new(Mutex::new(HashMap::new()));
let shared_state = Arc::clone(&state_response);
let mut d = Drone {
socket,
is_flying: false,
stream_on: false,
retry_count: 3,
last_command_time: Instant::now(),
shared_response,
shared_state,
read_state: HashMap::new(),
};
d.send_command_with_return("a", 6);
}
}