Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

actor API #537

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
- `examples/periodic-at2.rs`, an example of a periodic process with two tasks, with offset timing.
Here we depict two alternative usages of the timer type, explicit and trait based.
- book: Update `Monotonic` tips.
- actor API. for details see [RFC #52](https://github.com/rtic-rs/rfcs/pull/52)

### Fixed

Expand Down
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ version = "1.1.3"
[lib]
name = "rtic"

[[example]]
name = "actor-watermark"
required-features = ["memory-watermark"]

[dependencies]
cortex-m = "0.7.0"
cortex-m-rtic-macros = { path = "macros", version = "1.1.5" }
rtic-actor-traits = { path = "actor-traits" }
rtic-monotonic = "1.0.0"
rtic-core = "1.0.0"
heapless = "0.7.7"
Expand All @@ -42,15 +47,21 @@ version = "0.5.2"
[target.x86_64-unknown-linux-gnu.dev-dependencies]
trybuild = "1"

[features]
memory-watermark = ["cortex-m-rtic-macros/memory-watermark"]

[profile.release]
codegen-units = 1
lto = true

[workspace]
members = [
"actor-traits",
"macros",
"post-spy",
"xtask",
]
exclude = ["actor-example"]

# do not optimize proc-macro deps or build scripts
[profile.dev.build-override]
Expand All @@ -70,3 +81,5 @@ overflow-checks = false

[patch.crates-io]
lm3s6965 = { git = "https://github.com/japaric/lm3s6965" }
# remove when rtic-rs/rtic-syntax#75 is merged
rtic-syntax = { branch = "actor", git = "https://github.com/rtic-rs/rtic-syntax" }
1 change: 1 addition & 0 deletions actor-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
1 change: 1 addition & 0 deletions actor-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Should live in rtic-rs/rtic-examples repo
10 changes: 10 additions & 0 deletions actor-example/actors/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
98 changes: 98 additions & 0 deletions actor-example/actors/src/fake_temperature_sensor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use rtic_actor_traits::{Post, Receive};

use crate::{DoTemperatureRead, 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,
}
}
}

impl<P> Receive<DoTemperatureRead> for FakeTemperatureSensor<P>
where
P: Post<TemperatureReadingCelsius>,
{
fn receive(&mut self, _: DoTemperatureRead) {
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);

// manually send a message
let message = DoTemperatureRead;
sensor.receive(message);

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

// manually send a message
let message = DoTemperatureRead;
sensor.receive(message);

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

// manually send a message
let message = DoTemperatureRead;
sensor.receive(message);
sensor.receive(message);

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()
);
}
}
17 changes: 17 additions & 0 deletions actor-example/actors/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![no_std]

mod fake_temperature_sensor;
mod temperature_monitor;

// Actors
pub use fake_temperature_sensor::FakeTemperatureSensor;
pub use temperature_monitor::TemperatureMonitor;

// Messages
#[derive(Clone, Copy, Debug)]
pub struct DoTemperatureRead;

pub struct TemperatureAlert;

#[derive(Clone, Debug, PartialEq)]
pub struct TemperatureReadingCelsius(pub i32);
63 changes: 63 additions & 0 deletions actor-example/actors/src/temperature_monitor.rs
Original file line number Diff line number Diff line change
@@ -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");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is ok() called here when you could call expect directly on the return from post() i.e.
self.outbox.post(TemperatureAlert).expect("OOM");

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doing expect on the Result<T, E> value requires that E implements the fmt::Debug trait. doing ok().expect() places no trait requirements on the type E. TemperatureAlert does not implement the fmt::Debug trait so post(TemperatureAlert).expect() would not compile.

deriving Debug on the type and leaving out the ok() is also an option (no pun intended). I tend to write ok().expect() mostly out of bad habit. (less Result::unwrap calls produces smaller (code size) binaries but that's micro-optimizing and this is an example so that hardly matters; as I said: bad habit)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, ok makes sense. Thanks for the reply, I was mainly just curious more than critiquing

}
}
}

#[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());
}
}
14 changes: 14 additions & 0 deletions actor-example/firmware/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -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"
69 changes: 69 additions & 0 deletions actor-example/firmware/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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.3.0"
defmt-rtt = "0.3.1"
nrf52840-hal = "0.12.2"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
rtic-actor-traits = { path = "../../actor-traits" }
systick-monotonic = "1"

[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 # <-
Loading