From d1314797a5bbe1ca9546e5eaebb52a33503a7c64 Mon Sep 17 00:00:00 2001 From: James Pace Date: Fri, 24 Jul 2026 20:00:46 -0400 Subject: [PATCH] Conversion functions. --- CMakeLists.txt | 31 +++++- include/j7s_diagnostics_ros/conversions.hpp | 20 ++++ j7s_diagnostics_cxx/Cargo.lock | 10 +- j7s_diagnostics_cxx/src/lib.rs | 113 +++++++++++++++----- package.xml | 2 + src/conversions.cpp | 99 +++++++++++++++++ src/test.cpp | 7 +- 7 files changed, 243 insertions(+), 39 deletions(-) create mode 100644 include/j7s_diagnostics_ros/conversions.hpp create mode 100644 src/conversions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fffd056..bb4a205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(j7s_diagnostics_ros CXX) find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) +find_package(diagnostic_msgs REQUIRED) include(FetchContent) FetchContent_Declare( @@ -14,14 +15,11 @@ FetchContent_MakeAvailable(Corrosion) set(CMAKE_CXX_STANDARD 20) +# Rust bindings. corrosion_import_crate(MANIFEST_PATH j7s_diagnostics_cxx/Cargo.toml) corrosion_add_cxxbridge(j7s_diagnostics_cxx_bridge CRATE j7s_diagnostics_cxx FILES lib.rs) - -add_executable(test_bin src/test.cpp) -target_link_libraries(test_bin PRIVATE j7s_diagnostics_cxx_bridge j7s_diagnostics_cxx) - corrosion_install(TARGETS j7s_diagnostics_cxx EXPORT export_${PROJECT_NAME} ARCHIVE DESTINATION lib @@ -29,8 +27,31 @@ corrosion_install(TARGETS j7s_diagnostics_cxx RUNTIME DESTINATION bin ) +# The rest of the stuff. +# Note that rust/cxx doesn't support shared libraries so +# this library (and anything that uses it) will have +# to be statically linked. +add_library(conversions STATIC src/conversions.cpp) +target_include_directories(conversions PUBLIC + $ + $) +target_link_libraries(conversions PUBLIC + j7s_diagnostics_cxx_bridge + j7s_diagnostics_cxx + ${diagnostic_msgs_TARGETS} +) + +add_executable(test_bin src/test.cpp) +target_include_directories(test_bin PUBLIC + $ + $) +target_link_libraries(test_bin PRIVATE + conversions +) + install(TARGETS - test_bin + conversions + #test_bin DESTINATION lib/${PROJECT_NAME}) ament_export_targets(export_${PROJECT_NAME}) diff --git a/include/j7s_diagnostics_ros/conversions.hpp b/include/j7s_diagnostics_ros/conversions.hpp new file mode 100644 index 0000000..181305d --- /dev/null +++ b/include/j7s_diagnostics_ros/conversions.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "j7s_diagnostics_cxx_bridge/lib.h" +#include +#include + + +std::string convertString(const rust::String& rust); + +j7s_diagnostics_cxx::DiagnosticLevel convertLevel(const uint8_t rosLevel); + +uint8_t convertLevel(const j7s_diagnostics_cxx::DiagnosticLevel& level ); + +std::vector convertValues(const rust::Box& map); + +rust::Box convertValues(const std::vector& map); + +j7s_diagnostics_cxx::DiagnosticStatus fromMsg(const diagnostic_msgs::msg::DiagnosticStatus& msg); + +diagnostic_msgs::msg::DiagnosticStatus toMsg(const j7s_diagnostics_cxx::DiagnosticStatus& status); diff --git a/j7s_diagnostics_cxx/Cargo.lock b/j7s_diagnostics_cxx/Cargo.lock index 35c451e..6ea0fb1 100644 --- a/j7s_diagnostics_cxx/Cargo.lock +++ b/j7s_diagnostics_cxx/Cargo.lock @@ -26,9 +26,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.2" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", ] @@ -160,7 +160,7 @@ dependencies = [ [[package]] name = "j7s_diagnostics" version = "0.1.0" -source = "git+https://git.jpace121.net/public/j7s_diagnostics.git?branch=main#f40b64151807cbca03a021e66cb3bf3558e4619d" +source = "git+https://git.jpace121.net/public/j7s_diagnostics.git?branch=main#5f5e9c3b8aa82fd6d14f7a7931604a225fd6b551" dependencies = [ "limbo_graph", "thiserror", @@ -258,9 +258,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "3.0.1" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", diff --git a/j7s_diagnostics_cxx/src/lib.rs b/j7s_diagnostics_cxx/src/lib.rs index 651d618..c8e6ca4 100644 --- a/j7s_diagnostics_cxx/src/lib.rs +++ b/j7s_diagnostics_cxx/src/lib.rs @@ -2,7 +2,7 @@ use anyhow::anyhow; use std::collections::BTreeMap; -#[cxx::bridge] +#[cxx::bridge(namespace = "j7s_diagnostics_cxx")] mod ffi { // Shared structs whose fields can be seen in both C++ and Rust. #[derive(Clone, PartialOrd, PartialEq, Default)] @@ -21,13 +21,16 @@ mod ffi { name: String, message: String, hardware_id: String, - values: Box + values: Box, } extern "Rust" { // Functions in here can be seen on both sides, but are // defined in rust. // types can be seen in both languages, but only opaquely in C++. + #[Self = "DiagnosticStatus"] + fn build() -> DiagnosticStatus; + type OptionalString; #[Self = "OptionalString"] fn some(val: String) -> Box; @@ -36,29 +39,80 @@ mod ffi { fn is_some(self: &OptionalString) -> bool; fn value(self: &OptionalString) -> Result; - type StringMap; #[Self = "StringMap"] fn build() -> Box; fn get(self: &StringMap, key: &String) -> Box; fn insert(self: &mut StringMap, key: String, value: String); + fn keys(self: &StringMap) -> Vec; type DiagnosticTree; #[Self = "DiagnosticTree"] fn build() -> Box; - fn add_statuses(self: &mut DiagnosticTree, statuses: &Vec) -> Result<()> ; + fn add_statuses(self: &mut DiagnosticTree, statuses: &Vec) -> Result<()>; fn reconcile(self: &mut DiagnosticTree) -> Result<()>; fn aggregate(self: &DiagnosticTree) -> Result>; } } -fn diagnostic_status_to_rust(cpp: &crate::ffi::DiagnosticStatus) -> j7s_diagnostics::DiagnosticStatus { - todo!() +fn diagnostic_level_to_rust(cpp: &crate::ffi::DiagnosticLevel) -> j7s_diagnostics::DiagnosticLevel { + match cpp { + &crate::ffi::DiagnosticLevel::UNSET => j7s_diagnostics::DiagnosticLevel::UNSET, + &crate::ffi::DiagnosticLevel::OK => j7s_diagnostics::DiagnosticLevel::OK, + &crate::ffi::DiagnosticLevel::WARN => j7s_diagnostics::DiagnosticLevel::WARN, + &crate::ffi::DiagnosticLevel::ERROR => j7s_diagnostics::DiagnosticLevel::ERROR, + &crate::ffi::DiagnosticLevel::STALE => j7s_diagnostics::DiagnosticLevel::STALE, + _ => j7s_diagnostics::DiagnosticLevel::UNSET, + } } -fn diagnostic_status_from_rust(rust: &j7s_diagnostics::DiagnosticStatus) -> crate::ffi::DiagnosticStatus { - todo!() +fn diagnostic_level_from_rust( + rust: &j7s_diagnostics::DiagnosticLevel, +) -> crate::ffi::DiagnosticLevel { + match rust { + &j7s_diagnostics::DiagnosticLevel::UNSET => crate::ffi::DiagnosticLevel::UNSET, + &j7s_diagnostics::DiagnosticLevel::OK => crate::ffi::DiagnosticLevel::OK, + &j7s_diagnostics::DiagnosticLevel::WARN => crate::ffi::DiagnosticLevel::WARN, + &j7s_diagnostics::DiagnosticLevel::ERROR => crate::ffi::DiagnosticLevel::ERROR, + &j7s_diagnostics::DiagnosticLevel::STALE => crate::ffi::DiagnosticLevel::STALE, + } +} + +fn diagnostic_status_to_rust( + cpp: &crate::ffi::DiagnosticStatus, +) -> j7s_diagnostics::DiagnosticStatus { + j7s_diagnostics::DiagnosticStatus::new( + diagnostic_level_to_rust(&cpp.level), + cpp.name.clone(), + cpp.message.clone(), + cpp.hardware_id.clone(), + cpp.values.to_btree(), + ) +} + +fn diagnostic_status_from_rust( + rust: &j7s_diagnostics::DiagnosticStatus, +) -> crate::ffi::DiagnosticStatus { + crate::ffi::DiagnosticStatus { + level: diagnostic_level_from_rust(&rust.level()), + name: rust.name(), + message: rust.message(), + hardware_id: rust.hardware_id(), + values: Box::new(StringMap::from_btree(rust.values())), + } +} + +impl crate::ffi::DiagnosticStatus { + fn build() -> Self { + Self { + level: crate::ffi::DiagnosticLevel::default(), + name: String::default(), + message: String::default(), + hardware_id: String::default(), + values: StringMap::build(), + } + } } #[derive(Clone)] @@ -69,13 +123,14 @@ pub struct DiagnosticTree { impl DiagnosticTree { pub fn build() -> Box { let graph = j7s_diagnostics::DiagnosticGraph::new(); - let tree = DiagnosticTree { - graph: graph - }; + let tree = DiagnosticTree { graph: graph }; Box::new(tree) } - pub fn add_statuses(self: &mut DiagnosticTree, statuses: &Vec) -> anyhow::Result<()> { + pub fn add_statuses( + self: &mut DiagnosticTree, + statuses: &Vec, + ) -> anyhow::Result<()> { let mut converted_statuses = Vec::new(); for status in statuses.iter() { converted_statuses.push(diagnostic_status_to_rust(&status)); @@ -99,21 +154,17 @@ impl DiagnosticTree { #[derive(Clone)] pub struct OptionalString { - val: Option + val: Option, } impl OptionalString { pub fn some(val: String) -> Box { - let raw = Self { - val: Some(val) - }; + let raw = Self { val: Some(val) }; Box::new(raw) } pub fn none() -> Box { - let raw = Self { - val: None - }; + let raw = Self { val: None }; Box::new(raw) } @@ -131,22 +182,34 @@ impl OptionalString { #[derive(Clone)] pub struct StringMap { - map: BTreeMap + map: BTreeMap, } impl StringMap { pub fn build() -> Box { let raw = Self { - map: BTreeMap::::new() + map: BTreeMap::::new(), }; Box::new(raw) } + pub fn from_btree(map: BTreeMap) -> Self { + Self { map: map } + } + + fn keys(&self) -> Vec { + self.map.keys().map(|s| s.clone()).collect() + } + + pub fn to_btree(&self) -> BTreeMap { + self.map.clone() + } + pub fn get(&self, key: &String) -> Box { - match self.map.get(key) { - None => OptionalString::none(), - Some(val) => OptionalString::some(val.clone()), - } + match self.map.get(key) { + None => OptionalString::none(), + Some(val) => OptionalString::some(val.clone()), + } } pub fn insert(&mut self, key: String, value: String) { diff --git a/package.xml b/package.xml index a2b26cd..7fc9d65 100644 --- a/package.xml +++ b/package.xml @@ -9,6 +9,8 @@ ament_cmake + diagnostic_msgs + ament_cmake diff --git a/src/conversions.cpp b/src/conversions.cpp new file mode 100644 index 0000000..123c3af --- /dev/null +++ b/src/conversions.cpp @@ -0,0 +1,99 @@ +#include + +std::string convertString(const rust::String& rust) +{ + auto copy = rust; + return std::string(copy.c_str()); +} + +j7s_diagnostics_cxx::DiagnosticLevel convertLevel(const uint8_t rosLevel) +{ + switch(rosLevel) + { + case diagnostic_msgs::msg::DiagnosticStatus::OK: + return j7s_diagnostics_cxx::DiagnosticLevel::OK; + case diagnostic_msgs::msg::DiagnosticStatus::WARN: + return j7s_diagnostics_cxx::DiagnosticLevel::WARN; + case diagnostic_msgs::msg::DiagnosticStatus::ERROR: + return j7s_diagnostics_cxx::DiagnosticLevel::ERROR; + case diagnostic_msgs::msg::DiagnosticStatus::STALE: + default: + return j7s_diagnostics_cxx::DiagnosticLevel::STALE; + } +} + +uint8_t convertLevel(const j7s_diagnostics_cxx::DiagnosticLevel& level ) +{ + switch(level) + { + case j7s_diagnostics_cxx::DiagnosticLevel::OK: + return diagnostic_msgs::msg::DiagnosticStatus::OK; + case j7s_diagnostics_cxx::DiagnosticLevel::WARN: + return diagnostic_msgs::msg::DiagnosticStatus::WARN; + case j7s_diagnostics_cxx::DiagnosticLevel::ERROR: + return diagnostic_msgs::msg::DiagnosticStatus::ERROR; + case j7s_diagnostics_cxx::DiagnosticLevel::STALE: + case j7s_diagnostics_cxx::DiagnosticLevel::UNSET: + default: + return diagnostic_msgs::msg::DiagnosticStatus::STALE; + } +} + +std::vector convertValues(const rust::Box& map) +{ + std::vector toReturn; + + const rust::Vec keys = map->keys(); + if(keys.empty()) + { + return toReturn; + } + + for(const auto& key : keys) + { + const auto value = map->get(key); + diagnostic_msgs::msg::KeyValue msg; + msg.key = convertString(key); + msg.value = std::string(value->value().c_str()); + toReturn.emplace_back(msg); + } + + return toReturn; + +} + +rust::Box convertValues(const std::vector& map) +{ + rust::Box toReturn = j7s_diagnostics_cxx::StringMap::build(); + + for(const auto& pair: map) + { + toReturn->insert(rust::String(pair.key), rust::String(pair.value)); + } + + return toReturn; +} + +j7s_diagnostics_cxx::DiagnosticStatus fromMsg(const diagnostic_msgs::msg::DiagnosticStatus& msg) +{ + auto toReturn = j7s_diagnostics_cxx::DiagnosticStatus::build(); + toReturn.level = convertLevel(msg.level); + toReturn.name = rust::String(msg.name); + toReturn.message = rust::String(msg.message); + toReturn.hardware_id = rust::String(msg.hardware_id); + toReturn.values = convertValues(msg.values); + + return toReturn; +} + +diagnostic_msgs::msg::DiagnosticStatus toMsg(const j7s_diagnostics_cxx::DiagnosticStatus& status) +{ + diagnostic_msgs::msg::DiagnosticStatus toReturn; + toReturn.level = convertLevel(status.level); + toReturn.name = convertString(status.name); + toReturn.message = convertString(status.message); + toReturn.hardware_id = convertString(status.hardware_id); + toReturn.values = convertValues(status.values); + + return toReturn; +} diff --git a/src/test.cpp b/src/test.cpp index 72434ce..9dab5dd 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -1,17 +1,16 @@ -#include "j7s_diagnostics_cxx_bridge/lib.h" - #include +#include int main(int argc, char *argv[]) { - const auto my_string = OptionalString::some(rust::String("test")); + const auto my_string = j7s_diagnostics_cxx::OptionalString::some(rust::String("test")); if(my_string->is_some()) { auto as_string = my_string->value(); std::cout << as_string.c_str() << std::endl; } - auto my_map = StringMap::build(); + auto my_map = j7s_diagnostics_cxx::StringMap::build(); my_map->insert(rust::String("a"), rust::String("b")); return 0;