remote_control/
remote_control.rs

1//////////////////////////////////////////////////////////////////////////////
2//
3// Remote control usiing a game controller
4//
5// Developed & tested with a bluetooth XBox One controller (highly recommended)
6//
7//  Controls: 
8//  - Start (the one above the right stick): take off
9//  - Left Stick: go forwards/backwards and turn left/right
10//  - Right Stick: up/down and strafe left/right
11//  - D-Pad: flip
12//  - Left Shoulder + Right Shoulder: emergency stop
13//
14// Note that the Tello drone will automatically land if forced down to ~20cm 
15// above a surface
16// 
17//////////////////////////////////////////////////////////////////////////////
18
19extern crate tello_edu;
20
21use sdl2::controller::Axis;
22use sdl2::controller::Button;
23use sdl2::event::Event;
24
25
26use tello_edu::{TelloOptions, Tello, Result, TelloCommandSender, TelloCommand};
27
28
29fn main() {
30    let mut options = TelloOptions::default();
31
32    // we want to send commands...
33    let command_sender = options.with_command();
34
35    // run async Tokio runtime in a thread...
36    std::thread::spawn(move || {
37        let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
38            .enable_all()
39            .build()
40            .unwrap();
41
42        tokio_runtime.block_on(async {
43            fly(options).await.unwrap();
44        });
45    });
46
47    run_control(command_sender).expect("failed to run control");
48}
49
50fn run_control(command_sender: TelloCommandSender) -> anyhow::Result<(),  String> {
51    // This is required for certain controllers to work on Windows without the
52    // video subsystem enabled:
53    sdl2::hint::set("SDL_JOYSTICK_THREAD", "1");
54
55    let sdl_context = sdl2::init()?;
56    let game_controller_subsystem = sdl_context.game_controller()?;
57
58    let num_controllers = game_controller_subsystem
59        .num_joysticks()
60        .map_err(|e| format!("can't enumerate controllers: {}", e))?;
61
62
63    if num_controllers == 0 {
64        return Err("no game controllers found".to_string());
65    }
66
67    // use the first one available
68    let controller = (0..num_controllers)
69        .find_map(|id| {
70            if !game_controller_subsystem.is_game_controller(id) {
71                println!("{} is not a game controller", id);
72                return None;
73            }
74
75            match game_controller_subsystem.open(id) {
76                Ok(c) => {
77                    println!("using controller \"{}\"", c.name());
78                    Some(c)
79                }
80                Err(e) => {
81                    println!("failed to open controller {id}: {e:?}");
82                    None
83                }
84            }
85        })
86        .expect("failed to use any controller");
87
88    // remote control state
89    let mut left_right:i8 = 0;
90    let mut forwards_backwards:i8 = 0;
91    let mut up_down:i8 = 0;
92    let mut yaw:i8 = 0;
93
94    for event in sdl_context.event_pump()?.wait_iter() {
95
96        match event {
97            // both shoulder buttons together to immediately stop motors (and drop like a brick!)
98            Event::ControllerButtonDown { button: Button::LeftShoulder, .. } 
99            | Event::ControllerButtonDown { button: Button::RightShoulder, .. } => {
100                if controller.button(Button::LeftShoulder) && controller.button(Button::RightShoulder) {
101                    command_sender.send(TelloCommand::EmergencyStop)
102                }
103                else {
104                    Ok(())
105                }
106            }
107
108            // start button to take off
109            Event::ControllerButtonUp { button: Button::Start, .. } => {
110                command_sender.send(TelloCommand::TakeOff)
111            }
112
113            // X to land
114            Event::ControllerButtonDown { button: Button::X, .. } => {
115                left_right = 0;
116                forwards_backwards = 0;
117                up_down = 0;
118                yaw = 0;
119                command_sender.send(TelloCommand::Land)
120            }
121
122            // B to stop
123            Event::ControllerButtonDown { button: Button::B, .. } => {
124                left_right = 0;
125                forwards_backwards = 0;
126                up_down = 0;
127                yaw = 0;
128                command_sender.send(TelloCommand::RemoteControl { left_right, forwards_backwards, up_down, yaw} )
129                // command_sender.send(TelloCommand::StopAndHover)
130            }
131
132            // left stick Y to go forwards
133            Event::ControllerAxisMotion { axis: Axis::LeftY, value, .. } => {
134                forwards_backwards = -remote_control_value(value);
135                command_sender.send(TelloCommand::RemoteControl { left_right, forwards_backwards, up_down, yaw} )
136            }            
137
138            // left stick X to turn
139            Event::ControllerAxisMotion { axis: Axis::LeftX, value, .. } => {
140                yaw = remote_control_value(value);
141                command_sender.send(TelloCommand::RemoteControl { left_right, forwards_backwards, up_down, yaw} )
142            }            
143
144            // right stick Y to move vertically
145            Event::ControllerAxisMotion { axis: Axis::RightY, value, .. } => {
146                up_down = -remote_control_value(value);
147                command_sender.send(TelloCommand::RemoteControl { left_right, forwards_backwards, up_down, yaw} )
148            }            
149
150            // right stick X to strafe
151            Event::ControllerAxisMotion { axis: Axis::RightX, value, .. } => {
152                left_right = remote_control_value(value);
153                command_sender.send(TelloCommand::RemoteControl { left_right, forwards_backwards, up_down, yaw} )
154            }
155
156            // D-pad to flip
157            Event::ControllerButtonDown { button: Button::DPadLeft, .. } => {
158                command_sender.send(TelloCommand::FlipLeft)
159            }            
160            Event::ControllerButtonDown { button: Button::DPadRight, .. } => {
161                command_sender.send(TelloCommand::FlipRight)
162            }            
163            Event::ControllerButtonDown { button: Button::DPadUp, .. } => {
164                command_sender.send(TelloCommand::FlipForward)
165            }            
166            Event::ControllerButtonDown { button: Button::DPadDown, .. } => {
167                command_sender.send(TelloCommand::FlipBack)
168            }            
169
170            Event::Quit { .. } => break,
171            _ => Ok(()),
172        }.map_err(|err| format!("error sending command: {err}"))?;
173
174    }
175
176    Ok(())
177}
178
179//////////////////////////////////////////////////////////////////////////////
180
181const DEAD_ZONE:i16 = 10;
182const AXIS_MAX:f32 = 32767.0;
183
184/// Axis value to [-1.0, 1.0]
185fn normalize_axis_value(value:i16) -> Option<f32> {
186    if value > DEAD_ZONE || value < -DEAD_ZONE {
187        // outside dead zone
188        let normalized_value = value as f32 / AXIS_MAX;
189        if normalized_value > 1.0 {
190            Some(1.0)
191        }
192        else if normalized_value < -1.0 {
193            Some(-1.0)
194        }
195        else {
196            Some(normalized_value)
197        }
198    }
199    else {
200        // in dead zone
201        None
202    }
203}
204
205/// Axis value to [-100,100]
206fn remote_control_value(value:i16) -> i8 {
207    match normalize_axis_value(value) {
208        Some(v) => (v * 100.0) as i8,
209        None => 0
210    }
211}
212
213//////////////////////////////////////////////////////////////////////////////
214
215async fn fly(options:TelloOptions) -> Result<()> {
216    let drone = Tello::new()
217        .wait_for_wifi().await?;
218
219    let drone = drone.connect_with(options).await?;
220
221    drone.handle_commands().await?;
222
223    Ok(())
224}