diff options
| author | rafael <[email protected]> | 2024-07-28 00:19:54 +0200 |
|---|---|---|
| committer | rafael <[email protected]> | 2024-07-28 00:19:54 +0200 |
| commit | e05e5d33f0ab832b2ea1c48674c99b41581118be (patch) | |
| tree | bcc0313aa9c58b8e587d1460d892a3402283204b /examples/rp | |
| parent | 5d46b694ca60ee69d83eb2bd53f592b2c5b03d28 (diff) | |
review comments
Diffstat (limited to 'examples/rp')
| -rw-r--r-- | examples/rp/src/bin/orchestrate_tasks.rs | 66 |
1 files changed, 59 insertions, 7 deletions
diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs index b3c37de4c..0e21d5833 100644 --- a/examples/rp/src/bin/orchestrate_tasks.rs +++ b/examples/rp/src/bin/orchestrate_tasks.rs | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | //! - a task that generates random numbers in intervals of 90s | 14 | //! - a task that generates random numbers in intervals of 90s |
| 15 | //! - a task that notifies about being attached/disattached from usb power | 15 | //! - a task that notifies about being attached/disattached from usb power |
| 16 | //! - a task that measures vsys voltage in intervals of 30s | 16 | //! - a task that measures vsys voltage in intervals of 30s |
| 17 | //! - a task that consumes the state information and reacts to it | ||
| 17 | 18 | ||
| 18 | #![no_std] | 19 | #![no_std] |
| 19 | #![no_main] | 20 | #![no_main] |
| @@ -57,6 +58,7 @@ enum Events { | |||
| 57 | FirstRandomSeed(u32), | 58 | FirstRandomSeed(u32), |
| 58 | SecondRandomSeed(u32), | 59 | SecondRandomSeed(u32), |
| 59 | ThirdRandomSeed(u32), | 60 | ThirdRandomSeed(u32), |
| 61 | ResetFirstRandomSeed, | ||
| 60 | } | 62 | } |
| 61 | 63 | ||
| 62 | /// This is the type of Commands that we will send from the orchestrating task to the worker tasks. | 64 | /// This is the type of Commands that we will send from the orchestrating task to the worker tasks. |
| @@ -103,6 +105,11 @@ static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = ch | |||
| 103 | /// 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. | 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. |
| 104 | static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); | 106 | static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); |
| 105 | 107 | ||
| 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 | ||
| 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 | ||
| 110 | /// and depends on your use case. | ||
| 111 | static CONSUMER_CHANNEL: channel::Channel<CriticalSectionRawMutex, State, 1> = channel::Channel::new(); | ||
| 112 | |||
| 106 | // And now we can put all this into use | 113 | // And now we can put all this into use |
| 107 | 114 | ||
| 108 | /// 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 | 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,11 +123,11 @@ async fn main(spawner: Spawner) { | |||
| 116 | 123 | ||
| 117 | // spawn the tasks | 124 | // spawn the tasks |
| 118 | spawner.spawn(orchestrate(spawner)).unwrap(); | 125 | spawner.spawn(orchestrate(spawner)).unwrap(); |
| 119 | spawner.spawn(random_30s(spawner)).unwrap(); | ||
| 120 | spawner.spawn(random_60s(spawner)).unwrap(); | 126 | spawner.spawn(random_60s(spawner)).unwrap(); |
| 121 | spawner.spawn(random_90s(spawner)).unwrap(); | 127 | spawner.spawn(random_90s(spawner)).unwrap(); |
| 122 | spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); | 128 | spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); |
| 123 | spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); | 129 | spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); |
| 130 | spawner.spawn(consumer(spawner)).unwrap(); | ||
| 124 | } | 131 | } |
| 125 | 132 | ||
| 126 | /// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. | 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. |
| @@ -131,6 +138,9 @@ async fn orchestrate(_spawner: Spawner) { | |||
| 131 | // we need to have a receiver for the events | 138 | // we need to have a receiver for the events |
| 132 | let receiver = EVENT_CHANNEL.receiver(); | 139 | let receiver = EVENT_CHANNEL.receiver(); |
| 133 | 140 | ||
| 141 | // and we need a sender for the consumer task | ||
| 142 | let state_sender = CONSUMER_CHANNEL.sender(); | ||
| 143 | |||
| 134 | loop { | 144 | loop { |
| 135 | // we await on the receiver, this will block until a new event is available | 145 | // we await on the receiver, this will block until a new event is available |
| 136 | // 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 | 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 |
| @@ -172,23 +182,65 @@ async fn orchestrate(_spawner: Spawner) { | |||
| 172 | state.third_random_seed = seed; | 182 | state.third_random_seed = seed; |
| 173 | info!("Third random seed: {}", seed); | 183 | info!("Third random seed: {}", seed); |
| 174 | } | 184 | } |
| 185 | Events::ResetFirstRandomSeed => { | ||
| 186 | // update the state and/or react to the event here | ||
| 187 | state.times_we_got_first_random_seed = 0; | ||
| 188 | state.first_random_seed = 0; | ||
| 189 | info!("Resetting the first random seed counter"); | ||
| 190 | } | ||
| 175 | } | 191 | } |
| 176 | // we now have an altered state | 192 | // we now have an altered state |
| 177 | // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here | 193 | // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here |
| 178 | // for now we just keep it simple | 194 | // for now we just keep it simple |
| 179 | info!("State: {:?}", &state); | ||
| 180 | 195 | ||
| 181 | // here we react to the state, in this case here we want to stop the first random seed task after we got it a defined number of times | 196 | // we send the state to the consumer task |
| 182 | if state.times_we_got_first_random_seed == state.maximum_times_we_want_first_random_seed { | 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 |
| 183 | info!("Stopping the first random signal task"); | 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 |
| 184 | // we send a command to the task | 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. |
| 185 | STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); | 200 | // We keep it simple here. |
| 201 | state_sender.send(state.clone()).await; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 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 | ||
| 206 | /// and we could have multiple consumer tasks, each reacting to different parts of the state. | ||
| 207 | #[embassy_executor::task] | ||
| 208 | 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 { | ||
| 213 | // we await on the receiver, this will block until a new state is available | ||
| 214 | let state = receiver.receive().await; | ||
| 215 | // react to the state, in this case here we just log it | ||
| 216 | info!("The consumer has reveived this state: {:?}", &state); | ||
| 217 | |||
| 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 | ||
| 219 | match state.times_we_got_first_random_seed { | ||
| 220 | max if max == state.maximum_times_we_want_first_random_seed => { | ||
| 221 | info!("Stopping the first random signal task"); | ||
| 222 | // we send a command to the task | ||
| 223 | STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); | ||
| 224 | // we notify the orchestrator that we have sent the command | ||
| 225 | sender.send(Events::ResetFirstRandomSeed).await; | ||
| 226 | } | ||
| 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 | _ => {} | ||
| 186 | } | 237 | } |
| 187 | } | 238 | } |
| 188 | } | 239 | } |
| 189 | 240 | ||
| 190 | /// This task will generate random numbers in intervals of 30s | 241 | /// This task will generate random numbers in intervals of 30s |
| 191 | /// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. | 242 | /// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. |
| 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. | ||
| 192 | #[embassy_executor::task] | 244 | #[embassy_executor::task] |
| 193 | async fn random_30s(_spawner: Spawner) { | 245 | async fn random_30s(_spawner: Spawner) { |
| 194 | let mut rng = RoscRng; | 246 | let mut rng = RoscRng; |
