m365 0.1.1

A lightweight rust library to receive BLE Xiaomi M365 scooter messages
Documentation
use tracing::Level;
use tracing_subscriber::fmt::format::FmtSpan;

use btleplug::platform::{Peripheral};
use btleplug::api::BDAddr;
use tokio::io::{BufWriter, AsyncWriteExt};
use tokio::fs::File;
use pretty_hex::*;
use std::env;
use tracing_subscriber;
use std::path::Path;
use anyhow::Result;

use m365::{
  ScooterScanner, ScannerEvent,
  RegistrationRequest, RegistrationError,
  ConnectionHelper, AuthToken
};

async fn save_token(token : &AuthToken) -> Result<()> {
  let path = Path::new(".mi-token");
  tracing::info!("Saving token at {:?} with content {:?}", path, token.hex_dump());
  let f = File::create(path).await?;
  {
    let mut writer = BufWriter::new(f);
    writer.write(token).await?;
    writer.flush().await?;
  }
  Ok(())
}

async fn register(device: &Peripheral) -> Result<()> {
  let connection = ConnectionHelper::new(&device);

  loop {
    tracing::info!(">>> Press power button up to 5 seconds after beep!");
    connection.reconnect().await?;
    let mut request = RegistrationRequest::new(&device).await?;

    match request.start().await {
      Ok(token) => {
        save_token(&token).await?;
        break;
      },
      Err(RegistrationError::RestartNeeded) => {
        tracing::debug!("Restarting...");
        continue;
      },
      Err(_) => {
        tracing::error!("Unhandled error...");
      }
    }
  }

  Ok(())
}

#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
  tracing_subscriber::fmt()
    .with_max_level(Level::DEBUG)
    .with_span_events(FmtSpan::CLOSE)
    .init();

  let args: Vec<String> = env::args().collect();
  if args.len() < 2 || args[1].is_empty() {
    panic!("First argument is scooter mac address");
  }

  let mac = BDAddr::from_str_delim(&args[1]).expect("Invalid mac address");
  tracing::info!("Searching scooter with address: {}", mac);

  let mut scanner = ScooterScanner::new().await?;
  let mut rx = scanner.start().await?;

  while let Some(event) = rx.recv().await {
    match event {
      ScannerEvent::DiscoveredScooter(scooter) => {
        if scooter.addr == mac {
          tracing::info!("Found your scooter, starting registration");
          let device = scanner.peripheral(&scooter).await?;
          register(&device).await?;
          break;
        } else {
          tracing::info!("Found scooter nearby: {} with mac: {}", scooter.name.unwrap(), scooter.addr);
        }
      }
    }
  }

  Ok(())
}