add split-crate example

This commit is contained in:
Jorge Aparicio 2021-09-27 15:18:37 +02:00
parent a58be575cb
commit 11694e134f
11 changed files with 368 additions and 0 deletions

View file

@ -57,6 +57,7 @@ members = [
"post-spy",
"xtask",
]
exclude = ["actor-example"]
# do not optimize proc-macro deps or build scripts
[profile.dev.build-override]

1
actor-example/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target

1
actor-example/README.md Normal file
View file

@ -0,0 +1 @@
Should live in rtic-rs/rtic-examples repo

View file

@ -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"

View file

@ -0,0 +1,84 @@
use rtic_actor_traits::Post;
use crate::TemperatureReadingCelsius;
pub struct FakeTemperatureSensor<P>
where
P: Post<TemperatureReadingCelsius>,
{
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<P> FakeTemperatureSensor<P>
where
P: Post<TemperatureReadingCelsius>,
{
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::<TemperatureReadingCelsius>();
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::<TemperatureReadingCelsius>();
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::<TemperatureReadingCelsius>();
assert_eq!(
Some(&TemperatureReadingCelsius(initial_temperature)),
posted_messages.next()
);
assert_eq!(
Some(&TemperatureReadingCelsius(initial_temperature + delta)),
posted_messages.next()
);
}
}

View file

@ -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);

View file

@ -0,0 +1,63 @@
use rtic_actor_traits::{Post, Receive};
use crate::{TemperatureAlert, TemperatureReadingCelsius};
pub struct TemperatureMonitor<P>
where
P: Post<TemperatureAlert>,
{
outbox: P,
threshold: i32,
}
impl<P> TemperatureMonitor<P>
where
P: Post<TemperatureAlert>,
{
pub fn new(outbox: P, threshold: i32) -> Self {
Self { outbox, threshold }
}
}
impl<P> Receive<TemperatureReadingCelsius> for TemperatureMonitor<P>
where
P: Post<TemperatureAlert>,
{
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::<TemperatureAlert>();
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::<TemperatureAlert>();
assert_eq!(0, posted_messages.count());
}
}

View file

@ -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"

View file

@ -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 # <-

View file

@ -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<TemperatureAlert> for AlertHandler {
fn receive(&mut self, _: TemperatureAlert) {
defmt::error!("temperature alert");
firmware::exit()
}
}
struct TemperatureTracer;
impl Receive<TemperatureReadingCelsius> 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<Poster>,
#[init(TemperatureTracer)]
#[subscribe(TemperatureReadingCelsius)] // <- broadcast
temperature_tracer: TemperatureTracer,
}
#[local]
struct Local {
temperature_sensor: FakeTemperatureSensor<Poster>,
}
#[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 {}
}

View file

@ -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();
}
}