|
2 | 2 |
|
3 | 3 | import os |
4 | 4 | import sys |
5 | | -import rospy |
6 | | -import rospkg |
| 5 | +import rclpy |
| 6 | +import rclpy.time |
| 7 | +import ament_index_python |
7 | 8 | import mocha_core.srv |
8 | 9 | import yaml |
9 | 10 | import pdb |
10 | 11 |
|
11 | 12 | class Translator: |
12 | 13 | def __init__(self, this_robot, this_robot_id, |
13 | | - topic_name, topic_id, msg_type): |
| 14 | + topic_name, topic_id, msg_type, node=None): |
| 15 | + # Get the database utils module |
| 16 | + import database_utils as du |
14 | 17 | self.__du = du |
15 | 18 |
|
| 19 | + # Store or create the ROS2 node |
| 20 | + if node is None: |
| 21 | + # Initialize ROS2 if not already done |
| 22 | + if not rclpy.ok(): |
| 23 | + rclpy.init() |
| 24 | + self.__node = rclpy.create_node(f'translator_{this_robot}_{topic_id}') |
| 25 | + self.__node_owned = True |
| 26 | + else: |
| 27 | + self.__node = node |
| 28 | + self.__node_owned = False |
| 29 | + |
| 30 | + self.__logger = self.__node.get_logger() |
| 31 | + |
16 | 32 | self.__counter = 0 |
17 | 33 | self.__topic_name = topic_name |
18 | 34 | self.__topic_id = topic_id |
19 | 35 | self.__this_robot = this_robot |
20 | 36 | self.__this_robot_id = this_robot_id |
21 | | - self.__service_name = "integrate_database/AddUpdateDB" |
22 | | - self.__add_update_db = rospy.ServiceProxy( |
23 | | - self.__service_name, mocha_core.srv.AddUpdateDB |
| 37 | + self.__service_name = "add_update_db" |
| 38 | + |
| 39 | + # Create service client |
| 40 | + self.__add_update_db = self.__node.create_client( |
| 41 | + mocha_core.srv.AddUpdateDB, self.__service_name |
24 | 42 | ) |
25 | 43 |
|
26 | | - rospy.Subscriber( |
27 | | - self.__topic_name, msg_type, self.translator_cb |
| 44 | + # Create subscriber |
| 45 | + self.__subscription = self.__node.create_subscription( |
| 46 | + msg_type, self.__topic_name, self.translator_cb, 10 |
28 | 47 | ) |
29 | 48 |
|
30 | 49 | def translator_cb(self, data): |
31 | 50 | msg = data |
32 | 51 |
|
33 | | - rospy.wait_for_service(self.__service_name) |
| 52 | + # Wait for service to be available |
| 53 | + if not self.__add_update_db.wait_for_service(timeout_sec=5.0): |
| 54 | + self.__logger.error(f"Service {self.__service_name} not available") |
| 55 | + return |
| 56 | + |
34 | 57 | serialized_msg = self.__du.serialize_ros_msg(msg) |
35 | 58 | try: |
36 | | - ts = rospy.get_rostime() |
37 | | - answ = self.__add_update_db(self.__topic_id, |
38 | | - ts, |
39 | | - serialized_msg) |
40 | | - answ_header = answ.new_header |
41 | | - rospy.logdebug(f"{self.__this_robot} - Header insert " + |
42 | | - f"- {self.__topic_name} - {answ_header}") |
43 | | - self.__counter += 1 |
44 | | - except rospy.ServiceException as exc: |
45 | | - rospy.logerr(f"Service did not process request: {exc}") |
| 59 | + # Get current time |
| 60 | + ts = self.__node.get_clock().now().to_msg() |
| 61 | + |
| 62 | + # Create service request |
| 63 | + req = mocha_core.srv.AddUpdateDB.Request() |
| 64 | + req.topic_id = self.__topic_id |
| 65 | + req.timestamp = ts |
| 66 | + req.msg_content = serialized_msg |
| 67 | + |
| 68 | + # Call service asynchronously |
| 69 | + future = self.__add_update_db.call_async(req) |
| 70 | + rclpy.spin_until_future_complete(self.__node, future, timeout_sec=2.0) |
| 71 | + |
| 72 | + if future.done(): |
| 73 | + answ = future.result() |
| 74 | + answ_header = answ.new_header |
| 75 | + self.__logger.debug(f"{self.__this_robot} - Header insert " + |
| 76 | + f"- {self.__topic_name} - {answ_header}") |
| 77 | + self.__counter += 1 |
| 78 | + else: |
| 79 | + self.__logger.warning(f"Service call timed out for {self.__topic_name}") |
| 80 | + |
| 81 | + except Exception as exc: |
| 82 | + self.__logger.error(f"Service did not process request: {exc}") |
| 83 | + |
| 84 | + def shutdown(self): |
| 85 | + """Cleanup method for shutting down the translator""" |
| 86 | + if self.__node_owned and self.__node is not None: |
| 87 | + self.__node.destroy_node() |
| 88 | + if rclpy.ok(): |
| 89 | + rclpy.shutdown() |
46 | 90 |
|
47 | 91 |
|
48 | | -if __name__ == "__main__": |
49 | | - rospy.init_node("topic_translator", anonymous=False) |
| 92 | +def create_translator_node(robot_name, robot_configs_path=None, topic_configs_path=None, node=None): |
| 93 | + """ |
| 94 | + Create and run translator node for a given robot. |
| 95 | + Returns the node for cleanup. |
| 96 | + """ |
| 97 | + # Use provided node or create new one |
| 98 | + if node is None: |
| 99 | + # Initialize ROS2 if not already done |
| 100 | + if not rclpy.ok(): |
| 101 | + rclpy.init() |
| 102 | + node = rclpy.create_node(f"translator_{robot_name}") |
| 103 | + node_owned = True |
| 104 | + else: |
| 105 | + node_owned = False |
| 106 | + |
| 107 | + logger = node.get_logger() |
50 | 108 |
|
51 | | - # Get the mocha_core path |
52 | | - rospack = rospkg.RosPack() |
53 | | - ddb_path = rospack.get_path('mocha_core') |
54 | | - scripts_path = os.path.join(ddb_path, "scripts/core") |
55 | | - sys.path.append(scripts_path) |
| 109 | + # Get the mocha_core path using ament |
| 110 | + try: |
| 111 | + from ament_index_python.packages import get_package_share_directory |
| 112 | + ddb_path = get_package_share_directory('mocha_core') |
| 113 | + except: |
| 114 | + # Fallback for development |
| 115 | + current_dir = os.path.dirname(os.path.abspath(__file__)) |
| 116 | + ddb_path = os.path.join(current_dir, "..") |
| 117 | + |
| 118 | + # Add path for database_utils |
| 119 | + sys.path.append(os.path.join(ddb_path, ".")) |
56 | 120 | import database_utils as du |
57 | 121 |
|
58 | | - # Get robot from the parameters |
59 | | - this_robot = rospy.get_param("~robot_name") |
60 | | - |
61 | 122 | # Get the robot_config path and generate the robot_configs object |
62 | | - robot_configs_default = os.path.join(ddb_path, |
63 | | - "config/testConfigs/robot_configs.yaml") |
64 | | - robot_configs_path = rospy.get_param("~robot_configs", robot_configs_default) |
| 123 | + if robot_configs_path is None: |
| 124 | + robot_configs_path = os.path.join(ddb_path, "config", "testConfigs", "robot_configs.yaml") |
| 125 | + |
65 | 126 | with open(robot_configs_path, 'r') as f: |
66 | 127 | robot_configs = yaml.load(f, Loader=yaml.FullLoader) |
67 | | - if this_robot not in robot_configs.keys(): |
68 | | - rospy.logerr(f"Robot {this_robot} not in robot_configs") |
69 | | - rospy.signal_shutdown("Robot not in robot_configs") |
70 | | - rospy.spin() |
| 128 | + if robot_name not in robot_configs.keys(): |
| 129 | + logger.error(f"Robot {robot_name} not in robot_configs") |
| 130 | + if node_owned: |
| 131 | + rclpy.shutdown() |
| 132 | + raise ValueError(f"Robot {robot_name} not found in config") |
71 | 133 |
|
72 | 134 | # Get the topic_configs path and generate the topic_configs object |
73 | | - topic_configs_default = os.path.join(ddb_path, |
74 | | - "config/testConfigs/topic_configs.yaml") |
75 | | - topic_configs_path = rospy.get_param("~topic_configs", |
76 | | - topic_configs_default) |
| 135 | + if topic_configs_path is None: |
| 136 | + topic_configs_path = os.path.join(ddb_path, "config", "testConfigs", "topic_configs.yaml") |
| 137 | + |
77 | 138 | with open(topic_configs_path, 'r') as f: |
78 | 139 | topic_configs = yaml.load(f, Loader=yaml.FullLoader) |
79 | | - this_robot_topics = topic_configs[robot_configs[this_robot]["node-type"]] |
| 140 | + this_robot_topics = topic_configs[robot_configs[robot_name]["node-type"]] |
80 | 141 |
|
81 | 142 | # Get msg_types dict used to publish the topics |
82 | 143 | msg_types = du.msg_types(robot_configs, topic_configs) |
83 | 144 |
|
| 145 | + # Create translators for each topic |
| 146 | + translators = [] |
84 | 147 | for topic_id, topic in enumerate(this_robot_topics): |
85 | 148 | # Get robot id |
86 | | - robot_id = du.get_robot_id_from_name(robot_configs, this_robot) |
| 149 | + robot_id = du.get_robot_id_from_name(robot_configs, robot_name) |
87 | 150 | obj = msg_types[robot_id][topic_id]["obj"] |
88 | | - Translator(this_robot, |
89 | | - robot_id, |
90 | | - topic["msg_topic"], |
91 | | - topic_id, |
92 | | - obj) |
93 | | - rospy.spin() |
| 151 | + translator = Translator(robot_name, |
| 152 | + robot_id, |
| 153 | + topic["msg_topic"], |
| 154 | + topic_id, |
| 155 | + obj, |
| 156 | + node=node) |
| 157 | + translators.append(translator) |
| 158 | + |
| 159 | + return node |
| 160 | + |
| 161 | + |
| 162 | +if __name__ == "__main__": |
| 163 | + # Initialize ROS2 |
| 164 | + rclpy.init() |
| 165 | + |
| 166 | + # Create main node |
| 167 | + node = rclpy.create_node("topic_translator") |
| 168 | + logger = node.get_logger() |
| 169 | + |
| 170 | + # Declare parameters with defaults |
| 171 | + node.declare_parameter("robot_name", "") |
| 172 | + node.declare_parameter("robot_configs", "") |
| 173 | + node.declare_parameter("topic_configs", "") |
| 174 | + |
| 175 | + # Get robot from the parameters |
| 176 | + this_robot = node.get_parameter("robot_name").get_parameter_value().string_value |
| 177 | + if not this_robot: |
| 178 | + logger.error("robot_name parameter is required") |
| 179 | + rclpy.shutdown() |
| 180 | + sys.exit(1) |
| 181 | + |
| 182 | + # Get config file paths from parameters |
| 183 | + robot_configs_path = node.get_parameter("robot_configs").get_parameter_value().string_value |
| 184 | + topic_configs_path = node.get_parameter("topic_configs").get_parameter_value().string_value |
| 185 | + |
| 186 | + # Use empty strings as None for the function |
| 187 | + robot_configs_path = robot_configs_path if robot_configs_path else None |
| 188 | + topic_configs_path = topic_configs_path if topic_configs_path else None |
| 189 | + |
| 190 | + try: |
| 191 | + # Create translator node and translators |
| 192 | + create_translator_node(this_robot, robot_configs_path, topic_configs_path, node) |
| 193 | + |
| 194 | + # Spin the node |
| 195 | + rclpy.spin(node) |
| 196 | + except KeyboardInterrupt: |
| 197 | + pass |
| 198 | + finally: |
| 199 | + node.destroy_node() |
| 200 | + rclpy.shutdown() |
0 commit comments