Add support for facilities

This commit is contained in:
Mihai Dinculescu
2022-10-15 22:51:57 +01:00
parent 307b83ea95
commit 6be70c8566
23 changed files with 535 additions and 137 deletions

12
Cargo.lock generated
View File

@@ -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"

View File

@@ -33,12 +33,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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:?}");
}
}
_ => (),

View File

@@ -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"

View File

@@ -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
```

View File

@@ -25,12 +25,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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:?}");
}
}
_ => (),

View File

@@ -33,19 +33,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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::<GpsData>()?;
client.register_object::<OnGround>()?;
}
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;
}
}

View File

@@ -29,12 +29,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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::<GpsData>()?;
}
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:?}");
}
}
_ => (),

View File

@@ -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<Self, Self::Error> {
fn try_from(value: &Object) -> Result<Self, Self::Error> {
value.try_transmute::<GpsData>()
}
}
@@ -44,12 +44,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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:?}");
}
}
_ => (),

View File

@@ -0,0 +1,64 @@
use simconnect_sdk::{FacilityType, Notification, SimConnect};
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View File

@@ -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<Self, Self::Error> {
fn try_from(value: &simconnect_sdk::Object) -> Result<Self, Self::Error> {
value.try_transmute::<#name>()
}
}

View File

@@ -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");

View File

@@ -1,8 +0,0 @@
/// Airport data.
#[derive(Debug, Clone)]
pub struct AirportData {
pub icao: String,
pub lat: f64,
pub lon: f64,
pub alt: f64,
}

View File

@@ -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,
}
}
}

View File

@@ -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,

View 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>,
}

View File

@@ -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::*;

View File

@@ -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,
})
}
}

View File

@@ -1,5 +1,5 @@
/// SimConnect event notification group.
#[derive(Copy, Clone)]
#[derive(Debug)]
#[repr(u32)]
pub enum NotificationGroup {
Group0,

View File

@@ -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,
}
}
}

View File

@@ -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()
}

View File

@@ -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:?}");
//! }
//! }
//! _ => (),

View File

@@ -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 {

View File

@@ -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>;
}