Add support for facilities
This commit is contained in:
@@ -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");
|
||||
|
||||
|
@@ -1,8 +0,0 @@
|
||||
/// Airport data.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AirportData {
|
||||
pub icao: String,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
pub alt: f64,
|
||||
}
|
@@ -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<Condition> for u32 {
|
||||
fn from(condition: Condition) -> Self {
|
||||
match condition {
|
||||
Condition::None => 0,
|
||||
Condition::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
122
simconnect-sdk/src/domain/facilities.rs
Normal file
122
simconnect-sdk/src/domain/facilities.rs
Normal file
@@ -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<FacilityType> 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::<Airport>(),
|
||||
FacilityType::Waypoint => std::any::type_name::<Waypoint>(),
|
||||
FacilityType::NDB => std::any::type_name::<NDB>(),
|
||||
FacilityType::VOR => std::any::type_name::<VOR>(),
|
||||
}
|
||||
.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<f32>,
|
||||
/// The latitude of the glide slope transmitter in degrees.
|
||||
pub glide_lat: Option<f64>,
|
||||
/// The longitude of the glide slope transmitter in degrees.
|
||||
pub glide_lon: Option<f64>,
|
||||
/// The altitude of the glide slope transmitter in degrees.
|
||||
pub glide_alt: Option<f64>,
|
||||
/// The ILS approach angle in degrees.
|
||||
pub glide_slope_angle: Option<f32>,
|
||||
/// Frequency of the station in Hz.
|
||||
pub frequency: Option<u32>,
|
||||
}
|
@@ -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::*;
|
||||
|
@@ -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<AirportData>),
|
||||
Object(Object),
|
||||
/// A list of [crate::Airport].
|
||||
AirportList(Vec<Airport>),
|
||||
/// A list of [crate::Waypoint].
|
||||
WaypointList(Vec<Waypoint>),
|
||||
/// A list of [crate::NDB].
|
||||
NdbList(Vec<NDB>),
|
||||
/// A list of [crate::VOR].
|
||||
VorList(Vec<VOR>),
|
||||
/// 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<T: SimConnectObjectExt>(&self) -> Result<T, SimConnectError> {
|
||||
let type_id: String = std::any::type_name::<T>().into();
|
||||
let type_name: String = std::any::type_name::<T>().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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/// SimConnect event notification group.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum NotificationGroup {
|
||||
Group0,
|
||||
|
@@ -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<Period> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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::<GpsData>()?;
|
||||
//! }
|
||||
//! 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:?}");
|
||||
//! }
|
||||
//! }
|
||||
//! _ => (),
|
||||
|
@@ -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::<GpsData>()?;
|
||||
/// }
|
||||
/// 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<T: SimConnectObjectExt>(&mut self) -> Result<u32, SimConnectError> {
|
||||
let type_id: String = std::any::type_name::<T>().into();
|
||||
let type_name: String = std::any::type_name::<T>().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<Option<Notification>, 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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<u32, SimConnectError> {
|
||||
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 {
|
||||
|
@@ -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>;
|
||||
}
|
||||
|
Reference in New Issue
Block a user