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, train-selected concrete API contracts, and a participant authoring model where a role marker plus a direct trait implementation is a complete service, driver, tool, or simulator. 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 version-qualified contract name. Handles are body-typed (StatePublisher<T>, Subscriber<T>, Latest<T>, Querier<Req, Resp>), so the compiler - not a late check - rejects sending the wrong type on a topic. Publishing is additionally gated by the contract’s temporal role: the robot time a publisher can express is fixed by what the contract is, so a participant cannot stamp an instant it never reached.
  • One train-selected API facade. Official participants import phoxal::api, which names the complete concrete revision selected by the locked framework train. Contract identity is realized on the wire by the revision-qualified key (D1).
  • Participants are authored, not wired. A role attribute declares identity and associated Config/State/Api types; a direct Participant implementation owns lifecycle behavior, and run turns the marker into a binary. Use service for ordinary robot participants, driver for a participant launched once per robot.components entry, tool for host-side utilities, and simulator for simulation-only participants.

§Author a participant

A participant is a unit role marker, optional Config/State/Api types, and one direct trait implementation:

use phoxal::api;
use phoxal::prelude::*;

struct Api {
    state:  Latest<api::drive::State>,            // keep-last view of the drive state
    target: CommandPublisher<api::drive::Target>, // commanded drive target
}

#[phoxal::service(id = "avoid-obstacles", api = Api)]
struct AvoidObstacles;

impl Participant for AvoidObstacles {
    async fn setup(
        &self,
        ctx: &mut SetupContext<Self>,
        _config: Self::Config,
    ) -> Result<(Self::State, Self::Api)> {
        Ok(((), Api {
            state:  ctx.latest(api::topic::client().drive().state()).await?,
            target: ctx.command_publisher(api::topic::client().drive().target()).await?,
        }))
    }

    #[phoxal::step(hz = 50)]
    async fn step(
        &self,
        api: &Self::Api,
        _step: StepContext,
        _state: &mut Self::State,
    ) -> Result<()> {
        api.target.send(api::drive::Target {
            linear_x_mps: 0.2,
            angular_z_radps: 0.0,
            curvature_limit_radpm: None,
        })?;
        Ok(())
    }
}

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

What each piece does:

  • use phoxal::api; brings the versioned API module into scope; Api struct fields name train-selected bodies (api::drive::Target) directly, with no participant-local version attribute to keep in sync.
  • The role attribute records identity and sets associated types. Omitted Config, State, and Api default to ().
  • Handles are ordinary fields built in Participant::setup from typed topic builders and returned alongside mutable state.
  • #[phoxal::step(hz = ...)] adds a cadence to the trait’s step override.
  • ctx.query(owner_endpoint, Self::handler) registers typed query handlers; the endpoint fixes the handler’s request and response types at compile time.
  • The runner serializes step, query, reset, and shutdown access to State.
  • fn main() -> phoxal::Result<()> { phoxal::run::<R>() } is the default blocking entrypoint. For a custom Tokio main, call phoxal::tokio::run::<R>().await.

The four authoring kinds share the same metadata path but describe different runtime roles:

  • service is the ordinary typed participant surface.
  • driver is launched once per robot.components entry. Only a driver can call SetupContext::component to read the bound component instance.
  • tool is for host-side utilities that inspect the robot model through SetupContext::robot. Its privileged low-level transport is available only through the capability-gated SetupContext::bus method.
  • simulator is a normal participant for simulation-only processes. It carries a distinct kind and marker for simulation clock ownership.

Worked examples live in phoxal/examples/.

§Where to look next

  • The phoxal-api crate (phoxal::api, …) - the versioned API modules: version-local wire bodies, the ApiVersion / ContractBody traits, and the api-local topic builders, all generated by phoxal_api_tree!. A participant imports it directly with use phoxal::api as api;. The runner also links it for framework-owned out-of-band infrastructure contracts such as bus logs.
  • prelude - everything a participant author imports with use phoxal::prelude::*;: the handle types, SetupContext, StepContext, and Result.
  • bus - the typed contract vocabulary normal participants need: the key scheme, MessagePack codec, BusMetadata attachment, the four non-interchangeable time types, body-typed handles, and side-branded Topic values.
  • model - immutable canonical runtime robot facts decoded from the compiled robot.json; authored YAML and URDF live in phoxal-manifest.
  • The official service set ships alongside this crate in the workspace service/ tree (drive, localize, map, safety, …): full platform participants authored on exactly this surface, useful as reference reading.

Modules§

api
The concrete framework API revision selected by this release train. Concrete API revision v0.1 - version-local wire bodies + topics.
bus
Typed contract and handle vocabulary for normal participant authoring.
model
Curated canonical robot model.
prelude
Everything a participant author imports with use phoxal::prelude::*;.
tokio
Async host runner entrypoint for custom Tokio mains (phoxal::tokio::run::<Participant>().await).

Structs§

AssetId
A normalized, forward-slash logical asset identifier.
AssetResolver
Read-only resolver for the declared assets below <bundle>/assets.
ResetContext
Context for Participant::reset: the runner observed a different timeline and is about to begin releasing steps for that world history.
SetupContext
The sole IO-construction point, handed to Participant::setup.
StepContext
Per-step context: the robot instant this step reached, plus the capability to publish state at it.

Traits§

Participant
Participant lifecycle behavior.

Functions§

run
Run a participant to completion on a framework-owned blocking Tokio runtime.

Type Aliases§

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

Attribute Macros§

driver
Link a participant state struct to its Config/Api types as a component driver. The driver-shaped counterpart to [service].
service
Link a participant state struct to its Config/Api types as a checked service. Declare a service marker’s Config/State/Api types. Each omitted type defaults to (); identity defaults from CARGO_PKG_NAME.
simulator
Link a participant state struct to its Config/Api types as a simulation participant. The simulator-shaped counterpart to [service].
step
Attach a cadence to Participant::step. Attach a positive, finite frequency to the ordinary Participant::step override.
tool
Link a participant state struct to its Config as a raw-bus tool (Api defaults to () - tools stay raw-bus only). The tool-shaped counterpart to [service]. Api and Config default to () - tools stay raw-bus only (decided 2026-07-09), and a configless tool can launch without PHOXAL_CONFIG. An explicit config = Type remains required at launch unless that type itself accepts null (for example, Option<T>).

Derive Macros§

Config
Derive participant config identity from a Config struct (schema materialization is a later slice). Derive a compile-time Draft 2020-12 JSON Schema from the same supported #[serde(...)] attributes used by Deserialize: rename, rename_all, default, and deny_unknown_fields. Unsupported Serde attributes are a compile error rather than an approximate schema.