ROS2Names.cpp 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/RTTI/RTTI.h>
  9. #include <AzCore/std/string/regex.h>
  10. #include <ROS2/Utilities/ROS2Names.h>
  11. #include <rcl/validate_topic_name.h>
  12. #include <rmw/validate_namespace.h>
  13. namespace ROS2
  14. {
  15. AZStd::string ROS2Names::GetNamespacedName(const AZStd::string& ns, const AZStd::string& name)
  16. {
  17. if (ns.empty())
  18. {
  19. return name;
  20. }
  21. return AZStd::string::format("%s/%s", ns.c_str(), name.c_str());
  22. }
  23. AZStd::string ROS2Names::RosifyName(const AZStd::string& input)
  24. {
  25. AZStd::string rosified = input;
  26. if (input.empty())
  27. {
  28. return rosified;
  29. }
  30. const char underscore = '_';
  31. if (input[0] == underscore)
  32. {
  33. AZ_Warning(
  34. "RosifyName",
  35. false,
  36. "'%s' name starts with an underscore, which makes topic/namespace/parameter hidden by default. Is this intended?",
  37. input.c_str());
  38. }
  39. const AZStd::string stringToReplaceViolations(1, underscore);
  40. const AZStd::regex ros2Disallowedlist("[^0-9|a-z|A-Z|_]");
  41. rosified = AZStd::regex_replace(rosified, ros2Disallowedlist, stringToReplaceViolations);
  42. if (AZStd::isdigit(rosified[0]) || (input[0] != underscore && rosified[0] == underscore))
  43. { // Prepend "o3de_" if it would otherwise start with a number (which would violate ros2 name requirements)
  44. // Also, starting with '_' is not desired unless explicit. Topics/namespaces/parameters starting with "_" are hidden by default.
  45. const AZStd::string prependToNumberStart = "o3de_";
  46. rosified = prependToNumberStart + rosified;
  47. }
  48. if (input != rosified)
  49. {
  50. AZ_TracePrintf(
  51. "RosifyName",
  52. "Name '%s' has been changed to '%s' to conform with ros2 naming restrictions\n",
  53. input.c_str(),
  54. rosified.c_str());
  55. }
  56. return rosified;
  57. }
  58. AZ::Outcome<void, AZStd::string> ROS2Names::ValidateNamespace(const AZStd::string& ros2Namespace)
  59. {
  60. auto ros2GlobalizedNamespace = ros2Namespace;
  61. const char namespacePrefix = '/';
  62. if (!ros2Namespace.starts_with(namespacePrefix))
  63. { // Prepend "/" if not included, this is done automatically by rclcpp so "/"-less namespaces are ok.
  64. ros2GlobalizedNamespace = namespacePrefix + ros2Namespace;
  65. }
  66. int validationResult = 0;
  67. auto ret = rmw_validate_namespace(ros2GlobalizedNamespace.c_str(), &validationResult, NULL);
  68. if (ret != RMW_RET_OK)
  69. {
  70. AZ_Error("ValidateNamespace", false, "Call to rmw validation for namespace failed");
  71. return AZ::Failure(AZStd::string("Unable to validate namespace due to rmw error"));
  72. }
  73. if (validationResult != RMW_NAMESPACE_VALID)
  74. {
  75. return AZ::Failure(AZStd::string(rmw_namespace_validation_result_string(validationResult)));
  76. }
  77. return AZ::Success();
  78. }
  79. AZ::Outcome<void, AZStd::string> ROS2Names::ValidateNamespaceField(void* newValue, const AZ::Uuid& valueType)
  80. {
  81. if (azrtti_typeid<AZStd::string>() != valueType)
  82. {
  83. return AZ::Failure(AZStd::string("Unexpected field type: the only valid input is a character string"));
  84. }
  85. const AZStd::string& ros2Namespace(*reinterpret_cast<const AZStd::string*>(newValue));
  86. return ValidateNamespace(ros2Namespace);
  87. }
  88. AZ::Outcome<void, AZStd::string> ROS2Names::ValidateTopic(const AZStd::string& topic)
  89. {
  90. int validationResult = 0;
  91. [[maybe_unused]] size_t invalidIndex;
  92. if (rcl_validate_topic_name(topic.c_str(), &validationResult, &invalidIndex) != RCL_RET_OK)
  93. {
  94. AZ_Error("ValidateTopic", false, "Call to rcl validation for topic failed");
  95. return AZ::Failure(AZStd::string("Unable to validate topic due to rcl error"));
  96. }
  97. if (RCL_TOPIC_NAME_VALID != validationResult)
  98. {
  99. return AZ::Failure(AZStd::string(rcl_topic_name_validation_result_string(validationResult)));
  100. }
  101. return AZ::Success();
  102. }
  103. AZ::Outcome<void, AZStd::string> ROS2Names::ValidateTopicField(void* newValue, const AZ::Uuid& valueType)
  104. {
  105. if (azrtti_typeid<AZStd::string>() != valueType)
  106. {
  107. return AZ::Failure(AZStd::string("Unexpected field type: the only valid input is a character string"));
  108. }
  109. const AZStd::string& topic(*reinterpret_cast<const AZStd::string*>(newValue));
  110. return ValidateTopic(topic);
  111. }
  112. } // namespace ROS2