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