m365 0.1.1

A lightweight rust library to receive BLE Xiaomi M365 scooter messages
Documentation
use super::{MiSession, Payload};
use super::commands::{ScooterCommand, Direction, Attribute, ReadWrite};

use anyhow::Result;
use serde::Serialize;

#[derive(Debug, Serialize)]
pub enum Kers {
  Weak,
  Medium,
  Strong,
  Unknown
}

#[derive(Debug, Serialize)]
pub enum TailLight {
  Off,
  OnBrake,
  Always,
  Unknown
}

impl From<u16> for TailLight {
  fn from(byte: u16) -> Self {
    match byte {
      0x0 => TailLight::Off,
      0x1 => TailLight::OnBrake,
      0x2 => TailLight::Always,
      _   => TailLight::Unknown
    }
  }
}

impl From<u16> for Kers {
  fn from(byte: u16) -> Self {
    match byte {
      0x0 => Kers::Weak,
      0x1 => Kers::Medium,
      0x2 => Kers::Strong,
      _   => Kers::Unknown
    }
  }
}

#[derive(Debug, Serialize)]
pub struct SupplementaryInfo {
  kers: Kers,
  is_cruise: bool,
  tail_light: TailLight
}

impl TryFrom<Payload> for SupplementaryInfo {
  type Error = anyhow::Error;

  fn try_from(payload: Payload) -> Result<Self, Self::Error> {
    let mut payload = payload;
    payload.pop_head()?;

    Ok(
      SupplementaryInfo {
        kers: payload.pop_u16()?.into(),
        is_cruise: payload.pop_bool()?,
        tail_light: TailLight::from(payload.pop_u16()?),
      }
    )
  }
}

impl MiSession {
  pub async fn supplementary_info(&mut self) -> Result<SupplementaryInfo> {
    tracing::debug!("Reading supplementary information");

    self.send(&ScooterCommand {
      direction: Direction::MasterToBattery,
      read_write: ReadWrite::Read,
      attribute: Attribute::Supplementary,
      payload: vec![0x06]
    }).await?;

    let payload = self.read(2).await?;

    Ok(SupplementaryInfo::try_from(payload)?)
  }

  pub async fn is_cruise_on(&mut self) -> Result<bool> {
    tracing::debug!("Reading cruise state");

    self.send(&ScooterCommand {
      direction: Direction::MasterToMotor,
      read_write: ReadWrite::Read,
      attribute: Attribute::Cruise,
      payload: vec![0x02]
    }).await?;

    let mut payload = self.read(2).await?;
    payload.pop_head()?;

    Ok(payload.pop_bool()?)
  }

  pub async fn tail_light(&mut self) -> Result<TailLight> {
    tracing::debug!("Reading tail light state");

    self.send(&ScooterCommand {
      direction: Direction::MasterToMotor,
      read_write: ReadWrite::Read,
      attribute: Attribute::TailLight,
      payload: vec![0x02]
    }).await?;

    let mut payload = self.read(2).await?;
    payload.pop_head()?;

    Ok(
      TailLight::from(payload.pop_u16()?)
    )
  }

  pub async fn set_tail_light(&mut self, mode : TailLight) -> Result<()> {
    tracing::debug!("Setting tail light: {:?}", mode);

    let mode : u8 = match mode {
      TailLight::OnBrake => 0x01,
      TailLight::Always => 0x02,
      _ => 0x00
    };

    let payload = vec![mode, 0x00];

    self.send(&ScooterCommand {
      direction: Direction::MasterToMotor,
      read_write: ReadWrite::Write,
      attribute: Attribute::TailLight,
      payload
    }).await?;

    Ok(())
  }

  pub async fn set_cruise(&mut self, on : bool) -> Result<()> {
    tracing::debug!("Setting cruise enabled: {}", on);

    let payload = if on {
      vec![0x01, 0x00]
    } else {
      vec![0x00, 0x00]
    };

    self.send(&ScooterCommand {
      direction: Direction::MasterToMotor,
      read_write: ReadWrite::Write,
      attribute: Attribute::Cruise,
      payload
    }).await?;

    Ok(())
  }
}