diff --git a/.github/workflows/package_xml.yml b/.github/workflows/package_xml.yml new file mode 100644 index 00000000..4bd4a9aa --- /dev/null +++ b/.github/workflows/package_xml.yml @@ -0,0 +1,11 @@ +name: Validate package.xml + +on: + pull_request: + +jobs: + package-xml: + runs-on: ubuntu-latest + name: Validate package.xml + steps: + - uses: gazebo-tooling/action-gz-ci/validate_package_xml@jammy diff --git a/Changelog.md b/Changelog.md index 35574e6f..40ae04b6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,25 @@ ## Gazebo Transport 13.X +### Gazebo Transport 13.4.0 (2024-06-18) + +1. Add frequency to topic CLI. + * [Pull request #503](https://github.com/gazebosim/gz-transport/pull/503) + +### Gazebo Transport 13.3.0 (2024-06-05) + +1. Adding option to ignore local messages + * [Pull request #506](https://github.com/gazebosim/gz-transport/pull/506) + +1. Include Python tutorial in list of tutorials + * [Pull request #499](https://github.com/gazebosim/gz-transport/pull/499) + +1. Remove python3-distutils since it's not needed on Jammy + * [Pull request #496](https://github.com/gazebosim/gz-transport/pull/496) + +1. Add package.xml + * [Pull request #485](https://github.com/gazebosim/gz-transport/pull/485) + ### Gazebo Transport 13.2.0 (2024-04-09) 1. Use relative install path for gz tool data diff --git a/include/gz/transport/SubscribeOptions.hh b/include/gz/transport/SubscribeOptions.hh index 72cd78e6..1c1f0ef4 100644 --- a/include/gz/transport/SubscribeOptions.hh +++ b/include/gz/transport/SubscribeOptions.hh @@ -66,6 +66,18 @@ namespace gz /// \return The maximum number of messages per second. public: uint64_t MsgsPerSec() const; + /// \brief Set the value to ignore local messages or not. + /// \param[in] _ignore True when ignoring local messages + /// or false otherwise. + /// \sa IgnoreLocalMessages + public: void SetIgnoreLocalMessages(bool _ignore); + + /// \brief Whether the local messages should be ignored. + /// \return true when the local messages should be ignored or + /// false otherwise. + /// \sa SetIgnoreLocalMessages + public: bool IgnoreLocalMessages() const; + #ifdef _WIN32 // Disable warning C4251 which is triggered by // std::unique_ptr diff --git a/include/gz/transport/SubscriptionHandler.hh b/include/gz/transport/SubscriptionHandler.hh index 9e4d789b..6119e100 100644 --- a/include/gz/transport/SubscriptionHandler.hh +++ b/include/gz/transport/SubscriptionHandler.hh @@ -80,6 +80,10 @@ namespace gz /// \return A string representation of the handler UUID. public: std::string HandlerUuid() const; + /// \brief Return whether local messages are ignored or not. + /// \return True when local messages are ignored or false otherwise. + public: bool IgnoreLocalMessages() const; + /// \brief Check if message subscription is throttled. If so, verify /// whether the callback should be executed or not. /// \return true if the callback should be executed or false otherwise. diff --git a/package.xml b/package.xml new file mode 100644 index 00000000..dda99b37 --- /dev/null +++ b/package.xml @@ -0,0 +1,32 @@ + + + + gz-transport14 + 14.0.0 + Gazebo Transport: Provides fast and efficient asynchronous message passing, services, and data logging. + Carlos Agüero + Apache License 2.0 + https://github.com/gazebosim/gz-transport + + cmake + + gz-cmake4 + + gz-math8 + gz-msgs11 + gz-tools2 + gz-utils3 + libsqlite3-dev + libzmq3-dev + pkg-config + protobuf-dev + pybind11-dev + python3-dev + python3-psutil + python3-pytest + uuid + + + cmake + + diff --git a/src/Node.cc b/src/Node.cc index 94c25531..a931478d 100644 --- a/src/Node.cc +++ b/src/Node.cc @@ -338,6 +338,8 @@ bool Node::Publisher::Publish(const ProtoMsg &_msg) pubMsgDetails->msgCopy.reset(_msg.New()); pubMsgDetails->msgCopy->CopyFrom(_msg); + pubMsgDetails->publisherNodeUUID = this->dataPtr->publisher.NUuid(); + if (subscribers.haveLocal) { for (const auto &node : subscribers.localHandlers) diff --git a/src/NodeShared.cc b/src/NodeShared.cc index b026a244..6e6e8c45 100644 --- a/src/NodeShared.cc +++ b/src/NodeShared.cc @@ -1850,6 +1850,14 @@ void NodeSharedPrivate::PublishThread() // Send the message to all the local handlers. for (auto &handler : msgDetails->localHandlers) { + + // Check here if we want to ignore local publications. + if (handler->IgnoreLocalMessages() && + msgDetails->publisherNodeUUID == handler->NodeUuid()) + { + continue; + } + try { handler->RunLocalCallback(*(msgDetails->msgCopy.get()), diff --git a/src/NodeSharedPrivate.hh b/src/NodeSharedPrivate.hh index ccb9ba9e..812bacf3 100644 --- a/src/NodeSharedPrivate.hh +++ b/src/NodeSharedPrivate.hh @@ -167,6 +167,9 @@ namespace gz /// \brief Information about the topic and type. public: MessageInfo info; + + /// \brief Publisher's node UUID. + public: std::string publisherNodeUUID; }; /// \brief Publish thread used to process the pubQueue. diff --git a/src/Node_TEST.cc b/src/Node_TEST.cc index 157cb24b..0bfb79bc 100644 --- a/src/Node_TEST.cc +++ b/src/Node_TEST.cc @@ -2020,6 +2020,41 @@ TEST(NodeTest, PubThrottled) reset(); } +////////////////////////////////////////////////// +/// \brief This test creates one local publisher and subscriber and +/// checks that no messages are received when using SetIgnoreLocalMessages +/// is set to true. +TEST(NodeTest, IgnoreLocalMessages) +{ + reset(); + + msgs::Int32 msg; + msg.set_data(data); + + transport::Node node; + + auto pub = node.Advertise(g_topic); + EXPECT_TRUE(pub); + + transport::SubscribeOptions opts; + EXPECT_FALSE(opts.IgnoreLocalMessages()); + opts.SetIgnoreLocalMessages(true); + EXPECT_TRUE(opts.IgnoreLocalMessages()); + EXPECT_TRUE(node.Subscribe(g_topic, cb, opts)); + + // Should be true the first time + for (auto i = 0; i < 3; ++i) + { + EXPECT_TRUE(pub.Publish(msg)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // No messages should be received. + EXPECT_EQ(0, counter); + + reset(); +} + ////////////////////////////////////////////////// /// \brief This test spawns a service responser and a service requester. The /// requester uses a wrong type for the request argument. The test should verify diff --git a/src/SubscribeOptions.cc b/src/SubscribeOptions.cc index 6a0c1171..0f3cda76 100644 --- a/src/SubscribeOptions.cc +++ b/src/SubscribeOptions.cc @@ -33,9 +33,8 @@ SubscribeOptions::SubscribeOptions() ////////////////////////////////////////////////// SubscribeOptions::SubscribeOptions(const SubscribeOptions &_otherSubscribeOpts) - : dataPtr(new SubscribeOptionsPrivate()) + : dataPtr(new SubscribeOptionsPrivate(*_otherSubscribeOpts.dataPtr)) { - this->SetMsgsPerSec(_otherSubscribeOpts.MsgsPerSec()); } ////////////////////////////////////////////////// @@ -60,3 +59,15 @@ void SubscribeOptions::SetMsgsPerSec(const uint64_t _newMsgsPerSec) { this->dataPtr->msgsPerSec = _newMsgsPerSec; } + +////////////////////////////////////////////////// +bool SubscribeOptions::IgnoreLocalMessages() const +{ + return this->dataPtr->ignoreLocalMessages; +} + +////////////////////////////////////////////////// +void SubscribeOptions::SetIgnoreLocalMessages(bool _ignore) +{ + this->dataPtr->ignoreLocalMessages = _ignore; +} diff --git a/src/SubscribeOptionsPrivate.hh b/src/SubscribeOptionsPrivate.hh index 030ed63b..0920b1dd 100644 --- a/src/SubscribeOptionsPrivate.hh +++ b/src/SubscribeOptionsPrivate.hh @@ -41,6 +41,9 @@ namespace gz /// \brief Default message subscription rate. public: uint64_t msgsPerSec = kUnthrottled; + + /// \brief Whether local messages should be ignored or not. + public: bool ignoreLocalMessages = false; }; } } diff --git a/src/SubscriptionHandler.cc b/src/SubscriptionHandler.cc index abefad85..f41fe695 100644 --- a/src/SubscriptionHandler.cc +++ b/src/SubscriptionHandler.cc @@ -49,6 +49,12 @@ namespace gz return this->hUuid; } + ///////////////////////////////////////////////// + bool SubscriptionHandlerBase::IgnoreLocalMessages() const + { + return this->opts.IgnoreLocalMessages(); + } + ///////////////////////////////////////////////// bool SubscriptionHandlerBase::UpdateThrottling() { diff --git a/src/cmd/gz.cc b/src/cmd/gz.cc index 68b84343..adb948c1 100644 --- a/src/cmd/gz.cc +++ b/src/cmd/gz.cc @@ -1,5 +1,7 @@ /* - * Copyright (C) 2014 Open Source Robotics Foundation + * Copyright 2024 CogniPilot Foundation + * Copyright 2024 Open Source Robotics Foundation + * Copyright 2024 Rudis Laboratories * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +17,15 @@ * */ +#include #include +#include +#include +#include #include #include +#include +#include #include #include @@ -345,6 +353,78 @@ extern "C" void cmdTopicEcho(const char *_topic, } } +////////////////////////////////////////////////// +extern "C" void cmdTopicFrequency(const char *_topic) +{ + if (!_topic || std::string(_topic).empty()) + { + std::cerr << "Invalid topic. Topic must not be empty.\n"; + return; + } + using namespace std::chrono; + int count = 0; + const int samples = 11; + const int window = samples - 1; + std::vector timeV; + std::vector intervalV; + float sum = 0.0; + float dev = 0.0; + float mean = 0.0; + float stdDev = 0.0; + + std::function cb = [&](const ProtoMsg &) + { + if (count > samples || count == 0) + { + count = 0; + sum = 0.0; + dev = 0.0; + timeV.clear(); + intervalV.clear(); + } + if (count < samples) + { + time_point now = system_clock::now(); + duration duration = now.time_since_epoch(); + timeV.push_back(duration.count()); + } + else if (count == samples) + { + for (int i = 0; i < window; ++i) + { + intervalV.push_back(static_cast((timeV[i+1] + - timeV[i])) / 1e+9); + } + auto [min, max] = std::minmax_element(intervalV.begin(), + intervalV.end()); + mean = std::accumulate(std::begin(intervalV), + std::end(intervalV), 0.0) / window; + for (auto interval : intervalV) + { + dev += pow(interval - mean, 2); + } + stdDev = sqrt(dev / window); + std::cout << "\n" << std::endl; + for(int i = 0; i < window; ++i) + { + std::cout << "interval [" << i << "]: " + << intervalV[i] << "s" << std::endl; + } + std::cout << "average rate: " << 1.0 / mean << std::endl; + std::cout << "min: " << *min << "s max: " << *max + << "s std dev: " << stdDev << "s window: " + << window << std::endl; + } + ++count; + }; + + Node node; + if (!node.Subscribe(_topic, cb)) + return; + + waitForShutdown(); +} + ////////////////////////////////////////////////// extern "C" const char *gzVersion() { diff --git a/src/cmd/gz.hh b/src/cmd/gz.hh index e9a827eb..3ef433ea 100644 --- a/src/cmd/gz.hh +++ b/src/cmd/gz.hh @@ -1,5 +1,7 @@ /* - * Copyright (C) 2014 Open Source Robotics Foundation + * Copyright 2024 CogniPilot Foundation + * Copyright 2024 Open Source Robotics Foundation + * Copyright 2024 Rudis Laboratories * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,6 +94,10 @@ extern "C" { extern "C" void cmdTopicEcho(const char *_topic, const double _duration, int _count, MsgOutputFormat _outputFormat); +/// \brief External hook to execute 'gz topic -f' from the command line. +/// \param[in] _topic Topic name. +extern "C" void cmdTopicFrequency(const char *_topic); + /// \brief External hook to read the library version. /// \return C-string representing the version. Ex.: 0.1.2 extern "C" const char *gzVersion(); diff --git a/src/cmd/topic_main.cc b/src/cmd/topic_main.cc index 5bca79c5..e51a76c9 100644 --- a/src/cmd/topic_main.cc +++ b/src/cmd/topic_main.cc @@ -1,5 +1,7 @@ /* - * Copyright (C) 2021 Open Source Robotics Foundation + * Copyright 2024 CogniPilot Foundation + * Copyright 2024 Open Source Robotics Foundation + * Copyright 2024 Rudis Laboratories * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +32,8 @@ enum class TopicCommand kTopicList, kTopicInfo, kTopicPub, - kTopicEcho + kTopicEcho, + kTopicFrequency }; ////////////////////////////////////////////////// @@ -80,6 +83,9 @@ void runTopicCommand(const TopicOptions &_opt) cmdTopicEcho(_opt.topic.c_str(), _opt.duration, _opt.count, _opt.msgOutputFormat); break; + case TopicCommand::kTopicFrequency: + cmdTopicFrequency(_opt.topic.c_str()); + break; case TopicCommand::kNone: default: // In the event that there is no command, display help @@ -130,6 +136,14 @@ R"(Output data to screen. E.g.: gz topic -e -t /foo)") ->needs(topicOpt); + command->add_flag_callback("-f,--frequency", + [opt](){ + opt->command = TopicCommand::kTopicFrequency; + }, +R"(Calculate the frequency of a topic: + gz topic -f -t /foo)") + ->needs(topicOpt); + command->add_flag_callback("--json-output", [opt]() { opt->msgOutputFormat = MsgOutputFormat::kJSON; }, "Output messages in JSON format."); diff --git a/tutorials.md.in b/tutorials.md.in index e6a5a923..3ec181a5 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -11,13 +11,14 @@ Gazebo @GZ_DESIGNATION_CAP@ library and how to use the library effectively. 3. \subpage nodestopics "Nodes and Topics" 4. \subpage messages "Messages" 5. \subpage services "Services" -6. \subpage security "Security" -7. \subpage relay "Relay" -8. \subpage logging "Logging" -9. \subpage envvars "Environment Variables" -10. \subpage contribute "How to contribute" -11. \subpage development "Development" -12. \subpage topicstatistics "Topic Statistics" +6. \subpage python "Python Support" +7. \subpage security "Security" +8. \subpage relay "Relay" +9. \subpage logging "Logging" +10. \subpage envvars "Environment Variables" +11. \subpage contribute "How to contribute" +12. \subpage development "Development" +13. \subpage topicstatistics "Topic Statistics" ## License