realflight_bridge/lib.rs
1//! [![github]](https://github.com/wboayue/realflight-link) [![crates-io]](https://crates.io/crates/realflight-link) [![license]](https://opensource.org/licenses/MIT)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [license]: https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge&labelColor=555555
6//!
7//! RealFlight is a leading RC flight simulator that provides a realistic, physics-based environment for flying fixed-wing aircraft, helicopters, and drones. Used by both hobbyists and professionals, it simulates aerodynamics, wind conditions, and control responses, making it an excellent tool for flight control algorithm validation.
8//!
9//! RealFlightBridge is a Rust library that interfaces with RealFlight Link, enabling external flight controllers to interact with the simulator. It allows developers to:
10//!
11//! * Send control commands to simulated aircraft.
12//! * Receive real-time simulated flight data for state estimation and control.
13//! * Test stabilization and autonomy algorithms in a controlled environment.
14//!
15//! See [README](https://github.com/wboayue/realflight-link) for examples and usage.
16
17use std::error::Error;
18use std::sync::atomic::AtomicU32;
19use std::sync::atomic::Ordering;
20
21use serde::Deserialize;
22use serde::Serialize;
23use soap_client::tcp::TcpSoapClient;
24use std::time::Duration;
25use std::time::Instant;
26#[cfg(feature = "uom")]
27use uom::si::f32::*;
28
29#[cfg(test)]
30use soap_client::stub::StubSoapClient;
31
32#[cfg(any(test, feature = "bench-internals"))]
33pub use decoders::decode_simulator_state;
34
35#[cfg(not(any(test, feature = "bench-internals")))]
36use decoders::extract_element;
37
38#[cfg(any(test, feature = "bench-internals"))]
39pub use decoders::extract_element;
40
41pub mod bridge;
42mod decoders;
43
44mod soap_client;
45
46#[doc(inline)]
47pub use bridge::local::Configuration;
48#[doc(inline)]
49pub use bridge::local::RealFlightLocalBridge;
50#[doc(inline)]
51pub use bridge::remote::ProxyServer;
52#[doc(inline)]
53pub use bridge::remote::RealFlightRemoteBridge;
54#[doc(inline)]
55pub use bridge::RealFlightBridge;
56
57#[derive(Debug)]
58struct SoapResponse {
59 status_code: u32,
60 body: String,
61}
62
63impl From<SoapResponse> for Result<(), Box<dyn Error>> {
64 fn from(val: SoapResponse) -> Self {
65 match val.status_code {
66 200 => Ok(()),
67 _ => Err(decode_fault(&val).into()),
68 }
69 }
70}
71
72/// Control inputs for the RealFlight simulator using the standard RC channel mapping.
73/// Each channel value should be between 0.0 (minimum) and 1.0 (maximum).
74///
75/// # Standard RC Channel Mapping
76///
77/// The 12 available channels typically map to the following controls:
78///
79/// * Channel 1 (Aileron): Controls roll movement
80/// - 0.0: Full left roll
81/// - 0.5: Neutral
82/// - 1.0: Full right roll
83///
84/// * Channel 2 (Elevator): Controls pitch movement
85/// - 0.0: Full down pitch (nose down)
86/// - 0.5: Neutral
87/// - 1.0: Full up pitch (nose up)
88///
89/// * Channel 3 (Throttle): Controls engine power
90/// - 0.0: Zero throttle (engine off/idle)
91/// - 1.0: Full throttle
92///
93/// * Channel 4 (Rudder): Controls yaw movement
94/// - 0.0: Full left yaw
95/// - 0.5: Neutral
96/// - 1.0: Full right yaw
97///
98/// * Channel 5: Commonly used for flight modes
99/// - Often used as a 3-position switch (0.0, 0.5, 1.0)
100/// - Typical modes: Manual, Stabilized, Auto
101///
102/// * Channel 6: Commonly used for collective pitch (helicopters)
103/// - 0.0: Full negative pitch
104/// - 0.5: Zero pitch
105/// - 1.0: Full positive pitch
106///
107/// * Channels 7-12: Auxiliary channels
108/// - Can be mapped to various functions like:
109/// - Flaps
110/// - Landing gear
111/// - Camera gimbal
112/// - Lights
113/// - Custom functions#[derive(Default, Debug)]
114#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
115pub struct ControlInputs {
116 /// Array of 12 channel values, each between 0.0 and 1.0
117 pub channels: [f32; 12],
118}
119
120/// Represents the complete state of the simulated aircraft in RealFlight.
121/// All physical quantities use SI units through the `uom` crate.
122#[cfg(feature = "uom")]
123#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
124pub struct SimulatorState {
125 /// Previous control inputs that led to this state
126 pub previous_inputs: ControlInputs,
127 /// Velocity relative to the air mass [meters/second]
128 pub airspeed: Velocity,
129 /// Altitude above sea level [meters]
130 pub altitude_asl: Length,
131 /// Altitude above ground level [meters]
132 pub altitude_agl: Length,
133 /// Velocity relative to the ground [meters/second]
134 pub groundspeed: Velocity,
135 /// Pitch rate around body Y axis [degrees/second]
136 pub pitch_rate: AngularVelocity,
137 /// Roll rate around body X axis [degrees/second]
138 pub roll_rate: AngularVelocity,
139 /// Yaw rate around body Z axis [degrees/second]
140 pub yaw_rate: AngularVelocity,
141 /// Heading angle (true north reference) [degrees]
142 pub azimuth: Angle,
143 /// Pitch angle (nose up reference) [degrees]
144 pub inclination: Angle,
145 /// Roll angle (right wing down reference) [degrees]
146 pub roll: Angle,
147 /// Aircraft position along world X axis (North) [meters]
148 pub aircraft_position_x: Length,
149 /// Aircraft position along world Y axis (East) [meters]
150 pub aircraft_position_y: Length,
151 /// Velocity component along world X axis (North) [meters/second]
152 pub velocity_world_u: Velocity,
153 /// Velocity component along world Y axis (East) [meters/second]
154 pub velocity_world_v: Velocity,
155 /// Velocity component along world Z axis (Down) [meters/second]
156 pub velocity_world_w: Velocity,
157 /// Forward velocity in body frame [meters/second]
158 pub velocity_body_u: Velocity,
159 // Lateral velocity in body frame [meters/second]
160 pub velocity_body_v: Velocity,
161 // Vertical velocity in body frame [meters/second]
162 pub velocity_body_w: Velocity,
163 // Acceleration along world X axis (North) [meters/second²]
164 pub acceleration_world_ax: Acceleration,
165 // Acceleration along world Y axis (East) [meters/second²]
166 pub acceleration_world_ay: Acceleration,
167 // Acceleration along world Z axis (Down) [meters/second²]
168 pub acceleration_world_az: Acceleration,
169 // Acceleration along body X axis (Forward) [meters/second²]
170 pub acceleration_body_ax: Acceleration,
171 // Acceleration along body Y axis (Right) [meters/second²]
172 pub acceleration_body_ay: Acceleration,
173 /// Acceleration along body Z axis (Down) [meters/second²]
174 pub acceleration_body_az: Acceleration,
175 /// Wind velocity along world X axis [meters/second]
176 pub wind_x: Velocity,
177 /// Wind velocity along world Y axis [meters/second]
178 pub wind_y: Velocity,
179 /// Wind velocity along world Z axis [meters/second]
180 pub wind_z: Velocity,
181 /// Propeller RPM for piston/electric aircraft [revolutions/minute]
182 pub prop_rpm: f32,
183 /// Main rotor RPM for helicopters [revolutions/minute]
184 pub heli_main_rotor_rpm: f32,
185 /// Battery voltage [volts]
186 pub battery_voltage: ElectricPotential,
187 /// Current draw from battery [amperes]
188 pub battery_current_draw: ElectricCurrent,
189 /// Remaining battery capacity [milliamperes-hour]
190 pub battery_remaining_capacity: ElectricCharge,
191 /// Remaining fuel volume [ounces]
192 pub fuel_remaining: Volume,
193 /// True if aircraft is in a frozen/paused state
194 pub is_locked: bool,
195 /// True if aircraft has lost components due to damage
196 pub has_lost_components: bool,
197 /// True if any engine is currently running
198 pub an_engine_is_running: bool,
199 /// True if aircraft is in contact with ground
200 pub is_touching_ground: bool,
201 /// Current status message from simulator
202 pub current_aircraft_status: String,
203 /// Current simulation time
204 pub current_physics_time: Time,
205 /// Current time acceleration factor
206 pub current_physics_speed_multiplier: f32,
207 /// Quaternion X component (scalar)
208 pub orientation_quaternion_x: f32,
209 /// Quaternion Y component (scalar)
210 pub orientation_quaternion_y: f32,
211 /// Quaternion Z component (scalar)
212 pub orientation_quaternion_z: f32,
213 /// Quaternion W component (scalar)
214 pub orientation_quaternion_w: f32,
215 /// True if external flight controller is active
216 pub flight_axis_controller_is_active: bool,
217 /// True if reset button was pressed
218 pub reset_button_has_been_pressed: bool,
219}
220
221/// Represents the complete state of the simulated aircraft in RealFlight.
222/// All physical quantities use SI units through the `uom` crate.
223#[cfg(not(feature = "uom"))]
224#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
225pub struct SimulatorState {
226 /// Previous control inputs that led to this state
227 pub previous_inputs: ControlInputs,
228 /// Velocity relative to the air mass [meters/second]
229 pub airspeed: f32,
230 /// Altitude above sea level [meters]
231 pub altitude_asl: f32,
232 /// Altitude above ground level [meters]
233 pub altitude_agl: f32,
234 /// Velocity relative to the ground [meters/second]
235 pub groundspeed: f32,
236 /// Pitch rate around body Y axis [degrees/second]
237 pub pitch_rate: f32,
238 /// Roll rate around body X axis [degrees/second]
239 pub roll_rate: f32,
240 /// Yaw rate around body Z axis [degrees/second]
241 pub yaw_rate: f32,
242 /// Heading angle (true north reference) [degrees]
243 pub azimuth: f32,
244 /// Pitch angle (nose up reference) [degrees]
245 pub inclination: f32,
246 /// Roll angle (right wing down reference) [degrees]
247 pub roll: f32,
248 /// Aircraft position along world X axis (North) [meters]
249 pub aircraft_position_x: f32,
250 /// Aircraft position along world Y axis (East) [meters]
251 pub aircraft_position_y: f32,
252 /// Velocity component along world X axis (North) [meters/second]
253 pub velocity_world_u: f32,
254 /// Velocity component along world Y axis (East) [meters/second]
255 pub velocity_world_v: f32,
256 /// Velocity component along world Z axis (Down) [meters/second]
257 pub velocity_world_w: f32,
258 /// Forward velocity in body frame [meters/second]
259 pub velocity_body_u: f32,
260 // Lateral velocity in body frame [meters/second]
261 pub velocity_body_v: f32,
262 // Vertical velocity in body frame [meters/second]
263 pub velocity_body_w: f32,
264 // Acceleration along world X axis (North) [meters/second²]
265 pub acceleration_world_ax: f32,
266 // Acceleration along world Y axis (East) [meters/second²]
267 pub acceleration_world_ay: f32,
268 // Acceleration along world Z axis (Down) [meters/second²]
269 pub acceleration_world_az: f32,
270 // Acceleration along body X axis (Forward) [meters/second²]
271 pub acceleration_body_ax: f32,
272 // Acceleration along body Y axis (Right) [meters/second²]
273 pub acceleration_body_ay: f32,
274 /// Acceleration along body Z axis (Down) [meters/second²]
275 pub acceleration_body_az: f32,
276 /// Wind velocity along world X axis [meters/second]
277 pub wind_x: f32,
278 /// Wind velocity along world Y axis [meters/second]
279 pub wind_y: f32,
280 /// Wind velocity along world Z axis [meters/second]
281 pub wind_z: f32,
282 /// Propeller RPM for piston/electric aircraft [revolutions/minute]
283 pub prop_rpm: f32,
284 /// Main rotor RPM for helicopters [revolutions/minute]
285 pub heli_main_rotor_rpm: f32,
286 /// Battery voltage [volts]
287 pub battery_voltage: f32,
288 /// Current draw from battery [amperes]
289 pub battery_current_draw: f32,
290 /// Remaining battery capacity [milliamperes-hour]
291 pub battery_remaining_capacity: f32,
292 /// Remaining fuel volume [ounces]
293 pub fuel_remaining: f32,
294 /// True if aircraft is in a frozen/paused state
295 pub is_locked: bool,
296 /// True if aircraft has lost components due to damage
297 pub has_lost_components: bool,
298 /// True if any engine is currently running
299 pub an_engine_is_running: bool,
300 /// True if aircraft is in contact with ground
301 pub is_touching_ground: bool,
302 /// Current status message from simulator
303 pub current_aircraft_status: String,
304 /// Current simulation time [seconds]
305 pub current_physics_time: f32,
306 /// Current time acceleration factor
307 pub current_physics_speed_multiplier: f32,
308 /// Quaternion X component (scalar)
309 pub orientation_quaternion_x: f32,
310 /// Quaternion Y component (scalar)
311 pub orientation_quaternion_y: f32,
312 /// Quaternion Z component (scalar)
313 pub orientation_quaternion_z: f32,
314 /// Quaternion W component (scalar)
315 pub orientation_quaternion_w: f32,
316 /// True if external flight controller is active
317 pub flight_axis_controller_is_active: bool,
318 /// True if reset button was pressed
319 pub reset_button_has_been_pressed: bool,
320}
321
322/// Represents a snapshot of performance metrics for a running `RealFlightBridge`.
323///
324/// The `Statistics` struct is returned by [`RealFlightBridge::statistics`](crate::RealFlightBridge::statistics)
325/// and captures various counters and timings that can help diagnose performance issues
326/// or monitor real-time operation.
327///
328/// # Fields
329///
330/// - `runtime`: The total elapsed time since the `RealFlightBridge` instance was created.
331/// - `error_count`: The number of errors (e.g., connection errors, SOAP faults) encountered so far.
332/// - `frame_rate`: An approximate request rate, calculated as `(request_count / runtime)`.
333/// - `request_count`: The total number of SOAP requests sent to the simulator. Loops back to 0 after `u32::MAX`.
334///
335/// ```no_run
336/// use realflight_bridge::{RealFlightLocalBridge, Configuration};
337/// use std::error::Error;
338///
339/// fn main() -> Result<(), Box<dyn Error>> {
340/// let bridge = RealFlightLocalBridge::new()?;
341///
342/// // Send some commands...
343///
344/// // Now retrieve statistics to assess performance
345/// let stats = bridge.statistics();
346/// println!("Runtime: {:?}", stats.runtime);
347/// println!("Frequency: {:.2} Hz", stats.frequency);
348/// println!("Errors so far: {}", stats.error_count);
349///
350/// Ok(())
351/// }
352/// ```
353///
354/// This information can help identify connection bottlenecks, excessive errors,
355/// or confirm that a high-frequency control loop is operating as expected.
356#[derive(Debug)]
357pub struct Statistics {
358 pub runtime: Duration,
359 pub error_count: u32,
360 pub frequency: f32,
361 pub request_count: u32,
362}
363
364/// Statistics for the RealFlightBridge
365pub(crate) struct StatisticsEngine {
366 start_time: Instant,
367 error_count: AtomicU32,
368 request_count: AtomicU32,
369}
370
371impl StatisticsEngine {
372 pub fn new() -> Self {
373 StatisticsEngine {
374 start_time: Instant::now(),
375 error_count: AtomicU32::new(0),
376 request_count: AtomicU32::new(0),
377 }
378 }
379
380 pub fn snapshot(&self) -> Statistics {
381 Statistics {
382 runtime: self.start_time.elapsed(),
383 error_count: self.error_count(),
384 frequency: self.frame_rate(),
385 request_count: self.request_count(),
386 }
387 }
388
389 fn error_count(&self) -> u32 {
390 self.error_count.load(Ordering::Relaxed)
391 }
392
393 fn request_count(&self) -> u32 {
394 self.request_count.load(Ordering::Relaxed)
395 }
396
397 fn increment_request_count(&self) {
398 self.request_count.fetch_add(1, Ordering::Relaxed);
399 }
400
401 fn increment_error_count(&self) {
402 self.error_count.fetch_add(1, Ordering::Relaxed);
403 }
404
405 fn frame_rate(&self) -> f32 {
406 self.request_count() as f32 / self.start_time.elapsed().as_secs_f32()
407 }
408}
409
410impl Default for StatisticsEngine {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416fn decode_fault(response: &SoapResponse) -> String {
417 match extract_element("detail", &response.body) {
418 Some(message) => message,
419 None => "Failed to extract error message".into(),
420 }
421}
422
423#[cfg(test)]
424pub mod tests;