Dirty FSM

Crates.io Docs.rs Build Clippy Audit

dirty-fsm is a "Quick and Dirty" implementation of a finite state machine. Most of the concepts come from code I wrote as part of io.github.frc5024.lib5k.libkontrol.

Example

In the following example, I model a state machine that represents a claw and a button. When the button is pressed, the claw will toggle between open and closed.

We start by setting a feature flag and loading the library.

``rust ignore // This feature is required to use the new#[default]` macro on enum variants

![feature(derivedefaultenum)]

use dirty_fsm::*; use thiserror::Error; ```

Next, we define the states of the machine.

```rust ignore /// The possible states of the claw

[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]

enum ClawState { /// The claw is closed #[default] ClawClosed,

/// The claw is open
ClawOpen

} ```

Along with the states, we need to define some kind of error type (although if not needed at all, we can just use ()).

```rust ignore /// Defines errors that can occur while running actions

[derive(Debug, Error)]

enum ClawError { /// An example error #[error("Example error")] ExampleError, } ```

Next, we define the code to actually run during our first state (ClawClosed). This is a regular Rust struct, that implements the Action trait.

Action contains a few simple functions that are called at various points throughout the action's life:

```rust ignore /// Action that actually handles the claw being closed

[derive(Debug)]

struct ClawClosedAction;

impl Action for ClawClosedAction { fn on_register(&mut self) -> Result<(), ClawError> { println!("ClawClosedAction has been registered with the state machine"); Ok(()) }

fn on_first_run(&mut self, context: &bool) -> Result<(), ClawError> {
    println!("Button has been pressed, claw is closing");
    Ok(())
}

fn execute(
    &mut self,
    delta: &chrono::Duration,
    context: &bool,
) -> Result<crate::action::ActionFlag<ClawState>, ClawError> {
    println!("Claw code is running now");

    // If the button is pressed, switch to the next claw state
    if context {
        Ok(ActionFlag::SwitchState(ClawState::ClawOpen))
    } else {
        Ok(ActionFlag::Continue)
    }
}

fn on_finish(&mut self, interrupted: bool) -> Result<(), ClawError> {
    println!("ClawClosedAction is done executing");
    Ok(())
}

} ```

Since we have two states, this needs to be done again for the other state.

```rust ignore /// Action that actually handles the claw being opened

[derive(Debug)]

struct ClawOpenedAction;

impl Action for ClawOpenedAction { fn on_register(&mut self) -> Result<(), ()> { println!("ClawOpenedAction has been registered with the state machine"); Ok(()) }

fn on_first_run(&mut self, context: &bool) -> Result<(), ClawError> {
    println!("Button has been pressed, claw is opening");
    Ok(())
}

fn execute(
    &mut self,
    delta: &chrono::Duration,
    context: &bool,
) -> Result<crate::action::ActionFlag<ClawState>, ClawError> {
    println!("Claw code is running now");

    // If the button is pressed, throw an error as an example
    if *context {
        Err(ClawError::ExampleError)
    } else {
        Ok(ActionFlag::Continue)
    }
}

fn on_finish(&mut self, interrupted: bool) -> Result<(), ClawError> {
    println!("ClawOpenedAction is done executing");
    Ok(())
}

} ```

Finally, the code to start and run the state machine:

```rust ignore fn main() { // Create the state machine let mut clawmachine = StateMachine::new(); clawmachine.addaction(ClawState::ClawClosed, ClawClosedAction {}).unwrap(); clawmachine.add_action(ClawState::ClawOpen, ClawOpenedAction {}).unwrap();

// State. This example assumes some outside "force" is changing this value
let mut button_pressed = false;

// Run the state machine
loop {
    claw_machine.run(&mut button_pressed).unwrap();
}

} ```