4 releases

0.1.3 Apr 17, 2025
0.1.2 Apr 17, 2025
0.1.1 Apr 17, 2025
0.1.0 Apr 17, 2025

#685 in Asynchronous

Download history 282/week @ 2025-04-12 88/week @ 2025-04-19

370 downloads per month

MIT license

18KB
220 lines

Async fsm

Async fsm is a rust lib providing engine to create Async Finite State Machine.

Aync fsm is using tokio mpsc channel to send events into the StateMachine engine and providing channel to subscribe for the state chages. The more advanced logic of the transitions need to be implemented in the Transition trait.

Dependencies

  • tokio
  • log
  • async_trait

Example implementation

use async_fsm::*;
use async_trait::async_trait;
use log::info;
use log::LevelFilter;
use std::io::Write;

#[derive(Default, Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum State {
    #[default]
    Unknown,
    LowBattery,
    HalflyCharged,
    FullyCharged,
    Charging,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Event {
    BatteryLevel(u32),
    PlugIn,
    PlugOut,
}

#[derive(Debug, Default)]
struct UserData {}

#[derive(Clone)]
struct BatteryLevelState;

#[async_trait]
impl Transition<Event, State, UserData> for BatteryLevelState {
    async fn next(&mut self, event: Event, data: &Data<Event, State, UserData>) -> State {
        match event {
            Event::BatteryLevel(level) if level >= 80 => State::FullyCharged,
            Event::BatteryLevel(level) if level >= 50 => State::HalflyCharged,
            Event::BatteryLevel(_) => State::LowBattery,
            Event::PlugIn => State::Charging,
            _ => data.state,
        }
    }
}

struct ChargingState;
#[async_trait]
impl Transition<Event, State, UserData> for ChargingState {
    async fn next(&mut self, event: Event, data: &Data<Event, State, UserData>) -> State {
        match event {
            Event::PlugOut => State::Unknown, // switch to unknown state and wait for battery level update.
            _ => data.state,
        }
    }
}

#[tokio::main]
async fn main() {
    env_logger::Builder::new()
        .format(|buf, record| writeln!(buf, "[{}] {}", record.level(), record.args()))
        .filter(None, LevelFilter::Debug)
        .init();

    let (mut stm, event_sender) = StateMachine::<Event, State, UserData>::new(100);
    let battery_level_state = Box::new(BatteryLevelState {});
    stm.add_transition(State::Unknown, battery_level_state.clone());
    stm.add_transition(State::LowBattery, battery_level_state.clone());
    stm.add_transition(State::HalflyCharged, battery_level_state.clone());
    stm.add_transition(State::FullyCharged, battery_level_state.clone());
    stm.add_transition(State::Charging, Box::new(ChargingState {}));

    let mut state_subscription = stm.subscribe();

    let _ = tokio::spawn(async move {
        stm.process().await;
    });

    let states = tokio::spawn(async move {
        while let Ok(state) = state_subscription.recv().await {
            info!("Changed State: {state:?}");
        }
    });

    let _ = event_sender.send(Event::BatteryLevel(82)).await;
    let _ = event_sender.send(Event::BatteryLevel(45)).await;
    let _ = event_sender.send(Event::PlugIn).await;
    let _ = event_sender.send(Event::PlugOut).await;
    let _ = event_sender.send(Event::BatteryLevel(55)).await;

    let _ = states.await;
}

Dependencies

~3.5–9MB
~73K SLoC