import rclpy from rclpy.node import Node from rclpy.executors import SingleThreadedExecutor from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint from sensor_msgs.msg import JointState from pythonosc.dispatcher import Dispatcher from pythonosc.osc_server import BlockingOSCUDPServer import time class JointStateReader(Node): """Node to read joint names from the /joint_states topic.""" def __init__(self): super().__init__('JointStateReader') self.sub = self.create_subscription(JointState, 'joint_states', self.sub_callback, 1) self.joint_names = [] self.got_msg = False def sub_callback(self, msg): """Callback to read joint names from the first received message.""" if not self.joint_names: self.got_msg = True self.joint_names = list(msg.name) def get_joint_names(): """get joint names using an existing ROS2 context and ensure proper cleanup.""" #rclpy.init() node = JointStateReader() executor = SingleThreadedExecutor() executor.add_node(node) while not node.got_msg: executor.spin_once(timeout_sec=0) joint_list = node.joint_names node.destroy_node() # destroy the node #rclpy.shutdown() # Shutdown return joint_list # Return the joint names class ScaledJointTrajectoryPublisher(Node): """Node to publish joint trajectories based on OSC messages.""" def __init__(self, joint_names): super().__init__('scaled_joint_trajectory_publisher') # ROS2 Publisher self.publisher = self.create_publisher( JointTrajectory, '/scaled_joint_trajectory_controller/joint_trajectory', 10 ) # Use dynamically retrieved joint names self.joint_names = joint_names # Store received joint positions self.joint_positions = [0.0] * len(self.joint_names) # OSC Server Setup self.osc_ip = "0.0.0.0" self.osc_port = 8000 self.dispatcher = Dispatcher() # Register OSC handlers dynamically for joint in self.joint_names: self.dispatcher.map(f"/joint/{joint}", self.osc_joint_handler) self.get_logger().info("Scaled Joint Trajectory Publisher Ready") def osc_joint_handler(self, address, *args): """Handles incoming OSC messages for joint positions.""" joint_name = address.split("/")[-1] # Extract joint name if joint_name in self.joint_names: index = self.joint_names.index(joint_name) self.joint_positions[index] = args[0] # Update joint position self.get_logger().info(f"Updated {joint_name} to {args[0]}") # Publish the trajectory whenever a joint update is received self.send_trajectory(self.joint_positions) def send_trajectory(self, joint_positions, duration=2.0): """Publish a joint trajectory command to move the UR10e.""" msg = JointTrajectory() msg.joint_names = self.joint_names point = JointTrajectoryPoint() point.positions = joint_positions # Updated joint positions point.time_from_start.sec = int(duration) # Duration in seconds #point.time_from_start.nanosec = int((duration - int(duration)) * 1e9) # Nanoseconds msg.points.append(point) self.publisher.publish(msg) self.get_logger().info(f"Published Joint Trajectory: {joint_positions}") def main(): """Main function to get joint names and start the ROS 2 & OSC system.""" rclpy.init() joints = get_joint_names() #print(joints) node = ScaledJointTrajectoryPublisher(joints) # Setup OSC Server server = BlockingOSCUDPServer((node.osc_ip, node.osc_port), node.dispatcher) # Run both ROS 2 and OSC Server together try: while rclpy.ok(): server.handle_request() # Handle one OSC request at a time rclpy.spin_once(node, timeout_sec=0.1) # Process ROS callbacks except KeyboardInterrupt: print("") finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()