Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifying a shared variable from an async interrupt #158

Closed
joachimBurket opened this issue Sep 29, 2024 · 4 comments
Closed

Modifying a shared variable from an async interrupt #158

joachimBurket opened this issue Sep 29, 2024 · 4 comments

Comments

@joachimBurket
Copy link

Hi,

I'm working on a project with a StateMachine (using statig library), and I would like to send events to this StateMachine on InputPin events.

I'm configuring my input like this:

use std::error::Error;
use std::time::Duration;
use rppal::gpio::{Gpio, Event, InputPin, Level, Trigger};

const INPUT_PIN_GPIO: u8 = 27;

fn input_callback(event: Event) {
    println!("Input event: {:?}", event);

    // TODO: Here I want to send an event to my state machine
    // my_state_machine.handle("EventName");
}

fn main() -> Result<(), Box<dyn Error>> {
    // configure input pin
    let mut input_pin = Gpio::new()?.get(INPUT_PIN_GPIO)?.into_input_pullup();
    input_pin.set_async_interrupt(
        Trigger::FallingEdge,
        Some(Duration::from_millis(50)),
        input_callback,
    )?;

    //  initialize the state machine
   let mut my_state_machine = MyStateMachine::default().state_machine();
    my_state_machine.init();
}

How would be the best way to achieve this?
I'm not very experimented in Rust and instinctively I thought of some sort of global variable and a Mutex, but I can't figure out how to make it work and I'm not sure if I'm going in the right direction.

Thanks for your help and your work on this library!

NOTE: I simplified the use case for the example, but I have more than one Input that can send different events to the StateMachine

@CosminPerRam
Copy link
Contributor

When your function (input_callback) is ran, it starts in another thread rather than the one from your main function and because of this you must make sure that your instance (same piece of information) exists across threads (we can do this using Arc) and that it can be modified from another threads (using a Mutex).

You can do this in a lot of ways (some better and some worse), note that this is quite complex to do and understand in Rust, reading about threads and how to share data across them will make things a lot clearer!

An easy way to do this is by using the OnceCell library.

use std::sync::{Arc, Mutex, OnceLock};
use std::thread;

// define our global data, not containing anything at the moment
static GLOBAL_DATA: OnceLock<Arc<Mutex<i32>>> = OnceLock::new();
/*
Or you can use
static GLOBAL_DATA: LazyCell<Arc<Mutex<i32>>> = LazyCell::new(|| {
    println!("initializing");
    Arc::new(Mutex::new(92))
});
 */

fn main() {
    // Initialize our global data
    GLOBAL_DATA.set(Arc::new(Mutex::new(0))).unwrap();
    // Note the .unwrap() here, if a value has been set before you cannot set it again with this
    
    // This stores our threads handles
    let mut handles = vec![];

    // Lets make some threads
    for i in 0..5 {
        // the .unwrap() here will panic if you haven't set a value to the global instance
        let global_data = GLOBAL_DATA.get().unwrap();
        let handle = thread::spawn(move || {
            let mut num = global_data.lock().unwrap();
            *num += i;
            println!("Thread {i} set value to: {num}");
        });
        handles.push(handle);
    }

    // Wait for them to finish...
    for handle in handles {
        handle.join().unwrap();
    }

    let num = GLOBAL_DATA.get().unwrap().lock().unwrap();
    println!("Final value of global data: {num}");
}

Another solution would be to pass an instance of your data in your function (you mentioned that you want to pass more variables to this function, this is also an example on how to do it):

use std::error::Error;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use rppal::gpio::{Gpio, Event, Trigger};

const INPUT_PIN_GPIO: u8 = 27;

fn input_callback(event: Event, my_data: Arc<Mutex<i32>>) {
    println!("Input event: {:?}", event);
    *my_data.lock().unwrap() = 10;
}

fn main() -> Result<(), Box<dyn Error>> {
    // initialize our data
    let my_data = Arc::new(Mutex::new(0));
    
    // we need this because set_async_interrupt will move it
    let my_data_instance = my_data.clone();
    
    // configure input pin
    let mut input_pin = Gpio::new()?.get(INPUT_PIN_GPIO)?.into_input_pullup();
    input_pin.set_async_interrupt(
        Trigger::FallingEdge,
        Some(Duration::from_millis(50)),
        move |event| {
            // Note: you can add more parameters here now!
            input_callback(event, my_data_instance.clone());
        },
    )?;

    // and here, some later on in your program, can check your value
    println!("{}", my_data.lock().unwrap());
    
    Ok(())
}

@joachimBurket
Copy link
Author

Thank you so much for your great answer!
The second example is what I was looking for, I managed to make my code work with it.

Maybe this example could be added to the examples of the repository? There is no example using the async interrupts.

@CosminPerRam
Copy link
Contributor

@golemparts do you think it's worth to have an example that's similar to my second snippet? I'm up to make the PR.

@golemparts
Copy link
Owner

@golemparts do you think it's worth to have an example that's similar to my second snippet? I'm up to make the PR.

That would definitely be helpful, as these kinds of questions come up relatively often. If you can tweak it to be more inline with the style of the current examples I'd be happy to accept a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants