Skip to content

Commit 0ef6ce3

Browse files
committed
translatory.py migrated with new test case
1 parent cd4350e commit 0ef6ce3

File tree

3 files changed

+313
-48
lines changed

3 files changed

+313
-48
lines changed

mocha_core/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ install(DIRECTORY launch
4444
DESTINATION share/${PROJECT_NAME}
4545
)
4646

47+
# Install config files
48+
install(DIRECTORY config
49+
DESTINATION share/${PROJECT_NAME}
50+
)
51+
4752
if(BUILD_TESTING)
4853
# find_package(ament_lint_auto REQUIRED)
4954
find_package(ament_cmake_pytest REQUIRED)
@@ -65,6 +70,7 @@ if(BUILD_TESTING)
6570
ament_add_pytest_test(test_database_server test/test_database_server.py)
6671
ament_add_pytest_test(test_delay_2robots test/test_delay_2robots.py)
6772
ament_add_pytest_test(test_multiple_robots_sync test/test_multiple_robots_sync.py)
73+
ament_add_pytest_test(test_translator test/test_translator.py)
6874
endif()
6975

7076
ament_package()

mocha_core/mocha_core/translator.py

Lines changed: 155 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,199 @@
22

33
import os
44
import sys
5-
import rospy
6-
import rospkg
5+
import rclpy
6+
import rclpy.time
7+
import ament_index_python
78
import mocha_core.srv
89
import yaml
910
import pdb
1011

1112
class Translator:
1213
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
1417
self.__du = du
1518

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+
1632
self.__counter = 0
1733
self.__topic_name = topic_name
1834
self.__topic_id = topic_id
1935
self.__this_robot = this_robot
2036
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
2442
)
2543

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
2847
)
2948

3049
def translator_cb(self, data):
3150
msg = data
3251

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+
3457
serialized_msg = self.__du.serialize_ros_msg(msg)
3558
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()
4690

4791

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()
50108

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, "."))
56120
import database_utils as du
57121

58-
# Get robot from the parameters
59-
this_robot = rospy.get_param("~robot_name")
60-
61122
# 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+
65126
with open(robot_configs_path, 'r') as f:
66127
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")
71133

72134
# 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+
77138
with open(topic_configs_path, 'r') as f:
78139
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"]]
80141

81142
# Get msg_types dict used to publish the topics
82143
msg_types = du.msg_types(robot_configs, topic_configs)
83144

145+
# Create translators for each topic
146+
translators = []
84147
for topic_id, topic in enumerate(this_robot_topics):
85148
# 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)
87150
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

Comments
 (0)