Closed shanemmattner closed 1 year ago
Hi! The example below should work:
use std::thread::sleep;
use std::time::Duration;
use anyhow::Result;
use esp_idf_hal::gpio;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::prelude::Peripherals;
use statig::prelude::*;
enum Event {
TimerElapsed,
}
struct Blinky {
led: PinDriver<'static, gpio::Gpio27, gpio::Output>,
}
impl Blinky {
fn new(led: PinDriver<'static, gpio::Gpio27, gpio::Output>) -> Self {
Self { led }
}
}
#[state_machine(initial = "State::on()")]
impl Blinky {
#[action]
fn enter_on(&mut self) {
self.led.set_high().unwrap();
}
#[state(entry_action = "enter_on")]
fn on(event: &Event) -> Response<State> {
match event {
Event::TimerElapsed => Transition(State::off()),
}
}
#[action]
fn enter_off(&mut self) {
self.led.set_low().unwrap();
}
#[state(entry_action = "enter_off")]
fn off(&mut self, event: &Event) -> Response<State> {
match event {
Event::TimerElapsed => Transition(State::on()),
}
}
}
fn main() -> Result<()> {
esp_idf_sys::link_patches();
// Config logging.
esp_idf_svc::log::EspLogger::initialize_default();
let dp = Peripherals::take().unwrap();
let led = PinDriver::output(dp.pins.gpio27).unwrap();
let mut blinky = Blinky::new(led).state_machine().init();
loop {
sleep(Duration::from_millis(500));
blinky.handle(&Event::TimerElapsed);
}
}
I noticed that you also asked the question on the Rust user forum and I wanted to add some comments on the answer that was given there. It's important to understand that event handlers such as led_on
and led_off
are called when an event arrives in the associated state. So here for instance the led_on
handler is called when an event arrives in the LedOn
state:
fn led_on(&mut self, event: &Event) -> Response<State> {
self.led.set_high();
match event {
Event::TimerElapsed => Transition(State::LedOff),
_ => Super,
}
}
The thing is, if you set the led to high inside the led_on
event handler and then decide to transition to LedOff
, the led will be turned on in the LedOff
state and vice versa, which is probably not what you'd expect. 😅
So instead you could do this:
fn led_on(&mut self, event: &Event) -> Response<State> {
match event {
Event::TimerElapsed => {
self.led.set_low();
Transition(State::LedOff)
}
_ => Super,
}
}
Here we set the led to low just before transitioning to the LedOff
state, which will give the desired behaviour. However, this is not ideal as you have to make sure you set the led to low every time you transition to the LedOff
state. This is not really a problem in a simple example as this, but in larger state machines it can be easy to forget.
To solve this we can use a feature of statecharts called actions. These run when entering (or leaving) states during a transition, so if we set the led there it will always be done when entering the On
state.
#[action]
fn enter_on(&mut self) {
self.led.set_high().unwrap();
}
#[state(entry_action = "enter_on")]
fn on(event: &Event) -> Response<State> {
match event {
Event::TimerElapsed => Transition(State::off()),
}
}
Thank you @mdeloof! I appreciate your response and added commentary on what I thought was a working solution!
Can anyone give a small example of accessing a GPIO or some other peripheral from a state? I got the example
blinky
working on an ESP32-C3, but I can't get an LED to blink.