diff --git a/Cargo.lock b/Cargo.lock index beae9f7..f08d328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,9 +189,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libloading" @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", @@ -602,9 +602,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "valuable" diff --git a/README.md b/README.md index cb7d809..1abc8af 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ fn main() -> Result<(), Box> { Some(Notification::Open) => { println!("Open"); - // The struct must be registered after the connection is successfully open + // After the connection is successfully open, we register the struct client.register_object::()?; } - Some(Notification::Data(data)) => { + Some(Notification::Object(data)) => { if let Ok(gps_data) = GpsData::try_from(&data) { - println!("GPS Data: {gps_data:?}"); + println!("{gps_data:?}"); } } _ => (), diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3fb73b3..7730f4c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,20 +8,24 @@ license = "MIT" publish = false [[bin]] -name = "basic" -path = "src/basic.rs" +name = "data" +path = "src/data.rs" [[bin]] -name = "basic_with_tracing" -path = "src/basic_with_tracing.rs" +name = "data_with_tracing" +path = "src/data_with_tracing.rs" [[bin]] -name = "basic_without_macro" -path = "src/basic_without_macro.rs" +name = "data_without_macro" +path = "src/data_without_macro.rs" [[bin]] -name = "multiple_objects" -path = "src/multiple_objects.rs" +name = "data_multiple_objects" +path = "src/data_multiple_objects.rs" + +[[bin]] +name = "facilities" +path = "src/facilities.rs" [dependencies] tracing = "0.1" diff --git a/examples/README.md b/examples/README.md index 58d7cef..589c25b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,23 +10,29 @@ cd simconnect-sdk ## Receiving data ```bash -cargo run --bin basic +cargo run --bin data ``` -## Using tracing +## Receiving data using tracing ```bash -RUST_LOG=info cargo run --bin basic_with_tracing +RUST_LOG=info cargo run --bin data_with_tracing ``` ## Receiving data without the derive macro ```bash -cargo run --bin basic_without_macro +cargo run --bin data_without_macro ``` -## Multiple objects +## Receiving data using multiple objects ```bash -cargo run --bin multiple_objects +cargo run --bin data_multiple_objects +``` + +## Receiving facilities from cache + +```bash +cargo run --bin facilities ``` diff --git a/examples/src/basic.rs b/examples/src/data.rs similarity index 86% rename from examples/src/basic.rs rename to examples/src/data.rs index 79a022f..f509c67 100644 --- a/examples/src/basic.rs +++ b/examples/src/data.rs @@ -25,12 +25,12 @@ fn main() -> Result<(), Box> { Some(Notification::Open) => { println!("Open"); - // The struct must be registered after the connection is successfully open + // After the connection is successfully open, we register the struct client.register_object::()?; } - Some(Notification::Data(data)) => { + Some(Notification::Object(data)) => { if let Ok(gps_data) = GpsData::try_from(&data) { - println!("GPS Data: {gps_data:?}"); + println!("{gps_data:?}"); } } _ => (), diff --git a/examples/src/multiple_objects.rs b/examples/src/data_multiple_objects.rs similarity index 78% rename from examples/src/multiple_objects.rs rename to examples/src/data_multiple_objects.rs index 2b783dd..85f9814 100644 --- a/examples/src/multiple_objects.rs +++ b/examples/src/data_multiple_objects.rs @@ -33,19 +33,19 @@ fn main() -> Result<(), Box> { Some(Notification::Open) => { println!("Open"); - // The structs must be registered after the connection is successfully open + // After the connection is successfully open, we register the structs client.register_object::()?; client.register_object::()?; } - Some(Notification::Data(data)) => { + Some(Notification::Object(data)) => { if let Ok(gps_data) = GpsData::try_from(&data) { - println!("GPS Data: {gps_data:?}"); - // We've already got our object, there's no point in trying another in this iteration + println!("{gps_data:?}"); + // We've already got our data, there's no point in trying another in this iteration continue; } if let Ok(on_ground) = OnGround::try_from(&data) { - println!("On Ground data: {on_ground:?}"); - // We've already got our object, there's no point in trying another in this iteration + println!("{on_ground:?}"); + // We've already got our data, there's no point in trying another in this iteration continue; } } diff --git a/examples/src/basic_with_tracing.rs b/examples/src/data_with_tracing.rs similarity index 89% rename from examples/src/basic_with_tracing.rs rename to examples/src/data_with_tracing.rs index 32f9011..0e0042e 100644 --- a/examples/src/basic_with_tracing.rs +++ b/examples/src/data_with_tracing.rs @@ -29,12 +29,12 @@ fn main() -> Result<(), Box> { Some(Notification::Open) => { info!("Open"); - // The struct must be registered after the connection is successfully open + // After the connection is successfully open, we register the struct client.register_object::()?; } - Some(Notification::Data(data)) => { + Some(Notification::Object(data)) => { if let Ok(gps_data) = GpsData::try_from(&data) { - info!("GPS Data: {gps_data:?}"); + info!("{gps_data:?}"); } } _ => (), diff --git a/examples/src/basic_without_macro.rs b/examples/src/data_without_macro.rs similarity index 79% rename from examples/src/basic_without_macro.rs rename to examples/src/data_without_macro.rs index 3bb7bdf..74b326d 100644 --- a/examples/src/basic_without_macro.rs +++ b/examples/src/data_without_macro.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use simconnect_sdk::{ - Condition, DataType, Notification, NotificationData, Period, SimConnect, SimConnectError, + Condition, DataType, Notification, Object, Period, SimConnect, SimConnectError, SimConnectObjectExt, }; @@ -25,10 +25,10 @@ impl SimConnectObjectExt for GpsData { } } -impl TryFrom<&NotificationData> for GpsData { +impl TryFrom<&Object> for GpsData { type Error = SimConnectError; - fn try_from(value: &NotificationData) -> Result { + fn try_from(value: &Object) -> Result { value.try_transmute::() } } @@ -44,12 +44,12 @@ fn main() -> Result<(), Box> { Some(Notification::Open) => { println!("Open"); - // The struct must be registered after the connection is successfully open + // After the connection is successfully open, we register the struct client.register_object::()?; } - Some(Notification::Data(data)) => { + Some(Notification::Object(data)) => { if let Ok(gps_data) = GpsData::try_from(&data) { - println!("GPS Data: {gps_data:?}"); + println!("{gps_data:?}"); } } _ => (), diff --git a/examples/src/facilities.rs b/examples/src/facilities.rs new file mode 100644 index 0000000..96258c9 --- /dev/null +++ b/examples/src/facilities.rs @@ -0,0 +1,64 @@ +use simconnect_sdk::{FacilityType, Notification, SimConnect}; + +fn main() -> Result<(), Box> { + let client = SimConnect::new("Simple Program"); + + match client { + Ok(mut client) => loop { + let notification = client.get_next_dispatch()?; + + match notification { + Some(Notification::Open) => { + println!("Open"); + + // After the connection is successfully open, we subscribe to all facility types that we are interested in + client.subscribe_to_facilities(FacilityType::Airport)?; + client.subscribe_to_facilities(FacilityType::Waypoint)?; + client.subscribe_to_facilities(FacilityType::NDB)?; + client.subscribe_to_facilities(FacilityType::VOR)?; + } + Some(Notification::AirportList(data)) => { + for record in data { + // The returned list is quite large, so we look for a particular record + if record.icao == "EGSC" { + println!("{record:?}"); + } + } + } + Some(Notification::WaypointList(data)) => { + for record in data { + // The returned list is quite large, so we look for a particular record + if record.icao == "BRAIN" { + println!("{record:?}"); + } + } + } + Some(Notification::NdbList(data)) => { + for record in data { + // The returned list is quite large, so we look for a particular record + if record.icao == "CAM" { + println!("{record:?}"); + } + } + } + Some(Notification::VorList(data)) => { + for record in data { + // The returned list is quite large, so we look for a particular record + if record.icao == "LON" { + println!("{record:?}"); + } + } + } + _ => (), + } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); + }, + Err(e) => { + println!("Error: {e:?}") + } + } + + Ok(()) +} diff --git a/simconnect-sdk-derive/src/lib.rs b/simconnect-sdk-derive/src/lib.rs index f676667..df0c0d7 100644 --- a/simconnect-sdk-derive/src/lib.rs +++ b/simconnect-sdk-derive/src/lib.rs @@ -117,10 +117,10 @@ pub fn derive(input: TokenStream) -> TokenStream { Ok(()) } } - impl TryFrom<&simconnect_sdk::NotificationData> for #name { + impl TryFrom<&simconnect_sdk::Object> for #name { type Error = simconnect_sdk::SimConnectError; - fn try_from(value: &simconnect_sdk::NotificationData) -> Result { + fn try_from(value: &simconnect_sdk::Object) -> Result { value.try_transmute::<#name>() } } diff --git a/simconnect-sdk/build.rs b/simconnect-sdk/build.rs index 729891c..e200702 100644 --- a/simconnect-sdk/build.rs +++ b/simconnect-sdk/build.rs @@ -29,11 +29,19 @@ fn main() { .allowlist_type("SIMCONNECT_RECV_ID") .allowlist_type("SIMCONNECT_RECV_EVENT") .allowlist_type("SIMCONNECT_RECV_SIMOBJECT_DATA") + .allowlist_type("SIMCONNECT_RECV_FACILITIES_LIST") .allowlist_type("SIMCONNECT_RECV_AIRPORT_LIST") + .allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST") + .allowlist_type("SIMCONNECT_RECV_NDB_LIST") + .allowlist_type("SIMCONNECT_RECV_VOR_LIST") .allowlist_type("SIMCONNECT_CLIENT_DATA_PERIOD") .allowlist_type("SIMCONNECT_RECV_OPEN") .allowlist_type("SIMCONNECT_RECV_EXCEPTION") .allowlist_var("SIMCONNECT_DATA_REQUEST_FLAG_CHANGED") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME") .generate() .expect("Unable to generate bindings"); diff --git a/simconnect-sdk/src/domain/airport_data.rs b/simconnect-sdk/src/domain/airport_data.rs deleted file mode 100644 index ff6d841..0000000 --- a/simconnect-sdk/src/domain/airport_data.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// Airport data. -#[derive(Debug, Clone)] -pub struct AirportData { - pub icao: String, - pub lat: f64, - pub lon: f64, - pub alt: f64, -} diff --git a/simconnect-sdk/src/domain/condition.rs b/simconnect-sdk/src/domain/condition.rs index 60a5615..e45831c 100644 --- a/simconnect-sdk/src/domain/condition.rs +++ b/simconnect-sdk/src/domain/condition.rs @@ -1,8 +1,19 @@ +use crate::bindings; + /// Specifies under which conditions the data is to be sent by the server and received by the client. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Condition { /// The default, data will be sent strictly according to the defined period. None, /// Data will only be sent to the client when one or more values have changed. Changed, } + +impl From for u32 { + fn from(condition: Condition) -> Self { + match condition { + Condition::None => 0, + Condition::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, + } + } +} diff --git a/simconnect-sdk/src/domain/data_type.rs b/simconnect-sdk/src/domain/data_type.rs index d6a55eb..9677d93 100644 --- a/simconnect-sdk/src/domain/data_type.rs +++ b/simconnect-sdk/src/domain/data_type.rs @@ -1,5 +1,5 @@ -/// SimConnect object property data type. -#[derive(Debug, Clone)] +/// [`crate::SimConnectObject`] object property data type. +#[derive(Debug)] pub enum DataType { Float64, Bool, diff --git a/simconnect-sdk/src/domain/facilities.rs b/simconnect-sdk/src/domain/facilities.rs new file mode 100644 index 0000000..7378555 --- /dev/null +++ b/simconnect-sdk/src/domain/facilities.rs @@ -0,0 +1,122 @@ +use crate::bindings; + +/// Facility Type. The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft. +/// They can be requested using [`crate::SimConnect::subscribe_to_facilities`] or [`crate::SimConnect::request_facilities_list`]. +#[derive(Debug)] +pub enum FacilityType { + Airport, + Waypoint, + NDB, + VOR, +} + +impl From for i32 { + fn from(facility_type: FacilityType) -> Self { + match facility_type { + FacilityType::Airport => { + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT + } + FacilityType::Waypoint => { + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT + } + FacilityType::NDB => { + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_NDB + } + FacilityType::VOR => { + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_VOR + } + } + } +} + +impl FacilityType { + pub fn to_type_name(&self) -> String { + match self { + FacilityType::Airport => std::any::type_name::(), + FacilityType::Waypoint => std::any::type_name::(), + FacilityType::NDB => std::any::type_name::(), + FacilityType::VOR => std::any::type_name::(), + } + .into() + } +} + +/// Information on a single airport in the facilities cache. +#[derive(Debug, Clone)] +pub struct Airport { + /// ICAO of the facility. + pub icao: String, + /// Latitude of the airport in facility. + pub lat: f64, + /// Longitude of the airport in facility. + pub lon: f64, + /// Altitude of the facility in meters. + pub alt: f64, +} + +/// Information on a single waypoint in the facilities cache. +#[derive(Debug, Clone)] +pub struct Waypoint { + /// ICAO of the facility. + pub icao: String, + /// Latitude of the airport in facility. + pub lat: f64, + /// Longitude of the airport in facility. + pub lon: f64, + /// Altitude of the facility in meters. + pub alt: f64, + /// The magnetic variation of the waypoint in degrees. + pub mag_var: f32, +} + +/// Information on a single NDB station in the facilities cache. +#[derive(Debug, Clone)] +pub struct NDB { + /// ICAO of the facility. + pub icao: String, + /// Latitude of the airport in facility. + pub lat: f64, + /// Longitude of the airport in facility. + pub lon: f64, + /// Altitude of the facility in meters. + pub alt: f64, + /// The magnetic variation of the waypoint in degrees. + pub mag_var: f32, + /// Frequency of the station in Hz. + pub frequency: u32, +} + +/// Information on a single VOR station in the facilities cache. +#[derive(Debug, Clone)] +pub struct VOR { + /// ICAO of the facility. + pub icao: String, + /// Latitude of the airport in facility. + pub lat: f64, + /// Longitude of the airport in facility. + pub lon: f64, + /// Altitude of the facility in meters. + pub alt: f64, + /// The magnetic variation of the waypoint in degrees. + pub mag_var: f32, + /// True if the station has a NAV transmitter, and if so, `glide_lat`, `glide_lon` and `glide_alt` contain valid data. + pub has_nav_signal: bool, + /// True if the station transmits an ILS localizer angle, and if so `localizer` contains valid data. + pub has_localizer: bool, + /// True if the station transmits an ILS approach angle, and if so `glide_slope_angle` contains valid data. + pub has_glide_slope: bool, + /// True if the station transmits a DME signal, and if so the inherited DME fFrequency contains valid data. + pub has_dme: bool, + /// The ILS localizer angle in degrees. + pub localizer: Option, + /// The latitude of the glide slope transmitter in degrees. + pub glide_lat: Option, + /// The longitude of the glide slope transmitter in degrees. + pub glide_lon: Option, + /// The altitude of the glide slope transmitter in degrees. + pub glide_alt: Option, + /// The ILS approach angle in degrees. + pub glide_slope_angle: Option, + /// Frequency of the station in Hz. + pub frequency: Option, +} diff --git a/simconnect-sdk/src/domain/mod.rs b/simconnect-sdk/src/domain/mod.rs index 5ebc498..313616a 100644 --- a/simconnect-sdk/src/domain/mod.rs +++ b/simconnect-sdk/src/domain/mod.rs @@ -1,15 +1,15 @@ -mod airport_data; mod condition; mod data_type; mod event; +mod facilities; mod notification; mod notification_group; mod period; -pub use airport_data::*; pub use condition::*; pub use data_type::*; pub use event::*; +pub use facilities::*; pub use notification::*; pub use notification_group::*; pub use period::*; diff --git a/simconnect-sdk/src/domain/notification.rs b/simconnect-sdk/src/domain/notification.rs index ee5a7f9..3a3d42e 100644 --- a/simconnect-sdk/src/domain/notification.rs +++ b/simconnect-sdk/src/domain/notification.rs @@ -1,15 +1,22 @@ -use crate::{AirportData, Event, SimConnectError, SimConnectObjectExt}; +use crate::{Airport, Event, SimConnectError, SimConnectObjectExt, Waypoint, NDB, VOR}; /// Notification received from SimConnect. +#[derive(Debug)] pub enum Notification { /// SimConnect open Open, /// SimConnect event Event(Event), /// SimConnect object - Data(NotificationData), - /// SimConnect airport list - AirportList(Vec), + Object(Object), + /// A list of [crate::Airport]. + AirportList(Vec), + /// A list of [crate::Waypoint]. + WaypointList(Vec), + /// A list of [crate::NDB]. + NdbList(Vec), + /// A list of [crate::VOR]. + VorList(Vec), /// SimConnect quit Quit, /// SimConnect exception @@ -17,22 +24,23 @@ pub enum Notification { } /// Notification data object. -pub struct NotificationData { - pub(crate) type_id: String, +#[derive(Debug)] +pub struct Object { + pub(crate) type_name: String, pub(crate) data_addr: *const u32, } -impl NotificationData { +impl Object { pub fn try_transmute(&self) -> Result { - let type_id: String = std::any::type_name::().into(); + let type_name: String = std::any::type_name::().into(); - if self.type_id == type_id { + if self.type_name == type_name { let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) }; Ok(data.clone()) } else { Err(SimConnectError::ObjectMismatch { - actual: self.type_id.clone(), - expected: type_id, + actual: self.type_name.clone(), + expected: type_name, }) } } diff --git a/simconnect-sdk/src/domain/notification_group.rs b/simconnect-sdk/src/domain/notification_group.rs index e76cae1..4b33eb2 100644 --- a/simconnect-sdk/src/domain/notification_group.rs +++ b/simconnect-sdk/src/domain/notification_group.rs @@ -1,5 +1,5 @@ /// SimConnect event notification group. -#[derive(Copy, Clone)] +#[derive(Debug)] #[repr(u32)] pub enum NotificationGroup { Group0, diff --git a/simconnect-sdk/src/domain/period.rs b/simconnect-sdk/src/domain/period.rs index 078076c..4fadf23 100644 --- a/simconnect-sdk/src/domain/period.rs +++ b/simconnect-sdk/src/domain/period.rs @@ -1,9 +1,11 @@ +use crate::bindings; + /// Specifies how often the data is to be sent by the server and received by the client. /// 0 - every interval. /// 1 - every other interval. /// 2 - every third interval. /// etc. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Period { /// Specifies that the data should be sent once only. Note that this is not an efficient way of receiving data frequently, use one of the other periods if there is a regular frequency to the data request. Once, @@ -14,3 +16,14 @@ pub enum Period { /// Specifies that the data should be sent once every second. Second, } + +impl From for i32 { + fn from(period: Period) -> Self { + match period { + Period::Once => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_ONCE, + Period::VisualFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME, + Period::SimFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SIM_FRAME, + Period::Second => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, + } + } +} diff --git a/simconnect-sdk/src/helpers.rs b/simconnect-sdk/src/helpers.rs index ee6da5b..5faee53 100644 --- a/simconnect-sdk/src/helpers.rs +++ b/simconnect-sdk/src/helpers.rs @@ -3,12 +3,12 @@ use std::ffi::CString; pub fn fixed_c_str_to_string(data: &[i8]) -> String { let u8slice = unsafe { &*(data as *const _ as *const [u8]) }; - let mut value = u8slice.to_vec(); + let mut bytes = u8slice.to_vec(); - let pos = value.iter().position(|c| *c == 0).unwrap_or(value.len()); + let pos = bytes.iter().position(|c| *c == 0).unwrap_or(bytes.len()); - value.truncate(pos); - let icao = unsafe { CString::from_vec_unchecked(value) }; + bytes.truncate(pos); + let result = unsafe { CString::from_vec_unchecked(bytes) }; - icao.to_str().unwrap().to_string() + result.to_str().unwrap_or_default().to_string() } diff --git a/simconnect-sdk/src/lib.rs b/simconnect-sdk/src/lib.rs index bd18393..80e16cb 100644 --- a/simconnect-sdk/src/lib.rs +++ b/simconnect-sdk/src/lib.rs @@ -34,12 +34,12 @@ //! Some(Notification::Open) => { //! println!("Open"); //! -//! // The struct must be registered after the connection is successfully open +//! // After the connection is successfully open, we register the struct //! client.register_object::()?; //! } -//! Some(Notification::Data(data)) => { +//! Some(Notification::Object(data)) => { //! if let Ok(gps_data) = GpsData::try_from(&data) { -//! println!("GPS Data: {gps_data:?}"); +//! println!("{gps_data:?}"); //! } //! } //! _ => (), diff --git a/simconnect-sdk/src/simconnect.rs b/simconnect-sdk/src/simconnect.rs index 1364930..2a69623 100644 --- a/simconnect-sdk/src/simconnect.rs +++ b/simconnect-sdk/src/simconnect.rs @@ -1,9 +1,9 @@ use std::ffi::c_void; use crate::{ - as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, AirportData, - Condition, DataType, Event, Notification, NotificationData, NotificationGroup, Period, - SimConnectError, SimConnectObjectExt, + as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, Condition, + DataType, Event, FacilityType, Notification, NotificationGroup, Object, Period, + SimConnectError, SimConnectObjectExt, Waypoint, NDB, VOR, }; /// SimConnect SDK Client. @@ -36,12 +36,12 @@ use crate::{ /// Some(Notification::Open) => { /// println!("Open"); /// -/// // The struct must be registered after the connection is successfully open +/// // After the connection is successfully open, we register the struct /// client.register_object::()?; /// } -/// Some(Notification::Data(data)) => { +/// Some(Notification::Object(data)) => { /// if let Ok(gps_data) = GpsData::try_from(&data) { -/// println!("GPS Data: {gps_data:?}"); +/// println!("{gps_data:?}"); /// } /// } /// _ => (), @@ -91,31 +91,27 @@ impl SimConnect { }) } + // Register an object with SimConnect by assigning it an unique interval `request_id` and then calling the [`crate::SimConnectObjectExt::register`] method on the struct. + #[tracing::instrument(name = "SimConnect::register_object")] pub fn register_object(&mut self) -> Result { - let type_id: String = std::any::type_name::().into(); + let type_name: String = std::any::type_name::().into(); - if self.registered_objects.contains(&type_id) { - return Err(SimConnectError::ObjectAlreadyRegistered(type_id)); - } - - self.registered_objects.push(type_id.clone()); - - let id = self - .registered_objects - .iter() - .position(|p| p == &type_id) - .ok_or_else(|| { - SimConnectError::UnexpectedError("failed to find registered event".to_string()) - })?; - let id = u32::try_from(id)?; + let id = self.register_request_id(type_name)?; T::register(self, id)?; Ok(id) } + /// Associates a client defined event with a Microsoft Flight Simulator event name. + /// + /// WIP #[tracing::instrument(name = "SimConnect::register_event")] - pub fn register_event(&self, event: Event) -> Result<(), SimConnectError> { + pub fn register_event( + &self, + event: Event, + notification_group: NotificationGroup, + ) -> Result<(), SimConnectError> { success!(unsafe { bindings::SimConnect_MapClientEventToSimEvent( self.handle.as_ptr(), @@ -124,24 +120,30 @@ impl SimConnect { ) }); - let group = NotificationGroup::Group0; - success!(unsafe { bindings::SimConnect_AddClientEventToNotificationGroup( self.handle.as_ptr(), - group as u32, + notification_group as u32, event as u32, 0, ) }); success!(unsafe { - bindings::SimConnect_SetNotificationGroupPriority(self.handle.as_ptr(), group as u32, 1) + bindings::SimConnect_SetNotificationGroupPriority( + self.handle.as_ptr(), + notification_group as u32, + 1, + ) }); Ok(()) } + /// Add a Microsoft Flight Simulator simulation variable name to a client defined object definition. + /// + /// # Remarks + /// The [`crate::SimConnectObject`] macro will automatically call this method for you. #[tracing::instrument(name = "SimConnect::add_to_data_definition")] pub fn add_to_data_definition( &self, @@ -170,6 +172,13 @@ impl SimConnect { Ok(()) } + /// Request when the SimConnect client is to receive data values for a specific object. + /// + /// # Remarks + /// The [`crate::SimConnectObject`] macro will automatically call this method for you. + /// + /// It is possible to change the period of a request, by re-sending the [`crate::SimConnect::request_data_on_sim_object`] call with the same `request_id` parameters, but with a new `period`. + /// The one exception to this is the new period cannot be [`crate::Period::Once`], in this case a request with a new `request_id` should be sent. #[tracing::instrument(name = "SimConnect::request_data_on_sim_object")] pub fn request_data_on_sim_object( &self, @@ -179,26 +188,13 @@ impl SimConnect { interval: u32, ) -> Result<(), SimConnectError> { unsafe { - let simconnect_period = match period { - Period::Once => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_ONCE, - Period::VisualFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME, - Period::SimFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SIM_FRAME, - - Period::Second => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, - }; - - let simconnect_flags: u32 = match condition { - Condition::None => 0, - Condition::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, - }; - success!(bindings::SimConnect_RequestDataOnSimObject( self.handle.as_ptr(), request_id, request_id, request_id, - simconnect_period, - simconnect_flags, + period.into(), + condition.into(), 0, interval, 0, @@ -208,12 +204,28 @@ impl SimConnect { Ok(()) } - #[tracing::instrument(name = "SimConnect::subscribe_to_airport_list")] - pub fn subscribe_to_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> { + /// Request notifications when a facility of a certain type is added to the facilities cache. + /// + /// When this function is first called, a full list from the cache will be sent, thereafter just the additions will be transmitted. + /// No notification is given when a facility is removed from the cache. + /// To terminate these notifications use the [`crate::SimConnect::unsubscribe_to_facilities`] function. + /// + /// # Remarks + /// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft. + /// This radius varies depending on where the aircraft is in the world, but is at least large enough to encompass the whole of the reality bubble for airports and waypoints, and can be over 200 miles for VOR and NDB stations. + /// As the user aircraft moves facilities will be added to, and removed from, the cache. However, in the interests pf performance, hysteresis is built into the system. + #[tracing::instrument(name = "SimConnect::subscribe_to_facilities")] + pub fn subscribe_to_facilities( + &mut self, + facility_type: FacilityType, + ) -> Result<(), SimConnectError> { + let type_name = facility_type.to_type_name(); + let request_id = self.register_request_id(type_name)?; + unsafe { success!(bindings::SimConnect_SubscribeToFacilities( self.handle.as_ptr(), - bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, + facility_type.into(), request_id, )); } @@ -221,12 +233,24 @@ impl SimConnect { Ok(()) } - #[tracing::instrument(name = "SimConnect::request_airport_list")] - pub fn request_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> { + /// Request a list of all the facilities of a given type currently held in the facilities cache. + /// + /// # Remarks + /// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft. + /// This radius varies depending on where the aircraft is in the world, but is at least large enough to encompass the whole of the reality bubble for airports and waypoints, and can be over 200 miles for VOR and NDB stations. + /// As the user aircraft moves facilities will be added to, and removed from, the cache. However, in the interests pf performance, hysteresis is built into the system. + #[tracing::instrument(name = "SimConnect::request_facilities_list")] + pub fn request_facilities_list( + &mut self, + facility_type: FacilityType, + ) -> Result<(), SimConnectError> { + let type_name = facility_type.to_type_name(); + let request_id = self.register_request_id(type_name)?; + unsafe { success!(bindings::SimConnect_RequestFacilitiesList( self.handle.as_ptr(), - bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, + facility_type.into(), request_id, )); } @@ -235,6 +259,8 @@ impl SimConnect { } /// Receive the next SimConnect message. + /// + /// # Remarks /// This is a non-blocking function. If there are no messages to receive, it will return None immediately. /// When called in a loop, it is recommended to use a short sleep time. pub fn get_next_dispatch(&self) -> Result, SimConnectError> { @@ -273,12 +299,12 @@ impl SimConnect { match object_type { Some(object_type) => { - let data = NotificationData { - type_id: object_type.clone(), + let data = Object { + type_name: object_type.clone(), data_addr: std::ptr::addr_of!(event.dwData), }; - Some(Notification::Data(data)) + Some(Notification::Object(data)) } _ => None, } @@ -290,19 +316,141 @@ impl SimConnect { ) }; - let data = event - .rgData - .iter() - .map(|data| AirportData { - icao: fixed_c_str_to_string(&data.Icao), - lat: data.Latitude, - lon: data.Longitude, - alt: data.Altitude, + let data = (0..event._base.dwArraySize as usize) + .map(|i| { + // `rgData` is defined as a 1-element array, but it is actually a variable-length array. + let record = unsafe { event.rgData.get_unchecked(i) }; + + Airport { + icao: fixed_c_str_to_string(&record.Icao), + lat: record.Latitude, + lon: record.Longitude, + alt: record.Altitude, + } }) .collect::>(); Some(Notification::AirportList(data)) } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_WAYPOINT_LIST => { + let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST = unsafe { + std::mem::transmute_copy( + &(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST), + ) + }; + + let data = (0..event._base.dwArraySize as usize) + .map(|i| { + // `rgData` is defined as a 1-element array, but it is actually a variable-length array. + let record = unsafe { event.rgData.get_unchecked(i) }; + + Waypoint { + icao: fixed_c_str_to_string(&record._base.Icao), + lat: record._base.Latitude, + lon: record._base.Longitude, + alt: record._base.Altitude, + mag_var: record.fMagVar, + } + }) + .collect::>(); + + Some(Notification::WaypointList(data)) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NDB_LIST => { + let event: &bindings::SIMCONNECT_RECV_NDB_LIST = unsafe { + std::mem::transmute_copy( + &(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST), + ) + }; + + let data = (0..event._base.dwArraySize as usize) + .map(|i| { + // `rgData` is defined as a 1-element array, but it is actually a variable-length array. + let record = unsafe { event.rgData.get_unchecked(i) }; + + NDB { + icao: fixed_c_str_to_string(&record._base._base.Icao), + lat: record._base._base.Latitude, + lon: record._base._base.Longitude, + alt: record._base._base.Altitude, + mag_var: record._base.fMagVar, + frequency: record.fFrequency, + } + }) + .collect::>(); + + Some(Notification::NdbList(data)) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_VOR_LIST => { + let event: &bindings::SIMCONNECT_RECV_VOR_LIST = unsafe { + std::mem::transmute_copy( + &(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST), + ) + }; + + let data = (0..event._base.dwArraySize as usize) + .map(|i| { + // `rgData` is defined as a 1-element array, but it is actually a variable-length array. + let record = unsafe { event.rgData.get_unchecked(i) }; + + let has_nav_signal = record.Flags + & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL + == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL; + let has_localizer = record.Flags + & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER + == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER; + let has_glide_slope = record.Flags + & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE + == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE; + let has_dme = record.Flags & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME + == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME; + + VOR { + icao: fixed_c_str_to_string(&record._base._base._base.Icao), + lat: record._base._base._base.Latitude, + lon: record._base._base._base.Longitude, + alt: record._base._base._base.Altitude, + mag_var: record._base._base.fMagVar, + has_nav_signal, + has_localizer, + has_glide_slope, + has_dme, + localizer: if has_localizer { + Some(record.fLocalizer) + } else { + None + }, + glide_lat: if has_nav_signal { + Some(record.GlideLat) + } else { + None + }, + glide_lon: if has_nav_signal { + Some(record.GlideLon) + } else { + None + }, + glide_alt: if has_nav_signal { + Some(record.GlideAlt) + } else { + None + }, + glide_slope_angle: if has_glide_slope { + Some(record.fGlideSlopeAngle) + } else { + None + }, + frequency: if has_dme { + Some(record._base.fFrequency) + } else { + None + }, + } + }) + .collect::>(); + + Some(Notification::VorList(data)) + } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => { let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) }; Some(Notification::Exception(event.dwException)) @@ -315,6 +463,28 @@ impl SimConnect { Ok(result) } + + /// Register a request id in the internal state so that the user doesn't have to manually manage requests ids. + #[tracing::instrument(name = "SimConnect::register_request_id")] + fn register_request_id(&mut self, type_name: String) -> Result { + if self.registered_objects.contains(&type_name) { + return Err(SimConnectError::ObjectAlreadyRegistered(type_name)); + } + + self.registered_objects.push(type_name.clone()); + + // using the index for now because we don't unregister objects, yet + let id = self + .registered_objects + .iter() + .position(|p| p == &type_name) + .ok_or_else(|| { + SimConnectError::UnexpectedError("failed to find registered event".to_string()) + })?; + let id = u32::try_from(id)?; + + Ok(id) + } } impl Drop for SimConnect { diff --git a/simconnect-sdk/src/simconnect_object_ext.rs b/simconnect-sdk/src/simconnect_object_ext.rs index 1ec3812..f204c3b 100644 --- a/simconnect-sdk/src/simconnect_object_ext.rs +++ b/simconnect-sdk/src/simconnect_object_ext.rs @@ -1,6 +1,6 @@ -use crate::{NotificationData, SimConnect, SimConnectError}; +use crate::{Object, SimConnect, SimConnectError}; /// Trait to be implemented by objects that can be registered with SimConnect. -pub trait SimConnectObjectExt: Clone + for<'a> TryFrom<&'a NotificationData> { +pub trait SimConnectObjectExt: Clone + for<'a> TryFrom<&'a Object> { fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError>; }