Skip to content
Merged
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
14 changes: 13 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,24 @@ Processes the controller impl block. Distinguishes between:
* Creates `<StructName><MethodName>` stream type and `<StructName><MethodName>Args` struct.
* Signal methods are NOT exposed in the client API (controller emits them directly).

**Poll methods** (marked with `#[controller(poll_*)]`):
* Methods are called periodically at the specified interval.
* Three time unit attributes are supported:
* `#[controller(poll_seconds = N)]` - Poll every N seconds.
* `#[controller(poll_millis = N)]` - Poll every N milliseconds.
* `#[controller(poll_micros = N)]` - Poll every N microseconds.
* Methods with the same timeout value (same unit and value) are grouped into a single ticker arm.
* All methods in a group are called sequentially when the ticker fires (in declaration order).
* Poll methods are NOT exposed in the client API (internal to the controller).
* Uses `embassy_time::Ticker::every()` for timing.

**Getter/setter methods** (from struct field attributes):
* Receives getter/setter field info from struct processing.
* Generates client-side getter methods that request current field value.
* Generates client-side setter methods that update field value (and broadcast if published).

The generated `run()` method contains a `select_biased!` loop that receives method calls from
clients and dispatches them to the user's implementations.
clients, dispatches them to the user's implementations, and handles periodic poll method calls.

### Utilities (`src/util.rs`)
Case conversion functions (`pascal_to_snake_case`, `snake_to_pascal_case`) used for generating type and method names.
Expand All @@ -109,6 +120,7 @@ Case conversion functions (`pascal_to_snake_case`, `snake_to_pascal_case`) used
User code must have these dependencies (per README):
* `futures` with `async-await` feature.
* `embassy-sync` for channels and synchronization.
* `embassy-time` for poll method timing (only required if using poll methods).

Dev dependencies include `embassy-executor` and `embassy-time` for testing.

Expand Down
93 changes: 11 additions & 82 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,5 @@ embassy-sync = "0.7.2"
embassy-executor = { version = "0.9.1", features = [
"arch-std",
"executor-thread",
"defmt",
] }
embassy-time = { version = "0.5.0", features = [
"defmt",
"defmt-timestamp-uptime",
"tick-hz-32_768",
] }
embassy-time = { version = "0.5.0", features = ["mock-driver"] }
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,72 @@ methods:
signal events. The stream yields `<struct-name><method-name-in-pascal-case>Args` structs
(e.g., `ControllerPowerErrorArgs`) containing all signal arguments as public fields.

## Poll Methods

Methods can be marked for periodic execution using poll attributes. These methods are called
automatically by the controller's `run()` loop at the specified interval.

Three time unit attributes are supported:
* `#[controller(poll_seconds = N)]` - Poll every N seconds.
* `#[controller(poll_millis = N)]` - Poll every N milliseconds.
* `#[controller(poll_micros = N)]` - Poll every N microseconds.

Example:
```rust,no_run
use firmware_controller::controller;

#[controller]
mod sensor_controller {
pub struct Controller {
#[controller(publish)]
temperature: f32,
}

impl Controller {
// Called every 5 seconds.
#[controller(poll_seconds = 5)]
pub async fn read_temperature(&mut self) {
// Read from sensor and update state.
self.set_temperature(42.0).await;
}

// Called every 100ms.
#[controller(poll_millis = 100)]
pub async fn check_watchdog(&mut self) {
// Pet the watchdog.
}

// Both called every second (grouped together).
#[controller(poll_seconds = 1)]
pub async fn log_status(&self) {
// Log current status.
}

#[controller(poll_seconds = 1)]
pub async fn blink_led(&mut self) {
// Toggle LED.
}
}
}

fn main() {}
```

Key characteristics:
* Poll methods are **not** exposed in the client API. They are internal periodic tasks managed
entirely by the controller's `run()` loop.
* Methods with the same timeout value (same unit and value) are grouped into a single timer arm
and called sequentially when the timer fires (in declaration order).
* Uses `embassy_time::Ticker` for timing, which ensures consistent intervals regardless of method
execution time.

## Dependencies assumed

The `controller` macro assumes that you have the following dependencies in your `Cargo.toml`:

* `futures` with `async-await` feature enabled.
* `embassy-sync`
* `embassy-time` (only required if using poll methods)

## Known limitations & Caveats

Expand Down
Loading