diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7eaba3a..90081b00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,7 +363,7 @@ jobs: - name: Setup Rust cache uses: Swatinem/rust-cache@v2 with: - shared-key: ${{ runner.os }}-pureRust-checks + shared-key: ${{ runner.os }}-pureRust-checks-v2 - name: Set environment variables (macOS) if: runner.os == 'macOS' @@ -460,7 +460,7 @@ jobs: strategy: fail-fast: false matrix: - distro: [humble, jazzy, kilted] + distro: [humble, jazzy, kilted, lyrical] # Skip for draft PRs to save CI time during development if: | github.event_name == 'push' || @@ -563,7 +563,7 @@ jobs: strategy: fail-fast: false matrix: - distro: [humble, jazzy, kilted] + distro: [humble, jazzy, kilted, lyrical] # Skip for draft PRs to save CI time during development if: | github.event_name == 'push' || @@ -602,7 +602,7 @@ jobs: - name: Setup Rust cache uses: Swatinem/rust-cache@v2 with: - shared-key: ${{ matrix.distro }}-ros + shared-key: ${{ matrix.distro }}-ros-v2 - name: Run ROS tests run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f2c120d..c9763a06 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - distro: [humble, jazzy, kilted] + distro: [humble, jazzy, kilted, lyrical] include: - distro: humble image: ros:humble-ros-base @@ -51,6 +51,9 @@ jobs: - distro: kilted image: ros:kilted-ros-base hiroz_feature: kilted + - distro: lyrical + image: ros:lyrical-ros-base + hiroz_feature: lyrical container: image: ${{ matrix.image }} steps: diff --git a/README.md b/README.md index c4bc4174..ef0b14b4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Built by the [Zenoh](https://zenoh.io) team at [ZettaScale](https://www.zettasca ## Status -**Hiroz** is experimental software. It is tested with ROS 2 Jazzy, Humble, and Kilted. We make no guarantees with respect to other official distributions. +**Hiroz** is experimental software. It is tested with ROS 2 Jazzy, Humble, Kilted, and Lyrical. We make no guarantees with respect to other official distributions. ## Documentation diff --git a/crates/hiroz-bridge/Cargo.toml b/crates/hiroz-bridge/Cargo.toml index 2739ca08..ad0ed0ae 100644 --- a/crates/hiroz-bridge/Cargo.toml +++ b/crates/hiroz-bridge/Cargo.toml @@ -33,3 +33,5 @@ parking_lot = { workspace = true } default = ["jazzy"] jazzy = ["hiroz-msgs/jazzy"] humble = ["hiroz-msgs/humble", "hiroz/humble"] +kilted = ["hiroz-msgs/kilted", "hiroz/kilted"] +lyrical = ["hiroz-msgs/lyrical", "hiroz/lyrical"] diff --git a/crates/hiroz-console/tests/common/mod.rs b/crates/hiroz-console/tests/common/mod.rs index 87f255c6..181623f0 100644 --- a/crates/hiroz-console/tests/common/mod.rs +++ b/crates/hiroz-console/tests/common/mod.rs @@ -1,5 +1,4 @@ use std::process::{Child, Command, Stdio}; -use std::sync::atomic::{AtomicU16, Ordering}; use std::thread; use std::time::Duration; @@ -75,14 +74,6 @@ impl Drop for ProcessGuard { } } -/// Port counter for generating unique Zenoh router ports per test -static NEXT_PORT: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { - let pid = std::process::id(); - let base_port = 30000 + ((pid % 10000) as u16); - println!("Test process {} using base port {}", pid, base_port); - AtomicU16::new(base_port) -}); - /// Per-test Zenoh router configuration pub struct TestRouter { pub port: u16, @@ -91,34 +82,49 @@ pub struct TestRouter { } impl TestRouter { - /// Start a new Zenoh router session on a unique port for this test + /// Start a new Zenoh router session on a free OS-assigned port. + /// + /// Uses bind(:0) to avoid PID-derived port collisions when multiple test + /// binaries run in parallel (e.g. hiroz-tests and hiroz-console). pub fn new() -> Self { - let port = NEXT_PORT.fetch_add(1, Ordering::SeqCst); - let endpoint = format!("tcp/127.0.0.1:{}", port); - - println!("Starting Zenoh router on port {}...", port); - - let mut config = zenoh::Config::default(); - config.set_mode(Some(WhatAmI::Router)).unwrap(); - config - .insert_json5("listen/endpoints", &format!("[\"{}\"]", endpoint)) - .unwrap(); - config - .insert_json5("scouting/multicast/enabled", "false") - .unwrap(); - - let session = zenoh::open(config) - .wait() - .expect("Failed to open Zenoh router session"); - - thread::sleep(Duration::from_millis(500)); - println!("Zenoh router ready on {}", endpoint); - - Self { - port, - endpoint: endpoint.clone(), - _session: session, + for attempt in 0..5u32 { + let port = { + let listener = + std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind port 0"); + listener.local_addr().unwrap().port() + }; + let endpoint = format!("tcp/127.0.0.1:{}", port); + println!( + "Starting Zenoh router on port {} (attempt {})...", + port, + attempt + 1 + ); + + let mut config = zenoh::Config::default(); + config.set_mode(Some(WhatAmI::Router)).unwrap(); + config + .insert_json5("listen/endpoints", &format!("[\"{}\"]", endpoint)) + .unwrap(); + config + .insert_json5("scouting/multicast/enabled", "false") + .unwrap(); + + match zenoh::open(config).wait() { + Ok(session) => { + thread::sleep(Duration::from_millis(500)); + println!("Zenoh router ready on {}", endpoint); + return Self { + port, + endpoint, + _session: session, + }; + } + Err(e) => { + println!("Port {} unavailable ({}), retrying...", port, e); + } + } } + panic!("Failed to start Zenoh router after 5 attempts"); } pub fn endpoint(&self) -> &str { diff --git a/crates/hiroz-msgs/Cargo.toml b/crates/hiroz-msgs/Cargo.toml index 2f81f1c6..6cb28cc9 100644 --- a/crates/hiroz-msgs/Cargo.toml +++ b/crates/hiroz-msgs/Cargo.toml @@ -78,6 +78,7 @@ humble = ["hiroz/humble"] jazzy = ["hiroz/jazzy"] rolling = ["hiroz/rolling"] kilted = ["hiroz/kilted"] +lyrical = ["hiroz/lyrical"] python_registry = [ "pyo3", "parking_lot", diff --git a/crates/hiroz-msgs/build.rs b/crates/hiroz-msgs/build.rs index ba7c3203..1be639e1 100644 --- a/crates/hiroz-msgs/build.rs +++ b/crates/hiroz-msgs/build.rs @@ -324,6 +324,7 @@ fn discover_system_packages(packages: &[&str]) -> Result> { "/opt/ros/rolling", "/opt/ros/jazzy", "/opt/ros/kilted", + "/opt/ros/lyrical", "/opt/ros/humble", ]; diff --git a/crates/hiroz-py/Cargo.toml b/crates/hiroz-py/Cargo.toml index f354fb4a..20dce3c4 100644 --- a/crates/hiroz-py/Cargo.toml +++ b/crates/hiroz-py/Cargo.toml @@ -13,6 +13,7 @@ jazzy = ["hiroz/jazzy"] humble = ["hiroz/humble"] kilted = ["hiroz/kilted"] rolling = ["hiroz/rolling"] +lyrical = ["hiroz/lyrical"] [dependencies] pyo3 = { workspace = true } diff --git a/crates/hiroz-tests/Cargo.toml b/crates/hiroz-tests/Cargo.toml index 1b90e8e8..3d16facc 100644 --- a/crates/hiroz-tests/Cargo.toml +++ b/crates/hiroz-tests/Cargo.toml @@ -63,3 +63,4 @@ humble = ["hiroz/humble", "hiroz-msgs/humble"] jazzy = ["hiroz/jazzy", "hiroz-msgs/jazzy"] rolling = ["hiroz/rolling", "hiroz-msgs/rolling"] kilted = ["hiroz/kilted", "hiroz-msgs/kilted"] +lyrical = ["hiroz/lyrical", "hiroz-msgs/lyrical"] diff --git a/crates/hiroz-tests/tests/action_interop.rs b/crates/hiroz-tests/tests/action_interop.rs index 1979dc7c..800c99ed 100644 --- a/crates/hiroz-tests/tests/action_interop.rs +++ b/crates/hiroz-tests/tests/action_interop.rs @@ -8,12 +8,12 @@ use common::*; use hiroz::{Builder, action::server::ExecutingGoal}; // Distro-specific action interfaces: // - Humble/Jazzy: action_tutorials_cpp uses action_tutorials_interfaces -// - Kilted: action_tutorials_cpp uses example_interfaces -#[cfg(not(feature = "kilted"))] +// - Kilted/Lyrical: action_tutorials_cpp uses example_interfaces +#[cfg(not(any(feature = "kilted", feature = "lyrical")))] use hiroz_msgs::action_tutorials_interfaces::{ FibonacciFeedback, FibonacciGoal, FibonacciResult, action::Fibonacci, }; -#[cfg(feature = "kilted")] +#[cfg(any(feature = "kilted", feature = "lyrical"))] use hiroz_msgs::example_interfaces::{ FibonacciFeedback, FibonacciGoal, FibonacciResult, action::Fibonacci, }; @@ -55,13 +55,13 @@ async fn test_action_goal_accept_and_succeed() { for i in 2..=ord as usize { let next = seq[i - 1] + seq[i - 2]; seq.push(next); - #[cfg(feature = "kilted")] + #[cfg(any(feature = "kilted", feature = "lyrical"))] executing .publish_feedback(FibonacciFeedback { sequence: seq.clone(), }) .unwrap(); - #[cfg(not(feature = "kilted"))] + #[cfg(not(any(feature = "kilted", feature = "lyrical")))] executing .publish_feedback(FibonacciFeedback { partial_sequence: seq.clone(), @@ -272,13 +272,13 @@ async fn test_action_feedback_ordering() { let next = seq[i - 1] + seq[i - 2]; seq.push(next); // Publish feedback with the growing partial sequence - #[cfg(feature = "kilted")] + #[cfg(any(feature = "kilted", feature = "lyrical"))] executing .publish_feedback(FibonacciFeedback { sequence: seq.clone(), }) .unwrap(); - #[cfg(not(feature = "kilted"))] + #[cfg(not(any(feature = "kilted", feature = "lyrical")))] executing .publish_feedback(FibonacciFeedback { partial_sequence: seq.clone(), @@ -319,9 +319,9 @@ async fn test_action_feedback_ordering() { if let Some(mut fb_rx) = goal_handle.feedback() { tokio::spawn(async move { while let Some(fb) = fb_rx.recv().await { - #[cfg(feature = "kilted")] + #[cfg(any(feature = "kilted", feature = "lyrical"))] let seq = fb.sequence.clone(); - #[cfg(not(feature = "kilted"))] + #[cfg(not(any(feature = "kilted", feature = "lyrical")))] let seq = fb.partial_sequence.clone(); rf.lock().unwrap().push(seq); } diff --git a/crates/hiroz-tests/tests/type_description_interop.rs b/crates/hiroz-tests/tests/type_description_interop.rs index 9abb61b2..15e12c33 100644 --- a/crates/hiroz-tests/tests/type_description_interop.rs +++ b/crates/hiroz-tests/tests/type_description_interop.rs @@ -32,6 +32,18 @@ use hiroz_msgs::type_description_interfaces::{ }; use hiroz_schema::{FieldDescription, FieldTypeDescription, TypeDescription, TypeDescriptionMsg}; +// Lyrical's demo_nodes_cpp talker uses example_interfaces/msg/String instead of std_msgs/msg/String +#[cfg(not(feature = "lyrical"))] +const TALKER_TYPE_NAME: &str = "std_msgs/msg/String"; +#[cfg(feature = "lyrical")] +const TALKER_TYPE_NAME: &str = "example_interfaces/msg/String"; +#[cfg(not(feature = "lyrical"))] +const TALKER_TYPE_HASH: &str = + "RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18"; +#[cfg(feature = "lyrical")] +const TALKER_TYPE_HASH: &str = + "RIHS01_5509d866a579951f2fc6c19577c32605ba16f308cae7b498341d79536d4eb06b"; + /// Convert from wire format TypeDescription to hiroz-schema TypeDescriptionMsg. /// /// This is needed because the wire format uses generated ROS message types, @@ -133,13 +145,9 @@ fn test_get_type_description_from_ros2_talker() { // Give more time for ROS 2 node to fully initialize tokio::time::sleep(Duration::from_secs(3)).await; - // Request type description for std_msgs/msg/String - // Use the type hash that matches our computation let req = GetTypeDescriptionRequest { - type_name: "std_msgs/msg/String".to_string(), - type_hash: - "RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18" - .to_string(), + type_name: TALKER_TYPE_NAME.to_string(), + type_hash: TALKER_TYPE_HASH.to_string(), include_type_sources: true, }; @@ -175,7 +183,7 @@ fn test_get_type_description_from_ros2_talker() { ); assert_eq!( result.type_description.type_description.type_name, - "std_msgs/msg/String" + TALKER_TYPE_NAME ); assert_eq!(result.type_description.type_description.fields.len(), 1); assert_eq!( @@ -242,12 +250,9 @@ fn test_get_type_description_without_sources() { tokio::time::sleep(Duration::from_secs(3)).await; - // Request with valid hash (ROS 2 Jazzy requires hash to be provided) let req = GetTypeDescriptionRequest { - type_name: "std_msgs/msg/String".to_string(), - type_hash: - "RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18" - .to_string(), + type_name: TALKER_TYPE_NAME.to_string(), + type_hash: TALKER_TYPE_HASH.to_string(), include_type_sources: false, }; @@ -269,7 +274,7 @@ fn test_get_type_description_without_sources() { ); assert_eq!( result.type_description.type_description.type_name, - "std_msgs/msg/String" + TALKER_TYPE_NAME ); // Without include_type_sources, should have empty sources @@ -372,10 +377,8 @@ fn test_dynamic_subscriber_from_type_description() { tokio::time::sleep(Duration::from_secs(3)).await; let req = GetTypeDescriptionRequest { - type_name: "std_msgs/msg/String".to_string(), - type_hash: - "RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18" - .to_string(), + type_name: TALKER_TYPE_NAME.to_string(), + type_hash: TALKER_TYPE_HASH.to_string(), include_type_sources: false, }; diff --git a/crates/hiroz/Cargo.toml b/crates/hiroz/Cargo.toml index 0441ec40..70ff233e 100644 --- a/crates/hiroz/Cargo.toml +++ b/crates/hiroz/Cargo.toml @@ -83,10 +83,11 @@ ffi = [] # Default is jazzy (recommended for new projects) # For humble, use: --no-default-features --features humble,... # Note: Distro features are mutually exclusive - enabling multiple may cause conflicts -humble = ["no-type-hash", "no-jazzy", "no-rolling", "no-kilted"] -jazzy = ["type-hash", "no-humble", "no-rolling", "no-kilted"] -rolling = ["type-hash", "no-humble", "no-jazzy", "no-kilted"] -kilted = ["type-hash", "no-humble", "no-jazzy", "no-rolling"] +humble = ["no-type-hash", "no-jazzy", "no-rolling", "no-kilted", "no-lyrical"] +jazzy = ["type-hash", "no-humble", "no-rolling", "no-kilted", "no-lyrical"] +rolling = ["type-hash", "no-humble", "no-jazzy", "no-kilted", "no-lyrical"] +kilted = ["type-hash", "no-humble", "no-jazzy", "no-rolling", "no-lyrical"] +lyrical = ["type-hash", "no-humble", "no-jazzy", "no-rolling", "no-kilted"] # Internal features for distro compatibility humble-compat = [] @@ -98,6 +99,7 @@ no-humble = [] no-jazzy = [] no-rolling = [] no-kilted = [] +no-lyrical = [] # NOTE: Examples that use hiroz-msgs must be built from workspace root diff --git a/crates/hiroz/examples/demo_nodes/fibonacci_action_client.rs b/crates/hiroz/examples/demo_nodes/fibonacci_action_client.rs index 8a3ce34c..8291c2a3 100644 --- a/crates/hiroz/examples/demo_nodes/fibonacci_action_client.rs +++ b/crates/hiroz/examples/demo_nodes/fibonacci_action_client.rs @@ -1,10 +1,10 @@ use hiroz::{Builder, Result, context::ZContext}; // Distro-specific action interfaces: // - Humble/Jazzy: action_tutorials_cpp uses action_tutorials_interfaces -// - Kilted: action_tutorials_cpp uses example_interfaces -#[cfg(not(feature = "kilted"))] +// - Kilted/Lyrical: action_tutorials_cpp uses example_interfaces +#[cfg(not(any(feature = "kilted", feature = "lyrical")))] use hiroz_msgs::action_tutorials_interfaces::{FibonacciGoal, action::Fibonacci}; -#[cfg(feature = "kilted")] +#[cfg(any(feature = "kilted", feature = "lyrical"))] use hiroz_msgs::example_interfaces::{FibonacciGoal, action::Fibonacci}; // ANCHOR: full_example @@ -46,9 +46,9 @@ pub async fn run_fibonacci_action_client(ctx: ZContext, order: i32) -> Result Self { + Self { + supports_type_hash: true, + } + } + /// ROS 2 Rolling defaults /// /// - Rolling uses rmw_zenoh v0.2.x @@ -98,7 +108,7 @@ impl DistroDefaults { /// Get the default for the currently compiled distro based on feature flags /// When multiple distro features are enabled (e.g., with --all-features), - /// priority order is: humble > kilted > rolling > jazzy (default) + /// priority order is: humble > kilted > lyrical > rolling > jazzy (default) pub const fn current() -> Self { // Priority 1: Humble if cfg!(feature = "humble") { @@ -110,12 +120,17 @@ impl DistroDefaults { return Self::kilted(); } - // Priority 3: Rolling + // Priority 3: Lyrical + if cfg!(feature = "lyrical") { + return Self::lyrical(); + } + + // Priority 4: Rolling if cfg!(feature = "rolling") { return Self::rolling(); } - // Priority 4 (default): Jazzy + // Priority 5 (default): Jazzy Self::jazzy() } } diff --git a/crates/rmw-zenoh-rs/src/rmw.rs b/crates/rmw-zenoh-rs/src/rmw.rs index 24e94c73..884cb9e6 100644 --- a/crates/rmw-zenoh-rs/src/rmw.rs +++ b/crates/rmw-zenoh-rs/src/rmw.rs @@ -459,6 +459,7 @@ pub extern "C" fn rmw_publish( // Subscriptions #[unsafe(no_mangle)] +#[allow(clippy::needless_update)] pub extern "C" fn rmw_create_subscription( node: *const rmw_node_t, type_support: *const rosidl_message_type_support_t, @@ -738,6 +739,7 @@ pub extern "C" fn rmw_create_subscription( options: unsafe { *subscription_options }, can_loan_messages: false, is_cft_enabled: false, + ..Default::default() }); let subscription_ptr = Box::into_raw(subscription); @@ -749,6 +751,7 @@ pub extern "C" fn rmw_create_subscription( } #[unsafe(no_mangle)] +#[allow(clippy::needless_update)] pub extern "C" fn rmw_destroy_subscription( node: *mut rmw_node_t, subscription: *mut rmw_subscription_t, @@ -795,6 +798,7 @@ pub extern "C" fn rmw_destroy_subscription( options: subscription_box.options, can_loan_messages: subscription_box.can_loan_messages, is_cft_enabled: subscription_box.is_cft_enabled, + ..Default::default() }; RMW_RET_OK as _ diff --git a/docs/bindings/go-quick-start.md b/docs/bindings/go-quick-start.md index 0358d064..1a751b35 100644 --- a/docs/bindings/go-quick-start.md +++ b/docs/bindings/go-quick-start.md @@ -15,7 +15,7 @@ No Rust toolchain required when using the pre-built library. Download the static library and C header for your platform from the [Releases page](https://github.com/ZettaScaleLabs/hiroz/releases): -| Platform | Jazzy / Kilted / Rolling | Humble | +| Platform | Jazzy / Kilted / Lyrical / Rolling | Humble | |---|---|---| | Linux x86_64 | `libhiroz-jazzy-x86_64-unknown-linux-gnu.a` | `libhiroz-humble-x86_64-unknown-linux-gnu.a` | | Linux aarch64 | `libhiroz-jazzy-aarch64-unknown-linux-gnu.a` | `libhiroz-humble-aarch64-unknown-linux-gnu.a` | diff --git a/docs/bindings/python-quick-start.md b/docs/bindings/python-quick-start.md index b95a60b5..d85b39c1 100644 --- a/docs/bindings/python-quick-start.md +++ b/docs/bindings/python-quick-start.md @@ -14,7 +14,7 @@ Get a Python publisher and subscriber running in five minutes. No Rust or ROS 2 Go to the [Releases page](https://github.com/ZettaScaleLabs/hiroz/releases), find the latest release, and substitute `` below. Pick the tab for your ROS 2 distro — if you are new to hiroz and have no ROS 2 install, choose **Jazzy**. -=== "Jazzy / Kilted / Rolling" +=== "Jazzy / Kilted / Lyrical / Rolling" ```bash pip install https://github.com/ZettaScaleLabs/hiroz/releases/download//hiroz_msgs_py--py3-none-any.whl diff --git a/docs/bindings/python.md b/docs/bindings/python.md index 461708da..62d3de74 100644 --- a/docs/bindings/python.md +++ b/docs/bindings/python.md @@ -15,7 +15,7 @@ Python 3.11+. No Rust toolchain required. Pick a release version from the [Releases page](https://github.com/ZettaScaleLabs/hiroz/releases) and substitute `` below (e.g. `v0.2.0`). -**Jazzy / Kilted / Rolling:** +**Jazzy / Kilted / Lyrical / Rolling:** ```bash pip install https://github.com/ZettaScaleLabs/hiroz/releases/download//hiroz_msgs_py--py3-none-any.whl diff --git a/docs/core-concepts/actions-advanced.md b/docs/core-concepts/actions-advanced.md index 21d155ba..27951f55 100644 --- a/docs/core-concepts/actions-advanced.md +++ b/docs/core-concepts/actions-advanced.md @@ -45,11 +45,11 @@ pub async fn run_fibonacci_action_server(ctx: ZContext, timeout: Option Result **Important**: Different hiroz components have different ROS 2 version requirements: > -> - **hiroz core library**: Supports Humble, Jazzy, Kilted, Rolling +> - **hiroz core library**: Supports Humble, Jazzy, Kilted, Lyrical, Rolling > - **rmw-zenoh-rs**: Requires Jazzy or later (see [rmw-zenoh-rs chapter](../experimental/rmw-zenoh-rs.md#ros-2-version-requirements)) ## Supported Distributions | Distribution | Core Library | rmw-zenoh-rs | Type Hash Support | Default | |--------------|--------------|--------------|-------------------|---------| +| **Lyrical Luth** | ✅ Supported | ✅ Supported | ✅ Yes | No | | **Kilted Kaiju** | ✅ Supported | ✅ Supported | ✅ Yes | No | | **Jazzy Jalisco** | ✅ Supported | ✅ Supported | ✅ Yes | **Yes** | | **Humble Hawksbill** | ✅ Supported | ❌ **Not Supported** | ❌ Placeholder | No | @@ -25,7 +26,7 @@ hiroz supports multiple ROS 2 distributions through compile-time feature flags. The most significant difference between distributions is **type hash support**: -**Jazzy/Kilted/Rolling** (Modern): +**Jazzy/Kilted/Lyrical/Rolling** (Modern): - Supports real type hashes computed from message definitions - Format: `RIHS01_<64-hex-chars>` (ROS IDL Hash Standard version 1) @@ -87,7 +88,7 @@ cargo nextest run --no-default-features --features humble ### Using Other Distributions -For Rolling or Kilted, specify the distro feature: +For Rolling, Kilted, or Lyrical, specify the distro feature: ```bash # Build for Rolling @@ -95,4 +96,7 @@ cargo build --features rolling # Build for Kilted cargo build --features kilted + +# Build for Lyrical +cargo build --features lyrical ``` diff --git a/docs/getting-started/nix.md b/docs/getting-started/nix.md index 6c917b16..9b4ac4d2 100644 --- a/docs/getting-started/nix.md +++ b/docs/getting-started/nix.md @@ -37,6 +37,8 @@ nix develop # Specific ROS distros nix develop .#ros-jazzy # ROS 2 Jazzy +nix develop .#ros-kilted # ROS 2 Kilted +nix develop .#ros-lyrical # ROS 2 Lyrical (LTS) nix develop .#ros-rolling # ROS 2 Rolling # Pure Rust (no ROS 2 — fastest to enter) @@ -93,7 +95,7 @@ Your system environment remains intact — Nix installed nothing globally. |----------|-------------|---------| | **Core Rust development** | `.#pureRust` | Fast entry, no ROS 2 needed | | **Full ROS 2 development** | `.#ros-jazzy` | All ROS 2 tools available | -| **Multi-distro testing** | `.#ros-jazzy` + `.#ros-rolling` | Test against different distros | +| **Multi-distro testing** | `.#ros-jazzy` + `.#ros-lyrical` + `.#ros-rolling` | Test against different distros | | **CI/CD Pipelines** | `.#pureRust-ci` | Same environment as GitHub Actions | ## Troubleshooting diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 7bd70753..704fd115 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -10,7 +10,7 @@ Pre-built artifacts are available on the [Releases page](https://github.com/Zett | Artifact | Use case | Install | |---|---|---| | `hiroz-console` binary | Monitor any ROS 2 / hiroz system | [Console docs](../tools/console.md#installation) | -| `hiroz-bridge` binary | Bridge Humble ↔ Jazzy/Kilted | [Bridge docs](../user-guide/bridge.md#installation) | +| `hiroz-bridge` binary | Bridge Humble ↔ Jazzy/Kilted/Lyrical | [Bridge docs](../user-guide/bridge.md#installation) | | `hiroz_py` Python wheel | Python pub/sub & services | [Python quick start](../bindings/python-quick-start.md) | | `libhiroz` Go library | Go pub/sub & services | [Go quick start](../bindings/go-quick-start.md) | diff --git a/docs/reference/feature-comparison.md b/docs/reference/feature-comparison.md index c3d07ee4..0b8c0ca9 100644 --- a/docs/reference/feature-comparison.md +++ b/docs/reference/feature-comparison.md @@ -78,7 +78,7 @@ ROS 2 (Zenoh) means `rmw_zenoh_cpp`: a drop-in RMW plugin that gives existing rc | Feature | ROS 2 (DDS) | ROS 2 (Zenoh) | hiroz | |---------|:--------------------:|:-------------:|:-----:| | ROS 2 CLI (`ros2 topic`, `ros2 service`, …) | ✅ | ✅ | ✅ via `rmw_zenoh_cpp` | -| Jazzy / Kilted support | ✅ | ✅ | ✅ | +| Jazzy / Kilted / Lyrical support | ✅ | ✅ | ✅ | | Humble support | ✅ | ✅ | ✅ | | Cross-distro bridge (Humble ↔ Jazzy) | ❌ | ❌ | ✅ | | Python bindings | native | native | ✅ (`hiroz-py`) | diff --git a/docs/reference/feature-flags.md b/docs/reference/feature-flags.md index fbf443f6..1a2a3d3e 100644 --- a/docs/reference/feature-flags.md +++ b/docs/reference/feature-flags.md @@ -76,6 +76,35 @@ cargo build --no-default-features --features humble **Important:** Humble requires `--no-default-features` to avoid conflicts with the jazzy default. +### `kilted` + +Targets ROS 2 Kilted Kaiju with modern type hash support. + +```bash +cargo build --features kilted +``` + +**Features:** + +- ✅ Type hash support (RIHS01) +- ✅ Shared memory optimization +- ✅ Modern ROS 2 protocol + +### `lyrical` + +Targets ROS 2 Lyrical Luth (LTS, May 2026–May 2031) with modern type hash support. + +```bash +cargo build --features lyrical +``` + +**Features:** + +- ✅ Type hash support (RIHS01) +- ✅ Shared memory optimization +- ✅ Modern ROS 2 protocol +- ✅ LTS support until 2031 + ### `rolling` Target Rolling distribution: @@ -309,6 +338,8 @@ cargo build -p hiroz --features protobuf | hiroz | (none) | No | None | | hiroz | jazzy (default) | No | None | | hiroz | humble | No | None | +| hiroz | kilted | No | None | +| hiroz | lyrical | No | None | | hiroz | rolling | No | None | | hiroz | protobuf | No | prost, prost-types | | hiroz | rcl-z | Yes | RCL libraries | @@ -318,6 +349,8 @@ cargo build -p hiroz --features protobuf | hiroz-msgs | protobuf | No | prost, prost-types | | hiroz-msgs | jazzy (default) | No | None | | hiroz-msgs | humble | No | None | +| hiroz-msgs | kilted | No | None | +| hiroz-msgs | lyrical | No | None | | hiroz-codegen | protobuf | No | prost-build | ## Checking Active Features diff --git a/docs/user-guide/bridge.md b/docs/user-guide/bridge.md index ed390733..d171c7fa 100644 --- a/docs/user-guide/bridge.md +++ b/docs/user-guide/bridge.md @@ -1,11 +1,11 @@ # Cross-Distro Bridge `hiroz-bridge` is a standalone binary that transparently connects a **legacy** ROS 2 network -(Humble and earlier) and a **modern** ROS 2 network (Jazzy, Kilted, Rolling) over a shared +(Humble and earlier) and a **modern** ROS 2 network (Jazzy, Kilted, Lyrical, Rolling) over a shared Eclipse Zenoh backbone. Without the bridge, nodes from these two generations cannot talk to each other. -With it, a Jazzy or Kilted subscriber receives messages from a Humble publisher — and +With it, a Jazzy, Kilted, or Lyrical subscriber receives messages from a Humble publisher — and vice versa — with no changes to either node. ## Why Are They Incompatible? @@ -16,7 +16,7 @@ embedded in Zenoh key expressions during entity discovery: | Distribution | Generation | Type hash format | |---|---|---| | **Humble** | Legacy | `TypeHashNotSupported` (constant placeholder) | -| **Jazzy, Kilted, Rolling** | Modern | `RIHS01_<64 hex chars>` (real hash of the message definition) | +| **Jazzy, Kilted, Lyrical, Rolling** | Modern | `RIHS01_<64 hex chars>` (real hash of the message definition) | Because the key expressions don't match, subscribers on one side never see publishers on the other side. **CDR payloads are identical** — only the KE differs. @@ -34,9 +34,9 @@ The bridge fixes this by: ```mermaid graph TD accTitle: Cross-distro bridge connecting Humble and Jazzy Zenoh networks -accDescr: hiroz-bridge opens two Zenoh sessions, one to the legacy Humble router and one to the modern Jazzy or Kilted router, transparently forwarding messages between them. +accDescr: hiroz-bridge opens two Zenoh sessions, one to the legacy Humble router and one to the modern Jazzy, Kilted, or Lyrical router, transparently forwarding messages between them. H_node["Humble node
(TypeHashNotSupported)"] - J_node["Jazzy / Kilted node
(RIHS01_abcd…)"] + J_node["Jazzy / Kilted / Lyrical node
(RIHS01_abcd…)"] H_router(["Legacy router
127.0.0.1:7447"]) J_router(["Modern router
127.0.0.1:7448"]) @@ -58,7 +58,7 @@ never interfere with each other. Download the latest release for your platform from the [Releases page](https://github.com/ZettaScaleLabs/hiroz/releases). The bridge ships as separate binaries per ROS 2 distro generation: -| Platform | Jazzy / Kilted / Rolling | Humble | +| Platform | Jazzy / Kilted / Lyrical / Rolling | Humble | |---|---|---| | Linux x86_64 | `bin-bridge-jazzy-x86_64-linux` | `bin-bridge-humble-x86_64-linux` | | Linux aarch64 | `bin-bridge-jazzy-aarch64-linux` | `bin-bridge-humble-aarch64-linux` | @@ -96,7 +96,7 @@ ros2 run rmw_zenoh_cpp rmw_zenohd ``` ```bash -# Terminal B — modern (Jazzy/Kilted) router on a different port +# Terminal B — modern (Jazzy/Kilted/Lyrical) router on a different port zenohd --listen tcp/127.0.0.1:7448 --rest-http-port disabled ``` @@ -119,7 +119,7 @@ ros2 run demo_nodes_cpp talker ``` ```bash -# Terminal D — Jazzy/Kilted listener (hiroz) +# Terminal D — Jazzy/Kilted/Lyrical listener (hiroz) cargo run --example demo_nodes_listener -- --endpoint tcp/127.0.0.1:7448 ``` @@ -139,7 +139,7 @@ ros2 topic list --spin-time 5 --no-daemon ``` ```bash -# On the Jazzy/Kilted side — should show the Humble publisher's topic +# On the Jazzy/Kilted/Lyrical side — should show the Humble publisher's topic export RMW_IMPLEMENTATION=rmw_zenoh_cpp export ZENOH_CONFIG_OVERRIDE="connect/endpoints=[\"tcp/127.0.0.1:7448\"];scouting/multicast/enabled=false" ros2 topic list --spin-time 5 --no-daemon @@ -162,7 +162,7 @@ USAGE: OPTIONS: --humble-endpoint Zenoh locator for the legacy (Humble) network [default: tcp/127.0.0.1:7447] - --jazzy-endpoint Zenoh locator for the modern (Jazzy/Kilted) network + --jazzy-endpoint Zenoh locator for the modern (Jazzy/Kilted/Lyrical) network [default: tcp/127.0.0.1:7448] --domain-id ROS domain ID [default: 0] -h, --help Print help @@ -242,7 +242,7 @@ The message type is not in the bridge's hash registry. See [Custom Messages](#cu ### The bridge forwards messages but deserialization fails on the receiving side -Verify the message definition is identical on both sides. Humble and Jazzy/Kilted ship the +Verify the message definition is identical on both sides. Humble and Jazzy/Kilted/Lyrical ship the same built-in message definitions, but third-party packages may differ. Check that both [`rmw_zenoh_cpp`](https://github.com/ros2/rmw_zenoh) versions agree on the CDR layout. diff --git a/docs/user-guide/interop.md b/docs/user-guide/interop.md index d4792d52..a7ec43d1 100644 --- a/docs/user-guide/interop.md +++ b/docs/user-guide/interop.md @@ -2,8 +2,8 @@ hiroz nodes — whether written in Rust, Python, or Go — speak the same Eclipse Zenoh wire protocol as [`rmw_zenoh_cpp`](https://github.com/ros2/rmw_zenoh), the official ROS 2 middleware plugin for Zenoh. This means they interoperate transparently: a Go subscriber can receive messages from a ROS 2 C++ talker, a Python publisher can send to a Rust listener, and so on. -!!! tip "Mixing Humble with Jazzy or Kilted?" - If you need to bridge a **Humble** (legacy) network and a **Jazzy / Kilted** (modern) network, +!!! tip "Mixing Humble with Jazzy, Kilted, or Lyrical?" + If you need to bridge a **Humble** (legacy) network and a **Jazzy / Kilted / Lyrical** (modern) network, see the [Cross-Distro Bridge](./bridge.md) chapter. ## Prerequisites diff --git a/docs/user-guide/message-generation.md b/docs/user-guide/message-generation.md index cb08e41d..ccea1f9c 100644 --- a/docs/user-guide/message-generation.md +++ b/docs/user-guide/message-generation.md @@ -194,7 +194,7 @@ accDescr: Feature flags trigger discovery that checks for a system ROS installat ``` 1. **System ROS:** `$AMENT_PREFIX_PATH`, `$CMAKE_PREFIX_PATH` -2. **Standard paths:** `/opt/ros/{rolling,jazzy,kilted,humble}` +2. **Standard paths:** `/opt/ros/{rolling,jazzy,kilted,lyrical,humble}` 3. **Bundled assets:** Built-in message definitions in hiroz-codegen This fallback enables development without ROS 2 installation. diff --git a/flake.lock b/flake.lock index ba60ac42..b8b3ea5b 100644 --- a/flake.lock +++ b/flake.lock @@ -78,14 +78,15 @@ "nix-ros-overlay": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "rosdistro": "rosdistro" }, "locked": { - "lastModified": 1771629548, - "narHash": "sha256-PLEzaPyuWoQzYRYDCxIqTfCjt5CgxlAsQTK9kwHG2Qg=", + "lastModified": 1779526397, + "narHash": "sha256-HWFG5voy8Y9nDkhp3zzFThX99UmgSOsyFpmAglXdtvs=", "owner": "lopsided98", "repo": "nix-ros-overlay", - "rev": "ef37f29a14a4a40f17fe241995c0a4c2e115f338", + "rev": "1abff578f2919094ac076407c46658c0a0de41c9", "type": "github" }, "original": { @@ -112,16 +113,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1759381078, - "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev": "0182a361324364ae3f436a63005877674cf45efb", "type": "github" }, "original": { - "owner": "lopsided98", - "ref": "nix-ros", + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -154,6 +155,22 @@ "systems": "systems_2" } }, + "rosdistro": { + "flake": false, + "locked": { + "lastModified": 1779440547, + "narHash": "sha256-zwkFhi7/rGJ1DPykUsulXnAJXFxbR5YQO10GuFUEBfo=", + "owner": "ros", + "repo": "rosdistro", + "rev": "7111412b11e86fbb1751db4c4955d798f95e5cf3", + "type": "github" + }, + "original": { + "owner": "ros", + "repo": "rosdistro", + "type": "github" + } + }, "rust-overlay": { "inputs": { "nixpkgs": "nixpkgs_3" diff --git a/flake.nix b/flake.nix index 91894814..62019c01 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,7 @@ "jazzy" # (May 2024 - May 2029, LTS) <-- Default "humble" # (May 2022 - May 2027, LTS) "kilted" # (May 2025 - Nov 2026) + "lyrical" # (May 2026 - May 2031, LTS) "rolling" # continuous release, no EOL ]; diff --git a/scripts/lib/common.nu b/scripts/lib/common.nu index 12909301..b87c268f 100755 --- a/scripts/lib/common.nu +++ b/scripts/lib/common.nu @@ -2,7 +2,7 @@ # Shared utilities for hiroz test scripts -export const DISTROS = ["humble", "jazzy", "kilted"] +export const DISTROS = ["humble", "jazzy", "kilted", "lyrical"] # Check if running in CI environment export def is-ci [] { diff --git a/scripts/test-pure-rust.nu b/scripts/test-pure-rust.nu index 36a2b321..66ffbe3f 100755 --- a/scripts/test-pure-rust.nu +++ b/scripts/test-pure-rust.nu @@ -55,6 +55,7 @@ def check-distro-features [] { run-cmd "cargo check -p hiroz --no-default-features --features jazzy" run-cmd "cargo check -p hiroz --no-default-features --features rolling" run-cmd "cargo check -p hiroz --no-default-features --features kilted" + run-cmd "cargo check -p hiroz --no-default-features --features lyrical" } def test-shm [] {