1+ #!/usr/bin/env python3
2+
3+ import rclpy
4+ import copy
5+ import transforms3d
6+ import numpy as np
7+
8+ from rclpy .node import Node
9+ from nav_msgs .msg import Odometry
10+
11+ """
12+ The Spot ROS 2 driver reports the Twist part of the odometry in odom frame.
13+ According to ROS standards this should be reported in body frame.
14+ This script subscribes to the odometry, transforms it to body frame and publishes it
15+ to /odometry_corrected.
16+ """
17+
18+
19+ class OdometryTransformer (Node ):
20+
21+ def __init__ (self ):
22+ super ().__init__ ('odometry_transformer' )
23+
24+ self .odom_pub = self .create_publisher (
25+ Odometry ,
26+ '/odometry_corrected' ,
27+ 10 )
28+
29+ self .odom_sub = self .create_subscription (
30+ Odometry ,
31+ '/odometry' ,
32+ self .odometry_callback ,
33+ 10 )
34+ self .odom_sub # prevent unused variable warning
35+
36+ def odometry_callback (self , msg ):
37+ transformed_odom = self .transform_odom (msg )
38+
39+ self .odom_pub .publish (transformed_odom )
40+
41+ def transform_odom (self , base_odometry : Odometry ):
42+ """
43+ Copied from the ROS 1 fix:
44+ https://github.com/heuristicus/spot_ros/blob/2a1d26da976a6376b21b5af9ce5328899fb7a8c4/spot_driver/src/spot_driver/ros_helpers.py#L402
45+ """
46+
47+ # Current inverse rotation of body frame w.r.t. odom frame
48+ inverse_rotation = transforms3d .quaternions .quat2mat (
49+ transforms3d .quaternions .qinverse (
50+ [
51+ base_odometry .pose .pose .orientation .w ,
52+ base_odometry .pose .pose .orientation .x ,
53+ base_odometry .pose .pose .orientation .y ,
54+ base_odometry .pose .pose .orientation .z ,
55+ ]
56+ )
57+ )
58+
59+ # Transform the linear twist by rotating the vector according to the rotation from body to odom
60+ linear_twist = np .array (
61+ [
62+ [base_odometry .twist .twist .linear .x ],
63+ [base_odometry .twist .twist .linear .y ],
64+ [base_odometry .twist .twist .linear .z ],
65+ ]
66+ )
67+
68+ corrected_linear = inverse_rotation .dot (linear_twist )
69+
70+ # Do the same for the angular twist
71+ angular_twist = np .array (
72+ [
73+ [base_odometry .twist .twist .angular .x ],
74+ [base_odometry .twist .twist .angular .y ],
75+ [base_odometry .twist .twist .angular .z ],
76+ ]
77+ )
78+
79+ corrected_angular = inverse_rotation .dot (angular_twist )
80+
81+ corrected_odometry = copy .deepcopy (base_odometry )
82+ corrected_odometry .twist .twist .linear .x = corrected_linear [0 ][0 ]
83+ corrected_odometry .twist .twist .linear .y = corrected_linear [1 ][0 ]
84+ corrected_odometry .twist .twist .linear .z = corrected_linear [2 ][0 ]
85+ corrected_odometry .twist .twist .angular .x = corrected_angular [0 ][0 ]
86+ corrected_odometry .twist .twist .angular .y = corrected_angular [1 ][0 ]
87+ corrected_odometry .twist .twist .angular .z = corrected_angular [2 ][0 ]
88+
89+ return corrected_odometry
90+
91+
92+ def main (args = None ):
93+ rclpy .init (args = args )
94+
95+ odometry_transformer = OdometryTransformer ()
96+
97+ rclpy .spin (odometry_transformer )
98+
99+ odometry_transformer .destroy_node ()
100+ rclpy .shutdown ()
101+
102+
103+ if __name__ == '__main__' :
104+ main ()
0 commit comments