phoxal 0.11.0

Phoxal — production-oriented autonomous robot framework (engine, model, typed bus, contracts).
Documentation

phoxal

The Phoxal robot framework as one crate: a manifest-driven stack for production-oriented autonomous robots — a typed Zenoh bus, a derive-based runtime authoring model, and a single, dated API-version contract set.

Greenfield rewrite in progress (tmp/framework-rewrite). The bus + runtime engine and the macros are new; the official runtime/simulator/tool crates are being re-ported onto this surface and are temporarily out of the workspace.

[dependencies]
phoxal = "0.11"

The authoring model

A runtime is one struct of typed handles + one annotated inherent impl. The struct declares the contracts it uses (as handle fields) and the one API version it runs against; the impl declares the lifecycle.

use phoxal::api::y2026_1 as api;
use phoxal::prelude::*;

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

#[phoxal::runtime]
impl AvoidObstacles {
    #[setup]
    async fn setup(ctx: &mut SetupContext<Self>) -> Result<Self> {
        Ok(Self {
            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();
        // read inputs, publish version-local bodies
        self.target.publish_at(now, api::drive::Target {
            linear_x_mps: 0.2, angular_z_radps: 0.0,
        }).await?;
        Ok(())
    }
}

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

Key rules the example shows:

  • Import exactly one dated API module (use phoxal::api::y2026_1 as api;) and declare it on the derive (#[phoxal(api = y2026_1)], mandatory).
  • Handle fields use version-local bodies: Publisher<api::drive::State>, Latest<api::drive::Target>. The derive emits a ContractBody<Api = R::Api> assertion, so a body from another API version is a compile error.
  • Topics are api-local: api::topic::new().drive().state().
  • The wire body is the plain payload; api_version, family, and codec ride bus metadata. Normal runtimes never open Zenoh — the runner opens the bundle-selected bus profile before #[setup].
  • cargo run --example runtime_control_loop emit-apis prints the runtime's static metadata as one JSON document (the emit-apis subcommand) and exits.

Modules

Module What it is
[phoxal::api] Dated API-version modules (y2026_1, …) generated by phoxal_api_tree!: the marker enum Api (ApiVersion), version-local wire bodies + their ContractBody impls, and the api-local topic builders.
[phoxal::bus] The Zenoh-native bus_abi boundary: the key scheme <namespace>/robots/<robot-id>/<topic>, the MessagePack codec, the BusMetadata attachment, and the body-typed handles Publisher/Subscriber/Latest.
[phoxal::runtime] The static metadata traits the macros target, SetupContext/StepContext, the clock (RealClock/TestClock) + scheduler, ParticipantLaunch, emit-apis, and the runner (run / tokio::run).
[phoxal::model] Authored manifest schemas (robot.yaml, structure.urdf, …).

Entrypoints

  • Default (blocking): fn main() -> phoxal::Result<()> { phoxal::run::<R>() }.
  • Advanced (async): phoxal::tokio::run::<R>().await for custom Tokio mains.

Status

Pre-1.0, building in public. One API version per robot graph (#[phoxal(api = y2026_1)]); the wire body is the plain payload and the version identity rides bus metadata, never the key. Compatibility is checked at build/check time (emit-apis + phoxal-cli check), never by introspecting a running binary.

License

AGPL-3.0-only. A commercial license is available — see the repository.