simetry 0.2.3

Interface with telemetry of various racing and driving sims
Documentation
use crate::{Moment, Pedals, RacingFlags, Simetry};
use anyhow::Result;
use hyper::body::Buf;
use hyper::client::HttpConnector;
use hyper::{Client, Uri};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::time::Duration;
use tokio::time::timeout;
use uom::si::f64::{AngularVelocity, Velocity};

pub const DEFAULT_ADDRESS: &str = "0.0.0.0:25055";
pub const DEFAULT_URI: &str = "http://localhost:25055/";

#[derive(Debug)]
pub struct GenericHttpClient {
    name: String,
    client: Client<HttpConnector>,
    uri: Uri,
}

impl GenericHttpClient {
    pub async fn connect(uri: &str, retry_delay: Duration) -> Self {
        loop {
            if let Ok(client) = Self::try_connect(uri).await {
                return client;
            }
            tokio::time::sleep(retry_delay).await;
        }
    }

    pub async fn try_connect(uri: &str) -> Result<Self> {
        let mut slf = Self {
            name: "".to_string(),
            client: Client::new(),
            uri: uri.parse()?,
        };
        let sim_state = slf.query().await?;
        slf.name = sim_state.name;
        Ok(slf)
    }

    pub async fn query(&self) -> Result<SimState> {
        let response = self.client.get(self.uri.clone()).await?;
        let bytes = hyper::body::to_bytes(response.into_body()).await?;
        let data = serde_json::from_reader(bytes.reader())?;
        Ok(data)
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SimState {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub vehicle_left: Option<bool>,
    #[serde(default)]
    pub vehicle_right: Option<bool>,
    #[serde(default)]
    pub gear: Option<i8>,
    #[serde(default)]
    pub speed: Option<Velocity>,
    #[serde(default)]
    pub engine_rotation_speed: Option<AngularVelocity>,
    #[serde(default)]
    pub max_engine_rotation_speed: Option<AngularVelocity>,
    #[serde(default)]
    pub pit_limiter_engaged: Option<bool>,
    #[serde(default)]
    pub in_pit_lane: Option<bool>,
    #[serde(default)]
    pub shift_point: Option<AngularVelocity>,
    #[serde(default)]
    pub flags: Option<RacingFlags>,
    #[serde(default)]
    pub vehicle_brand_id: Option<String>,
    #[serde(default)]
    pub vehicle_model_id: Option<String>,
    #[serde(default)]
    pub vehicle_unique_id: Option<String>,
    #[serde(default)]
    pub left_turn_indicator_on: Option<bool>,
    #[serde(default)]
    pub right_turn_indicator_on: Option<bool>,
    #[serde(default)]
    pub hazard_indicator_on: Option<bool>,
    #[serde(default)]
    pub ignition_on: Option<bool>,
    #[serde(default)]
    pub starter_on: Option<bool>,
    #[serde(default)]
    pub pedals: Option<Pedals>,
    #[serde(default)]
    pub pedals_raw: Option<Pedals>,
}

#[async_trait::async_trait]
impl Simetry for GenericHttpClient {
    fn name(&self) -> &str {
        &self.name
    }

    async fn next_moment(&mut self) -> Option<Box<dyn Moment + Send + Sync + 'static>> {
        let data = timeout(Duration::from_secs(2), self.query())
            .await
            .ok()?
            .ok()?;
        if data.name != self.name {
            return None;
        }
        Some(Box::new(data))
    }
}

impl Moment for SimState {
    fn vehicle_gear(&self) -> Option<i8> {
        self.gear
    }

    fn vehicle_velocity(&self) -> Option<Velocity> {
        self.speed
    }

    fn vehicle_engine_rotation_speed(&self) -> Option<AngularVelocity> {
        self.engine_rotation_speed
    }

    fn vehicle_max_engine_rotation_speed(&self) -> Option<AngularVelocity> {
        self.max_engine_rotation_speed
    }

    fn is_pit_limiter_engaged(&self) -> Option<bool> {
        self.pit_limiter_engaged
    }

    fn is_vehicle_in_pit_lane(&self) -> Option<bool> {
        self.in_pit_lane
    }

    fn is_vehicle_left(&self) -> Option<bool> {
        self.vehicle_left
    }

    fn is_vehicle_right(&self) -> Option<bool> {
        self.vehicle_right
    }

    fn shift_point(&self) -> Option<AngularVelocity> {
        self.shift_point
    }

    fn flags(&self) -> Option<RacingFlags> {
        self.flags.clone()
    }

    fn vehicle_brand_id(&self) -> Option<Cow<str>> {
        Some(self.vehicle_brand_id.as_ref()?.into())
    }

    fn vehicle_model_id(&self) -> Option<Cow<str>> {
        Some(self.vehicle_model_id.as_ref()?.into())
    }

    fn vehicle_unique_id(&self) -> Option<Cow<str>> {
        Some(self.vehicle_unique_id.as_ref()?.into())
    }

    fn is_left_turn_indicator_on(&self) -> Option<bool> {
        self.left_turn_indicator_on
    }

    fn is_right_turn_indicator_on(&self) -> Option<bool> {
        self.right_turn_indicator_on
    }

    fn is_hazard_indicator_on(&self) -> Option<bool> {
        self.hazard_indicator_on
    }

    fn is_ignition_on(&self) -> Option<bool> {
        self.ignition_on
    }

    fn is_starter_on(&self) -> Option<bool> {
        self.starter_on
    }

    fn pedals(&self) -> Option<Pedals> {
        self.pedals.clone()
    }

    fn pedals_raw(&self) -> Option<Pedals> {
        self.pedals_raw.clone()
    }
}