Skip to content

Commit

Permalink
Merge pull request #174 from Carter12s/fix-gendeps-in-message-definition
Browse files Browse the repository at this point in the history
Fixed code generation to include fully expanded message definition
  • Loading branch information
Carter12s authored Jul 8, 2024
2 parents 4e438e1 + ebaf8d3 commit 8e0066c
Show file tree
Hide file tree
Showing 16 changed files with 22,836 additions and 394 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Fixed
- Bug with ros1 native publishers not parsing connection headers correctly
- Bug with message_definitions provided by Publisher in the connection header not being the fully expanded definition.

### Changed

Expand Down
14 changes: 7 additions & 7 deletions roslibrust/examples/ros1_talker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_interfaces/std_msgs");
roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_interfaces");

#[cfg(feature = "ros1")]
#[tokio::main]
Expand All @@ -14,14 +14,14 @@ async fn main() -> Result<(), anyhow::Error> {
let nh = NodeHandle::new("http://localhost:11311", "talker_rs")
.await
.map_err(|err| err)?;
let publisher = nh.advertise::<std_msgs::String>("/chatter", 1).await?;
let publisher = nh
.advertise::<geometry_msgs::PointStamped>("/my_point", 1)
.await?;

for count in 0..50 {
publisher
.publish(&std_msgs::String {
data: format!("hello world from rust {count}"),
})
.await?;
let mut msg = geometry_msgs::PointStamped::default();
msg.point.x = count as f64;
publisher.publish(&msg).await?;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}

Expand Down
3 changes: 2 additions & 1 deletion roslibrust/src/ros1/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{
sync::Arc,
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
io::AsyncWriteExt,
sync::{mpsc, RwLock},
};

Expand Down Expand Up @@ -82,6 +82,7 @@ impl Publication {
tcp_nodelay: false,
service: None,
};
log::trace!("Publisher connection header: {responding_conn_header:?}");

let subscriber_streams = Arc::new(RwLock::new(Vec::new()));

Expand Down
2 changes: 1 addition & 1 deletion roslibrust/src/ros1/tcpros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub async fn establish_connection(
server_uri: &str,
conn_header: ConnectionHeader,
) -> Result<TcpStream, std::io::Error> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::io::AsyncWriteExt;

// Okay in Shane's version of this the server_uri is coming in as "rosrpc://localhost:41105"
// which is causing this to break...
Expand Down
13 changes: 11 additions & 2 deletions roslibrust_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ pub fn generate_service(service: ServiceFile) -> Result<TokenStream, Error> {
})
}

/// Turns a string into a TokenStream that represents a raw string literal of the string
pub fn generate_raw_string_literal(value: &str) -> TokenStream {
let wrapped = format!("r#\"{}\"#", value);
TokenStream::from_str(&wrapped).unwrap()
}

pub fn generate_struct(msg: MessageFile) -> Result<TokenStream, Error> {
let ros_type_name = msg.get_full_name();
let attrs = derive_attrs();
Expand Down Expand Up @@ -80,7 +86,10 @@ pub fn generate_struct(msg: MessageFile) -> Result<TokenStream, Error> {

let struct_name = format_ident!("{}", msg.parsed.name);
let md5sum = msg.md5sum;
let definition = msg.parsed.source.trim();
let definition = msg.definition;

// Raw here is only used to make the generated code look better.
let raw_message_definition = generate_raw_string_literal(&definition);

let mut base = quote! {
#[allow(non_snake_case)]
Expand All @@ -92,7 +101,7 @@ pub fn generate_struct(msg: MessageFile) -> Result<TokenStream, Error> {
impl ::roslibrust_codegen::RosMessageType for #struct_name {
const ROS_TYPE_NAME: &'static str = #ros_type_name;
const MD5SUM: &'static str = #md5sum;
const DEFINITION: &'static str = #definition;
const DEFINITION: &'static str = #raw_message_definition;
}
};

Expand Down
67 changes: 62 additions & 5 deletions roslibrust_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use log::*;
use proc_macro2::TokenStream;
use quote::quote;
use simple_error::{bail, SimpleError as Error};
use std::collections::{BTreeMap, VecDeque};
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::fmt::{Debug, Display};
use std::path::PathBuf;
use utils::Package;
Expand Down Expand Up @@ -68,16 +68,23 @@ pub trait RosServiceType {
pub struct MessageFile {
pub(crate) parsed: ParsedMessageFile,
pub(crate) md5sum: String,
// This is the expanded definition of the message for use in message_definition field of
// a connection header.
// See how https://wiki.ros.org/ROS/TCPROS references gendeps --cat
// See https://wiki.ros.org/roslib/gentools for an example of the output
pub(crate) definition: String,
pub(crate) is_fixed_length: bool,
}

impl MessageFile {
fn resolve(parsed: ParsedMessageFile, graph: &BTreeMap<String, MessageFile>) -> Option<Self> {
let md5sum = Self::compute_md5sum(&parsed, graph)?;
let definition = Self::compute_full_definition(&parsed, graph)?;
let is_fixed_length = Self::determine_if_fixed_length(&parsed, graph)?;
Some(MessageFile {
parsed,
md5sum,
definition,
is_fixed_length,
})
}
Expand Down Expand Up @@ -111,7 +118,7 @@ impl MessageFile {
}

pub fn get_definition(&self) -> &str {
&self.parsed.source
&self.definition
}

fn compute_md5sum(
Expand Down Expand Up @@ -159,6 +166,54 @@ impl MessageFile {
Some(md5sum_content)
}

/// Returns the set of all referenced non-intrinsic field types in this type or any of its dependencies
fn get_unique_field_types(
parsed: &ParsedMessageFile,
graph: &BTreeMap<String, MessageFile>,
) -> Option<BTreeSet<String>> {
let mut unique_field_types = BTreeSet::new();
for field in &parsed.fields {
let field_type = field.field_type.field_type.as_str();
if is_intrinsic_type(parsed.version.unwrap_or(RosVersion::ROS1), field_type) {
continue;
}
let sub_message = graph.get(field.get_full_name().as_str())?;
// Note: need to add both the field that is referenced AND its sub-dependencies
unique_field_types.insert(field.get_full_name());
let mut sub_deps = Self::get_unique_field_types(&sub_message.parsed, graph)?;
unique_field_types.append(&mut sub_deps);
}
Some(unique_field_types)
}

/// Computes the full definition of the message, including all referenced custom types
/// For reference see: https://wiki.ros.org/roslib/gentools
/// Implementation in gentools: https://github.com/strawlab/ros/blob/c3a8785f9d9551cc05cd74000c6536e2244bb1b1/core/roslib/src/roslib/gentools.py#L245
fn compute_full_definition(
parsed: &ParsedMessageFile,
graph: &BTreeMap<String, MessageFile>,
) -> Option<String> {
let mut definition_content = String::new();
definition_content.push_str(&format!("{}\n", parsed.source.trim()));
let sep: &str =
"================================================================================\n";
for field in Self::get_unique_field_types(parsed, graph)? {
let Some(sub_message) = graph.get(&field) else {
log::error!(
"Unable to find message type: {field:?}, while computing full definition of {}",
parsed.get_full_name()
);
return None;
};
definition_content.push_str(sep);
definition_content.push_str(&format!("MSG: {}\n", sub_message.get_full_name()));
definition_content.push_str(&format!("{}\n", sub_message.get_definition().trim()));
}
// Remove trailing \n added by concatenation logic
definition_content.pop();
Some(definition_content)
}

fn determine_if_fixed_length(
parsed: &ParsedMessageFile,
graph: &BTreeMap<String, MessageFile>,
Expand Down Expand Up @@ -278,8 +333,11 @@ impl From<String> for RosLiteral {
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub struct FieldType {
// Present when an externally referenced package is used
// Note: support for messages within same package is spotty...
pub package_name: Option<String>,
// Redundantly store the name of the package the field is in
// This is so that when an external package_name is not present
// we can still construct the full name of the field "package/field_type"
pub source_package: String,
// Explicit text of type without array specifier
pub field_type: String,
// Metadata indicating whether the field is a collection.
Expand Down Expand Up @@ -321,7 +379,7 @@ impl FieldInfo {
.field_type
.package_name
.as_ref()
.expect(&format!("Expected package name for field {self:#?}"));
.unwrap_or(&self.field_type.source_package);
format!("{field_package}/{}", self.field_type.field_type)
}
}
Expand Down Expand Up @@ -648,7 +706,6 @@ fn parse_ros_files(
"srv" => {
let srv_file = parse_ros_service_file(&contents, name, &pkg, &path)?;
parsed_services.push(srv_file);
// TODO ask shane, shouldn't we be pushing request and response to messages here?
}
"msg" => {
let msg = parse_ros_message_file(&contents, name, &pkg, &path)?;
Expand Down
3 changes: 3 additions & 0 deletions roslibrust_codegen/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ fn parse_field_type(type_str: &str, array_info: Option<Option<usize>>, pkg: &Pac
Some(pkg.name.clone())
}
},
source_package: pkg.name.clone(),
field_type: items[0].to_string(),
array_info,
}
Expand All @@ -166,12 +167,14 @@ fn parse_field_type(type_str: &str, array_info: Option<Option<usize>>, pkg: &Pac
if items[0] == "builtin_interfaces" {
FieldType {
package_name: None,
source_package: pkg.name.clone(),
field_type: type_str.to_string(),
array_info,
}
} else {
FieldType {
package_name: Some(items[0].to_string()),
source_package: pkg.name.clone(),
field_type: items[1].to_string(),
array_info,
}
Expand Down
14 changes: 13 additions & 1 deletion roslibrust_genmsg/test_package/include/geometry_msgs/Polygon.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,19 @@ struct Definition< ::geometry_msgs::Polygon_<ContainerAllocator>>
{
return "#A specification of a polygon where the first and last points are assumed to be connected"
"Point32[] points"
"";
"================================================================================"
"MSG: geometry_msgs/Point32"
"# This contains the position of a point in free space(with 32 bits of precision)."
"# It is recommeded to use Point wherever possible instead of Point32. "
"# "
"# This recommendation is to promote interoperability. "
"#"
"# This message is designed to take up less space when sending"
"# lots of points at once, as in the case of a PointCloud. "
""
"float32 x"
"float32 y"
"float32 z";
}

static const char* value(const ::geometry_msgs::Polygon_<ContainerAllocator>&) { return value(); }
Expand Down
19 changes: 16 additions & 3 deletions roslibrust_genmsg/test_package/include/sensor_msgs/BatteryState.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,7 @@ struct Definition< ::sensor_msgs::BatteryState_<ContainerAllocator>>
{
static constexpr char const* value()
{
return ""
"# Constants are chosen to match the enums in the linux kernel"
return "# Constants are chosen to match the enums in the linux kernel"
"# defined in include/linux/power_supply.h as of version 3.7"
"# The one difference is for style reasons the constants are"
"# all uppercase not mixed case."
Expand Down Expand Up @@ -296,7 +295,21 @@ struct Definition< ::sensor_msgs::BatteryState_<ContainerAllocator>>
" # If individual temperatures unknown but number of cells known set each to NaN"
"string location # The location into which the battery is inserted. (slot number or plug)"
"string serial_number # The best approximation of the battery serial number"
"";
"================================================================================"
"MSG: std_msgs/Header"
"# Standard metadata for higher-level stamped data types."
"# This is generally used to communicate timestamped data "
"# in a particular coordinate frame."
"# "
"# sequence ID: consecutively increasing ID "
"uint32 seq"
"#Two-integer timestamp that is expressed as:"
"# * stamp.sec: seconds (stamp_secs) since epoch (in Python the variable is called 'secs')"
"# * stamp.nsec: nanoseconds since stamp_secs (in Python the variable is called 'nsecs')"
"# time-handling sugar is provided by the client library"
"time stamp"
"#Frame this data is associated with"
"string frame_id";
}

static const char* value(const ::sensor_msgs::BatteryState_<ContainerAllocator>&) { return value(); }
Expand Down
37 changes: 36 additions & 1 deletion roslibrust_genmsg/test_package/include/sensor_msgs/CameraInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,42 @@ struct Definition< ::sensor_msgs::CameraInfo_<ContainerAllocator>>
"# The default setting of roi (all values 0) is considered the same as"
"# full resolution (roi.width = width, roi.height = height)."
"RegionOfInterest roi"
"";
"================================================================================"
"MSG: sensor_msgs/RegionOfInterest"
"# This message is used to specify a region of interest within an image."
"#"
"# When used to specify the ROI setting of the camera when the image was"
"# taken, the height and width fields should either match the height and"
"# width fields for the associated image; or height = width = 0"
"# indicates that the full resolution image was captured."
""
"uint32 x_offset # Leftmost pixel of the ROI"
" # (0 if the ROI includes the left edge of the image)"
"uint32 y_offset # Topmost pixel of the ROI"
" # (0 if the ROI includes the top edge of the image)"
"uint32 height # Height of ROI"
"uint32 width # Width of ROI"
""
"# True if a distinct rectified ROI should be calculated from the \"raw\""
"# ROI in this message. Typically this should be False if the full image"
"# is captured (ROI not used), and True if a subwindow is captured (ROI"
"# used)."
"bool do_rectify"
"================================================================================"
"MSG: std_msgs/Header"
"# Standard metadata for higher-level stamped data types."
"# This is generally used to communicate timestamped data "
"# in a particular coordinate frame."
"# "
"# sequence ID: consecutively increasing ID "
"uint32 seq"
"#Two-integer timestamp that is expressed as:"
"# * stamp.sec: seconds (stamp_secs) since epoch (in Python the variable is called 'secs')"
"# * stamp.nsec: nanoseconds since stamp_secs (in Python the variable is called 'nsecs')"
"# time-handling sugar is provided by the client library"
"time stamp"
"#Frame this data is associated with"
"string frame_id";
}

static const char* value(const ::sensor_msgs::CameraInfo_<ContainerAllocator>&) { return value(); }
Expand Down
3 changes: 1 addition & 2 deletions roslibrust_genmsg/test_package/include/std_msgs/Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,7 @@ struct Definition< ::std_msgs::Header_<ContainerAllocator>>
"# time-handling sugar is provided by the client library"
"time stamp"
"#Frame this data is associated with"
"string frame_id"
"";
"string frame_id";
}

static const char* value(const ::std_msgs::Header_<ContainerAllocator>&) { return value(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ struct Definition< ::std_srvs::TriggerResponse_<ContainerAllocator>>
static constexpr char const* value()
{
return "bool success # indicate successful run of triggered service"
"string message # informational, e.g. for error messages"
"";
"string message # informational, e.g. for error messages";
}

static const char* value(const ::std_srvs::TriggerResponse_<ContainerAllocator>&) { return value(); }
Expand Down
Loading

0 comments on commit 8e0066c

Please sign in to comment.