ubmsc 0.1.1

A client for Battery Management Systems (BMS).
Documentation
mod args;
mod cmdline;

#[cfg(feature = "exporter")]
mod exporter;

#[cfg(feature = "pull")]
mod pull;

#[cfg(feature = "push")]
mod push;

use args::Args;
use btleplug::{api::Manager as _, platform::Manager};
use std::sync::Arc;
use tokio::{
    signal::ctrl_c,
    sync::Notify,
    task::{spawn, JoinSet},
};
use tracing as log;
use ubmsc::{CellData, Client, DeviceId, DeviceInfo, Error, Format, Options, Result};

#[cfg(feature = "exporter")]
use exporter::{Encoding, Exporter};

#[cfg(feature = "metrics")]
use ubmsc::Metrics;

#[cfg_attr(feature = "multi-thread", tokio::main)]
#[cfg_attr(not(feature = "multi-thread"), tokio::main(flavor = "current_thread"))]
async fn main() -> Result<()> {
    let args = Args::from_cmdline();

    if args.version {
        println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
        if !env!("CARGO_PKG_DESCRIPTION").is_empty() {
            println!("{}", env!("CARGO_PKG_DESCRIPTION"));
        }
        return Ok(());
    }

    #[cfg(feature = "tracing-subscriber")]
    if let Some(log) = args.log_filter() {
        use tracing_subscriber::prelude::*;

        let registry = tracing_subscriber::registry().with(log);

        #[cfg(all(feature = "stderr", feature = "journal"))]
        let registry = registry.with(if !args.journal {
            Some(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr))
        } else {
            None
        });

        #[cfg(all(feature = "stderr", not(feature = "journal")))]
        let registry =
            registry.with(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr));

        #[cfg(feature = "journal")]
        let registry = registry.with(if args.journal {
            Some(tracing_journald::Layer::new()?)
        } else {
            None
        });

        registry.init();
    }

    log::info!("Start...");
    log::trace!("{args:?}");

    if !args.has_action() {
        println!("Please specify the action: -i -c -e");
        return Ok(());
    }

    let mut main = Main::new(args);

    main.run().await.map_err(|error| {
        log::error!("Exit with error: {error}");
        error
    })?;

    log::info!("Stop...");

    Ok(())
}

pub struct Main {
    args: Args,
    intr: Arc<Notify>,
    clients: Vec<Client>,
}

impl core::ops::Deref for Main {
    type Target = Args;
    fn deref(&self) -> &Self::Target {
        &self.args
    }
}

impl Main {
    pub fn new(args: Args) -> Self {
        let intr = Self::intr_notify();
        let clients = Vec::default();

        Self {
            args,
            intr,
            clients,
        }
    }

    pub async fn run(&mut self) -> Result<()> {
        let manager = Manager::new().await?;

        self.open_clients(&manager).await?;

        if self.has_command() {
            self.run_commands().await?;
        }

        #[cfg(feature = "pull")]
        if self.has_server() {
            self.run_exporter_server().await?;
        }

        #[cfg(feature = "push")]
        if self.has_client() {
            self.run_exporter_client().await?;
        }

        self.close_clients().await?;

        Ok(())
    }

    fn intr_notify() -> Arc<Notify> {
        let notify = Arc::new(Notify::new());

        spawn({
            let notify = notify.clone();
            async move {
                log::debug!("Avait ctrl-c signal");
                if let Err(error) = ctrl_c().await {
                    log::error!("Error while processing ctrl-c: {error}");
                }
                notify.notify_waiters();
            }
        });

        notify
    }

    async fn open_clients(&mut self, manager: &Manager) -> Result<()> {
        let options = self.client_options();

        let adapter = manager
            .adapters()
            .await?
            .into_iter()
            .next()
            .ok_or_else(|| {
                log::error!("No Bluetooth adapters found");
                Error::NotFound
            })?;

        let found_devices: Vec<_>;

        let devices = if self.device.is_empty() {
            log::warn!("No devices passed. Scan to find all...");
            found_devices = Client::find(&adapter, &options).await?;
            &found_devices
        } else {
            &self.args.device
        };

        log::debug!("Use {} devices", devices.len());

        if devices.is_empty() {
            println!("No BMS devices found!");
            return Err(Error::NotFound);
        }

        for device_id in devices {
            let client = Client::new(&adapter, device_id, &options);
            self.clients.push(client);
        }

        Ok(())
    }

    async fn close_clients(&mut self) -> Result<()> {
        let mut joins = JoinSet::new();

        for client in self.clients.drain(..) {
            joins.spawn(async move {
                if let Err(error) = client.close().await {
                    log::error!("Error while closing client: {error}");
                }
            });
        }

        let _ = joins.join_all().await;

        Ok(())
    }
}