From d18026e8b28c3ba8e66575f05f444f3d9c51c506 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 7 Jan 2021 12:39:27 -0800 Subject: [PATCH] Generate valid topics everywhere (support names with spaces) (#522) Signed-off-by: Louise Poubel --- CMakeLists.txt | 2 +- examples/worlds/spaces.sdf | 108 ++++++++++++++++++ include/ignition/gazebo/Util.hh | 15 +++ src/LevelManager.cc | 10 +- src/SimulationRunner.cc | 13 ++- src/Util.cc | 22 ++++ src/Util_TEST.cc | 47 +++++++- src/gui/Gui.cc | 33 ++++-- src/gui/GuiRunner.cc | 18 ++- src/gui/plugins/scene3d/Scene3D.cc | 17 +++ .../apply_joint_force/ApplyJointForce.cc | 12 +- .../battery_plugin/LinearBatteryPlugin.cc | 31 ++++- src/systems/breadcrumbs/Breadcrumbs.cc | 13 ++- .../CameraVideoRecorder.cc | 29 ++++- .../detachable_joint/DetachableJoint.cc | 16 ++- src/systems/diff_drive/DiffDrive.cc | 21 +++- .../joint_controller/JointController.cc | 11 +- .../JointPositionController.cc | 12 +- src/systems/log/LogRecord.cc | 24 +++- .../LogicalAudioSensorPlugin.cc | 13 ++- .../MulticopterVelocityControl.cc | 25 +++- .../MulticopterMotorModel.cc | 10 +- .../scene_broadcaster/SceneBroadcaster.cc | 16 ++- src/systems/touch_plugin/TouchPlugin.cc | 9 +- .../triggered_publisher/TriggeredPublisher.cc | 10 +- src/systems/user_commands/UserCommands.cc | 18 ++- .../velocity_control/VelocityControl.cc | 10 +- src/systems/wind_effects/WindEffects.cc | 12 +- 28 files changed, 498 insertions(+), 79 deletions(-) create mode 100644 examples/worlds/spaces.sdf diff --git a/CMakeLists.txt b/CMakeLists.txt index ce24763e77..4d4f036811 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,7 @@ set(IGN_FUEL_TOOLS_VER ${ignition-fuel_tools5_VERSION_MAJOR}) #-------------------------------------- # Find ignition-gui -ign_find_package(ignition-gui4 REQUIRED VERSION 4.1) +ign_find_package(ignition-gui4 REQUIRED VERSION 4.1.1) set(IGN_GUI_VER ${ignition-gui4_VERSION_MAJOR}) ign_find_package (Qt5 COMPONENTS diff --git a/examples/worlds/spaces.sdf b/examples/worlds/spaces.sdf new file mode 100644 index 0000000000..0109d50943 --- /dev/null +++ b/examples/worlds/spaces.sdf @@ -0,0 +1,108 @@ + + + + + + 0.001 + 1.0 + + + + + + + + + + 1.0 1.0 1.0 + 0.8 0.8 0.8 + + + + true + 0 0 10 0 0 0 + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + + 1000 + 0.9 + 0.01 + 0.001 + + -0.5 0.1 -0.9 + + + + true + + + + + 0 0 1 + + + + + + + 0 0 1 + 100 100 + + + + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + + + + + + + 0 0 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 1 0 0 1 + 1 0 0 1 + 1 0 0 1 + + + + + + diff --git a/include/ignition/gazebo/Util.hh b/include/ignition/gazebo/Util.hh index 39d790f7f9..3e9b9c8fec 100644 --- a/include/ignition/gazebo/Util.hh +++ b/include/ignition/gazebo/Util.hh @@ -139,6 +139,21 @@ namespace ignition const Entity &_entity, const EntityComponentManager &_ecm); + /// \brief Helper function to generate a valid transport topic, given + /// a list of topics ordered by preference. The generated topic will be, + /// in this order: + /// + /// 1. The first topic unchanged, if valid. + /// 2. A valid version of the first topic, if possible. + /// 3. The second topic unchanged, if valid. + /// 4. A valid version of the second topic, if possible. + /// 5. ... + /// 6. If no valid topics could be generated, return an empty string. + /// + /// \param[in] _topics Topics ordered by preference. + std::string IGNITION_GAZEBO_VISIBLE validTopic( + const std::vector &_topics); + /// \brief Environment variable holding resource paths. const std::string kResourcePathEnv{"IGN_GAZEBO_RESOURCE_PATH"}; diff --git a/src/LevelManager.cc b/src/LevelManager.cc index d3dcbec7d0..1b54f01848 100644 --- a/src/LevelManager.cc +++ b/src/LevelManager.cc @@ -76,8 +76,14 @@ LevelManager::LevelManager(SimulationRunner *_runner, const bool _useLevels) this->ReadLevelPerformerInfo(); this->CreatePerformers(); - std::string service = "/world/"; - service += this->runner->sdfWorld->Name() + "/level/set_performer"; + std::string service = transport::TopicUtils::AsValidTopic("/world/" + + this->runner->sdfWorld->Name() + "/level/set_performer"); + if (service.empty()) + { + ignerr << "Failed to generate set_performer topic for world [" + << this->runner->sdfWorld->Name() << "]" << std::endl; + return; + } this->node.Advertise(service, &LevelManager::OnSetPerformer, this); } diff --git a/src/SimulationRunner.cc b/src/SimulationRunner.cc index 9777417c33..e047ffaa39 100644 --- a/src/SimulationRunner.cc +++ b/src/SimulationRunner.cc @@ -157,15 +157,20 @@ SimulationRunner::SimulationRunner(const sdf::World *_world, // World control transport::NodeOptions opts; + std::string ns{"/world/" + this->worldName}; if (this->networkMgr) { - opts.SetNameSpace(this->networkMgr->Namespace() + - "/world/" + this->worldName); + ns = this->networkMgr->Namespace() + ns; } - else + + auto validNs = transport::TopicUtils::AsValidTopic(ns); + if (validNs.empty()) { - opts.SetNameSpace("/world/" + this->worldName); + ignerr << "Invalid namespace [" << ns + << "], not initializing runner transport." << std::endl; + return; } + opts.SetNameSpace(validNs); this->node = std::make_unique(opts); diff --git a/src/Util.cc b/src/Util.cc index 5b5995ae9e..828483d0f0 100644 --- a/src/Util.cc +++ b/src/Util.cc @@ -24,6 +24,7 @@ #endif #include #include +#include #include "ignition/gazebo/components/Actor.hh" #include "ignition/gazebo/components/Collision.hh" @@ -416,6 +417,27 @@ ignition::gazebo::Entity topLevelModel(const Entity &_entity, } return entity; } + +////////////////////////////////////////////////// +std::string validTopic(const std::vector &_topics) +{ + for (const auto &topic : _topics) + { + auto validTopic = transport::TopicUtils::AsValidTopic(topic); + if (validTopic.empty()) + { + ignerr << "Topic [" << topic << "] is invalid, ignoring." << std::endl; + continue; + } + if (validTopic != topic) + { + igndbg << "Topic [" << topic << "] changed to valid topic [" + << validTopic << "]" << std::endl; + } + return validTopic; + } + return std::string(); +} } } } diff --git a/src/Util_TEST.cc b/src/Util_TEST.cc index 501fdb5939..d699b3dc3b 100644 --- a/src/Util_TEST.cc +++ b/src/Util_TEST.cc @@ -16,6 +16,7 @@ */ #include +#include #include #include @@ -36,8 +37,18 @@ using namespace ignition; using namespace gazebo; +/// \brief Tests for Util.hh +class UtilTest : public ::testing::Test +{ + // Documentation inherited + protected: void SetUp() override + { + common::Console::SetVerbosity(4); + } +}; + ///////////////////////////////////////////////// -TEST(UtilTest, ScopedName) +TEST_F(UtilTest, ScopedName) { EntityComponentManager ecm; @@ -219,7 +230,7 @@ TEST(UtilTest, ScopedName) } ///////////////////////////////////////////////// -TEST(UtilTest, EntityTypeId) +TEST_F(UtilTest, EntityTypeId) { EntityComponentManager ecm; @@ -264,7 +275,7 @@ TEST(UtilTest, EntityTypeId) } ///////////////////////////////////////////////// -TEST(UtilTest, EntityTypeStr) +TEST_F(UtilTest, EntityTypeStr) { EntityComponentManager ecm; @@ -309,7 +320,7 @@ TEST(UtilTest, EntityTypeStr) } ///////////////////////////////////////////////// -TEST(UtilTest, RemoveParentScopedName) +TEST_F(UtilTest, RemoveParentScopedName) { EXPECT_EQ(removeParentScope("world/world_name", "/"), "world_name"); EXPECT_EQ(removeParentScope("world::world_name::light::lightA_name", "::"), @@ -324,7 +335,7 @@ TEST(UtilTest, RemoveParentScopedName) } ///////////////////////////////////////////////// -TEST(UtilTest, AsFullPath) +TEST_F(UtilTest, AsFullPath) { const std::string relativeUriUnix{"meshes/collision.dae"}; const std::string relativeUriWindows{"meshes\\collision.dae"}; @@ -408,7 +419,7 @@ TEST(UtilTest, AsFullPath) } ///////////////////////////////////////////////// -TEST(UtilTest, TopLevelModel) +TEST_F(UtilTest, TopLevelModel) { EntityComponentManager ecm; @@ -463,3 +474,27 @@ TEST(UtilTest, TopLevelModel) // model C should have itself as the top level entity EXPECT_EQ(modelCEntity, topLevelModel(modelCEntity, ecm)); } + +///////////////////////////////////////////////// +TEST_F(UtilTest, ValidTopic) +{ + std::string good{"good"}; + std::string fixable{"not bad~"}; + std::string invalid{"@~@~@~"}; + + EXPECT_EQ("good", validTopic({good})); + EXPECT_EQ("not_bad", validTopic({fixable})); + EXPECT_EQ("", validTopic({invalid})); + + EXPECT_EQ("good", validTopic({good, fixable})); + EXPECT_EQ("not_bad", validTopic({fixable, good})); + + EXPECT_EQ("good", validTopic({good, invalid})); + EXPECT_EQ("good", validTopic({invalid, good})); + + EXPECT_EQ("not_bad", validTopic({fixable, invalid})); + EXPECT_EQ("not_bad", validTopic({invalid, fixable})); + + EXPECT_EQ("not_bad", validTopic({fixable, invalid, good})); + EXPECT_EQ("good", validTopic({invalid, good, fixable})); +} diff --git a/src/gui/Gui.cc b/src/gui/Gui.cc index b375e912fe..1c4912a6d2 100644 --- a/src/gui/Gui.cc +++ b/src/gui/Gui.cc @@ -194,18 +194,31 @@ std::unique_ptr createGui( // Request GUI info for each world result = false; - service = std::string("/world/" + worldName + "/gui/info"); - - igndbg << "Requesting GUI from [" << service << "]..." << std::endl; - - // Request and block ignition::msgs::GUI res; - executed = node.Request(service, timeout, res, result); + service = transport::TopicUtils::AsValidTopic("/world/" + worldName + + "/gui/info"); + if (service.empty()) + { + ignerr << "Failed to generate valid service for world [" << worldName + << "]" << std::endl; + } + else + { + igndbg << "Requesting GUI from [" << service << "]..." << std::endl; - if (!executed) - ignerr << "Service call timed out for [" << service << "]" << std::endl; - else if (!result) - ignerr << "Service call failed for [" << service << "]" << std::endl; + // Request and block + executed = node.Request(service, timeout, res, result); + + if (!executed) + { + ignerr << "Service call timed out for [" << service << "]" + << std::endl; + } + else if (!result) + { + ignerr << "Service call failed for [" << service << "]" << std::endl; + } + } // GUI runner auto runner = new ignition::gazebo::GuiRunner(worldName); diff --git a/src/gui/GuiRunner.cc b/src/gui/GuiRunner.cc index 565838a06c..6f88a441fe 100644 --- a/src/gui/GuiRunner.cc +++ b/src/gui/GuiRunner.cc @@ -40,7 +40,14 @@ GuiRunner::GuiRunner(const std::string &_worldName) winWorldNames.append(QString::fromStdString(_worldName)); win->setProperty("worldNames", winWorldNames); - this->stateTopic = "/world/" + _worldName + "/state"; + this->stateTopic = transport::TopicUtils::AsValidTopic("/world/" + + _worldName + "/state"); + if (this->stateTopic.empty()) + { + ignerr << "Failed to generate valid topic for world [" << _worldName << "]" + << std::endl; + return; + } common::addFindFileURICallback([] (common::URI _uri) { @@ -63,6 +70,15 @@ void GuiRunner::RequestState() std::string id = std::to_string(gui::App()->applicationPid()); std::string reqSrv = this->node.Options().NameSpace() + "/" + id + "/state_async"; + auto reqSrvValid = transport::TopicUtils::AsValidTopic(reqSrv); + if (reqSrvValid.empty()) + { + ignerr << "Failed to generate valid service [" << reqSrv << "]" + << std::endl; + return; + } + reqSrv = reqSrvValid; + this->node.Advertise(reqSrv, &GuiRunner::OnStateAsyncService, this); ignition::msgs::StringMsg req; req.set_data(reqSrv); diff --git a/src/gui/plugins/scene3d/Scene3D.cc b/src/gui/plugins/scene3d/Scene3D.cc index 2e51b6f440..a91f4821c4 100644 --- a/src/gui/plugins/scene3d/Scene3D.cc +++ b/src/gui/plugins/scene3d/Scene3D.cc @@ -1029,6 +1029,15 @@ void IgnRenderer::HandleModelPlacement() this->dataPtr->createCmdService = "/world/" + this->worldName + "/create"; } + this->dataPtr->createCmdService = transport::TopicUtils::AsValidTopic( + this->dataPtr->createCmdService); + if (this->dataPtr->createCmdService.empty()) + { + ignerr << "Failed to create valid create command service for world [" + << this->worldName <<"]" << std::endl; + return; + } + this->dataPtr->node.Request(this->dataPtr->createCmdService, req, cb); this->dataPtr->isPlacing = false; this->dataPtr->mouseDirty = false; @@ -1247,6 +1256,14 @@ void IgnRenderer::HandleMouseTransformControl() this->dataPtr->poseCmdService = "/world/" + this->worldName + "/set_pose"; } + this->dataPtr->poseCmdService = transport::TopicUtils::AsValidTopic( + this->dataPtr->poseCmdService); + if (this->dataPtr->poseCmdService.empty()) + { + ignerr << "Failed to create valid pose command service for world [" + << this->worldName <<"]" << std::endl; + return; + } this->dataPtr->node.Request(this->dataPtr->poseCmdService, req, cb); } diff --git a/src/systems/apply_joint_force/ApplyJointForce.cc b/src/systems/apply_joint_force/ApplyJointForce.cc index 582d46e101..042f585565 100644 --- a/src/systems/apply_joint_force/ApplyJointForce.cc +++ b/src/systems/apply_joint_force/ApplyJointForce.cc @@ -25,6 +25,7 @@ #include "ignition/gazebo/components/JointForceCmd.hh" #include "ignition/gazebo/Model.hh" +#include "ignition/gazebo/Util.hh" using namespace ignition; using namespace gazebo; @@ -93,8 +94,15 @@ void ApplyJointForce::Configure(const Entity &_entity, } // Subscribe to commands - std::string topic{"/model/" + this->dataPtr->model.Name(_ecm) + "/joint/" + - this->dataPtr->jointName + "/cmd_force"}; + auto topic = transport::TopicUtils::AsValidTopic("/model/" + + this->dataPtr->model.Name(_ecm) + "/joint/" + this->dataPtr->jointName + + "/cmd_force"); + if (topic.empty()) + { + ignerr << "Failed to create valid topic for [" << this->dataPtr->jointName + << "]" << std::endl; + return; + } this->dataPtr->node.Subscribe(topic, &ApplyJointForcePrivate::OnCmdForce, this->dataPtr.get()); diff --git a/src/systems/battery_plugin/LinearBatteryPlugin.cc b/src/systems/battery_plugin/LinearBatteryPlugin.cc index 47530eff67..8c8f94b32d 100644 --- a/src/systems/battery_plugin/LinearBatteryPlugin.cc +++ b/src/systems/battery_plugin/LinearBatteryPlugin.cc @@ -297,16 +297,28 @@ void LinearBatteryPlugin::Configure(const Entity &_entity, "/battery/" + _sdf->Get("battery_name") + "/recharge/stop"; - this->dataPtr->node.Advertise(enableRechargeTopic, + auto validEnableRechargeTopic = transport::TopicUtils::AsValidTopic( + enableRechargeTopic); + auto validDisableRechargeTopic = transport::TopicUtils::AsValidTopic( + disableRechargeTopic); + if (validEnableRechargeTopic.empty() || validDisableRechargeTopic.empty()) + { + ignerr << "Failed to create valid topics. Not valid: [" + << enableRechargeTopic << "] and [" << disableRechargeTopic + << "]" << std::endl; + return; + } + + this->dataPtr->node.Advertise(validEnableRechargeTopic, &LinearBatteryPluginPrivate::OnEnableRecharge, this->dataPtr.get()); - this->dataPtr->node.Advertise(disableRechargeTopic, + this->dataPtr->node.Advertise(validDisableRechargeTopic, &LinearBatteryPluginPrivate::OnDisableRecharge, this->dataPtr.get()); if (_sdf->HasElement("recharge_by_topic")) { - this->dataPtr->node.Subscribe(enableRechargeTopic, + this->dataPtr->node.Subscribe(validEnableRechargeTopic, &LinearBatteryPluginPrivate::OnEnableRecharge, this->dataPtr.get()); - this->dataPtr->node.Subscribe(disableRechargeTopic, + this->dataPtr->node.Subscribe(validDisableRechargeTopic, &LinearBatteryPluginPrivate::OnDisableRecharge, this->dataPtr.get()); } } @@ -338,10 +350,19 @@ void LinearBatteryPlugin::Configure(const Entity &_entity, // Setup battery state topic std::string stateTopic{"/model/" + this->dataPtr->model.Name(_ecm) + "/battery/" + this->dataPtr->battery->Name() + "/state"}; + + auto validStateTopic = transport::TopicUtils::AsValidTopic(stateTopic); + if (validStateTopic.empty()) + { + ignerr << "Failed to create valid state topic [" + << stateTopic << "]" << std::endl; + return; + } + transport::AdvertiseMessageOptions opts; opts.SetMsgsPerSec(50); this->dataPtr->statePub = this->dataPtr->node.Advertise( - stateTopic, opts); + validStateTopic, opts); } ///////////////////////////////////////////////// diff --git a/src/systems/breadcrumbs/Breadcrumbs.cc b/src/systems/breadcrumbs/Breadcrumbs.cc index a06789d845..ba98482007 100644 --- a/src/systems/breadcrumbs/Breadcrumbs.cc +++ b/src/systems/breadcrumbs/Breadcrumbs.cc @@ -42,6 +42,7 @@ #include "ignition/gazebo/components/Performer.hh" #include "ignition/gazebo/components/Pose.hh" #include "ignition/gazebo/components/World.hh" +#include "ignition/gazebo/Util.hh" using namespace ignition; using namespace gazebo; @@ -132,11 +133,15 @@ void Breadcrumbs::Configure(const Entity &_entity, } // Subscribe to commands - std::string topic{"/model/" + this->model.Name(_ecm) + "/breadcrumbs/" + - this->modelRoot.ModelByIndex(0)->Name() + "/deploy"}; - + std::vector topics; if (_sdf->HasElement("topic")) - topic = _sdf->Get("topic"); + { + topics.push_back(_sdf->Get("topic")); + } + topics.push_back("/model/" + + this->model.Name(_ecm) + "/breadcrumbs/" + + this->modelRoot.ModelByIndex(0)->Name() + "/deploy"); + auto topic = validTopic(topics); this->node.Subscribe(topic, &Breadcrumbs::OnDeploy, this); this->remainingPub = this->node.Advertise(topic + "/remaining"); diff --git a/src/systems/camera_video_recorder/CameraVideoRecorder.cc b/src/systems/camera_video_recorder/CameraVideoRecorder.cc index 52c24ad0f9..cadc7b4b3d 100644 --- a/src/systems/camera_video_recorder/CameraVideoRecorder.cc +++ b/src/systems/camera_video_recorder/CameraVideoRecorder.cc @@ -200,7 +200,13 @@ void CameraVideoRecorder::Configure( // video recorder service topic name if (_sdf->HasElement("service")) { - this->dataPtr->service = _sdf->Get("service"); + this->dataPtr->service = transport::TopicUtils::AsValidTopic( + _sdf->Get("service")); + if (this->dataPtr->service.empty()) + { + ignerr << "Service [" << _sdf->Get("service") + << "] not valid. Ignoring." << std::endl; + } } this->dataPtr->eventMgr = &_eventMgr; @@ -208,7 +214,15 @@ void CameraVideoRecorder::Configure( sdf::Sensor sensorSdf = cameraEntComp->Data(); std::string topic = sensorSdf.Topic(); if (topic.empty()) - topic = scopedName(_entity, _ecm) + "/image"; + { + auto scoped = scopedName(_entity, _ecm); + topic = transport::TopicUtils::AsValidTopic(scoped + "/image"); + if (topic.empty()) + { + ignerr << "Failed to generate valid topic for entity [" << scoped + << "]" << std::endl; + } + } this->dataPtr->sensorTopic = topic; } @@ -363,8 +377,15 @@ void CameraVideoRecorder::PostUpdate(const UpdateInfo &, if (this->dataPtr->service.empty()) { - this->dataPtr->service = scopedName(this->dataPtr->entity, _ecm) + - "/record_video"; + auto scoped = scopedName(this->dataPtr->entity, _ecm); + this->dataPtr->service = transport::TopicUtils::AsValidTopic(scoped + + "/record_video"); + if (this->dataPtr->service.empty()) + { + ignerr << "Failed to create valid service for [" << scoped << "]" + << std::endl; + } + return; } this->dataPtr->node.Advertise(this->dataPtr->service, diff --git a/src/systems/detachable_joint/DetachableJoint.cc b/src/systems/detachable_joint/DetachableJoint.cc index 1ed2125a1f..0946aa9505 100644 --- a/src/systems/detachable_joint/DetachableJoint.cc +++ b/src/systems/detachable_joint/DetachableJoint.cc @@ -15,6 +15,8 @@ * */ +#include + #include #include @@ -22,13 +24,14 @@ #include -#include "ignition/gazebo/Model.hh" #include "ignition/gazebo/components/DetachableJoint.hh" #include "ignition/gazebo/components/Link.hh" #include "ignition/gazebo/components/Model.hh" #include "ignition/gazebo/components/Name.hh" #include "ignition/gazebo/components/ParentEntity.hh" #include "ignition/gazebo/components/Pose.hh" +#include "ignition/gazebo/Model.hh" +#include "ignition/gazebo/Util.hh" #include "DetachableJoint.hh" @@ -93,9 +96,14 @@ void DetachableJoint::Configure(const Entity &_entity, } // Setup detach topic - std::string defaultTopic{"/model/" + this->model.Name(_ecm) + - "/detachable_joint/detach"}; - this->topic = _sdf->Get("topic", defaultTopic).first; + std::vector topics; + if (_sdf->HasElement("topic")) + { + topics.push_back(_sdf->Get("topic")); + } + topics.push_back("/model/" + this->model.Name(_ecm) + + "/detachable_joint/detach"); + this->topic = validTopic(topics); this->suppressChildWarning = _sdf->Get("suppress_child_warning", this->suppressChildWarning) diff --git a/src/systems/diff_drive/DiffDrive.cc b/src/systems/diff_drive/DiffDrive.cc index dfd78b7769..1686beebcd 100644 --- a/src/systems/diff_drive/DiffDrive.cc +++ b/src/systems/diff_drive/DiffDrive.cc @@ -36,6 +36,7 @@ #include "ignition/gazebo/components/JointVelocityCmd.hh" #include "ignition/gazebo/Link.hh" #include "ignition/gazebo/Model.hh" +#include "ignition/gazebo/Util.hh" #include "SpeedLimiter.hh" @@ -253,16 +254,26 @@ void DiffDrive::Configure(const Entity &_entity, this->dataPtr->wheelRadius, this->dataPtr->wheelRadius); // Subscribe to commands - std::string topic{"/model/" + this->dataPtr->model.Name(_ecm) + "/cmd_vel"}; + std::vector topics; if (_sdf->HasElement("topic")) - topic = _sdf->Get("topic"); + { + topics.push_back(_sdf->Get("topic")); + } + topics.push_back("/model/" + this->dataPtr->model.Name(_ecm) + "/cmd_vel"); + auto topic = validTopic(topics); + this->dataPtr->node.Subscribe(topic, &DiffDrivePrivate::OnCmdVel, this->dataPtr.get()); - std::string odomTopic{"/model/" + this->dataPtr->model.Name(_ecm) + - "/odometry"}; + std::vector odomTopics; if (_sdf->HasElement("odom_topic")) - odomTopic = _sdf->Get("odom_topic"); + { + odomTopics.push_back(_sdf->Get("odom_topic")); + } + odomTopics.push_back("/model/" + this->dataPtr->model.Name(_ecm) + + "/odometry"); + auto odomTopic = validTopic(odomTopics); + this->dataPtr->odomPub = this->dataPtr->node.Advertise( odomTopic); diff --git a/src/systems/joint_controller/JointController.cc b/src/systems/joint_controller/JointController.cc index 42990d4447..dd2f645f1d 100644 --- a/src/systems/joint_controller/JointController.cc +++ b/src/systems/joint_controller/JointController.cc @@ -131,8 +131,15 @@ void JointController::Configure(const Entity &_entity, } // Subscribe to commands - std::string topic{"/model/" + this->dataPtr->model.Name(_ecm) + "/joint/" + - this->dataPtr->jointName + "/cmd_vel"}; + std::string topic = transport::TopicUtils::AsValidTopic("/model/" + + this->dataPtr->model.Name(_ecm) + "/joint/" + this->dataPtr->jointName + + "/cmd_vel"); + if (topic.empty()) + { + ignerr << "Failed to create topic for joint [" << this->dataPtr->jointName + << "]" << std::endl; + return; + } this->dataPtr->node.Subscribe(topic, &JointControllerPrivate::OnCmdVel, this->dataPtr.get()); diff --git a/src/systems/joint_position_controller/JointPositionController.cc b/src/systems/joint_position_controller/JointPositionController.cc index c048ff0e23..5eaefaf45e 100644 --- a/src/systems/joint_position_controller/JointPositionController.cc +++ b/src/systems/joint_position_controller/JointPositionController.cc @@ -147,9 +147,15 @@ void JointPositionController::Configure(const Entity &_entity, this->dataPtr->posPid.Init(p, i, d, iMax, iMin, cmdMax, cmdMin, cmdOffset); // Subscribe to commands - std::string topic{"/model/" + this->dataPtr->model.Name(_ecm) + - "/joint/" + this->dataPtr->jointName + "/" + - std::to_string(this->dataPtr->jointIndex) + "/cmd_pos"}; + std::string topic = transport::TopicUtils::AsValidTopic("/model/" + + this->dataPtr->model.Name(_ecm) + "/joint/" + this->dataPtr->jointName + + "/" + std::to_string(this->dataPtr->jointIndex) + "/cmd_pos"); + if (topic.empty()) + { + ignerr << "Failed to create topic for joint [" << this->dataPtr->jointName + << "]" << std::endl; + return; + } this->dataPtr->node.Subscribe( topic, &JointPositionControllerPrivate::OnCmdPos, this->dataPtr.get()); diff --git a/src/systems/log/LogRecord.cc b/src/systems/log/LogRecord.cc index 233d7049a9..3febe2343a 100644 --- a/src/systems/log/LogRecord.cc +++ b/src/systems/log/LogRecord.cc @@ -291,11 +291,31 @@ bool LogRecordPrivate::Start(const std::string &_logPath, // Use directory basename as topic name, to be able to retrieve at playback std::string sdfTopic = "/" + common::basename(this->logPath) + "/sdf"; - this->sdfPub = this->node.Advertise(sdfTopic, this->sdfMsg.GetTypeName()); + auto validSdfTopic = transport::TopicUtils::AsValidTopic(sdfTopic); + if (!validSdfTopic.empty()) + { + this->sdfPub = this->node.Advertise(validSdfTopic, + this->sdfMsg.GetTypeName()); + } + else + { + ignerr << "Failed to generate valid topic to publish SDF. Tried [" + << sdfTopic << "]." << std::endl; + } // TODO(louise) Combine with SceneBroadcaster's state topic std::string stateTopic = "/world/" + this->worldName + "/changed_state"; - this->statePub = this->node.Advertise(stateTopic); + auto validStateTopic = transport::TopicUtils::AsValidTopic(stateTopic); + if (!validStateTopic.empty()) + { + this->statePub = this->node.Advertise( + validStateTopic); + } + else + { + ignerr << "Failed to generate valid topic to publish state. Tried [" + << stateTopic << "]." << std::endl; + } // Append file name std::string dbPath = common::joinPaths(this->logPath, "state.tlog"); diff --git a/src/systems/logical_audio_sensor_plugin/LogicalAudioSensorPlugin.cc b/src/systems/logical_audio_sensor_plugin/LogicalAudioSensorPlugin.cc index 33b9310b2b..78ab1b7d65 100644 --- a/src/systems/logical_audio_sensor_plugin/LogicalAudioSensorPlugin.cc +++ b/src/systems/logical_audio_sensor_plugin/LogicalAudioSensorPlugin.cc @@ -403,14 +403,21 @@ void LogicalAudioSensorPluginPrivate::CreateAudioSource( }; // create services for this source - const auto full_name = scopedName(entity, _ecm); - if (!this->node.Advertise(full_name + "/play", playSrvCb)) + const auto fullName = scopedName(entity, _ecm); + auto validName = transport::TopicUtils::AsValidTopic(fullName); + if (validName.empty()) + { + ignerr << "Failed to create valid topics with entity scoped name [" + << fullName << "]" << std::endl; + return; + } + if (!this->node.Advertise(validName + "/play", playSrvCb)) { ignerr << "Error advertising the play source service for source " << id << " in entity " << _parent << ". " << kSourceSkipMsg; return; } - if (!this->node.Advertise(full_name + "/stop", stopSrvCb)) + if (!this->node.Advertise(validName + "/stop", stopSrvCb)) { ignerr << "Error advertising the stop source service for source " << id << " in entity " << _parent << ". " << kSourceSkipMsg; diff --git a/src/systems/multicopter_control/MulticopterVelocityControl.cc b/src/systems/multicopter_control/MulticopterVelocityControl.cc index edeed38318..9eb4b248b4 100644 --- a/src/systems/multicopter_control/MulticopterVelocityControl.cc +++ b/src/systems/multicopter_control/MulticopterVelocityControl.cc @@ -259,8 +259,15 @@ void MulticopterVelocityControl::Configure(const Entity &_entity, if (sdfClone->HasElement("robotNamespace")) { - this->robotNamespace = - sdfClone->Get("robotNamespace"); + this->robotNamespace = transport::TopicUtils::AsValidTopic( + sdfClone->Get("robotNamespace")); + if (this->robotNamespace.empty()) + { + ignerr << "Robot namespace [" + << sdfClone->Get("robotNamespace") <<"] is invalid." + << std::endl; + return; + } } else { @@ -270,9 +277,23 @@ void MulticopterVelocityControl::Configure(const Entity &_entity, sdfClone->Get("commandSubTopic", this->commandSubTopic, this->commandSubTopic); + this->commandSubTopic = transport::TopicUtils::AsValidTopic( + this->commandSubTopic); + if (this->commandSubTopic.empty()) + { + ignerr << "Invalid command sub-topic." << std::endl; + return; + } sdfClone->Get("enableSubTopic", this->enableSubTopic, this->enableSubTopic); + this->enableSubTopic = transport::TopicUtils::AsValidTopic( + this->enableSubTopic); + if (this->enableSubTopic.empty()) + { + ignerr << "Invalid enable sub-topic." << std::endl; + return; + } // Subscribe to actuator command messages std::string topic{this->robotNamespace + "/" + this->commandSubTopic}; diff --git a/src/systems/multicopter_motor_model/MulticopterMotorModel.cc b/src/systems/multicopter_motor_model/MulticopterMotorModel.cc index 730103d407..dbb5102d00 100644 --- a/src/systems/multicopter_motor_model/MulticopterMotorModel.cc +++ b/src/systems/multicopter_motor_model/MulticopterMotorModel.cc @@ -357,8 +357,14 @@ void MulticopterMotorModel::Configure(const Entity &_entity, this->dataPtr->refMotorInput); // Subscribe to actuator command messages - std::string topic{this->dataPtr->robotNamespace + "/" - + this->dataPtr->commandSubTopic}; + std::string topic = transport::TopicUtils::AsValidTopic( + this->dataPtr->robotNamespace + "/" + this->dataPtr->commandSubTopic); + if (topic.empty()) + { + ignerr << "Failed to create topic for [" << this->dataPtr->robotNamespace + << "]" << std::endl; + return; + } this->dataPtr->node.Subscribe(topic, &MulticopterMotorModelPrivate::OnActuatorMsg, this->dataPtr.get()); } diff --git a/src/systems/scene_broadcaster/SceneBroadcaster.cc b/src/systems/scene_broadcaster/SceneBroadcaster.cc index 576a081bc0..0918c756bb 100644 --- a/src/systems/scene_broadcaster/SceneBroadcaster.cc +++ b/src/systems/scene_broadcaster/SceneBroadcaster.cc @@ -444,8 +444,16 @@ void SceneBroadcasterPrivate::PoseUpdate(const UpdateInfo &_info, ////////////////////////////////////////////////// void SceneBroadcasterPrivate::SetupTransport(const std::string &_worldName) { + auto ns = transport::TopicUtils::AsValidTopic("/world/" + _worldName); + if (ns.empty()) + { + ignerr << "Failed to create valid namespace for world [" << _worldName + << "]" << std::endl; + return; + } + transport::NodeOptions opts; - opts.SetNameSpace("/world/" + _worldName); + opts.SetNameSpace(ns); this->node = std::make_unique(opts); // Scene info service @@ -487,7 +495,7 @@ void SceneBroadcasterPrivate::SetupTransport(const std::string &_worldName) << stateAsyncService << "]" << std::endl; // Scene info topic - std::string sceneTopic{"/world/" + _worldName + "/scene/info"}; + std::string sceneTopic{ns + "/scene/info"}; this->scenePub = this->node->Advertise(sceneTopic); @@ -495,7 +503,7 @@ void SceneBroadcasterPrivate::SetupTransport(const std::string &_worldName) << "]" << std::endl; // Entity deletion publisher - std::string deletionTopic{"/world/" + _worldName + "/scene/deletion"}; + std::string deletionTopic{ns + "/scene/deletion"}; this->deletionPub = this->node->Advertise(deletionTopic); @@ -504,7 +512,7 @@ void SceneBroadcasterPrivate::SetupTransport(const std::string &_worldName) << std::endl; // State topic - std::string stateTopic{"/world/" + _worldName + "/state"}; + std::string stateTopic{ns + "/state"}; this->statePub = this->node->Advertise(stateTopic); diff --git a/src/systems/touch_plugin/TouchPlugin.cc b/src/systems/touch_plugin/TouchPlugin.cc index daa1098aff..ccd177bb90 100644 --- a/src/systems/touch_plugin/TouchPlugin.cc +++ b/src/systems/touch_plugin/TouchPlugin.cc @@ -163,7 +163,14 @@ void TouchPluginPrivate::Load(const EntityComponentManager &_ecm, ignerr << "Missing required parameter " << std::endl; return; } - this->ns = _sdf->Get("namespace"); + this->ns = transport::TopicUtils::AsValidTopic(_sdf->Get( + "namespace")); + if (this->ns.empty()) + { + ignerr << " [" << _sdf->Get("namespace") + << "] is invalid." << std::endl; + return; + } // Target time if (!_sdf->HasElement("time")) diff --git a/src/systems/triggered_publisher/TriggeredPublisher.cc b/src/systems/triggered_publisher/TriggeredPublisher.cc index ddbcb58de7..8fd37edba8 100644 --- a/src/systems/triggered_publisher/TriggeredPublisher.cc +++ b/src/systems/triggered_publisher/TriggeredPublisher.cc @@ -486,10 +486,11 @@ void TriggeredPublisher::Configure(const Entity &, return; } - this->inputTopic = inputElem->Get("topic"); + auto inTopic = inputElem->Get("topic"); + this->inputTopic = transport::TopicUtils::AsValidTopic(inTopic); if (this->inputTopic.empty()) { - ignerr << "Input topic cannot be empty\n"; + ignerr << "Invalid input topic [" << inTopic << "]" << std::endl; return; } @@ -538,10 +539,11 @@ void TriggeredPublisher::Configure(const Entity &, ignerr << "Output message type cannot be empty\n"; continue; } - info.topic = outputElem->Get("topic"); + auto topic = outputElem->Get("topic"); + info.topic = transport::TopicUtils::AsValidTopic(topic); if (info.topic.empty()) { - ignerr << "Output topic cannot be empty\n"; + ignerr << "Invalid topic [" << topic << "]" << std::endl; continue; } const std::string msgStr = outputElem->Get(); diff --git a/src/systems/user_commands/UserCommands.cc b/src/systems/user_commands/UserCommands.cc index 25e1a3ec15..12aa08671a 100644 --- a/src/systems/user_commands/UserCommands.cc +++ b/src/systems/user_commands/UserCommands.cc @@ -223,27 +223,37 @@ void UserCommands::Configure(const Entity &_entity, const components::Name *constCmp = _ecm.Component(_entity); const std::string &worldName = constCmp->Data(); + auto validWorldName = transport::TopicUtils::AsValidTopic(worldName); + if (validWorldName.empty()) + { + ignerr << "World name [" << worldName + << "] doesn't work well with transport, services not advertised." + << std::endl; + return; + } + // Create service - std::string createService{"/world/" + worldName + "/create"}; + std::string createService{"/world/" + validWorldName + "/create"}; this->dataPtr->node.Advertise(createService, &UserCommandsPrivate::CreateService, this->dataPtr.get()); // Create service for EntityFactory_V - std::string createServiceMultiple{"/world/" + worldName + "/create_multiple"}; + std::string createServiceMultiple{"/world/" + validWorldName + + "/create_multiple"}; this->dataPtr->node.Advertise(createServiceMultiple, &UserCommandsPrivate::CreateServiceMultiple, this->dataPtr.get()); ignmsg << "Create service on [" << createService << "]" << std::endl; // Remove service - std::string removeService{"/world/" + worldName + "/remove"}; + std::string removeService{"/world/" + validWorldName + "/remove"}; this->dataPtr->node.Advertise(removeService, &UserCommandsPrivate::RemoveService, this->dataPtr.get()); ignmsg << "Remove service on [" << removeService << "]" << std::endl; // Pose service - std::string poseService{"/world/" + worldName + "/set_pose"}; + std::string poseService{"/world/" + validWorldName + "/set_pose"}; this->dataPtr->node.Advertise(poseService, &UserCommandsPrivate::PoseService, this->dataPtr.get()); diff --git a/src/systems/velocity_control/VelocityControl.cc b/src/systems/velocity_control/VelocityControl.cc index d6e07f48e7..78b1913d67 100644 --- a/src/systems/velocity_control/VelocityControl.cc +++ b/src/systems/velocity_control/VelocityControl.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -26,6 +27,7 @@ #include "ignition/gazebo/components/AngularVelocityCmd.hh" #include "ignition/gazebo/components/LinearVelocityCmd.hh" #include "ignition/gazebo/Model.hh" +#include "ignition/gazebo/Util.hh" #include "VelocityControl.hh" @@ -87,9 +89,13 @@ void VelocityControl::Configure(const Entity &_entity, } // Subscribe to commands - std::string topic{"/model/" + this->dataPtr->model.Name(_ecm) + "/cmd_vel"}; + std::vector topics; if (_sdf->HasElement("topic")) - topic = _sdf->Get("topic"); + { + topics.push_back(_sdf->Get("topic")); + } + topics.push_back("/model/" + this->dataPtr->model.Name(_ecm) + "/cmd_vel"); + auto topic = validTopic(topics); this->dataPtr->node.Subscribe( topic, &VelocityControlPrivate::OnCmdVel, this->dataPtr.get()); diff --git a/src/systems/wind_effects/WindEffects.cc b/src/systems/wind_effects/WindEffects.cc index 11ba62836d..76a70ea0b3 100644 --- a/src/systems/wind_effects/WindEffects.cc +++ b/src/systems/wind_effects/WindEffects.cc @@ -305,12 +305,20 @@ void WindEffectsPrivate::Load(EntityComponentManager &_ecm, ////////////////////////////////////////////////// void WindEffectsPrivate::SetupTransport(const std::string &_worldName) { + auto validWorldName = transport::TopicUtils::AsValidTopic(_worldName); + if (validWorldName.empty()) + { + ignerr << "Failed to setup transport, invalid world name [" << _worldName + << "]" << std::endl; + return; + } + // Wind seed velocity topic - this->node.Subscribe("/world/" + _worldName + "/wind", + this->node.Subscribe("/world/" + validWorldName + "/wind", &WindEffectsPrivate::OnWindMsg, this); // Wind info service - this->node.Advertise("/world/" + _worldName + "/wind_info", + this->node.Advertise("/world/" + validWorldName + "/wind_info", &WindEffectsPrivate::WindInfoService, this); }