Skip to content

Commit fdb1a8f

Browse files
authored
State Machine Implementation (#16)
* Initial orchestrator implementation * Migrate argus to use a bin + lib architecture, move SM to corresponding file * Added more tests * Moved SM to shared resources * Add last state guard and infinite orchestrator loop * Added tasks * Removed redundant states
1 parent 4c9d05b commit fdb1a8f

File tree

5 files changed

+135
-28
lines changed

5 files changed

+135
-28
lines changed

boards/argus/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name = "argus"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[lib]
7+
name = "argus"
8+
path = "src/lib.rs"
9+
610
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
711

812
[dependencies]

boards/argus/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![no_std]
2+
3+
pub mod adc_manager;
4+
pub mod can_manager;
5+
pub mod data_manager;
6+
pub mod state_machine;
7+
pub mod time_manager;
8+
pub mod traits;
9+
pub mod types;

boards/argus/src/main.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ compile_error!(
66
"You must enable exactly one of the features: 'pressure', 'temperature', or 'strain'."
77
);
88

9-
// mod state_machine;
10-
pub mod adc_manager;
11-
mod can_manager;
12-
mod data_manager;
13-
mod time_manager;
14-
mod traits;
15-
mod types;
9+
use argus::*;
1610

1711
use adc_manager::AdcManager;
1812
use chrono::NaiveDate;
@@ -24,7 +18,7 @@ use messages::CanMessage;
2418
use panic_probe as _;
2519
use rtic_monotonics::systick::prelude::*;
2620
use rtic_sync::{channel::*, make_channel};
27-
use smlang::statemachine;
21+
use state_machine as sm;
2822
use stm32h7xx_hal::gpio::gpioa::{PA2, PA3};
2923
use stm32h7xx_hal::gpio::PA4;
3024
use stm32h7xx_hal::gpio::{Edge, ExtiPin, Pin};
@@ -41,18 +35,6 @@ const DATA_CHANNEL_CAPACITY: usize = 10;
4135

4236
systick_monotonic!(Mono, 500);
4337

44-
statemachine! {
45-
transitions: {
46-
*Init + Start = Idle,
47-
Idle | Recovery + WantsCollection = Collection,
48-
Idle + NoConfig = Calibration,
49-
Collection + WantsProcessing = Processing,
50-
Calibration + Configured = Idle,
51-
Fault + FaultCleared = Idle,
52-
_ + FaultDetected = Fault,
53-
}
54-
}
55-
5638
#[inline(never)]
5739
#[defmt::panic_handler]
5840
fn panic() -> ! {
@@ -80,11 +62,11 @@ mod app {
8062
rtc: rtc::Rtc,
8163
adc_manager: AdcManager<Pin<ADC2_RST_PIN_PORT, ADC2_RST_PIN_ID, Output<PushPull>>>,
8264
time_manager: TimeManager,
65+
state_machine: sm::StateMachine<traits::Context>,
8366
}
8467

8568
#[local]
8669
struct LocalResources {
87-
state_machine: StateMachine<traits::Context>,
8870
can_sender: Sender<'static, CanMessage, DATA_CHANNEL_CAPACITY>,
8971
led_red: PA2<Output<PushPull>>,
9072
led_green: PA3<Output<PushPull>>,
@@ -259,12 +241,13 @@ mod app {
259241
let mut data_manager = DataManager::new();
260242
data_manager.set_reset_reason(reset);
261243
let em = ErrorManager::new();
262-
let state_machine = StateMachine::new(traits::Context {});
244+
let state_machine = sm::StateMachine::new(traits::Context {});
263245

264246
blink::spawn().ok();
265247
// send_data_internal::spawn(can_receiver).ok();
266248
reset_reason_send::spawn().ok();
267249
state_send::spawn().ok();
250+
sm_orchestrate::spawn().ok();
268251
info!("Online");
269252

270253
(
@@ -277,18 +260,66 @@ mod app {
277260
rtc,
278261
adc_manager,
279262
time_manager,
263+
state_machine,
280264
},
281265
LocalResources {
282266
adc1_int,
283267
adc2_int,
284268
can_sender,
285269
led_red,
286270
led_green,
287-
state_machine,
288271
},
289272
)
290273
}
291274

275+
/// The state machine orchestrator.
276+
/// Handles the current state of the ARGUS system.
277+
#[task(priority = 2, shared = [&state_machine])]
278+
async fn sm_orchestrate(cx: sm_orchestrate::Context) {
279+
let mut last_state = cx.shared.state_machine.state();
280+
loop {
281+
let state = cx.shared.state_machine.state();
282+
if state != last_state {
283+
_ = match state {
284+
sm::States::Calibration => spawn!(sm_calibrate),
285+
sm::States::Collection => spawn!(sm_collect),
286+
sm::States::Fault => spawn!(sm_fault),
287+
sm::States::Idle => spawn!(sm_idle),
288+
sm::States::Init => spawn!(sm_init),
289+
};
290+
291+
last_state = state;
292+
}
293+
294+
Mono::delay(100.millis()).await;
295+
}
296+
}
297+
298+
#[task(priority = 3)]
299+
async fn sm_calibrate(cx: sm_calibrate::Context) {
300+
todo!()
301+
}
302+
303+
#[task(priority = 3)]
304+
async fn sm_collect(cx: sm_collect::Context) {
305+
todo!()
306+
}
307+
308+
#[task(priority = 3)]
309+
async fn sm_fault(cx: sm_fault::Context) {
310+
todo!()
311+
}
312+
313+
#[task(priority = 3)]
314+
async fn sm_idle(cx: sm_idle::Context) {
315+
todo!()
316+
}
317+
318+
#[task(priority = 3)]
319+
async fn sm_init(cx: sm_init::Context) {
320+
todo!()
321+
}
322+
292323
#[task(priority = 3, binds = EXTI15_10, shared = [adc_manager], local = [adc1_int])]
293324
fn adc1_data_ready(mut cx: adc1_data_ready::Context) {
294325
info!("new data available come through");

boards/argus/src/state_machine.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use smlang::statemachine;
2+
3+
statemachine! {
4+
transitions: {
5+
*Init + Start = Idle,
6+
Idle + WantsCollection = Collection,
7+
Idle + NoConfig = Calibration,
8+
Calibration + Configured = Idle,
9+
Fault + FaultCleared = Idle,
10+
_ + FaultDetected = Fault,
11+
}
12+
}
13+
14+
#[cfg(test)]
15+
pub mod tests {
16+
use super::*;
17+
use crate::traits;
18+
19+
#[test]
20+
pub fn sm_should_transition_between_states_on_event() {
21+
let mut sm = StateMachine::new(traits::Context {});
22+
23+
// Should be in init by default
24+
assert!(*sm.state() == States::Init);
25+
26+
// Should transition to idle on start
27+
_ = sm.process_event(Events::Start).unwrap();
28+
assert!(*sm.state() == States::Idle);
29+
30+
// Should transition to calibration when there is no config
31+
_ = sm.process_event(Events::NoConfig).unwrap();
32+
assert!(*sm.state() == States::Calibration);
33+
34+
// Should transition back to idle when configured
35+
_ = sm.process_event(Events::Configured).unwrap();
36+
assert!(*sm.state() == States::Idle);
37+
38+
// Should transition to collection when asked
39+
_ = sm.process_event(Events::WantsCollection).unwrap();
40+
assert!(*sm.state() == States::Collection);
41+
}
42+
43+
#[test]
44+
pub fn sm_should_handle_fault_in_any_state() {
45+
let mut sm = StateMachine::new(traits::Context {});
46+
47+
// Should be in init by default
48+
assert!(*sm.state() == States::Init);
49+
50+
// Should transition to fault on fault detected
51+
_ = sm.process_event(Events::FaultDetected).unwrap();
52+
assert!(*sm.state() == States::Fault);
53+
54+
// Should transition to idle on fault cleared
55+
_ = sm.process_event(Events::FaultCleared).unwrap();
56+
assert!(*sm.state() == States::Idle);
57+
58+
// Check fault transition from another state
59+
_ = sm.process_event(Events::WantsCollection).unwrap();
60+
_ = sm.process_event(Events::FaultDetected).unwrap();
61+
assert!(*sm.state() == States::Fault);
62+
63+
// Should transition to idle on fault cleared
64+
_ = sm.process_event(Events::FaultCleared).unwrap();
65+
assert!(*sm.state() == States::Idle);
66+
}
67+
}

boards/argus/src/traits.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
//! This module should be refactored out.
2-
use crate::States;
2+
use crate::state_machine::{StateMachineContext, States};
33
use messages::state::DeviceState;
44

5-
use crate::StateMachineContext;
6-
75
pub struct Context {}
86

97
impl StateMachineContext for Context {}
@@ -13,10 +11,8 @@ impl From<States> for DeviceState {
1311
match value {
1412
States::Idle => DeviceState::Idle,
1513
States::Calibration => DeviceState::Calibration,
16-
States::Recovery => DeviceState::Recovery,
1714
States::Collection => DeviceState::Collection,
1815
States::Init => DeviceState::Init,
19-
States::Processing => DeviceState::Processing,
2016
States::Fault => DeviceState::Fault,
2117
}
2218
}

0 commit comments

Comments
 (0)