Accessing pins from state machine

I'm trying to make a blinky app targeting the ESP32-C3 using statig HSM crate but I can't figure out how to access pins (or other peripherals) from the states.

I got the statig blinky example working which prints out state transitions and events. Can anyone give some advice on the proper way to access peripherals in this scenario?

Here's the code I have so far:

use core::fmt::Debug;
use esp_idf_hal::prelude::*;
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use statig::prelude::*;
use statig::StateOrSuperstate;

fn main() {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();
    let _led = peripherals.pins.gpio8.into_output().unwrap();

    let mut state_machine = Blinky::default().state_machine().init();

    state_machine.handle(&Event::TimerElapsed);
    state_machine.handle(&Event::ButtonPressed);
    state_machine.handle(&Event::TimerElapsed);
    state_machine.handle(&Event::ButtonPressed);
}


#[derive(Debug, Default)]
pub struct Blinky;

// The event that will be handled by the state machine.
#[derive(Debug)]
pub enum Event {
    TimerElapsed,
    ButtonPressed,
}

/// The `state_machine` procedural macro generates the `State` and `Superstate`
/// enums by parsing the function signatures with a `state`, `superstate` or
/// `action` attribute. It also implements the `statig::State` and
/// `statig::Superstate` traits.
#[state_machine(
    // This sets the initial state to `led_on`.
    initial = "State::led_on()",
    // Derive the Debug trait on the `State` enum.
    state(derive(Debug)),
    // Derive the Debug trait on the `Superstate` enum.
    superstate(derive(Debug)),
    // Set the `on_transition` callback.
    on_transition = "Self::on_transition",
    // Set the `on_dispatch` callback.
    on_dispatch = "Self::on_dispatch"
)]
impl Blinky {
    /// The `#[state]` attibute marks this as a state handler.  By default the
    /// `event` argument will map to the event handler by the state machine.
    /// Every state must return a `Response<State>`.
    #[state(superstate = "blinking")]
    fn led_on(event: &Event) -> Response<State> {
        match event {
            // When we receive a `TimerElapsed` event we transition to the `led_off` state.
            Event::TimerElapsed => Transition(State::led_off()),
            // Other events are deferred to the superstate, in this case `blinking`.
            _ => Super,
        }
    }

    #[state(superstate = "blinking")]
    fn led_off(event: &Event) -> Response<State> {
        match event {
            Event::TimerElapsed => Transition(State::led_on()),
            _ => Super,
        }
    }

    /// The `#[superstate]` attribute marks this as a superstate handler.
    #[superstate]
    fn blinking(event: &Event) -> Response<State> {
        match event {
            Event::ButtonPressed => Transition(State::not_blinking()),
            _ => Super,
        }
    }

    #[state]
    fn not_blinking(event: &Event) -> Response<State> {
        match event {
            Event::ButtonPressed => Transition(State::led_on()),
            // Altough this state has no superstate, we can still defer the event which
            // will cause the event to be handled by an implicit `top` superstate.
            _ => Super,
        }
    }
}

impl Blinky {
    // The `on_transition` callback that will be called after every transition.
    fn on_transition(&mut self, source: &State, target: &State) {
        println!("transitioned from `{:?}` to `{:?}`", source, target);
    }

    fn on_dispatch(&mut self, state: StateOrSuperstate<Self>, event: &Event) {
        on_dispatch(self, state, event);
    }
}

// The `on_dispatch` callback that will be called before an event is dispatched
// to a state or superstate.
fn on_dispatch<M, S, E>(state_machine: M, state: S, event: E)
where
    M: Debug,
    S: Debug,
    E: Debug,
{
    println!(
        "{:?}: dispatching `{:?}` to `{:?}`",
        state_machine, event, state
    );
}

I have no experience with statig crate. So, this is just my guess.
I think you want to define Blinky like this.

pub struct Blinky {
    led: PinDriver<'static, esp_idf_hal::gpio::Gpio8, Output>,
}

Instead of calling Blinky::default(), you init Blinky with your led.

And change impl Blinky to take &mut self so that you can set the led high or low.

fn led_on(&mut self, event: &Event) -> Response<State> {
    self.led.set_high();
    match event {
        Event::TimerElapsed => Transition(State::LedOff),
        _ => Super,
    }
}
2 Likes

Thank you @lonesometraveler, that worked! I had to comment out on_dispatch() as the pins don't have Debug implemented.

Here's the working code for anyone interested:

use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported

use core::fmt::Debug;
use embedded_hal::digital::v2::OutputPin;
use esp_idf_hal::{gpio, prelude::*};

use statig::prelude::*;
// use statig::StateOrSuperstate;

use std::thread;
use std::time::Duration;

fn main() {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();
    let led = peripherals.pins.gpio8.into_output().unwrap();

    let mut state_machine = Blinky { led: led }.state_machine().init();

    loop {
        thread::sleep(Duration::from_millis(1000));

        state_machine.handle(&Event::TimerElapsed);
        // state_machine.handle(&Event::ButtonPressed);
        thread::sleep(Duration::from_millis(1000));

        state_machine.handle(&Event::TimerElapsed);
        // state_machine.handle(&Event::ButtonPressed);
    }
}
// #[derive(Debug, Default)]
pub struct Blinky {
    led: gpio::Gpio8<gpio::Output>,
}

// The event that will be handled by the state machine.
#[derive(Debug)]
pub enum Event {
    TimerElapsed,
    ButtonPressed,
}

/// The `state_machine` procedural macro generates the `State` and `Superstate`
/// enums by parsing the function signatures with a `state`, `superstate` or
/// `action` attribute. It also implements the `statig::State` and
/// `statig::Superstate` traits.
#[state_machine(
    // This sets the initial state to `led_on`.
    initial = "State::led_on()",
    // Derive the Debug trait on the `State` enum.
    state(derive(Debug)),
    // Derive the Debug trait on the `Superstate` enum.
    superstate(derive(Debug)),
    // Set the `on_transition` callback.
    on_transition = "Self::on_transition",
    // Set the `on_dispatch` callback.
    // on_dispatch = "Self::on_dispatch"
)]
impl Blinky {
    /// The `#[state]` attibute marks this as a state handler.  By default the
    /// `event` argument will map to the event handler by the state machine.
    /// Every state must return a `Response<State>`.
    #[state(superstate = "blinking")]
    fn led_on(&mut self, event: &Event) -> Response<State> {
        self.led.set_high().unwrap();
        println!("Set high");
        match event {
            // When we receive a `TimerElapsed` event we transition to the `led_off` state.
            Event::TimerElapsed => Transition(State::led_off()),
            // Other events are deferred to the superstate, in this case `blinking`.
            _ => Super,
        }
    }

    #[state(superstate = "blinking")]
    fn led_off(&mut self, event: &Event) -> Response<State> {
        self.led.set_low().unwrap();
        println!("Set low");
        match event {
            Event::TimerElapsed => Transition(State::led_on()),
            _ => Super,
        }
    }

    /// The `#[superstate]` attribute marks this as a superstate handler.
    #[superstate]
    fn blinking(event: &Event) -> Response<State> {
        match event {
            Event::ButtonPressed => Transition(State::not_blinking()),
            _ => Super,
        }
    }

    #[state]
    fn not_blinking(event: &Event) -> Response<State> {
        match event {
            Event::ButtonPressed => Transition(State::led_on()),
            // Altough this state has no superstate, we can still defer the event which
            // will cause the event to be handled by an implicit `top` superstate.
            _ => Super,
        }
    }
}

impl Blinky {
    // The `on_transition` callback that will be called after every transition.
    fn on_transition(&mut self, source: &State, target: &State) {
        println!("transitioned from `{:?}` to `{:?}`", source, target);
    }

    // fn on_dispatch(&mut self, state: StateOrSuperstate<Self>, event: &Event) {
    //     on_dispatch(self, state, event);
    // }
}

// The `on_dispatch` callback that will be called before an event is dispatched
// to a state or superstate.
// fn on_dispatch<M, S, E>(state_machine: M, state: S, event: E)
// where
//     M: Debug,
//     S: Debug,
//     E: Debug,
// {
//     println!(
//         "{:?}: dispatching `{:?}` to `{:?}`",
//         state_machine, event, state
//     );
// }
2 Likes