diff --git a/Cargo.toml b/Cargo.toml index ef32ec19b9..036dc6e99f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ members = [ "post-spy", "xtask", ] +exclude = ["actor-example"] # do not optimize proc-macro deps or build scripts [profile.dev.build-override] diff --git a/actor-example/.gitignore b/actor-example/.gitignore new file mode 100644 index 0000000000..1de565933b --- /dev/null +++ b/actor-example/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/actor-example/README.md b/actor-example/README.md new file mode 100644 index 0000000000..cbf1064c27 --- /dev/null +++ b/actor-example/README.md @@ -0,0 +1 @@ +Should live in rtic-rs/rtic-examples repo diff --git a/actor-example/actors/Cargo.toml b/actor-example/actors/Cargo.toml new file mode 100644 index 0000000000..48f8ac53d4 --- /dev/null +++ b/actor-example/actors/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "actors" +version = "0.1.0" +edition = "2018" + +[dependencies.rtic-actor-traits] +path = "../../actor-traits" + +[dev-dependencies.rtic-post-spy] +path = "../../post-spy" diff --git a/actor-example/actors/src/fake_temperature_sensor.rs b/actor-example/actors/src/fake_temperature_sensor.rs new file mode 100644 index 0000000000..4b4db0530d --- /dev/null +++ b/actor-example/actors/src/fake_temperature_sensor.rs @@ -0,0 +1,84 @@ +use rtic_actor_traits::Post; + +use crate::TemperatureReadingCelsius; + +pub struct FakeTemperatureSensor

+where + P: Post, +{ + delta: i32, + outbox: P, + temperature: i32, +} + +// a real temperature sensor would use the embedded-hal traits (e.g. I2C) or some higher level trait +impl

FakeTemperatureSensor

+where + P: Post, +{ + pub fn new(outbox: P, initial_temperature: i32, delta: i32) -> Self { + Self { + delta, + outbox, + temperature: initial_temperature, + } + } + + pub fn read(&mut self) { + self.outbox + .post(TemperatureReadingCelsius(self.temperature)) + .expect("OOM"); + self.temperature += self.delta; + } +} + +#[cfg(test)] +mod tests { + use rtic_post_spy::PostSpy; + + use super::*; + + #[test] + fn on_read_it_posts_reading() { + let mut sensor = FakeTemperatureSensor::new(PostSpy::default(), 0, 0); + sensor.read(); + + let spy = sensor.outbox; + let posted_messages = spy.posted_messages::(); + assert_eq!(1, posted_messages.count()); + } + + #[test] + fn reading_starts_at_initial_temperature() { + let initial_temperature = 1; + let mut sensor = FakeTemperatureSensor::new(PostSpy::default(), initial_temperature, 0); + sensor.read(); + + let spy = sensor.outbox; + let mut posted_messages = spy.posted_messages::(); + assert_eq!( + Some(&TemperatureReadingCelsius(initial_temperature)), + posted_messages.next() + ); + } + + #[test] + fn reading_changes_by_delta() { + let initial_temperature = 42; + let delta = 1; + let mut sensor = FakeTemperatureSensor::new(PostSpy::default(), initial_temperature, delta); + sensor.read(); + sensor.read(); + + let spy = sensor.outbox; + let mut posted_messages = spy.posted_messages::(); + assert_eq!( + Some(&TemperatureReadingCelsius(initial_temperature)), + posted_messages.next() + ); + assert_eq!( + Some(&TemperatureReadingCelsius(initial_temperature + delta)), + posted_messages.next() + ); + } +} diff --git a/actor-example/actors/src/lib.rs b/actor-example/actors/src/lib.rs new file mode 100644 index 0000000000..dae2c619e5 --- /dev/null +++ b/actor-example/actors/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] + +mod fake_temperature_sensor; +mod temperature_monitor; + +// Actors +pub use fake_temperature_sensor::FakeTemperatureSensor; +pub use temperature_monitor::TemperatureMonitor; + +// Messages +pub struct TemperatureAlert; + +#[derive(Clone, Debug, PartialEq)] +pub struct TemperatureReadingCelsius(pub i32); diff --git a/actor-example/actors/src/temperature_monitor.rs b/actor-example/actors/src/temperature_monitor.rs new file mode 100644 index 0000000000..6fdedd47c8 --- /dev/null +++ b/actor-example/actors/src/temperature_monitor.rs @@ -0,0 +1,63 @@ +use rtic_actor_traits::{Post, Receive}; + +use crate::{TemperatureAlert, TemperatureReadingCelsius}; + +pub struct TemperatureMonitor

+where + P: Post, +{ + outbox: P, + threshold: i32, +} + +impl

TemperatureMonitor

+where + P: Post, +{ + pub fn new(outbox: P, threshold: i32) -> Self { + Self { outbox, threshold } + } +} + +impl

Receive for TemperatureMonitor

+where + P: Post, +{ + fn receive(&mut self, temperature: TemperatureReadingCelsius) { + if temperature.0 >= self.threshold { + self.outbox.post(TemperatureAlert).ok().expect("OOM"); + } + } +} + +#[cfg(test)] +mod tests { + use rtic_post_spy::PostSpy; + + use super::*; + + #[test] + fn when_temperature_is_above_threshold_it_posts_alert_once() { + let mut monitor = TemperatureMonitor::new(PostSpy::default(), 0); + + // manually send a message + let message = TemperatureReadingCelsius(1); + monitor.receive(message); + + let spy = monitor.outbox; + let posted_messages = spy.posted_messages::(); + assert_eq!(1, posted_messages.count()); + } + + #[test] + fn when_temperature_is_below_threshold_it_does_not_post_alert() { + let mut monitor = TemperatureMonitor::new(PostSpy::default(), 0); + + let message = TemperatureReadingCelsius(-1); + monitor.receive(message); + + let spy = monitor.outbox; + let posted_messages = spy.posted_messages::(); + assert_eq!(0, posted_messages.count()); + } +} diff --git a/actor-example/firmware/.cargo/config.toml b/actor-example/firmware/.cargo/config.toml new file mode 100644 index 0000000000..ca7ce85309 --- /dev/null +++ b/actor-example/firmware/.cargo/config.toml @@ -0,0 +1,14 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nrf52840" +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tdefmt.x", + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[alias] +rb = "run --bin" +rrb = "run --release --bin" diff --git a/actor-example/firmware/Cargo.toml b/actor-example/firmware/Cargo.toml new file mode 100644 index 0000000000..52bc0e8834 --- /dev/null +++ b/actor-example/firmware/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "firmware" +edition = "2018" +version = "0.1.0" + +[dependencies] +actors = { path = "../actors" } +cortex-m = "0.7.1" +cortex-m-rt = "0.6.13" +cortex-m-rtic = { path = "../.." } +defmt = "0.2.0" +defmt-rtt = "0.2.0" +nrf52840-hal = "0.12.2" +panic-probe = { version = "0.2.0", features = ["print-defmt"] } +rtic-actor-traits = { path = "../../actor-traits" } +systick-monotonic = "0.1.0-alpha.0" + +[features] +# set logging levels here +default = [ + "defmt-default", + # "dependency-a/defmt-trace", +] + +# do NOT modify these features +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- \ No newline at end of file diff --git a/actor-example/firmware/src/bin/temperature-monitor.rs b/actor-example/firmware/src/bin/temperature-monitor.rs new file mode 100644 index 0000000000..69b0d5d8c2 --- /dev/null +++ b/actor-example/firmware/src/bin/temperature-monitor.rs @@ -0,0 +1,91 @@ +#![no_main] +#![no_std] + +use firmware as _; + +#[rtic::app(device = nrf52840_hal::pac, dispatchers = [RADIO])] +mod app { + use actors::{ + FakeTemperatureSensor, TemperatureAlert, TemperatureMonitor, TemperatureReadingCelsius, + }; + use rtic::time::duration::*; + use rtic_actor_traits::Receive; + use systick_monotonic::Systick; + + // configuration + const TEMPERATURE_THRESHOLD: i32 = 37; + const INITIAL_FAKE_TEMPERATURE: i32 = 35; + const FAKE_TEMPERATURE_DELTA: i32 = 1; + + // app-specific actors + struct AlertHandler; + + impl Receive for AlertHandler { + fn receive(&mut self, _: TemperatureAlert) { + defmt::error!("temperature alert"); + firmware::exit() + } + } + + struct TemperatureTracer; + + impl Receive for TemperatureTracer { + fn receive(&mut self, reading: TemperatureReadingCelsius) { + defmt::trace!("temperature: {} C", reading.0); + } + } + + #[actors] + struct Actors { + #[init(AlertHandler)] + #[subscribe(TemperatureAlert)] + alert_handler: AlertHandler, + + #[subscribe(TemperatureReadingCelsius)] // <- broadcast + temperature_monitor: TemperatureMonitor, + + #[init(TemperatureTracer)] + #[subscribe(TemperatureReadingCelsius)] // <- broadcast + temperature_tracer: TemperatureTracer, + } + + #[local] + struct Local { + temperature_sensor: FakeTemperatureSensor, + } + + #[monotonic(binds = SysTick, default = true)] + type Monotonic = Systick<100>; // 100 Hz + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics, Actors) { + let systick = cx.core.SYST; + let mono = Systick::new(systick, 48_000_000); + + let poster = cx.poster; + let temperature_monitor = TemperatureMonitor::new(poster, TEMPERATURE_THRESHOLD); + let temperature_sensor = + FakeTemperatureSensor::new(poster, INITIAL_FAKE_TEMPERATURE, FAKE_TEMPERATURE_DELTA); + + // kick start the system + periodic::spawn().expect("OOM"); + + ( + Shared {}, + Local { temperature_sensor }, + init::Monotonics(mono), + Actors { + temperature_monitor, + }, + ) + } + + #[task(local = [temperature_sensor])] + fn periodic(cx: periodic::Context) { + cx.local.temperature_sensor.read(); + periodic::spawn_after(1.seconds()).expect("OOM"); + } + + #[shared] + struct Shared {} +} diff --git a/actor-example/firmware/src/lib.rs b/actor-example/firmware/src/lib.rs new file mode 100644 index 0000000000..927b27d763 --- /dev/null +++ b/actor-example/firmware/src/lib.rs @@ -0,0 +1,20 @@ +#![no_std] + +use defmt_rtt as _; // global logger +use nrf52840_hal as _; // memory layout + +use panic_probe as _; + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +/// Terminates the application and makes `probe-run` exit with exit-code = 0 +pub fn exit() -> ! { + loop { + cortex_m::asm::bkpt(); + } +}