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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"examples/weather-mesh-demo/weather-station-alpha",
"examples/weather-mesh-demo/weather-station-beta",
"examples/weather-mesh-demo/weather-station-gamma",
"examples/hello-mailbox",
]
exclude = ["_external"]
resolver = "2"
Expand Down
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,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-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta; 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-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox; do \
printf "$(YELLOW) → Formatting $$pkg$(NC)\n"; \
cargo fmt -p $$pkg 2>/dev/null || true; \
done
Expand All @@ -128,7 +128,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-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta; 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-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox; 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 @@ -275,6 +275,8 @@ examples:
cargo build --package weather-station-beta
@printf "$(YELLOW) → Building weather-station-gamma (embedded, embassy runtime)$(NC)\n"
cargo build --package weather-station-gamma --target thumbv7em-none-eabihf
@printf "$(YELLOW) → Building hello-mailbox (sync)$(NC)\n"
cargo build --package hello-mailbox
@printf "$(GREEN)All examples built successfully!$(NC)\n"

## Security & Quality commands
Expand Down Expand Up @@ -414,7 +416,7 @@ check: fmt-check clippy test test-embedded test-wasm deny
@printf "$(BLUE)✓ Embedded target compatibility verified$(NC)\n"
@printf "$(BLUE)✓ WASM target compatibility verified$(NC)\n"
@printf "$(BLUE)✓ Dependencies verified (deny)$(NC)\n"

## WASM commands
wasm:
@printf "$(GREEN)Building WASM adapter with wasm-pack...$(NC)\n"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ docker compose up
| --- | --- | --- |
| **SPMC Ring** | Bounded stream with independent consumers | Sensor telemetry, event logs |
| **SingleLatest** | Only the current value matters | Feature flags, config, UI state |
| **Mailbox** | Latest instruction wins | Device commands, actuation, RPC |
| [**Mailbox**](https://github.com/aimdb-dev/aimdb/tree/main/examples/hello-mailbox) | Latest instruction wins | Device commands, actuation, RPC |

- **Capabilities are unlocked by traits.** Implement `Streamable` to cross WASM/WebSocket/CLI boundaries, `Migratable` for typed schema evolution, `Observable` for monitoring, `Linkable` for wire-format connectors. Without the trait, the type system says no.
- **Connectors that ship today:** MQTT, KNX, WebSocket. Writing your own is one trait impl.
Expand Down
13 changes: 13 additions & 0 deletions examples/hello-mailbox/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "hello-mailbox"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "AimDB minimal 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"] }
aimdb-sync = { path = "../../aimdb-sync" }
tokio = { workspace = true, features = ["full"] }
35 changes: 35 additions & 0 deletions examples/hello-mailbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# hello-mailbox: 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.
it runs 2 rounds:
- the first round sends 3 Colors and the MailBox only gets the last message.
- the second round sends 2 Colors and the MailBox only gets the last message.

## How to run
From the workspace root, run:
```
cargo run -p hello-mailbox
```
**Expected output**
```
=== hello-mailbox: Mailbox buffer demo ===

Round 1
1. Firing three rapid commands BEFORE consumer exists: Red → Green → Blue
2. Consumer created AFTER the burst — reads once:
✓ Got: Blue ← only the latest survived
(Red and Green were overwritten before anyone could read them)

Round 2
1. Firing two rapid commands BEFORE consumer exists: Red → Green
2. Consumer created AFTER the burst — reads once:
✓ Got: Green ← only the latest survived (Green)
3. Shutting down...
✓ Done.
```
86 changes: 86 additions & 0 deletions examples/hello-mailbox/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use aimdb_core::{buffer::BufferCfg, AimDbBuilder};
use aimdb_sync::AimDbBuilderSyncExt;
use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

// 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
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== hello-mailbox: 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);
});

let handle = builder.attach()?;
let producer = handle.producer::<Led>("actuator.led")?;

{
// Produce quickly BEFORE creating the consumer
println!(" Round 1 ");
println!("1. Firing three rapid commands BEFORE consumer exists: Red → Green → Blue");
producer.set(Led { color: Color::Red })?;
producer.set(Led {
color: Color::Green,
})?;
producer.set(Led { color: Color::Blue })?;
thread::sleep(Duration::from_millis(100));

// Now we create the consumer — it will only see the last value in the Mailbox
println!("2. Consumer created AFTER the burst — reads once:");
let consumer = handle.consumer::<Led>("actuator.led")?;
thread::sleep(Duration::from_millis(100));

match consumer.try_get() {
Ok(msg) => println!(" ✓ Got: {:?} ← only the latest survived", msg.color),
Err(_) => println!(" (mailbox was already empty)"),
}

println!(" (Red and Green were overwritten before anyone could read them)\n");
}

println!(" Round 2 ");
println!("1. Firing two rapid commands BEFORE consumer exists: Red → Green");
// Create the color burst again
producer.set(Led { color: Color::Red })?;
producer.set(Led {
color: Color::Green,
})?;
thread::sleep(Duration::from_millis(100));

println!("2. Consumer created AFTER the burst — reads once:");
let consumer2 = handle.consumer::<Led>("actuator.led")?;
thread::sleep(Duration::from_millis(100));

match consumer2.try_get() {
Ok(msg) => println!(
" ✓ Got: {:?} ← only the latest survived (Green)",
msg.color
),
Err(_) => println!(" (mailbox was already empty)"),
}

println!("3. Shutting down...");
handle.detach()?;
println!(" ✓ Done.");
Ok(())
}