Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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' ||
Expand Down Expand Up @@ -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' ||
Expand Down Expand Up @@ -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: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions crates/hiroz-bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
76 changes: 41 additions & 35 deletions crates/hiroz-console/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicU16, Ordering};
use std::thread;
use std::time::Duration;

Expand Down Expand Up @@ -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<AtomicU16> = 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,
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions crates/hiroz-msgs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ humble = ["hiroz/humble"]
jazzy = ["hiroz/jazzy"]
rolling = ["hiroz/rolling"]
kilted = ["hiroz/kilted"]
lyrical = ["hiroz/lyrical"]
python_registry = [
"pyo3",
"parking_lot",
Expand Down
1 change: 1 addition & 0 deletions crates/hiroz-msgs/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ fn discover_system_packages(packages: &[&str]) -> Result<Vec<PathBuf>> {
"/opt/ros/rolling",
"/opt/ros/jazzy",
"/opt/ros/kilted",
"/opt/ros/lyrical",
"/opt/ros/humble",
];

Expand Down
1 change: 1 addition & 0 deletions crates/hiroz-py/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jazzy = ["hiroz/jazzy"]
humble = ["hiroz/humble"]
kilted = ["hiroz/kilted"]
rolling = ["hiroz/rolling"]
lyrical = ["hiroz/lyrical"]

[dependencies]
pyo3 = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions crates/hiroz-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
18 changes: 9 additions & 9 deletions crates/hiroz-tests/tests/action_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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);
}
Expand Down
37 changes: 20 additions & 17 deletions crates/hiroz-tests/tests/type_description_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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,
};

Expand All @@ -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
Expand Down Expand Up @@ -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,
};

Expand Down
10 changes: 6 additions & 4 deletions crates/hiroz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions crates/hiroz/examples/demo_nodes/fibonacci_action_client.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -46,9 +46,9 @@ pub async fn run_fibonacci_action_client(ctx: ZContext, order: i32) -> Result<Ve
tokio::spawn(async move {
while let Some(fb) = feedback_stream.recv().await {
// Distro-specific feedback field names
#[cfg(feature = "kilted")]
#[cfg(any(feature = "kilted", feature = "lyrical"))]
println!("Feedback: {:?}", fb.sequence);
#[cfg(not(feature = "kilted"))]
#[cfg(not(any(feature = "kilted", feature = "lyrical")))]
println!("Feedback: {:?}", fb.partial_sequence);
}
});
Expand Down
Loading
Loading