/
ZODifferentialDriveController.cs
356 lines (286 loc) · 13.7 KB
/
ZODifferentialDriveController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
using System;
using System.Threading.Tasks;
using UnityEngine;
using Newtonsoft.Json.Linq;
using ZO.ROS.MessageTypes;
using ZO.ROS.MessageTypes.Geometry;
using ZO.ROS.MessageTypes.Nav;
using ZO.ROS;
using ZO.ROS.Unity;
using ZO.Util;
using ZO.Physics;
using ZO.Document;
namespace ZO.ROS.Controllers {
/// <summary>
/// Overview:
/// --------
/// Controller for differential drive wheel systems. Control is in the
/// form of a velocity command, that is split then sent on the two wheels
/// of a differential drive wheel base. Odometry is computed from the
/// feedback from the hardware, and published.
///
/// Velocity commands:
/// ------------------
/// The controller works with a velocity twist from which it extracts
/// the x component of the linear velocity and the z component of the angular
/// velocity. Velocities on other components are ignored.
/// </summary>
/// <reference>
/// See: https://github.com/ros-controls/ros_controllers/blob/indigo-devel/diff_drive_controller/include/diff_drive_controller/diff_drive_controller.h
/// </reference>
/// TODO: Make this a ZOROSUnityGameObjectBase and a controller interface
public class ZODifferentialDriveController : ZOROSUnityGameObjectBase {
public ZOSimOccurrence _connectedBody;
/// <summary>
/// The main rigid body of the controller. Would usually be the "main chassis" of a robot.
/// </summary>
/// <value></value>
public Rigidbody ConnectedRigidBody {
get { return _connectedBody.GetComponent<Rigidbody>(); }
}
[SerializeField] [ZOReadOnly] public ZOHingeJoint _rightWheelMotor;
/// <summary>
/// Hinge joint that drives the right wheel.
/// </summary>
/// <value></value>
public ZOHingeJoint RightWheelMotor {
get => _rightWheelMotor;
set => _rightWheelMotor = value;
}
[SerializeField] [ZOReadOnly] public ZOHingeJoint _leftWheelMotor;
/// <summary>
/// Hinge joint that drive the left wheel.
/// </summary>
/// <value></value>
public ZOHingeJoint LeftWheelMotor {
get => _leftWheelMotor;
set => _leftWheelMotor = value;
}
public float _wheelRadius = 0;
public float _wheelSeperation = 0;
public bool _steerOpposite = false;
private float _linearVelocity = 0;
private float _angularVelocity = 0;
// NOTE: this can only be called in FixedUpdate
public float LinearVelocity {
set {
_linearVelocity = value;
UpdateMotors();
}
get => _linearVelocity;
}
// NOTE: this can only be called in FixedUpdate
public float AngularVelocity {
set {
_angularVelocity = value;
UpdateMotors();
}
get => _angularVelocity;
}
public override string Type {
get {
return "controller.differential_drive";
}
}
void OnValidate() {
// make sure we have a name
if (string.IsNullOrEmpty(Name)) {
Name = gameObject.name + "_" + Type;
}
}
protected override void ZOAwake() {
base.ZOAwake();
// make sure we have a name
if (string.IsNullOrEmpty(Name)) {
Name = gameObject.name + "_" + Type;
}
}
protected override void ZOFixedUpdate() {
// update the motors from the twist message
LinearVelocity = (float)-_twistMessage.linear.x * Mathf.Rad2Deg;
if (_steerOpposite == true) {
AngularVelocity = (float)-_twistMessage.angular.z * Mathf.Rad2Deg;
} else {
AngularVelocity = (float)_twistMessage.angular.z * Mathf.Rad2Deg;
}
}
protected override void ZOFixedUpdateHzSynchronized() {
// publish odometry
if (ZOROSBridgeConnection.Instance.IsConnected) {
// Publish odom on TF as well
_transformMessage.header.Update();
_transformMessage.header.frame_id = ZOROSUnityManager.Instance.WorldFrameId; // connect to the world
_transformMessage.child_frame_id = "odom_combined";
_transformMessage.UnityLocalTransform = ConnectedRigidBody.transform;
ZOROSUnityManager.Instance.BroadcastTransform(_transformMessage);
// NOTE: Just echoing back the true odometry.
// TODO: calculat the odometry see: CalculateOdometryOpenLoop
_odometryMessage.Update(); // update times stamps
// BUGBUG: not super clear on where the pose should be?
_odometryMessage.pose.pose.GlobalUnityTransform = ConnectedRigidBody.transform;
// get velocity in /odom frame
Vector3 linear = ConnectedRigidBody.velocity;
_odometryMessage.twist.twist.angular.z = -ConnectedRigidBody.angularVelocity.y; // NOTE: negating velocity?
float yaw = ConnectedRigidBody.transform.localRotation.eulerAngles.y * Mathf.Deg2Rad;
_odometryMessage.twist.twist.linear.x = Mathf.Cos(yaw) * linear.z + Mathf.Sin(yaw) * linear.x;
_odometryMessage.twist.twist.linear.y = Mathf.Cos(yaw) * linear.x - Mathf.Sin(yaw) * linear.z;
// set covariance
// see: https://robotics.stackexchange.com/questions/15265/ros-gazebo-odometry-issue
// # Odometry covariances for the encoder output of the robot. These values should
// # be tuned to your robot's sample odometry data, but these values are a good place
// # to start
// pose_covariance_diagonal : [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
// twist_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
_odometryMessage.pose.covariance[0] = 1e-3;
_odometryMessage.pose.covariance[7] = 1e-3;
_odometryMessage.pose.covariance[14] = 1e6;
_odometryMessage.pose.covariance[21] = 1e6;
_odometryMessage.pose.covariance[28] = 1e6;
_odometryMessage.pose.covariance[35] = 1e3;
_odometryMessage.twist.covariance[0] = 1e-3;
_odometryMessage.twist.covariance[7] = 1e-3;
_odometryMessage.twist.covariance[14] = 1e6;
_odometryMessage.twist.covariance[21] = 1e6;
_odometryMessage.twist.covariance[28] = 1e6;
_odometryMessage.twist.covariance[35] = 1e3;
// BUGBUG: not super clear on this being a child of map?
_odometryMessage.header.frame_id = ZOROSUnityManager.Instance.WorldFrameId;
_odometryMessage.child_frame_id = "odom";
ZOROSBridgeConnection.Instance.Publish<OdometryMessage>(_odometryMessage, "/odom");
}
// todo broadcast various joint transforms
// see: https://github.com/ros-simulation/gazebo_ros_pkgs/blob/kinetic-devel/gazebo_plugins/src/gazebo_ros_diff_drive.cpp
/*
void GazeboRosDiffDrive::publishWheelTF()
{
ros::Time current_time = ros::Time::now();
for ( int i = 0; i < 2; i++ ) {
std::string wheel_frame = gazebo_ros_->resolveTF(joints_[i]->GetChild()->GetName ());
std::string wheel_parent_frame = gazebo_ros_->resolveTF(joints_[i]->GetParent()->GetName ());
#if GAZEBO_MAJOR_VERSION >= 8
ignition::math::Pose3d poseWheel = joints_[i]->GetChild()->RelativePose();
#else
ignition::math::Pose3d poseWheel = joints_[i]->GetChild()->GetRelativePose().Ign();
#endif
tf::Quaternion qt ( poseWheel.Rot().X(), poseWheel.Rot().Y(), poseWheel.Rot().Z(), poseWheel.Rot().W() );
tf::Vector3 vt ( poseWheel.Pos().X(), poseWheel.Pos().Y(), poseWheel.Pos().Z() );
tf::Transform tfWheel ( qt, vt );
transform_broadcaster_->sendTransform (
tf::StampedTransform ( tfWheel, current_time, wheel_parent_frame, wheel_frame ) );
}
} */
}
private void UpdateMotors() {
float leftVelocity = (LinearVelocity + AngularVelocity * _wheelSeperation / 2.0f) / _wheelRadius;
float rightVelocity = (LinearVelocity - AngularVelocity * _wheelSeperation / 2.0f) / _wheelRadius;
// BUGBUG: if this seems to fast the do a deg2rad on it?
_rightWheelMotor.Velocity = rightVelocity * Mathf.Deg2Rad;
_leftWheelMotor.Velocity = leftVelocity * Mathf.Deg2Rad;
}
private float _odomHeading = 0;
private float _odomX = 0;
private float _odomY = 0;
/// <summary>
/// See: https://github.com/ros-controls/ros_controllers/blob/noetic-devel/diff_drive_controller/src/odometry.cpp
/// </summary>
private void CalculateOdometryOpenLoop() {
/* see: https://github.com/ros-simulation/gazebo_ros_pkgs/blob/kinetic-devel/gazebo_plugins/src/gazebo_ros_diff_drive.cpp
void GazeboRosDiffDrive::UpdateOdometryEncoder()
{
double vl = joints_[LEFT]->GetVelocity ( 0 );
double vr = joints_[RIGHT]->GetVelocity ( 0 );
#if GAZEBO_MAJOR_VERSION >= 8
common::Time current_time = parent->GetWorld()->SimTime();
#else
common::Time current_time = parent->GetWorld()->GetSimTime();
#endif
double seconds_since_last_update = ( current_time - last_odom_update_ ).Double();
last_odom_update_ = current_time;
double b = wheel_separation_;
// Book: Sigwart 2011 Autonompus Mobile Robots page:337
double sl = vl * ( wheel_diameter_ / 2.0 ) * seconds_since_last_update;
double sr = vr * ( wheel_diameter_ / 2.0 ) * seconds_since_last_update;
double ssum = sl + sr;
double sdiff;
if(legacy_mode_)
{
sdiff = sl - sr;
}
else
{
sdiff = sr - sl;
}
double dx = ( ssum ) /2.0 * cos ( pose_encoder_.theta + ( sdiff ) / ( 2.0*b ) );
double dy = ( ssum ) /2.0 * sin ( pose_encoder_.theta + ( sdiff ) / ( 2.0*b ) );
double dtheta = ( sdiff ) /b;
pose_encoder_.x += dx;
pose_encoder_.y += dy;
pose_encoder_.theta += dtheta;
double w = dtheta/seconds_since_last_update;
double v = sqrt ( dx*dx+dy*dy ) /seconds_since_last_update;
tf::Quaternion qt;
tf::Vector3 vt;
qt.setRPY ( 0,0,pose_encoder_.theta );
vt = tf::Vector3 ( pose_encoder_.x, pose_encoder_.y, 0 );
odom_.pose.pose.position.x = vt.x();
odom_.pose.pose.position.y = vt.y();
odom_.pose.pose.position.z = vt.z();
odom_.pose.pose.orientation.x = qt.x();
odom_.pose.pose.orientation.y = qt.y();
odom_.pose.pose.orientation.z = qt.z();
odom_.pose.pose.orientation.w = qt.w();
odom_.twist.twist.angular.z = w;
odom_.twist.twist.linear.x = dx/seconds_since_last_update;
odom_.twist.twist.linear.y = dy/seconds_since_last_update;
}
*/
float dt = Time.deltaTime;
if (Mathf.Abs(AngularVelocity) < 1e-6f) {
// Runge-Kutta 2nd order integration
float direction = _odomHeading + AngularVelocity * 0.5f;
_odomX += LinearVelocity * Mathf.Cos(direction);
_odomY += LinearVelocity * Mathf.Sin(direction);
_odomHeading += AngularVelocity;
} else {
float headingOld = _odomHeading;
float r = LinearVelocity / AngularVelocity;
_odomHeading += AngularVelocity;
_odomX += r * (Mathf.Sin(_odomHeading) - Mathf.Sin(headingOld));
_odomY += r * (Mathf.Cos(_odomHeading) - Mathf.Cos(headingOld));
}
}
/// <summary>
/// ROS control twist message topic.
/// To test: `rosrun turtlebot3 turtlebot3_teleop_key`
/// </summary>
public string _ROSTopicSubscription = "/cmd_vel";
private TwistMessage _twistMessage = new TwistMessage();
private OdometryMessage _odometryMessage = new OdometryMessage();
/// <summary>
/// The "odom" transform published on TF.
/// </summary>
private TransformStampedMessage _transformMessage = new TransformStampedMessage();
public override void OnROSBridgeConnected(ZOROSUnityManager rosUnityManager) {
Debug.Log("INFO: ZODifferentialDriveController::OnROSBridgeConnected");
// subscribe to Twist Message
ZOROSBridgeConnection.Instance.Subscribe<TwistMessage>(Name, _ROSTopicSubscription, _twistMessage.MessageType, OnROSTwistMessageReceived);
// adverise Odometry Message
ZOROSBridgeConnection.Instance.Advertise("/odom", _odometryMessage.MessageType);
}
public override void OnROSBridgeDisconnected(ZOROSUnityManager rosUnityManager) {
ZOROSBridgeConnection.Instance.UnAdvertise(_ROSTopicSubscription);
Debug.Log("INFO: ZODifferentialDriveController::OnROSBridgeDisconnected");
}
/// <summary>
/// Handles subscribed to `TwistMessage` which controls the differential control steering and drive.
/// </summary>
/// <param name="rosBridgeConnection">ROS Bridge Connection</param>
/// <param name="msg">TwistMessage</param>
/// <returns></returns>
public Task OnROSTwistMessageReceived(ZOROSBridgeConnection rosBridgeConnection, ZOROSMessageInterface msg) {
_twistMessage = (TwistMessage)msg;
// Debug.Log("INFO: Twist Message Received: linear " + _twistMessage.linear.ToString() + " angular: " + _twistMessage.angular.ToString());
return Task.CompletedTask;
}
}
}