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, framework-owned semantic API contracts, and a participant authoring model where a role marker plus a direct trait implementation is a complete service or driver. 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 family-rooted contract name, and that body is the endpoint. Handles are endpoint-typed (StatePublisher<E>, StateView<E>, SampleReceiver<E>, Querier<E>), 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 authoring API facade. Official participants import phoxal::api, the complete robot contract family. Contract identity is realized on the wire by the family-rooted key; compatibility between participants is the framework train version they were built from.
  • Participants are authored, not wired. A role attribute declares identity and associated Config/State/Api types; a direct Participant implementation owns lifecycle behavior, and phoxal::run turns the marker into a binary. Use service for ordinary robot participants, driver for a participant launched once per robot.components entry, and brain for the robot project’s one mandatory composition root.

§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:  StateView<api::drive::State>,       // keep-last drive state
    target: SetpointPublisher<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.state_view(api::topics().drive().state().client()).await?,
            target: ctx.setpoint_publisher(api::topics().drive().target().client())?,
        }))
    }

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

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

What each piece does:

  • use phoxal::api; brings the robot contract family into scope; Api struct fields name its bodies (api::drive::Target) directly - the body is the endpoint, so there is no second descriptor identity and no participant-local contract 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, and the runner supplies trusted requester QueryContext provenance.
  • 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 three authoring kinds share the same metadata path but describe different runtime roles:

  • #[phoxal::service] is the ordinary typed participant surface.
  • #[phoxal::driver] is launched once per driven robot.components entry, under that entry’s own id. Only a driver can call SetupContext::component to read the component it is bound to, or SetupContext::connection to read how that component is wired to the machine. The entry’s driver: block is two slots with one owner each: a connection from the framework’s closed model::connection vocabulary, and a config the driver binary alone gives shape to.
  • #[phoxal::brain] is the robot project’s one mandatory composition root: the root Cargo package’s binary, staged as bin/brain. Its identity is fixed to brain and its Config is always (); it owns mission and intent policy as ordinary Rust code and holds no capability a service does not. It is never declared under robot.yaml services:.

Worked examples live in phoxal/examples/.

§Where to look next

  • phoxal::api - the robot contract family: the wire bodies and the dynamic topic tree they are declared in, module by module (each module a branch of child nodes or a leaf of endpoints, never both). A participant imports it directly with use phoxal::api; and walks it from api::topics(). The runner also uses the sibling runtime family for framework-owned out-of-band infrastructure contracts such as bus logs, which a participant never names itself.
  • phoxal::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, endpoint-typed handles, and side-branded Topic values.
  • model - immutable canonical robot facts read from the bundle’s manifest.json. Bundle assembly and host-side reading are phoxal::bundle, and the authored document readers are phoxal::authoring; both are host-role surfaces a launched participant never reaches.
  • geometry and SampleSchedule - the small shared arithmetic every official participant would otherwise reimplement.
  • The official service set ships alongside this crate in the workspace services/ tree (drive, localize, map, safety, …): full platform participants authored on exactly this surface, useful as reference reading.

§Host SDKs

A robot developer writes participants; the processes around a robot - the CLI and Operator applications that attach to a running execution, an external simulator that owns a world, the source compiler behind phoxal build - are separate consumer roles. They are the same crate and the same train, selected by a Cargo feature so that none of them crowds the participant surface above.

  • session - phoxal::session: attach to one running execution. Session uniquely owns the transport and the lifecycle; a cloneable SessionHandle performs typed operations. The profile also publishes the runtime and supervisor contract families, the participant launch encoder, and the embedded participant-metadata reader.
  • simulator - phoxal::simulator: stand an external world process in for a robot’s component drivers. SimulatorSession owns typed component IO, delegated presence, and the world clock, without handing out the raw transport underneath.
  • authoring - phoxal::authoring: the authored-source layer (robot.yaml, component.yaml, simulation.yaml, URDF), its JSON schemas, and the compiler that turns them into a model::Robot. A launched participant never reads an authored document.

The supervisor’s own implementation is phoxal::supervisor::host, behind the supervisor profile and hidden from these docs: it is the body of the framework-owned phoxal-supervisor executable, not an SDK. Everything a client has to agree with it about is phoxal::supervisor::api and phoxal::supervisor::rendezvous, which the session profile publishes.

Every path named in this section is spelled rather than linked, because the profile that publishes it is not the profile you are reading these docs in unless you enabled it. docs.rs enables all of them.

Profiles are additive compilation and visibility controls, never authority boundaries: Cargo unifies features, and who may do what at runtime remains process ownership and the constructible API.

Re-exports§

pub use crate::bundle::ParticipantAssets as ParticipantAssetResolver;participant
pub use crate::model::AssetId;

Modules§

apiparticipant or session or simulator
The robot contract family: the surface a participant authors against.
authoringauthoring
Authored manifest readers and deterministic source-to-canonical compilation.
bundleauthoring or simulator or supervisor
The persisted bundle boundary.
bus
The Phoxal bus ABI floor: the Zenoh-native wire boundary, plus the family, payload, and endpoint-semantic primitives the bus client is generic over.
geometry
Planar geometry every participant that reasons about pose or heading needs.
identity
The identity axes that reach the wire.
model
Canonical immutable runtime robot model.
participantauthoring or session or supervisor
The participant engine: the authoring traits, the role-gated capability surface, the setup/step/reset contexts, the clock and step scheduler, the launch contract, and the runner that drives them.
preludeparticipant
Everything a participant author imports with use phoxal::prelude::*;.
runtimesession or simulator or supervisor
What a running Phoxal process says about itself.
sessionsession
Application-neutral attachment to one running Phoxal execution.
simulatorsimulator
The external simulator host SDK.
supervisor
The supervisor boundary.
testingtest-harness
Explicit in-process participant testing support.
tokioparticipant
Async host runner entrypoint for custom Tokio mains (phoxal::tokio::run::<Participant>().await).
version
The one compatibility identity that crosses a Phoxal process boundary.

Structs§

QueryContextparticipant and (authoring or session or supervisor)
Trusted requester provenance for one admitted query.
ResetContextparticipant and (authoring or session or supervisor)
Context for Participant::reset: the runner observed a different timeline and is about to begin releasing steps for that world history.
SampleSchedule
A publish cadence expressed as nanosecond deadlines on a logical timeline.
SetupContextparticipant and (authoring or session or supervisor)
The sole IO-construction point, handed to Participant::setup.
StepContextparticipant and (authoring or session or supervisor)
Per-step context: the robot instant this step reached, plus the capability to publish state at it.

Enums§

ManagedTaskPolicyparticipant and (authoring or session or supervisor)
What completion a managed task promises to the participant runner.
MissedTickPolicy
The one policy used when a producer reaches a deadline after one or more deadlines have already passed.

Traits§

Participantparticipant and (authoring or session or supervisor)
Participant lifecycle behavior.

Functions§

runparticipant and (authoring or session or supervisor)
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 phoxal::prelude. Result<T, Error>

Attribute Macros§

brainparticipant
Declare the one mandatory root brain, the robot project’s composition root.
driverparticipant
Link a participant state struct to its Config/Api types as a component driver, and optionally declare the one connection kind it accepts. The driver-shaped counterpart to [service]: one process per driven robot.components entry, launched under that entry’s own id.
serviceparticipant
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.
stepparticipant
Attach a cadence to Participant::step. Attach a positive, finite frequency to the ordinary Participant::step override.

Derive Macros§

Configparticipant
Derive a participant config’s compile-time JSON Schema from a Config struct. 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.