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 completerobotcontract 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/Apitypes; a directParticipantimplementation owns lifecycle behavior, andphoxal::runturns the marker into a binary. Useservicefor ordinary robot participants,driverfor a participant launched once perrobot.componentsentry, andbrainfor 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 therobotcontract family into scope;Apistruct 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, andApidefault to(). - Handles are ordinary fields built in
Participant::setupfrom 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 requesterQueryContextprovenance.- 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, callphoxal::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 drivenrobot.componentsentry, under that entry’s own id. Only a driver can callSetupContext::componentto read the component it is bound to, orSetupContext::connectionto read how that component is wired to the machine. The entry’sdriver:block is two slots with one owner each: aconnectionfrom the framework’s closedmodel::connectionvocabulary, and aconfigthe driver binary alone gives shape to.#[phoxal::brain]is the robot project’s one mandatory composition root: the root Cargo package’s binary, staged asbin/brain. Its identity is fixed tobrainand itsConfigis always(); it owns mission and intent policy as ordinary Rust code and holds no capability a service does not. It is never declared underrobot.yamlservices:.
Worked examples live in phoxal/examples/.
§Where to look next
phoxal::api- therobotcontract 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 withuse phoxal::api;and walks it fromapi::topics(). The runner also uses the siblingruntimefamily 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 withuse phoxal::prelude::*;: the handle types,SetupContext,StepContext, andResult.bus- the typed contract vocabulary normal participants need: the key scheme, MessagePack codec,BusMetadataattachment, the four non-interchangeable time types, endpoint-typed handles, and side-brandedTopicvalues.model- immutable canonical robot facts read from the bundle’smanifest.json. Bundle assembly and host-side reading arephoxal::bundle, and the authored document readers arephoxal::authoring; both are host-role surfaces a launched participant never reaches.geometryandSampleSchedule- 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.Sessionuniquely owns the transport and the lifecycle; a cloneableSessionHandleperforms typed operations. The profile also publishes theruntimeandsupervisorcontract 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.SimulatorSessionowns 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 amodel::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;participantpub use crate::model::AssetId;
Modules§
- api
participantorsessionorsimulator - The
robotcontract family: the surface a participant authors against. - authoring
authoring - Authored manifest readers and deterministic source-to-canonical compilation.
- bundle
authoringorsimulatororsupervisor - 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.
- participant
authoringorsessionorsupervisor - 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.
- prelude
participant - Everything a participant author imports with
use phoxal::prelude::*;. - runtime
sessionorsimulatororsupervisor - What a running Phoxal process says about itself.
- session
session - Application-neutral attachment to one running Phoxal execution.
- simulator
simulator - The external simulator host SDK.
- supervisor
- The supervisor boundary.
- testing
test-harness - Explicit in-process participant testing support.
- tokio
participant - 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§
- Query
Context participantand (authoringorsessionorsupervisor) - Trusted requester provenance for one admitted query.
- Reset
Context participantand (authoringorsessionorsupervisor) - Context for
Participant::reset: the runner observed a different timeline and is about to begin releasing steps for that world history. - Sample
Schedule - A publish cadence expressed as nanosecond deadlines on a logical timeline.
- Setup
Context participantand (authoringorsessionorsupervisor) - The sole IO-construction point, handed to
Participant::setup. - Step
Context participantand (authoringorsessionorsupervisor) - Per-step context: the robot instant this step reached, plus the capability to publish state at it.
Enums§
- Managed
Task Policy participantand (authoringorsessionorsupervisor) - What completion a managed task promises to the participant runner.
- Missed
Tick Policy - The one policy used when a producer reaches a deadline after one or more deadlines have already passed.
Traits§
- Participant
participantand (authoringorsessionorsupervisor) - Participant lifecycle behavior.
Functions§
- run
participantand (authoringorsessionorsupervisor) - Run a participant to completion on a framework-owned blocking Tokio runtime.
Type Aliases§
- Result
- The framework result type (
anyhow-backed). Authoring code uses bareResult<T>viaphoxal::prelude.Result<T, Error>
Attribute Macros§
- brain
participant - Declare the one mandatory root brain, the robot project’s composition root.
- driver
participant - Link a participant state struct to its
Config/Apitypes as a component driver, and optionally declare the one connection kind it accepts. The driver-shaped counterpart to [service]: one process per drivenrobot.componentsentry, launched under that entry’s own id. - service
participant - Link a participant state struct to its
Config/Apitypes as a checked service. Declare a service marker’sConfig/State/Apitypes. Each omitted type defaults to(); identity defaults fromCARGO_PKG_NAME. - step
participant - Attach a cadence to
Participant::step. Attach a positive, finite frequency to the ordinaryParticipant::stepoverride.
Derive Macros§
- Config
participant - Derive a participant config’s compile-time JSON Schema from a
Configstruct. Derive a compile-time Draft 2020-12 JSON Schema from the same supported#[serde(...)]attributes used byDeserialize:rename,rename_all,default, anddeny_unknown_fields. Unsupported Serde attributes are a compile error rather than an approximate schema.