From 8440041221fb1a057313401ee7265d9937511c0f Mon Sep 17 00:00:00 2001 From: jbernavaprah Date: Tue, 30 Jun 2026 14:13:09 +0200 Subject: [PATCH] feat: port drivers to the #00 framework (nested API + owner capability) Bring the component drivers onto the plan #00 framework surface (DoD #1): the nested component API, the `phoxal-api` crate, and the L2 owner capability. - depend on the #00 framework via a pinned git bridge (until it is released to crates.io): `phoxal` and `phoxal-api` at framework rev e8c4ce8. Each crate adds `phoxal-api = { workspace = true }`. - import `use phoxal_api::y2026_1 as api;` (was `phoxal::api`). - nested contract types: `api::component::motor::Command`, `encoder::Sample`, `imu::Sample`, `accelerometer::Sample`, `gyroscope::Sample`, `camera::Frame` + `camera::Encoding`, `depth::Frame` + `depth::Encoding` + `depth::InvalidSamplePolicy`, `range::Sample` + `range::Limits`, `gnss::Sample` (no flat `api::component::` left). `SensorHealth` is now per-capability (`imu::SensorHealth`, `range::SensorHealth`). - a driver OWNS its component capabilities, so every topic is acquired through the L2 owner builder `api::topic::internal::new(cap).component(i).(c).()` with `let cap = ctx.owner_capability();`. ddsm115 subscribes `motor::Command` (command role) and publishes `encoder::Sample` (state); sensor drivers publish their `*::sample` / `*::frame` (state). Green: cargo build/test/clippy -D warnings/fmt against the #00 framework. --- Cargo.lock | 43 ++++++++++++--- Cargo.toml | 4 +- bno085/Cargo.toml | 3 +- bno085/src/main.rs | 56 ++++++++++--------- ddsm115/Cargo.toml | 3 +- ddsm115/src/main.rs | 50 +++++++++-------- oak_d_lite/Cargo.toml | 3 +- oak_d_lite/src/main.rs | 119 ++++++++++++++++++++++------------------- vl53l1x/Cargo.toml | 3 +- vl53l1x/src/main.rs | 28 +++++----- zed_f9p/Cargo.toml | 3 +- zed_f9p/src/main.rs | 24 +++++---- 12 files changed, 205 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9d4e83..16ea85f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1913,9 +1913,8 @@ dependencies = [ [[package]] name = "phoxal" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5510ff7df9f5ca50a51632cb7dde67dbc64f06c141cdf975e3a4516d1a3263c3" +version = "0.18.0" +source = "git+https://github.com/phoxal/framework?rev=e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee#e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" dependencies = [ "anyhow", "arc-swap", @@ -1923,8 +1922,10 @@ dependencies = [ "derive-new", "dotenvy", "nalgebra", + "phoxal-bus", "phoxal-macros", "rmp-serde", + "schemars 1.2.1", "serde", "serde_bytes", "serde_json", @@ -1937,12 +1938,39 @@ dependencies = [ "zenoh", ] +[[package]] +name = "phoxal-api" +version = "0.18.0" +source = "git+https://github.com/phoxal/framework?rev=e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee#e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" +dependencies = [ + "phoxal-bus", + "phoxal-macros", + "serde", + "serde_bytes", +] + +[[package]] +name = "phoxal-bus" +version = "0.18.0" +source = "git+https://github.com/phoxal/framework?rev=e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee#e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" +dependencies = [ + "arc-swap", + "rmp-serde", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "zenoh", +] + [[package]] name = "phoxal-component-bno085" version = "0.1.0" dependencies = [ "anyhow", "phoxal", + "phoxal-api", "serde_json", ] @@ -1952,6 +1980,7 @@ version = "0.1.0" dependencies = [ "anyhow", "phoxal", + "phoxal-api", "serde_json", ] @@ -1961,6 +1990,7 @@ version = "0.1.0" dependencies = [ "anyhow", "phoxal", + "phoxal-api", "serde_json", ] @@ -1970,6 +2000,7 @@ version = "0.1.0" dependencies = [ "anyhow", "phoxal", + "phoxal-api", "serde_json", ] @@ -1979,14 +2010,14 @@ version = "0.1.0" dependencies = [ "anyhow", "phoxal", + "phoxal-api", "serde_json", ] [[package]] name = "phoxal-macros" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a910c9e9e43b91ae51ee28634a16a9adadb38c28ba489487590b86ca65a27" +version = "0.18.0" +source = "git+https://github.com/phoxal/framework?rev=e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee#e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 53d1645..45a77b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ async-trait = "0.1.89" clap = { version = "4.6.1", features = ["derive"] } tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "time", "signal", "sync", "fs"] } tracing = "0.1.44" -phoxal = { version = "0.16" } +# git bridge to the #00 framework until it is released to crates.io (plan #00). +phoxal = { git = "https://github.com/phoxal/framework", rev = "e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" } +phoxal-api = { git = "https://github.com/phoxal/framework", rev = "e8c4ce8395ef9afeb676a6c20e24f0eee00e8aee" } [workspace.lints.rust] unused = "warn" diff --git a/bno085/Cargo.toml b/bno085/Cargo.toml index e092a91..76d8057 100644 --- a/bno085/Cargo.toml +++ b/bno085/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true license.workspace = true publish = false -description = "Phoxal catalog component — bno085." +description = "Phoxal catalog component - bno085." documentation.workspace = true homepage.workspace = true repository.workspace = true @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = { workspace = true } phoxal = { workspace = true } +phoxal-api = { workspace = true } [dev-dependencies] serde_json = "1" diff --git a/bno085/src/main.rs b/bno085/src/main.rs index 141075e..eac861e 100644 --- a/bno085/src/main.rs +++ b/bno085/src/main.rs @@ -1,20 +1,20 @@ //! `bno085` — BNO085 IMU component driver stub. use anyhow::{Result, bail}; -use phoxal::api::y2026_1 as api; use phoxal::model::component::v1::capability::Capability; use phoxal::prelude::*; +use phoxal_api::y2026_1 as api; const STEP_HZ: f64 = 100.0; #[derive(phoxal::Runtime)] #[phoxal(id = "bno085", api = y2026_1)] struct Bno085 { - imu: Vec>, + imu: Vec>, imu_divisors: Vec, - accelerometer: Vec>, + accelerometer: Vec>, accelerometer_divisors: Vec, - gyroscope: Vec>, + gyroscope: Vec>, gyroscope_divisors: Vec, } @@ -28,6 +28,9 @@ struct CapabilitySchedule { impl Bno085 { #[setup] async fn setup(ctx: &mut SetupContext) -> Result { + // Owner opt-in (plan #00 L2): the runner-minted capability that the owner + // (`internal`) topic builder requires. This driver OWNS its component node. + let cap = ctx.owner_capability(); let instance = ctx.component()?.to_string(); let (imu_slots, accelerometer_slots, gyroscope_slots) = { let robot = ctx.robot()?; @@ -69,9 +72,10 @@ impl Bno085 { for slot in imu_slots { imu.push( ctx.publisher( - api::topic::new() - .component() - .imu_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .imu(&slot.capability_id) + .sample(), ) .await?, ); @@ -83,9 +87,10 @@ impl Bno085 { for slot in accelerometer_slots { accelerometer.push( ctx.publisher( - api::topic::new() - .component() - .accelerometer_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .accelerometer(&slot.capability_id) + .sample(), ) .await?, ); @@ -97,9 +102,10 @@ impl Bno085 { for slot in gyroscope_slots { gyroscope.push( ctx.publisher( - api::topic::new() - .component() - .gyroscope_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .gyroscope(&slot.capability_id) + .sample(), ) .await?, ); @@ -171,8 +177,8 @@ fn is_due(step_index: u64, divisor: u64) -> bool { divisor <= 1 || step_index % divisor == 0 } -fn imu_sample() -> api::component::ImuSample { - api::component::ImuSample { +fn imu_sample() -> api::component::imu::Sample { + api::component::imu::Sample { orientation: Some([1.0, 0.0, 0.0, 0.0]), angular_velocity_radps: [0.0; 3], linear_acceleration_mps2: [0.0; 3], @@ -180,19 +186,19 @@ fn imu_sample() -> api::component::ImuSample { noise_density: None, sensor_frame_id: None, measured_at_ns: None, - health: api::component::SensorHealth::Nominal, + health: api::component::imu::SensorHealth::Nominal, bias: None, } } -fn accelerometer_sample() -> api::component::AccelerometerSample { - api::component::AccelerometerSample { +fn accelerometer_sample() -> api::component::accelerometer::Sample { + api::component::accelerometer::Sample { linear_acceleration: [0.0; 3], } } -fn gyroscope_sample() -> api::component::GyroscopeSample { - api::component::GyroscopeSample { +fn gyroscope_sample() -> api::component::gyroscope::Sample { + api::component::gyroscope::Sample { angular_velocity: [0.0; 3], } } @@ -204,8 +210,8 @@ fn main() -> phoxal::Result<()> { #[cfg(test)] mod tests { use super::{Bno085, divisor_for_rate, is_due}; - use phoxal::api::ContractBody; - use phoxal::api::y2026_1 as api; + use phoxal_api::ContractBody; + use phoxal_api::y2026_1 as api; #[test] fn divisor_rounds_to_fixed_step_clock() { @@ -224,9 +230,9 @@ mod tests { assert_eq!(value["artifact"]["id"], "bno085"); let contracts = value["required_contracts"].as_array().unwrap(); for family in [ - ::FAMILY, - ::FAMILY, - ::FAMILY, + ::FAMILY, + ::FAMILY, + ::FAMILY, ] { assert!( contracts diff --git a/ddsm115/Cargo.toml b/ddsm115/Cargo.toml index a4330e3..f018739 100644 --- a/ddsm115/Cargo.toml +++ b/ddsm115/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true license.workspace = true publish = false -description = "Phoxal catalog component — ddsm115." +description = "Phoxal catalog component - ddsm115." documentation.workspace = true homepage.workspace = true repository.workspace = true @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = { workspace = true } phoxal = { workspace = true } +phoxal-api = { workspace = true } [dev-dependencies] serde_json = "1" diff --git a/ddsm115/src/main.rs b/ddsm115/src/main.rs index 14eb848..2de3077 100644 --- a/ddsm115/src/main.rs +++ b/ddsm115/src/main.rs @@ -12,8 +12,8 @@ //! `phoxal-cli` at check/deploy time (not shipped as a framework image). use anyhow::Result; -use phoxal::api::y2026_1 as api; use phoxal::prelude::*; +use phoxal_api::y2026_1 as api; /// The motor / encoder capability names on a ddsm115 component instance /// (matching `component.yaml`). @@ -28,31 +28,36 @@ struct Ddsm115 { position_rad: f64, velocity_radps: f32, // Handles on this instance's dynamic per-component topics. - command: Subscriber, - encoder: Publisher, + command: Subscriber, + encoder: Publisher, } #[phoxal::runtime] impl Ddsm115 { #[setup] async fn setup(ctx: &mut SetupContext) -> Result { + // Owner opt-in (plan #00 L2): the runner-minted capability that the owner + // (`internal`) topic builder requires. This driver OWNS its component node. + let cap = ctx.owner_capability(); let instance = ctx.component()?.to_string(); // Prove the instance exists in the robot model (binds this driver to it). let _ = ctx.robot()?.component_instance(&instance)?; let command = ctx .subscribe( - api::topic::new() - .component() - .motor_command(&instance, MOTOR_CAPABILITY), + api::topic::internal::new(cap) + .component(&instance) + .motor(MOTOR_CAPABILITY) + .command(), ) .subscriber() .await?; let encoder = ctx .publisher( - api::topic::new() - .component() - .encoder_sample(&instance, ENCODER_CAPABILITY), + api::topic::internal::new(cap) + .component(&instance) + .encoder(ENCODER_CAPABILITY) + .sample(), ) .await?; @@ -84,7 +89,7 @@ impl Ddsm115 { self.encoder .publish_at( now, - api::component::EncoderSample { + api::component::encoder::Sample { position_rad: self.position_rad, velocity_radps: self.velocity_radps, }, @@ -105,11 +110,11 @@ impl Ddsm115 { /// The new commanded velocity for a motor command (`Stop` → 0; `Torque` is not /// modeled by this driver, so it holds the last velocity). -fn velocity_from(command: api::component::MotorCommand, current: f32) -> f32 { +fn velocity_from(command: api::component::motor::Command, current: f32) -> f32 { match command { - api::component::MotorCommand::Velocity(v) => v, - api::component::MotorCommand::Stop => 0.0, - api::component::MotorCommand::Torque(_) => current, + api::component::motor::Command::Velocity(v) => v, + api::component::motor::Command::Stop => 0.0, + api::component::motor::Command::Torque(_) => current, } } @@ -125,18 +130,21 @@ fn main() -> phoxal::Result<()> { #[cfg(test)] mod tests { use super::{integrate, velocity_from}; - use phoxal::api::ContractBody; - use phoxal::api::y2026_1 as api; + use phoxal_api::ContractBody; + use phoxal_api::y2026_1 as api; #[test] fn command_maps_to_velocity() { assert_eq!( - velocity_from(api::component::MotorCommand::Velocity(2.5), 1.0), + velocity_from(api::component::motor::Command::Velocity(2.5), 1.0), 2.5 ); - assert_eq!(velocity_from(api::component::MotorCommand::Stop, 3.0), 0.0); assert_eq!( - velocity_from(api::component::MotorCommand::Torque(9.0), 1.5), + velocity_from(api::component::motor::Command::Stop, 3.0), + 0.0 + ); + assert_eq!( + velocity_from(api::component::motor::Command::Torque(9.0), 1.5), 1.5 ); } @@ -154,11 +162,11 @@ mod tests { assert_eq!(value["artifact"]["id"], "ddsm115"); let contracts = value["required_contracts"].as_array().unwrap(); assert!(contracts.iter().any(|c| { - c["family"] == ::FAMILY + c["family"] == ::FAMILY && c["direction"] == "subscribe" })); assert!(contracts.iter().any(|c| { - c["family"] == ::FAMILY + c["family"] == ::FAMILY && c["direction"] == "publish" })); } diff --git a/oak_d_lite/Cargo.toml b/oak_d_lite/Cargo.toml index 0d0820e..904d3d3 100644 --- a/oak_d_lite/Cargo.toml +++ b/oak_d_lite/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true license.workspace = true publish = false -description = "Phoxal catalog component — oak_d_lite." +description = "Phoxal catalog component - oak_d_lite." documentation.workspace = true homepage.workspace = true repository.workspace = true @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = { workspace = true } phoxal = { workspace = true } +phoxal-api = { workspace = true } [dev-dependencies] serde_json = "1" diff --git a/oak_d_lite/src/main.rs b/oak_d_lite/src/main.rs index 6aa90a9..fd7f925 100644 --- a/oak_d_lite/src/main.rs +++ b/oak_d_lite/src/main.rs @@ -1,24 +1,24 @@ //! `oak_d_lite` — OAK-D Lite camera/depth/IMU component driver stub. use anyhow::{Result, bail}; -use phoxal::api::y2026_1 as api; use phoxal::model::component::v1::capability::{CameraMode, Capability}; use phoxal::prelude::*; +use phoxal_api::y2026_1 as api; const STEP_HZ: f64 = 100.0; #[derive(phoxal::Runtime)] #[phoxal(id = "oak_d_lite", api = y2026_1)] struct OakDLite { - camera: Vec>, + camera: Vec>, camera_specs: Vec, - depth: Vec>, + depth: Vec>, depth_specs: Vec, - imu: Vec>, + imu: Vec>, imu_divisors: Vec, - accelerometer: Vec>, + accelerometer: Vec>, accelerometer_divisors: Vec, - gyroscope: Vec>, + gyroscope: Vec>, gyroscope_divisors: Vec, } @@ -33,7 +33,7 @@ struct CameraSpec { divisor: u64, width: u32, height: u32, - encoding: api::component::CameraEncoding, + encoding: api::component::camera::Encoding, data_len: usize, } @@ -61,6 +61,9 @@ struct SensorSlot { impl OakDLite { #[setup] async fn setup(ctx: &mut SetupContext) -> Result { + // Owner opt-in (plan #00 L2): the runner-minted capability that the owner + // (`internal`) topic builder requires. This driver OWNS its component node. + let cap = ctx.owner_capability(); let instance = ctx.component()?.to_string(); let (camera_slots, depth_slots, imu_slots, accelerometer_slots, gyroscope_slots) = { let robot = ctx.robot()?; @@ -149,9 +152,10 @@ impl OakDLite { for slot in camera_slots { camera.push( ctx.publisher( - api::topic::new() - .component() - .camera_frame(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .camera(&slot.capability_id) + .frame(), ) .await?, ); @@ -163,9 +167,10 @@ impl OakDLite { for slot in depth_slots { depth.push( ctx.publisher( - api::topic::new() - .component() - .depth_frame(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .depth(&slot.capability_id) + .frame(), ) .await?, ); @@ -177,9 +182,10 @@ impl OakDLite { for slot in imu_slots { imu.push( ctx.publisher( - api::topic::new() - .component() - .imu_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .imu(&slot.capability_id) + .sample(), ) .await?, ); @@ -191,9 +197,10 @@ impl OakDLite { for slot in accelerometer_slots { accelerometer.push( ctx.publisher( - api::topic::new() - .component() - .accelerometer_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .accelerometer(&slot.capability_id) + .sample(), ) .await?, ); @@ -205,9 +212,10 @@ impl OakDLite { for slot in gyroscope_slots { gyroscope.push( ctx.publisher( - api::topic::new() - .component() - .gyroscope_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .gyroscope(&slot.capability_id) + .sample(), ) .await?, ); @@ -295,19 +303,19 @@ fn is_due(step_index: u64, divisor: u64) -> bool { divisor <= 1 || step_index % divisor == 0 } -fn encoding_for_mode(mode: CameraMode) -> api::component::CameraEncoding { +fn encoding_for_mode(mode: CameraMode) -> api::component::camera::Encoding { match mode { - CameraMode::Mono => api::component::CameraEncoding::L8, - CameraMode::Rgb => api::component::CameraEncoding::Rgb8, + CameraMode::Mono => api::component::camera::Encoding::L8, + CameraMode::Rgb => api::component::camera::Encoding::Rgb8, } } -fn channels_for_encoding(encoding: api::component::CameraEncoding) -> usize { +fn channels_for_encoding(encoding: api::component::camera::Encoding) -> usize { match encoding { - api::component::CameraEncoding::L8 => 1, - api::component::CameraEncoding::Rgb8 => 3, - api::component::CameraEncoding::Rgba8 => 4, - api::component::CameraEncoding::Jpeg | api::component::CameraEncoding::Png => 1, + api::component::camera::Encoding::L8 => 1, + api::component::camera::Encoding::Rgb8 => 3, + api::component::camera::Encoding::Rgba8 => 4, + api::component::camera::Encoding::Jpeg | api::component::camera::Encoding::Png => 1, } } @@ -322,8 +330,8 @@ fn depth_sample_len(width: u32, height: u32) -> Option { frame_byte_len(width, height, 1) } -fn camera_frame(spec: &CameraSpec) -> api::component::CameraFrame { - api::component::CameraFrame { +fn camera_frame(spec: &CameraSpec) -> api::component::camera::Frame { + api::component::camera::Frame { width: spec.width, height: spec.height, encoding: spec.encoding, @@ -336,11 +344,11 @@ fn camera_frame(spec: &CameraSpec) -> api::component::CameraFrame { } } -fn depth_frame(spec: &DepthSpec) -> api::component::DepthFrame { - api::component::DepthFrame { +fn depth_frame(spec: &DepthSpec) -> api::component::depth::Frame { + api::component::depth::Frame { samples_mm: vec![0u16; spec.sample_len], - encoding: api::component::DepthEncoding::U16Millimeters, - invalid_sample_policy: api::component::DepthInvalidSamplePolicy::ZeroIsInvalid, + encoding: api::component::depth::Encoding::U16Millimeters, + invalid_sample_policy: api::component::depth::InvalidSamplePolicy::ZeroIsInvalid, width: Some(spec.width), height: Some(spec.height), intrinsics: None, @@ -351,8 +359,8 @@ fn depth_frame(spec: &DepthSpec) -> api::component::DepthFrame { } } -fn imu_sample() -> api::component::ImuSample { - api::component::ImuSample { +fn imu_sample() -> api::component::imu::Sample { + api::component::imu::Sample { orientation: Some([1.0, 0.0, 0.0, 0.0]), angular_velocity_radps: [0.0; 3], linear_acceleration_mps2: [0.0; 3], @@ -360,19 +368,19 @@ fn imu_sample() -> api::component::ImuSample { noise_density: None, sensor_frame_id: None, measured_at_ns: None, - health: api::component::SensorHealth::Nominal, + health: api::component::imu::SensorHealth::Nominal, bias: None, } } -fn accelerometer_sample() -> api::component::AccelerometerSample { - api::component::AccelerometerSample { +fn accelerometer_sample() -> api::component::accelerometer::Sample { + api::component::accelerometer::Sample { linear_acceleration: [0.0; 3], } } -fn gyroscope_sample() -> api::component::GyroscopeSample { - api::component::GyroscopeSample { +fn gyroscope_sample() -> api::component::gyroscope::Sample { + api::component::gyroscope::Sample { angular_velocity: [0.0; 3], } } @@ -386,23 +394,26 @@ mod tests { use super::{ OakDLite, channels_for_encoding, divisor_for_rate, encoding_for_mode, frame_byte_len, }; - use phoxal::api::ContractBody; - use phoxal::api::y2026_1 as api; use phoxal::model::component::v1::capability::CameraMode; + use phoxal_api::ContractBody; + use phoxal_api::y2026_1 as api; #[test] fn camera_encoding_and_frame_size_follow_mode() { assert!(matches!( encoding_for_mode(CameraMode::Mono), - api::component::CameraEncoding::L8 + api::component::camera::Encoding::L8 )); assert!(matches!( encoding_for_mode(CameraMode::Rgb), - api::component::CameraEncoding::Rgb8 + api::component::camera::Encoding::Rgb8 )); - assert_eq!(channels_for_encoding(api::component::CameraEncoding::L8), 1); assert_eq!( - channels_for_encoding(api::component::CameraEncoding::Rgb8), + channels_for_encoding(api::component::camera::Encoding::L8), + 1 + ); + assert_eq!( + channels_for_encoding(api::component::camera::Encoding::Rgb8), 3 ); assert_eq!(frame_byte_len(640, 480, 3), Some(921_600)); @@ -416,11 +427,11 @@ mod tests { assert_eq!(value["artifact"]["id"], "oak_d_lite"); let contracts = value["required_contracts"].as_array().unwrap(); for family in [ - ::FAMILY, - ::FAMILY, - ::FAMILY, - ::FAMILY, - ::FAMILY, + ::FAMILY, + ::FAMILY, + ::FAMILY, + ::FAMILY, + ::FAMILY, ] { assert!( contracts diff --git a/vl53l1x/Cargo.toml b/vl53l1x/Cargo.toml index ef1e0e0..309ea76 100644 --- a/vl53l1x/Cargo.toml +++ b/vl53l1x/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true license.workspace = true publish = false -description = "Phoxal catalog component — vl53l1x." +description = "Phoxal catalog component - vl53l1x." documentation.workspace = true homepage.workspace = true repository.workspace = true @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = { workspace = true } phoxal = { workspace = true } +phoxal-api = { workspace = true } [dev-dependencies] serde_json = "1" diff --git a/vl53l1x/src/main.rs b/vl53l1x/src/main.rs index 2e8b03b..2b4a995 100644 --- a/vl53l1x/src/main.rs +++ b/vl53l1x/src/main.rs @@ -1,16 +1,16 @@ //! `vl53l1x` — VL53L1X range component driver stub. use anyhow::{Result, bail}; -use phoxal::api::y2026_1 as api; use phoxal::model::component::v1::capability::Capability; use phoxal::prelude::*; +use phoxal_api::y2026_1 as api; const STEP_HZ: f64 = 20.0; #[derive(phoxal::Runtime)] #[phoxal(id = "vl53l1x", api = y2026_1)] struct Vl53l1x { - range: Vec>, + range: Vec>, range_specs: Vec, } @@ -31,6 +31,9 @@ struct RangeSpec { impl Vl53l1x { #[setup] async fn setup(ctx: &mut SetupContext) -> Result { + // Owner opt-in (plan #00 L2): the runner-minted capability that the owner + // (`internal`) topic builder requires. This driver OWNS its component node. + let cap = ctx.owner_capability(); let instance = ctx.component()?.to_string(); let slots = { let robot = ctx.robot()?; @@ -63,9 +66,10 @@ impl Vl53l1x { for slot in slots { range.push( ctx.publisher( - api::topic::new() - .component() - .range_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .range(&slot.capability_id) + .sample(), ) .await?, ); @@ -110,16 +114,16 @@ fn is_due(step_index: u64, divisor: u64) -> bool { divisor <= 1 || step_index % divisor == 0 } -fn range_sample(spec: &RangeSpec) -> api::component::RangeSample { - api::component::RangeSample { +fn range_sample(spec: &RangeSpec) -> api::component::range::Sample { + api::component::range::Sample { distance_m: spec.max_range_m, - limits: Some(api::component::Limits { + limits: Some(api::component::range::Limits { min_m: spec.min_range_m, max_m: spec.max_range_m, }), measured_at_ns: None, quality: None, - health: api::component::SensorHealth::Nominal, + health: api::component::range::SensorHealth::Nominal, } } @@ -130,8 +134,8 @@ fn main() -> phoxal::Result<()> { #[cfg(test)] mod tests { use super::{Vl53l1x, divisor_for_rate, range_sample}; - use phoxal::api::ContractBody; - use phoxal::api::y2026_1 as api; + use phoxal_api::ContractBody; + use phoxal_api::y2026_1 as api; #[test] fn divisor_and_stub_distance_follow_range_config() { @@ -155,7 +159,7 @@ mod tests { assert_eq!(value["artifact"]["id"], "vl53l1x"); let contracts = value["required_contracts"].as_array().unwrap(); assert!(contracts.iter().any(|c| { - c["family"] == ::FAMILY + c["family"] == ::FAMILY && c["direction"] == "publish" })); } diff --git a/zed_f9p/Cargo.toml b/zed_f9p/Cargo.toml index 41e705d..605be68 100644 --- a/zed_f9p/Cargo.toml +++ b/zed_f9p/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true rust-version.workspace = true license.workspace = true publish = false -description = "Phoxal catalog component — zed_f9p." +description = "Phoxal catalog component - zed_f9p." documentation.workspace = true homepage.workspace = true repository.workspace = true @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = { workspace = true } phoxal = { workspace = true } +phoxal-api = { workspace = true } [dev-dependencies] serde_json = "1" diff --git a/zed_f9p/src/main.rs b/zed_f9p/src/main.rs index 41635f4..e04e6eb 100644 --- a/zed_f9p/src/main.rs +++ b/zed_f9p/src/main.rs @@ -1,16 +1,16 @@ //! `zed_f9p` — u-blox ZED-F9P GNSS component driver stub. use anyhow::{Result, bail}; -use phoxal::api::y2026_1 as api; use phoxal::model::component::v1::capability::Capability; use phoxal::prelude::*; +use phoxal_api::y2026_1 as api; const STEP_HZ: f64 = 10.0; #[derive(phoxal::Runtime)] #[phoxal(id = "zed_f9p", api = y2026_1)] struct ZedF9p { - gnss: Vec>, + gnss: Vec>, gnss_divisors: Vec, } @@ -24,6 +24,9 @@ struct GnssSlot { impl ZedF9p { #[setup] async fn setup(ctx: &mut SetupContext) -> Result { + // Owner opt-in (plan #00 L2): the runner-minted capability that the owner + // (`internal`) topic builder requires. This driver OWNS its component node. + let cap = ctx.owner_capability(); let instance = ctx.component()?.to_string(); let slots = { let robot = ctx.robot()?; @@ -53,9 +56,10 @@ impl ZedF9p { for slot in slots { gnss.push( ctx.publisher( - api::topic::new() - .component() - .gnss_sample(&instance, &slot.capability_id), + api::topic::internal::new(cap) + .component(&instance) + .gnss(&slot.capability_id) + .sample(), ) .await?, ); @@ -103,8 +107,8 @@ fn is_due(step_index: u64, divisor: u64) -> bool { divisor <= 1 || step_index % divisor == 0 } -fn gnss_sample() -> api::component::GnssSample { - api::component::GnssSample { +fn gnss_sample() -> api::component::gnss::Sample { + api::component::gnss::Sample { latitude: 0.0, longitude: 0.0, altitude: 0.0, @@ -119,8 +123,8 @@ fn main() -> phoxal::Result<()> { #[cfg(test)] mod tests { use super::{ZedF9p, divisor_for_rate, gnss_sample}; - use phoxal::api::ContractBody; - use phoxal::api::y2026_1 as api; + use phoxal_api::ContractBody; + use phoxal_api::y2026_1 as api; #[test] fn divisor_and_stub_sample_are_stable() { @@ -139,7 +143,7 @@ mod tests { assert_eq!(value["artifact"]["id"], "zed_f9p"); let contracts = value["required_contracts"].as_array().unwrap(); assert!(contracts.iter().any(|c| { - c["family"] == ::FAMILY + c["family"] == ::FAMILY && c["direction"] == "publish" })); }