Skip to main content

Crate phoxal

Crate phoxal 

Source
Expand description

§phoxal

A production-oriented framework for autonomous robots.

Phoxal gives a robot a small, strongly-typed core: a contract bus over Zenoh, a single dated API version per robot graph, and a runtime authoring model where one struct of typed handles plus a couple of attribute macros is a complete participant. The framework owns the awkward parts - argument parsing, bus connection, scheduling, query serving, shutdown, and health - so the code you write is the robot’s behavior, not its plumbing.

Three ideas hold it together:

  • A typed contract bus. Every message is a plain serde body bound to one contract family and one API version. Handles are body-typed (Publisher<T>, Subscriber<T>, Latest<T>, Querier<Req, Resp>), so the compiler - not a runtime check - rejects sending the wrong type on a topic.
  • One dated API version per graph. API versions are dated modules (api::y2026_1, …), not semver crates. A runtime authors against exactly one of them; mixing bodies from two versions is a compile error.
  • Runtimes are authored, not wired. You write a struct and an impl; the #[derive(Runtime)] and #[phoxal::runtime] macros derive the static metadata, and run turns the type into a binary.

§Author a runtime

A runtime is one struct of typed handles and one annotated inherent impl. The struct declares the contracts it uses (as handle fields) and its one API version; the impl declares the lifecycle. This is the whole getting-started surface:

use phoxal::api::y2026_1 as api;   // select ONE dated API version
use phoxal::prelude::*;

#[derive(phoxal::Runtime)]
#[phoxal(id = "avoid-obstacles", api = y2026_1)]
struct AvoidObstacles {
    state:  Latest<api::drive::State>,    // keep-last view of the drive state
    target: Publisher<api::drive::Target>, // commanded drive target
}

#[phoxal::runtime]
impl AvoidObstacles {
    #[setup]
    async fn setup(ctx: &mut SetupContext<Self>) -> Result<Self> {
        Ok(Self {
            // api-local topic builders bind each handle to this API version
            state:  ctx.subscribe(api::topic::new().drive().state()).latest().await?,
            target: ctx.publisher(api::topic::new().drive().target()).await?,
        })
    }

    #[step(hz = 50)]
    async fn step(&mut self, step: StepContext) -> Result<()> {
        let now = step.time();
        self.target.publish_at(now, api::drive::Target {
            linear_x_mps: 0.2,
            angular_z_radps: 0.0,
            curvature_limit_radpm: None,
        }).await?;
        Ok(())
    }
}

fn main() -> phoxal::Result<()> { phoxal::run::<AvoidObstacles>() }

What each piece does:

  • use phoxal::api::y2026_1 as api; and #[phoxal(api = y2026_1)] pick the runtime’s single API version. Every handle body type comes through api::…; switching versions is a one-line edit at the top plus the attribute.
  • Handle fields name version-local bodies (Publisher<api::drive::Target>, Latest<api::drive::State>). A body from another API version does not compile.
  • All handles are built in #[setup] from api-local topic builders (api::topic::new().drive().state()); long-lived ones become struct fields.
  • #[step(hz = ...)] is the scheduled control loop; the runner owns timing and delivers logical time via StepContext. Query servers use #[server] / #[server_snapshot], and #[shutdown] runs graceful cleanup before the bus closes.
  • fn main() -> phoxal::Result<()> { phoxal::run::<R>() } is the default blocking entrypoint. For a custom Tokio main, call phoxal::tokio::run::<R>().await.

The runner also exposes an emit-apis subcommand (cargo run --example runtime_control_loop emit-apis) that prints a runtime’s static metadata as one JSON document and exits, without opening the bus. Worked examples live in phoxal/examples/.

§Where to look next

  • api - the dated API-version modules (y2026_1, …): version-local wire bodies, the ApiVersion / ContractBody traits, and the api-local topic builders, all generated by phoxal_api_tree!.
  • prelude - everything a runtime author imports with use phoxal::prelude::*;: the handle types, SetupContext / StepContext, and Result.
  • runtime - the authoring surface behind the macros: the static metadata traits, the contexts, the clock and scheduler, and the runner (run / tokio::run).
  • bus - the Zenoh-native bus_abi boundary: the key scheme, the MessagePack codec, the BusMetadata attachment, and the body-typed handles.
  • model - the authored manifest schemas (robot.yaml, structure.urdf, component.yaml, …) that runtimes and the CLI parse.
  • The official runtime set ships alongside this crate in the workspace runtime/ tree (drive, localize, map, safety, …): full platform runtimes authored on exactly this surface, useful as reference reading.

Re-exports§

pub use runtime::run;
pub use schemars;

Modules§

api
The single API layer (D60/D61).
bus
The Zenoh-native bus_abi boundary (D26/D62).
model
Authored manifest model.
prelude
Everything a runtime author imports with use phoxal::prelude::*;.
runtime
The runtime engine: static metadata traits, contexts, clock, launch contract, emit-apis, and the runner.
tokio
Async host runner entrypoints for custom Tokio mains (phoxal::tokio::run::<Runtime>().await).
util

Macros§

phoxal_api_tree
Declare a dated API-version tree of version-local wire bodies + topics.

Type Aliases§

Result
The framework result type (anyhow-backed). Authoring code uses bare Result<T> via the prelude. Result<T, Error>

Attribute Macros§

runtime
The bare #[phoxal::runtime] attribute for a runtime’s inherent impl. The bare #[phoxal::runtime] attribute on a runtime’s inherent impl.

Derive Macros§

Runtime
Derive the static metadata for a runtime struct. See the crate docs. Derive the static metadata for a runtime struct.