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
9 changes: 9 additions & 0 deletions Cargo.lock

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

24 changes: 6 additions & 18 deletions Cargo.toml
Comment thread
CernyMil marked this conversation as resolved.
Comment thread
CernyMil marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ members = [
"examples/weather-mesh-demo/weather-station-beta",
"examples/weather-mesh-demo/weather-station-gamma",
"examples/hello-mailbox",
"examples/hello-mailbox-async",
"examples/hello-single-latest-async",
]
exclude = ["_external"]
Expand All @@ -55,16 +56,10 @@ categories = ["database-implementations", "embedded", "asynchronous"]

[workspace.dependencies]
# Core async runtime
tokio = { version = "1.47.1", default-features = false, features = [
"macros",
"rt-multi-thread",
] }
tokio = { version = "1.47.1", default-features = false, features = ["macros", "rt-multi-thread"] }

# Serialization (no_std compatible by default)
serde = { version = "1.0", default-features = false, features = [
"derive",
"alloc",
] }
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }

# Error handling
thiserror = "2.0.16"
Expand All @@ -85,9 +80,7 @@ tracing = { version = "0.1", default-features = false }

# Async utilities
futures = "0.3"
futures-util = { version = "0.3", default-features = false, features = [
"alloc",
] }
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }

# CLI (for aimdb-cli)
clap = { version = "4.0", features = ["derive"] }
Expand Down Expand Up @@ -126,9 +119,7 @@ embassy-net = { version = "0.9.0", path = "./_external/embassy/embassy-net", fea
"medium-ethernet",
"proto-ipv6",
] }
embassy-usb = { version = "0.6.0", path = "./_external/embassy/embassy-usb", features = [
"defmt",
] }
embassy-usb = { version = "0.6.0", path = "./_external/embassy/embassy-usb", features = ["defmt"] }
embassy-futures = { version = "0.1.2", path = "./_external/embassy/embassy-futures" }

# Embedded HAL for peripheral abstractions
Expand All @@ -143,10 +134,7 @@ embedded-nal-async = "0.8.0"
embedded-storage = "0.3.1"

# Embedded runtime and utilities
cortex-m = { version = "0.7.6", features = [
"inline-asm",
"critical-section-single-core",
] }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
critical-section = "1.1"
static_cell = "2"
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ test:

fmt:
@printf "$(GREEN)Formatting code (workspace members only)...$(NC)\n"
@for pkg in aimdb-executor aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-single-latest-async; do \
@for pkg in aimdb-executor aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async; do \
printf "$(YELLOW) → Formatting $$pkg$(NC)\n"; \
cargo fmt -p $$pkg 2>/dev/null || true; \
done
Expand All @@ -185,7 +185,7 @@ fmt:
fmt-check:
@printf "$(GREEN)Checking code formatting (workspace members only)...$(NC)\n"
@FAILED=0; \
for pkg in aimdb-executor aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-single-latest-async; do \
for pkg in aimdb-executor aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async; do \
printf "$(YELLOW) → Checking $$pkg$(NC)\n"; \
if ! cargo fmt -p $$pkg -- --check 2>&1; then \
printf "$(RED)❌ Formatting check failed for $$pkg$(NC)\n"; \
Expand Down Expand Up @@ -386,6 +386,8 @@ examples:
cargo build --package remote-access-demo
@printf "$(YELLOW) → Building hello-mailbox (sync)$(NC)\n"
cargo build --package hello-mailbox
@printf "$(YELLOW) → Building hello-mailbox-async $(NC)\n"
cargo build --package hello-mailbox-async
@printf "$(YELLOW) → Building hello-single-latest-async$(NC)\n"
cargo build --package hello-single-latest-async
@printf "$(GREEN)All examples built successfully!$(NC)\n"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ docker compose up
| --- | --- | --- |
| **SPMC Ring** | Bounded stream with independent consumers | Sensor telemetry, event logs |
| [**SingleLatest**](examples/hello-single-latest-async) | Only the current value matters | Feature flags, config, UI state |
| [**Mailbox**](examples/hello-mailbox) | Latest instruction wins | Device commands, actuation, RPC |
| [**Mailbox**](examples/hello-mailbox) / [**async Mailbox**](examples/hello-mailbox-async)| Latest instruction wins | Device commands, actuation, RPC |

**Four capability traits** — opt-in, type-checked:

Expand Down
25 changes: 5 additions & 20 deletions aimdb-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,14 @@ defmt = ["dep:defmt"] # Embedded logging via probe (no_std)
# Independent of `profiling`; works in no_std (only needs heap + atomics).
# `portable-atomic/critical-section` provides the 64-bit-atomic fallback on
# targets without native 64-bit atomics (e.g. thumbv7em); no-op elsewhere.
metrics = [
"alloc",
"portable-atomic/fallback",
"portable-atomic/critical-section",
]
metrics = ["alloc", "portable-atomic/fallback", "portable-atomic/critical-section"]

# Automatic stage profiling (.source()/.tap()/.link() timing).
# Independent of `metrics`; works in no_std (only needs heap + a runtime clock).
# `portable-atomic/critical-section` provides the 64-bit-atomic fallback on targets
# without native 64-bit atomics (e.g. thumbv7em); it's a no-op where native atomics
# exist, and embedded binaries already supply a `critical-section` impl.
profiling = [
"alloc",
"portable-atomic/fallback",
"portable-atomic/critical-section",
]
profiling = ["alloc", "portable-atomic/fallback", "portable-atomic/critical-section"]

# Testing features
test-utils = ["std"]
Expand All @@ -104,9 +96,7 @@ futures-util = { version = "0.3", default-features = false, features = [
# Runtime-neutral `oneshot` for the session engines (alloc-backed, no_std-ready).
# `futures-channel`'s `mpsc` is std-only, so the engines use `async-channel` for
# that (below); only its `oneshot` is used here.
futures-channel = { version = "0.3", default-features = false, features = [
"alloc",
] }
futures-channel = { version = "0.3", default-features = false, features = ["alloc"] }
# Runtime-neutral mpsc (bounded + unbounded) for the session engines — one
# alloc-backed implementation for every runtime (tokio + Embassy). Owned,
# cloneable `Arc`-based senders (so the `'static` per-connection/-subscription
Expand All @@ -133,15 +123,10 @@ tracing = { workspace = true, optional = true }
defmt = { workspace = true, optional = true }

# Synchronization primitives for no_std
spin = { version = "0.9", default-features = false, features = [
"mutex",
"spin_mutex",
] }
spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex"] }

# Hash map for no_std (same implementation as std::collections::HashMap)
hashbrown = { version = "0.15", default-features = false, features = [
"default-hasher",
] }
hashbrown = { version = "0.15", default-features = false, features = ["default-hasher"] }

[dev-dependencies]
# For no_std testing
Expand Down
12 changes: 12 additions & 0 deletions examples/hello-mailbox-async/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "hello-mailbox-async"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "AimDB minimal async example demonstrating Mailbox buffer (latest-wins) semantics"
publish = false

[dependencies]
aimdb-core = { path = "../../aimdb-core", features = ["std"] }
aimdb-tokio-adapter = { path = "../../aimdb-tokio-adapter", features = ["tokio-runtime"] }
tokio = { workspace = true, features = ["time"] }
25 changes: 25 additions & 0 deletions examples/hello-mailbox-async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# hello-mailbox-async: async Mailbox buffer demo
The Mailbox buffer keeps only the last message written. It's like a real mailbox where only the most recent letter is visible.

## When to use
This Mailbox buffer demo is useful in a variety of scenarios where you want to retain only the last message, for example:
- When you have a robotic arm that needs to execute a sequence of commands, and you only want to execute the last command, or even if the robot gets too much commands in a short period of time.

## How it works
The Mailbox buffer demo works by simulating a mailbox buffer that retains only the last message using async API.
It sends 3 Colors and the MailBox only gets the last message.

## How to run
From the workspace root, run:
```
cargo run -p hello-mailbox-async
```
**Expected output**
```
=== hello-mailbox-async: Mailbox buffer demo ===
Firing three rapid commands BEFORE consumer exists: Red → Green → Blue
✓ Got: Blue ← only the latest survived
Shutting down...
✓ Done.
```
Comment thread
CernyMil marked this conversation as resolved.
55 changes: 55 additions & 0 deletions examples/hello-mailbox-async/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use aimdb_core::{buffer::BufferCfg, AimDbBuilder};
use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt};
use std::sync::Arc;

// Enum of colors for the LED
#[derive(Debug, Clone, PartialEq, Eq)]
enum Color {
Red,
Green,
Blue,
}

// Struct representing the LED state
#[derive(Debug, Clone, PartialEq, Eq)]
struct Led {
color: Color,
}

// Main function
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== hello-mailbox-async: Mailbox buffer demo ===\n");

// configuration
let adapter = Arc::new(TokioAdapter);
let mut builder = AimDbBuilder::new().runtime(adapter);

builder.configure::<Led>("actuator.led", |reg| {
reg.buffer(BufferCfg::Mailbox)
.source(|ctx, producer| async move {
// Produce quickly BEFORE creating the consumer
println!("Firing three rapid commands BEFORE consumer exists: Red → Green → Blue");
producer.produce(Led { color: Color::Red });
producer.produce(Led {
color: Color::Green,
});
producer.produce(Led { color: Color::Blue });
ctx.time().sleep_millis(100).await;
})
.tap(|ctx, consumer| async move {
// Now we create the consumer — it will only see the last value in the Mailbox
let mut reader = consumer.subscribe();
ctx.time().sleep_millis(100).await;

match reader.recv().await {
Ok(msg) => println!(" ✓ Got: {:?} ← only the latest survived", msg.color),
Err(_) => println!(" (mailbox was already empty)"),
}
});
});
builder.run().await?;
println!("Shutting down...");
println!(" ✓ Done.");
Ok(())
}
1 change: 1 addition & 0 deletions examples/hello-mailbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ cargo run -p hello-mailbox
✓ Got: Green ← only the latest survived (Green)
3. Shutting down...
✓ Done.
```