diff options
| author | Dario Nieuwenhuis <[email protected]> | 2025-01-07 22:06:29 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-01-07 22:06:29 +0100 |
| commit | 92fa6536722368b8cb8cabdb42640dc017fb26cb (patch) | |
| tree | 4d27473352b9a9c6db9d85a50818c5279a910132 /examples/rp | |
| parent | 90cb610ef72d45526a3702c0667f2f38b096077b (diff) | |
| parent | 463a3346de74ce354bf5bb55b67eb77e7c5ace73 (diff) | |
Merge pull request #3701 from 1-rafael-1/improve-orchestrate-example
Improve orchestrate_tasks example: shared state and better documentation
Diffstat (limited to 'examples/rp')
| -rw-r--r-- | examples/rp/src/bin/orchestrate_tasks.rs | 311 |
1 files changed, 156 insertions, 155 deletions
diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs index 0e21d5833..7ff004860 100644 --- a/examples/rp/src/bin/orchestrate_tasks.rs +++ b/examples/rp/src/bin/orchestrate_tasks.rs | |||
| @@ -1,20 +1,18 @@ | |||
| 1 | //! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. | 1 | //! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. |
| 2 | //! | 2 | //! |
| 3 | //! We demonstrate how to: | 3 | //! The system consists of several tasks: |
| 4 | //! - use a channel to send messages between tasks, in this case here in order to have one task control the state of the system. | 4 | //! - Three tasks that generate random numbers at different intervals (simulating i.e. sensor readings) |
| 5 | //! - use a signal to terminate a task. | 5 | //! - A task that monitors USB power connection (hardware event handling) |
| 6 | //! - use command channels to send commands to another task. | 6 | //! - A task that reads system voltage (ADC sampling) |
| 7 | //! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple futures. | 7 | //! - A consumer task that processes all this information |
| 8 | //! | 8 | //! |
| 9 | //! There are more patterns to orchestrate tasks, this is just one example. | 9 | //! The system maintains state in a single place, wrapped in a Mutex. |
| 10 | //! | 10 | //! |
| 11 | //! We will use these tasks to generate example "state information": | 11 | //! We demonstrate how to: |
| 12 | //! - a task that generates random numbers in intervals of 60s | 12 | //! - use a mutex to maintain shared state between tasks |
| 13 | //! - a task that generates random numbers in intervals of 30s | 13 | //! - use a channel to send events between tasks |
| 14 | //! - a task that generates random numbers in intervals of 90s | 14 | //! - use an orchestrator task to coordinate tasks and handle state transitions |
| 15 | //! - a task that notifies about being attached/disattached from usb power | 15 | //! - use signals to notify about state changes and terminate tasks |
| 16 | //! - a task that measures vsys voltage in intervals of 30s | ||
| 17 | //! - a task that consumes the state information and reacts to it | ||
| 18 | 16 | ||
| 19 | #![no_std] | 17 | #![no_std] |
| 20 | #![no_main] | 18 | #![no_main] |
| @@ -28,15 +26,13 @@ use embassy_rp::clocks::RoscRng; | |||
| 28 | use embassy_rp::gpio::{Input, Pull}; | 26 | use embassy_rp::gpio::{Input, Pull}; |
| 29 | use embassy_rp::{bind_interrupts, peripherals}; | 27 | use embassy_rp::{bind_interrupts, peripherals}; |
| 30 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | 28 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; |
| 29 | use embassy_sync::mutex::Mutex; | ||
| 31 | use embassy_sync::{channel, signal}; | 30 | use embassy_sync::{channel, signal}; |
| 32 | use embassy_time::{Duration, Timer}; | 31 | use embassy_time::{Duration, Timer}; |
| 33 | use rand::RngCore; | 32 | use rand::RngCore; |
| 34 | use {defmt_rtt as _, panic_probe as _}; | 33 | use {defmt_rtt as _, panic_probe as _}; |
| 35 | 34 | ||
| 36 | // This is just some preparation, see example `assign_resources.rs` for more information on this. We prep the rresources that we will be using in different tasks. | 35 | // Hardware resource assignment. See other examples for different ways of doing this. |
| 37 | // **Note**: This will not work with a board that has a wifi chip, because the wifi chip uses pins 24 and 29 for its own purposes. A way around this in software | ||
| 38 | // is not trivial, at least if you intend to use wifi, too. Workaround is to wire from vsys and vbus pins to appropriate pins on the board through a voltage divider. Then use those pins. | ||
| 39 | // For this example it will not matter much, the concept of what we are showing remains valid. | ||
| 40 | assign_resources! { | 36 | assign_resources! { |
| 41 | vsys: Vsys { | 37 | vsys: Vsys { |
| 42 | adc: ADC, | 38 | adc: ADC, |
| @@ -47,228 +43,233 @@ assign_resources! { | |||
| 47 | }, | 43 | }, |
| 48 | } | 44 | } |
| 49 | 45 | ||
| 46 | // Interrupt binding - required for hardware peripherals like ADC | ||
| 50 | bind_interrupts!(struct Irqs { | 47 | bind_interrupts!(struct Irqs { |
| 51 | ADC_IRQ_FIFO => InterruptHandler; | 48 | ADC_IRQ_FIFO => InterruptHandler; |
| 52 | }); | 49 | }); |
| 53 | 50 | ||
| 54 | /// This is the type of Events that we will send from the worker tasks to the orchestrating task. | 51 | /// Events that worker tasks send to the orchestrator |
| 55 | enum Events { | 52 | enum Events { |
| 56 | UsbPowered(bool), | 53 | UsbPowered(bool), // USB connection state changed |
| 57 | VsysVoltage(f32), | 54 | VsysVoltage(f32), // New voltage reading |
| 58 | FirstRandomSeed(u32), | 55 | FirstRandomSeed(u32), // Random number from 30s timer |
| 59 | SecondRandomSeed(u32), | 56 | SecondRandomSeed(u32), // Random number from 60s timer |
| 60 | ThirdRandomSeed(u32), | 57 | ThirdRandomSeed(u32), // Random number from 90s timer |
| 61 | ResetFirstRandomSeed, | 58 | ResetFirstRandomSeed, // Signal to reset the first counter |
| 62 | } | 59 | } |
| 63 | 60 | ||
| 64 | /// This is the type of Commands that we will send from the orchestrating task to the worker tasks. | 61 | /// Commands that can control task behavior. |
| 65 | /// Note that we are lazy here and only have one command, you might want to have more. | 62 | /// Currently only used to stop tasks, but could be extended for other controls. |
| 66 | enum Commands { | 63 | enum Commands { |
| 67 | /// This command will stop the appropriate worker task | 64 | /// Signals a task to stop execution |
| 68 | Stop, | 65 | Stop, |
| 69 | } | 66 | } |
| 70 | 67 | ||
| 71 | /// This is the state of the system, we will use this to orchestrate the system. This is a simple example, in a real world application this would be more complex. | 68 | /// The central state of our system, shared between tasks. |
| 72 | #[derive(Default, Debug, Clone, Format)] | 69 | #[derive(Clone, Format)] |
| 73 | struct State { | 70 | struct State { |
| 74 | usb_powered: bool, | 71 | usb_powered: bool, |
| 75 | vsys_voltage: f32, | 72 | vsys_voltage: f32, |
| 76 | first_random_seed: u32, | 73 | first_random_seed: u32, |
| 77 | second_random_seed: u32, | 74 | second_random_seed: u32, |
| 78 | third_random_seed: u32, | 75 | third_random_seed: u32, |
| 76 | first_random_seed_task_running: bool, | ||
| 79 | times_we_got_first_random_seed: u8, | 77 | times_we_got_first_random_seed: u8, |
| 80 | maximum_times_we_want_first_random_seed: u8, | 78 | maximum_times_we_want_first_random_seed: u8, |
| 81 | } | 79 | } |
| 82 | 80 | ||
| 81 | /// A formatted view of the system status, used for logging. Used for the below `get_system_summary` fn. | ||
| 82 | #[derive(Format)] | ||
| 83 | struct SystemStatus { | ||
| 84 | power_source: &'static str, | ||
| 85 | voltage: f32, | ||
| 86 | } | ||
| 87 | |||
| 83 | impl State { | 88 | impl State { |
| 84 | fn new() -> Self { | 89 | const fn new() -> Self { |
| 85 | Self { | 90 | Self { |
| 86 | usb_powered: false, | 91 | usb_powered: false, |
| 87 | vsys_voltage: 0.0, | 92 | vsys_voltage: 0.0, |
| 88 | first_random_seed: 0, | 93 | first_random_seed: 0, |
| 89 | second_random_seed: 0, | 94 | second_random_seed: 0, |
| 90 | third_random_seed: 0, | 95 | third_random_seed: 0, |
| 96 | first_random_seed_task_running: false, | ||
| 91 | times_we_got_first_random_seed: 0, | 97 | times_we_got_first_random_seed: 0, |
| 92 | maximum_times_we_want_first_random_seed: 3, | 98 | maximum_times_we_want_first_random_seed: 3, |
| 93 | } | 99 | } |
| 94 | } | 100 | } |
| 101 | |||
| 102 | /// Returns a formatted summary of power state and voltage. | ||
| 103 | /// Shows how to create methods that work with shared state. | ||
| 104 | fn get_system_summary(&self) -> SystemStatus { | ||
| 105 | SystemStatus { | ||
| 106 | power_source: if self.usb_powered { | ||
| 107 | "USB powered" | ||
| 108 | } else { | ||
| 109 | "Battery powered" | ||
| 110 | }, | ||
| 111 | voltage: self.vsys_voltage, | ||
| 112 | } | ||
| 113 | } | ||
| 95 | } | 114 | } |
| 96 | 115 | ||
| 97 | /// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events. | 116 | /// The shared state protected by a mutex |
| 98 | /// We use a channel with an arbitrary size of 10, the precise size of the queue depends on your use case. This depends on how many events we | 117 | static SYSTEM_STATE: Mutex<CriticalSectionRawMutex, State> = Mutex::new(State::new()); |
| 99 | /// expect to be generated in a given time frame and how fast the orchestrator can react to them. And then if we rather want the senders to wait for | 118 | |
| 100 | /// new slots in the queue or if we want the orchestrator to have a backlog of events to process. In this case here we expect to always be enough slots | 119 | /// Channel for events from worker tasks to the orchestrator |
| 101 | /// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting. | ||
| 102 | /// For the events we - in this case here - do not want to loose any events, so a channel is a good choice. See embassy_sync docs for other options. | ||
| 103 | static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new(); | 120 | static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new(); |
| 104 | 121 | ||
| 105 | /// Signal for stopping the first random signal task. We use a signal here, because we need no queue. It is suffiient to have one signal active. | 122 | /// Signal used to stop the first random number task |
| 106 | static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); | 123 | static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); |
| 107 | 124 | ||
| 108 | /// Channel for the state that we want the consumer task to react to. We use a channel here, because we want to have a queue of state changes, although | 125 | /// Signal for notifying about state changes |
| 109 | /// we want the queue to be of size 1, because we want to finish rwacting to the state change before the next one comes in. This is just a design choice | 126 | static STATE_CHANGED: signal::Signal<CriticalSectionRawMutex, ()> = signal::Signal::new(); |
| 110 | /// and depends on your use case. | ||
| 111 | static CONSUMER_CHANNEL: channel::Channel<CriticalSectionRawMutex, State, 1> = channel::Channel::new(); | ||
| 112 | |||
| 113 | // And now we can put all this into use | ||
| 114 | 127 | ||
| 115 | /// This is the main task, that will not do very much besides spawning the other tasks. This is a design choice, you could do the | ||
| 116 | /// orchestrating here. This is to show that we do not need a main loop here, the system will run indefinitely as long as at least one task is running. | ||
| 117 | #[embassy_executor::main] | 128 | #[embassy_executor::main] |
| 118 | async fn main(spawner: Spawner) { | 129 | async fn main(spawner: Spawner) { |
| 119 | // initialize the peripherals | ||
| 120 | let p = embassy_rp::init(Default::default()); | 130 | let p = embassy_rp::init(Default::default()); |
| 121 | // split the resources, for convenience - see above | ||
| 122 | let r = split_resources! {p}; | 131 | let r = split_resources! {p}; |
| 123 | 132 | ||
| 124 | // spawn the tasks | ||
| 125 | spawner.spawn(orchestrate(spawner)).unwrap(); | 133 | spawner.spawn(orchestrate(spawner)).unwrap(); |
| 126 | spawner.spawn(random_60s(spawner)).unwrap(); | 134 | spawner.spawn(random_60s(spawner)).unwrap(); |
| 127 | spawner.spawn(random_90s(spawner)).unwrap(); | 135 | spawner.spawn(random_90s(spawner)).unwrap(); |
| 136 | // `random_30s` is not spawned here, butin the orchestrate task depending on state | ||
| 128 | spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); | 137 | spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); |
| 129 | spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); | 138 | spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); |
| 130 | spawner.spawn(consumer(spawner)).unwrap(); | 139 | spawner.spawn(consumer(spawner)).unwrap(); |
| 131 | } | 140 | } |
| 132 | 141 | ||
| 133 | /// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. | 142 | /// Main task that processes all events and updates system state. |
| 134 | #[embassy_executor::task] | 143 | #[embassy_executor::task] |
| 135 | async fn orchestrate(_spawner: Spawner) { | 144 | async fn orchestrate(spawner: Spawner) { |
| 136 | let mut state = State::new(); | ||
| 137 | |||
| 138 | // we need to have a receiver for the events | ||
| 139 | let receiver = EVENT_CHANNEL.receiver(); | 145 | let receiver = EVENT_CHANNEL.receiver(); |
| 140 | 146 | ||
| 141 | // and we need a sender for the consumer task | ||
| 142 | let state_sender = CONSUMER_CHANNEL.sender(); | ||
| 143 | |||
| 144 | loop { | 147 | loop { |
| 145 | // we await on the receiver, this will block until a new event is available | 148 | // Do nothing until we receive any event |
| 146 | // as an alternative to this, we could also await on multiple channels, this would block until at least one of the channels has an event | ||
| 147 | // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html | ||
| 148 | // The task random_30s does a select, if you want to have a look at that. | ||
| 149 | // Another reason to use select may also be that we want to have a timeout, so we can react to the absence of events within a time frame. | ||
| 150 | // We keep it simple here. | ||
| 151 | let event = receiver.receive().await; | 149 | let event = receiver.receive().await; |
| 152 | 150 | ||
| 153 | // react to the events | 151 | // Scope in which we want to lock the system state. As an alternative we could also call `drop` on the state |
| 154 | match event { | 152 | { |
| 155 | Events::UsbPowered(usb_powered) => { | 153 | let mut state = SYSTEM_STATE.lock().await; |
| 156 | // update the state and/or react to the event here | 154 | |
| 157 | state.usb_powered = usb_powered; | 155 | match event { |
| 158 | info!("Usb powered: {}", usb_powered); | 156 | Events::UsbPowered(usb_powered) => { |
| 159 | } | 157 | state.usb_powered = usb_powered; |
| 160 | Events::VsysVoltage(voltage) => { | 158 | info!("Usb powered: {}", usb_powered); |
| 161 | // update the state and/or react to the event here | 159 | info!("System summary: {}", state.get_system_summary()); |
| 162 | state.vsys_voltage = voltage; | 160 | } |
| 163 | info!("Vsys voltage: {}", voltage); | 161 | Events::VsysVoltage(voltage) => { |
| 164 | } | 162 | state.vsys_voltage = voltage; |
| 165 | Events::FirstRandomSeed(seed) => { | 163 | info!("Vsys voltage: {}", voltage); |
| 166 | // update the state and/or react to the event here | 164 | } |
| 167 | state.first_random_seed = seed; | 165 | Events::FirstRandomSeed(seed) => { |
| 168 | // here we change some meta state, we count how many times we got the first random seed | 166 | state.first_random_seed = seed; |
| 169 | state.times_we_got_first_random_seed += 1; | 167 | state.times_we_got_first_random_seed += 1; |
| 170 | info!( | 168 | info!( |
| 171 | "First random seed: {}, and that was iteration {} of receiving this.", | 169 | "First random seed: {}, and that was iteration {} of receiving this.", |
| 172 | seed, &state.times_we_got_first_random_seed | 170 | seed, &state.times_we_got_first_random_seed |
| 173 | ); | 171 | ); |
| 174 | } | 172 | } |
| 175 | Events::SecondRandomSeed(seed) => { | 173 | Events::SecondRandomSeed(seed) => { |
| 176 | // update the state and/or react to the event here | 174 | state.second_random_seed = seed; |
| 177 | state.second_random_seed = seed; | 175 | info!("Second random seed: {}", seed); |
| 178 | info!("Second random seed: {}", seed); | 176 | } |
| 179 | } | 177 | Events::ThirdRandomSeed(seed) => { |
| 180 | Events::ThirdRandomSeed(seed) => { | 178 | state.third_random_seed = seed; |
| 181 | // update the state and/or react to the event here | 179 | info!("Third random seed: {}", seed); |
| 182 | state.third_random_seed = seed; | 180 | } |
| 183 | info!("Third random seed: {}", seed); | 181 | Events::ResetFirstRandomSeed => { |
| 182 | state.times_we_got_first_random_seed = 0; | ||
| 183 | state.first_random_seed = 0; | ||
| 184 | info!("Resetting the first random seed counter"); | ||
| 185 | } | ||
| 184 | } | 186 | } |
| 185 | Events::ResetFirstRandomSeed => { | 187 | |
| 186 | // update the state and/or react to the event here | 188 | // Handle task orchestration based on state |
| 187 | state.times_we_got_first_random_seed = 0; | 189 | // Just placed as an example here, could be hooked into the event system, puton a timer, ... |
| 188 | state.first_random_seed = 0; | 190 | match state.times_we_got_first_random_seed { |
| 189 | info!("Resetting the first random seed counter"); | 191 | max if max == state.maximum_times_we_want_first_random_seed => { |
| 192 | info!("Stopping the first random signal task"); | ||
| 193 | STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); | ||
| 194 | EVENT_CHANNEL.sender().send(Events::ResetFirstRandomSeed).await; | ||
| 195 | } | ||
| 196 | 0 => { | ||
| 197 | let respawn_first_random_seed_task = !state.first_random_seed_task_running; | ||
| 198 | // Deliberately dropping the Mutex lock here to release it before a lengthy operation | ||
| 199 | drop(state); | ||
| 200 | if respawn_first_random_seed_task { | ||
| 201 | info!("(Re)-Starting the first random signal task"); | ||
| 202 | spawner.spawn(random_30s(spawner)).unwrap(); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | _ => {} | ||
| 190 | } | 206 | } |
| 191 | } | 207 | } |
| 192 | // we now have an altered state | 208 | |
| 193 | // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here | 209 | STATE_CHANGED.signal(()); |
| 194 | // for now we just keep it simple | ||
| 195 | |||
| 196 | // we send the state to the consumer task | ||
| 197 | // since the channel has a size of 1, this will block until the consumer task has received the state, which is what we want here in this example | ||
| 198 | // **Note:** It is bad design to send too much data between tasks, with no clear definition of what "too much" is. In this example we send the | ||
| 199 | // whole state, in a real world application you might want to send only the data, that is relevant to the consumer task AND only when it has changed. | ||
| 200 | // We keep it simple here. | ||
| 201 | state_sender.send(state.clone()).await; | ||
| 202 | } | 210 | } |
| 203 | } | 211 | } |
| 204 | 212 | ||
| 205 | /// This task will consume the state information and react to it. This is a simple example, in a real world application this would be more complex | 213 | /// Task that monitors state changes and logs system status. |
| 206 | /// and we could have multiple consumer tasks, each reacting to different parts of the state. | ||
| 207 | #[embassy_executor::task] | 214 | #[embassy_executor::task] |
| 208 | async fn consumer(spawner: Spawner) { | 215 | async fn consumer(_spawner: Spawner) { |
| 209 | // we need to have a receiver for the state | ||
| 210 | let receiver = CONSUMER_CHANNEL.receiver(); | ||
| 211 | let sender = EVENT_CHANNEL.sender(); | ||
| 212 | loop { | 216 | loop { |
| 213 | // we await on the receiver, this will block until a new state is available | 217 | // Wait for state change notification |
| 214 | let state = receiver.receive().await; | 218 | STATE_CHANGED.wait().await; |
| 215 | // react to the state, in this case here we just log it | 219 | |
| 216 | info!("The consumer has reveived this state: {:?}", &state); | 220 | let state = SYSTEM_STATE.lock().await; |
| 217 | 221 | info!( | |
| 218 | // here we react to the state, in this case here we want to start or stop the first random signal task depending on the state of the system | 222 | "State update - {} | Seeds - First: {} (count: {}/{}, running: {}), Second: {}, Third: {}", |
| 219 | match state.times_we_got_first_random_seed { | 223 | state.get_system_summary(), |
| 220 | max if max == state.maximum_times_we_want_first_random_seed => { | 224 | state.first_random_seed, |
| 221 | info!("Stopping the first random signal task"); | 225 | state.times_we_got_first_random_seed, |
| 222 | // we send a command to the task | 226 | state.maximum_times_we_want_first_random_seed, |
| 223 | STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); | 227 | state.first_random_seed_task_running, |
| 224 | // we notify the orchestrator that we have sent the command | 228 | state.second_random_seed, |
| 225 | sender.send(Events::ResetFirstRandomSeed).await; | 229 | state.third_random_seed |
| 226 | } | 230 | ); |
| 227 | 0 => { | ||
| 228 | // we start the task, which presents us with an interesting problem, because we may return here before the task has started | ||
| 229 | // here we just try and log if the task has started, in a real world application you might want to handle this more gracefully | ||
| 230 | info!("Starting the first random signal task"); | ||
| 231 | match spawner.spawn(random_30s(spawner)) { | ||
| 232 | Ok(_) => info!("Successfully spawned random_30s task"), | ||
| 233 | Err(e) => info!("Failed to spawn random_30s task: {:?}", e), | ||
| 234 | } | ||
| 235 | } | ||
| 236 | _ => {} | ||
| 237 | } | ||
| 238 | } | 231 | } |
| 239 | } | 232 | } |
| 240 | 233 | ||
| 241 | /// This task will generate random numbers in intervals of 30s | 234 | /// Task that generates random numbers every 30 seconds until stopped. |
| 242 | /// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. | 235 | /// Shows how to handle both timer events and stop signals. |
| 243 | /// Note that we are not spawning this task from main, as we will show how such a task can be spawned and closed dynamically. | 236 | /// As an example of some routine we want to be on or off depending on other needs. |
| 244 | #[embassy_executor::task] | 237 | #[embassy_executor::task] |
| 245 | async fn random_30s(_spawner: Spawner) { | 238 | async fn random_30s(_spawner: Spawner) { |
| 239 | { | ||
| 240 | let mut state = SYSTEM_STATE.lock().await; | ||
| 241 | state.first_random_seed_task_running = true; | ||
| 242 | } | ||
| 243 | |||
| 246 | let mut rng = RoscRng; | 244 | let mut rng = RoscRng; |
| 247 | let sender = EVENT_CHANNEL.sender(); | 245 | let sender = EVENT_CHANNEL.sender(); |
| 246 | |||
| 248 | loop { | 247 | loop { |
| 249 | // we either await on the timer or the signal, whichever comes first. | 248 | // Wait for either 30s timer or stop signal (like select() in Go) |
| 250 | let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await; | 249 | match select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await { |
| 251 | match futures { | ||
| 252 | Either::First(_) => { | 250 | Either::First(_) => { |
| 253 | // we received are operating on the timer | ||
| 254 | info!("30s are up, generating random number"); | 251 | info!("30s are up, generating random number"); |
| 255 | let random_number = rng.next_u32(); | 252 | let random_number = rng.next_u32(); |
| 256 | sender.send(Events::FirstRandomSeed(random_number)).await; | 253 | sender.send(Events::FirstRandomSeed(random_number)).await; |
| 257 | } | 254 | } |
| 258 | Either::Second(_) => { | 255 | Either::Second(_) => { |
| 259 | // we received the signal to stop | ||
| 260 | info!("Received signal to stop, goodbye!"); | 256 | info!("Received signal to stop, goodbye!"); |
| 257 | |||
| 258 | let mut state = SYSTEM_STATE.lock().await; | ||
| 259 | state.first_random_seed_task_running = false; | ||
| 260 | |||
| 261 | break; | 261 | break; |
| 262 | } | 262 | } |
| 263 | } | 263 | } |
| 264 | } | 264 | } |
| 265 | } | 265 | } |
| 266 | 266 | ||
| 267 | /// This task will generate random numbers in intervals of 60s | 267 | /// Task that generates random numbers every 60 seconds. As an example of some routine. |
| 268 | #[embassy_executor::task] | 268 | #[embassy_executor::task] |
| 269 | async fn random_60s(_spawner: Spawner) { | 269 | async fn random_60s(_spawner: Spawner) { |
| 270 | let mut rng = RoscRng; | 270 | let mut rng = RoscRng; |
| 271 | let sender = EVENT_CHANNEL.sender(); | 271 | let sender = EVENT_CHANNEL.sender(); |
| 272 | |||
| 272 | loop { | 273 | loop { |
| 273 | Timer::after(Duration::from_secs(60)).await; | 274 | Timer::after(Duration::from_secs(60)).await; |
| 274 | let random_number = rng.next_u32(); | 275 | let random_number = rng.next_u32(); |
| @@ -276,11 +277,12 @@ async fn random_60s(_spawner: Spawner) { | |||
| 276 | } | 277 | } |
| 277 | } | 278 | } |
| 278 | 279 | ||
| 279 | /// This task will generate random numbers in intervals of 90s | 280 | /// Task that generates random numbers every 90 seconds. . As an example of some routine. |
| 280 | #[embassy_executor::task] | 281 | #[embassy_executor::task] |
| 281 | async fn random_90s(_spawner: Spawner) { | 282 | async fn random_90s(_spawner: Spawner) { |
| 282 | let mut rng = RoscRng; | 283 | let mut rng = RoscRng; |
| 283 | let sender = EVENT_CHANNEL.sender(); | 284 | let sender = EVENT_CHANNEL.sender(); |
| 285 | |||
| 284 | loop { | 286 | loop { |
| 285 | Timer::after(Duration::from_secs(90)).await; | 287 | Timer::after(Duration::from_secs(90)).await; |
| 286 | let random_number = rng.next_u32(); | 288 | let random_number = rng.next_u32(); |
| @@ -288,31 +290,30 @@ async fn random_90s(_spawner: Spawner) { | |||
| 288 | } | 290 | } |
| 289 | } | 291 | } |
| 290 | 292 | ||
| 291 | /// This task will notify if we are connected to usb power | 293 | /// Task that monitors USB power connection. As an example of some Interrupt somewhere. |
| 292 | #[embassy_executor::task] | 294 | #[embassy_executor::task] |
| 293 | pub async fn usb_power(_spawner: Spawner, r: Vbus) { | 295 | pub async fn usb_power(_spawner: Spawner, r: Vbus) { |
| 294 | let mut vbus_in = Input::new(r.pin_24, Pull::None); | 296 | let mut vbus_in = Input::new(r.pin_24, Pull::None); |
| 295 | let sender = EVENT_CHANNEL.sender(); | 297 | let sender = EVENT_CHANNEL.sender(); |
| 298 | |||
| 296 | loop { | 299 | loop { |
| 297 | sender.send(Events::UsbPowered(vbus_in.is_high())).await; | 300 | sender.send(Events::UsbPowered(vbus_in.is_high())).await; |
| 298 | vbus_in.wait_for_any_edge().await; | 301 | vbus_in.wait_for_any_edge().await; |
| 299 | } | 302 | } |
| 300 | } | 303 | } |
| 301 | 304 | ||
| 302 | /// This task will measure the vsys voltage in intervals of 30s | 305 | /// Task that reads system voltage through ADC. As an example of some continuous sensor reading. |
| 303 | #[embassy_executor::task] | 306 | #[embassy_executor::task] |
| 304 | pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { | 307 | pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { |
| 305 | let mut adc = Adc::new(r.adc, Irqs, Config::default()); | 308 | let mut adc = Adc::new(r.adc, Irqs, Config::default()); |
| 306 | let vsys_in = r.pin_29; | 309 | let vsys_in = r.pin_29; |
| 307 | let mut channel = Channel::new_pin(vsys_in, Pull::None); | 310 | let mut channel = Channel::new_pin(vsys_in, Pull::None); |
| 308 | let sender = EVENT_CHANNEL.sender(); | 311 | let sender = EVENT_CHANNEL.sender(); |
| 312 | |||
| 309 | loop { | 313 | loop { |
| 310 | // read the adc value | 314 | Timer::after(Duration::from_secs(30)).await; |
| 311 | let adc_value = adc.read(&mut channel).await.unwrap(); | 315 | let adc_value = adc.read(&mut channel).await.unwrap(); |
| 312 | // convert the adc value to voltage. | ||
| 313 | // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc | ||
| 314 | let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; | 316 | let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; |
| 315 | sender.send(Events::VsysVoltage(voltage)).await; | 317 | sender.send(Events::VsysVoltage(voltage)).await; |
| 316 | Timer::after(Duration::from_secs(30)).await; | ||
| 317 | } | 318 | } |
| 318 | } | 319 | } |
