1+ using System . Threading . Tasks ;
2+ using UnityEngine ;
3+ using Newtonsoft . Json . Linq ;
4+ using ZO . ROS . MessageTypes . Sensor ;
5+ using ZO . ROS . MessageTypes . Geometry ;
6+ using ZO . ROS . Publisher ;
7+ using ZO . ROS . Unity ;
8+ using ZO . ROS . MessageTypes . Nav ;
9+ using ZO . Math ;
10+ using ZO . Document ;
11+ using ZO . Sensors ;
12+
13+
14+ namespace ZO . ROS . Publisher {
15+
16+ /// <summary>
17+ /// Publish fake Odometry by just taking the transform of whatever this script is.
18+ ///
19+ /// Implemented noise mode: https://blog.lxsang.me/post/id/16
20+ /// </summary>
21+ [ RequireComponent ( typeof ( Rigidbody ) ) ]
22+ public class ZOROSFakeOdometryPublisher : ZOROSUnityGameObjectBase {
23+
24+ public bool _applyNoise = false ;
25+ public float _a1 = 0.05f ;
26+ public float _a2 = 15.0f ;
27+ public float _a3 = 0.05f ;
28+ public float _a4 = 0.01f ;
29+
30+ public Rigidbody Rigidbody {
31+ get {
32+ return gameObject . GetComponent < Rigidbody > ( ) ;
33+ }
34+ }
35+
36+ private ZOGaussianNoiseModel _noiseModel = new ZOGaussianNoiseModel ( ) ;
37+ private OdometryMessage _currentOdometryMessage = null ;
38+ private OdometryMessage _previousOdometryMessage = null ;
39+ private TransformStampedMessage _transformStampedMessage = new TransformStampedMessage ( ) ;
40+
41+ public override string Type {
42+ get { return "publisher.odometry" ; }
43+ }
44+
45+
46+ protected override void ZOOnValidate ( ) {
47+ base . ZOOnValidate ( ) ;
48+ if ( string . IsNullOrEmpty ( ROSTopic ) == true ) {
49+ ROSTopic = "/odom" ;
50+ }
51+
52+ if ( UpdateRateHz == 0 ) {
53+ UpdateRateHz = 20 ;
54+ }
55+
56+ }
57+
58+ protected override void ZOFixedUpdateHzSynchronized ( ) {
59+ base . ZOFixedUpdateHzSynchronized ( ) ;
60+
61+ // publish odometry
62+ if ( ZOROSBridgeConnection . Instance . IsConnected ) {
63+
64+ // See: http://wiki.ros.org/navigation/Tutorials/RobotSetup/Odom
65+
66+ _currentOdometryMessage = new OdometryMessage ( ) ;
67+
68+ // Publish odom on TF as well
69+ _transformStampedMessage . header . Update ( ) ;
70+ _transformStampedMessage . header . frame_id = ZOROSUnityManager . Instance . WorldFrameId ; // connect to the world
71+ _transformStampedMessage . child_frame_id = "odom" ;
72+ _transformStampedMessage . UnityLocalTransform = Rigidbody . transform ;
73+ ZOROSUnityManager . Instance . BroadcastTransform ( _transformStampedMessage ) ;
74+
75+
76+ _currentOdometryMessage . Update ( ) ; // update times stamps
77+
78+ // BUGBUG: not super clear on where the pose should be?
79+ _currentOdometryMessage . pose . pose . GlobalUnityTransform = Rigidbody . transform ;
80+
81+ // get velocity in /odom frame
82+ Vector3 linear = Rigidbody . velocity ;
83+ _currentOdometryMessage . twist . twist . angular . z = - Rigidbody . angularVelocity . y ; // NOTE: negating velocity?
84+
85+ float yaw = Rigidbody . transform . localRotation . eulerAngles . y * Mathf . Deg2Rad ;
86+ _currentOdometryMessage . twist . twist . linear . x = Mathf . Cos ( yaw ) * linear . z + Mathf . Sin ( yaw ) * linear . x ;
87+ _currentOdometryMessage . twist . twist . linear . y = Mathf . Cos ( yaw ) * linear . x - Mathf . Sin ( yaw ) * linear . z ;
88+ // set covariance
89+ // see: https://robotics.stackexchange.com/questions/15265/ros-gazebo-odometry-issue
90+ // # Odometry covariances for the encoder output of the robot. These values should
91+ // # be tuned to your robot's sample odometry data, but these values are a good place
92+ // # to start
93+ // pose_covariance_diagonal : [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
94+ // twist_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
95+ _currentOdometryMessage . pose . covariance [ 0 ] = 1e-3 ;
96+ _currentOdometryMessage . pose . covariance [ 7 ] = 1e-3 ;
97+ _currentOdometryMessage . pose . covariance [ 14 ] = 1e6 ;
98+
99+ _currentOdometryMessage . pose . covariance [ 21 ] = 1e6 ;
100+ _currentOdometryMessage . pose . covariance [ 28 ] = 1e6 ;
101+ _currentOdometryMessage . pose . covariance [ 35 ] = 1e3 ;
102+
103+ _currentOdometryMessage . twist . covariance [ 0 ] = 1e-3 ;
104+ _currentOdometryMessage . twist . covariance [ 7 ] = 1e-3 ;
105+ _currentOdometryMessage . twist . covariance [ 14 ] = 1e6 ;
106+ _currentOdometryMessage . twist . covariance [ 21 ] = 1e6 ;
107+ _currentOdometryMessage . twist . covariance [ 28 ] = 1e6 ;
108+ _currentOdometryMessage . twist . covariance [ 35 ] = 1e3 ;
109+
110+ // BUGBUG: not super clear on this being a child of map?
111+ _currentOdometryMessage . header . frame_id = ZOROSUnityManager . Instance . WorldFrameId ;
112+ _currentOdometryMessage . child_frame_id = GetComponent < ZOSimOccurrence > ( ) . Name ;
113+
114+ if ( _applyNoise ) {
115+ if ( _previousOdometryMessage != null ) {
116+ ApplyNoise ( ref _currentOdometryMessage , _previousOdometryMessage ) ;
117+ }
118+ }
119+
120+ ZOROSBridgeConnection . Instance . Publish < OdometryMessage > ( _currentOdometryMessage , ROSTopic ) ;
121+ _previousOdometryMessage = _currentOdometryMessage ;
122+ }
123+
124+ }
125+
126+
127+
128+
129+ private void Initialize ( ) {
130+ // advertise
131+ ROSBridgeConnection . Advertise ( ROSTopic , OdometryMessage . Type ) ;
132+
133+ }
134+
135+ public override void OnROSBridgeConnected ( ZOROSUnityManager rosUnityManager ) {
136+ Debug . Log ( "INFO: ZOImagePublisher::OnROSBridgeConnected" ) ;
137+ Initialize ( ) ;
138+
139+ }
140+
141+ public override void OnROSBridgeDisconnected ( ZOROSUnityManager rosUnityManager ) {
142+ Debug . Log ( "INFO: ZOImagePublisher::OnROSBridgeDisconnected" ) ;
143+ ROSBridgeConnection ? . UnAdvertise ( ROSTopic ) ;
144+ }
145+
146+ /// <summary>
147+ /// Apply noise to OdometryMessage.
148+ ///
149+ /// See: https://blog.lxsang.me/post/id/16
150+ /// </summary>
151+ /// <param name="currentOdomMessage"></param>
152+ /// <param name="previousOdomMessage"></param>
153+ private void ApplyNoise ( ref OdometryMessage currentOdomMessage , OdometryMessage previousOdomMessage ) {
154+
155+ double dx = currentOdomMessage . pose . pose . position . x - previousOdomMessage . pose . pose . position . x ;
156+ double dy = currentOdomMessage . pose . pose . position . y - previousOdomMessage . pose . pose . position . y ;
157+ double trans = System . Math . Sqrt ( dx * dx + dy * dy ) ;
158+ QuaternionMessage quaternionMessage = previousOdomMessage . pose . pose . orientation ;
159+ Quaternion q = quaternionMessage . UnityQuaternion ;
160+ double r = q . eulerAngles . z * Mathf . Deg2Rad ;
161+ double p = q . eulerAngles . x * Mathf . Deg2Rad ;
162+ double theta1 = q . eulerAngles . y * Mathf . Deg2Rad ;
163+ q = currentOdomMessage . pose . pose . orientation . UnityQuaternion ;
164+ double theta2 = q . eulerAngles . y * Mathf . Deg2Rad ;
165+
166+ double rot1 = System . Math . Atan2 ( dy , dx ) - theta1 ;
167+ double rot2 = theta2 - theta1 - rot1 ;
168+
169+ double sd_rot1 = _a1 * System . Math . Abs ( rot1 ) + ( _a2 * Mathf . Deg2Rad ) * trans ;
170+ double sd_rot2 = _a1 * System . Math . Abs ( rot2 ) + ( _a2 * Mathf . Deg2Rad ) * trans ;
171+ double sd_trans = _a3 * trans + _a4 * ( System . Math . Abs ( rot1 ) + System . Math . Abs ( rot2 ) ) ;
172+
173+ _noiseModel . StdDev = sd_trans * sd_trans ;
174+ trans = _noiseModel . Apply ( trans ) ;
175+
176+ _noiseModel . StdDev = sd_rot1 * sd_rot1 ;
177+ rot1 = _noiseModel . Apply ( rot1 ) ;
178+
179+ _noiseModel . StdDev = sd_rot2 * sd_rot2 ;
180+ rot2 = _noiseModel . Apply ( rot2 ) ;
181+
182+ currentOdomMessage . pose . pose . position . x += trans * System . Math . Cos ( theta1 + rot1 ) ;
183+ currentOdomMessage . pose . pose . position . y += trans * System . Math . Sin ( theta1 + rot1 ) ;
184+
185+ theta2 += ( rot1 + rot2 ) ;
186+
187+ Quaternion yawQuaternion = Quaternion . EulerAngles ( 0 , ( float ) theta2 , 0 ) ;
188+ currentOdomMessage . pose . pose . orientation . FromUnityQuaternion ( yawQuaternion ) ;
189+
190+ }
191+
192+
193+ }
194+ }
0 commit comments