Create Your First Node¶
This guide walks you through creating a custom Bubbaloop node that publishes data via Zenoh.
Prerequisites¶
- Pixi installed
- Rust toolchain (installed via pixi)
zenohdrunning locallybubbaloopCLI built and in your PATH
Where to Create Your Node¶
Every node is its own Git repository, separate from the main bubbaloop repo. Create it anywhere on your filesystem:
Your node will depend on bubbaloop-node-sdk and bubbaloop-schemas via Git (not local paths). This means you can develop, build, and publish independently.
Step 1: Scaffold¶
This creates:
my-sensor/
Cargo.toml # Depends on bubbaloop-node + bubbaloop-node-build + bubbaloop-schemas
node.yaml # Node manifest
pixi.toml # Build environment
config.yaml # Runtime configuration (publish_topic, rate_hz)
build.rs # Proto compilation (one-liner via bubbaloop-node-build)
src/
main.rs # Node trait impl + run_node() (edit this)
Step 2: Implement Your Logic¶
Edit src/main.rs. The generated scaffold uses the Node SDK — you only implement init() and run():
use bubbaloop_node::{Node, NodeContext, JsonPublisher};
#[async_trait::async_trait]
impl Node for MySensorNode {
type Config = Config;
fn name() -> &'static str { "my-sensor" }
fn descriptor() -> &'static [u8] { DESCRIPTOR }
async fn init(ctx: &NodeContext, config: &Config) -> anyhow::Result<Self> {
// SDK handles topic construction and encoding
let publisher = ctx.publisher_json(&config.publish_topic).await?;
Ok(Self { publisher, rate_hz: config.rate_hz })
}
async fn run(self, ctx: NodeContext) -> anyhow::Result<()> {
let mut shutdown_rx = ctx.shutdown_rx.clone();
let mut tick = tokio::time::interval(std::time::Duration::from_secs_f64(1.0 / self.rate_hz));
loop {
tokio::select! {
biased;
_ = shutdown_rx.changed() => break,
_ = tick.tick() => {
// YOUR SENSOR LOGIC HERE
self.publisher.put(&reading).await.ok();
}
}
}
Ok(())
}
}
The SDK automatically handles: Zenoh session, health heartbeat, schema queryable, config loading, signal handling, and logging. You focus on your sensor logic.
Topics use two key spaces — global for network-visible data, local for SHM-only:
bubbaloop/global/{machine_id}/{suffix} ← visible to dashboard, CLI, remote machines
bubbaloop/local/{machine_id}/{suffix} ← SHM zero-copy, same-machine only
Step 3: Build¶
Step 4: Test Locally¶
# Terminal 1: Start zenoh router
zenohd
# Terminal 2: Run your node
pixi run run
# or
./target/release/my-sensor_node -c config.yaml
Verify output with:
Step 5: Register with Daemon¶
bubbaloop node add .
bubbaloop node build my-sensor
bubbaloop node install my-sensor
bubbaloop node start my-sensor
bubbaloop node logs my-sensor -f
Step 6: Publish¶
Push your node to GitHub so others can install it:
git init && git add -A && git commit -m "Initial node"
gh repo create my-sensor --public --push --source .
Others install with:
Available Schemas¶
The bubbaloop-schemas crate provides these protobuf types:
| Type | Module | Feature |
|---|---|---|
Header |
root | (default) |
CompressedImage, RawImage |
camera |
(default) |
CurrentWeather, HourlyForecast, DailyForecast |
weather |
(default) |
SystemMetrics |
system_telemetry |
(default) |
NetworkStatus |
network_monitor |
(default) |
TopicsConfig |
config |
config |
Next Steps¶
- The SDK handles health heartbeats automatically — no manual setup needed
- Add custom protobuf messages in
protos/and reference them inrun() - Add node-specific config fields to your
Configstruct andconfig.yaml - See Node Development Guide for the full SDK reference
- See Node Marketplace for publishing and discovery