6#include "TinyRobotics/coordinates/Coordinate.h"
7#include "TinyRobotics/imu/IMU2D.h"
8#include "TinyRobotics/planning/Path.h"
10namespace tinyrobotics {
13
14
15
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
71template <
typename T =
float>
74 MotionController2D(
IMotionState2D& motionState,
float maxSpeedKmh = 10,
75 float maxSteeringAngleDeg = 30.0f,
76 float accelDistanceM = 2.0f)
77 : motionStateSource(motionState),
78 accelDistanceM(accelDistanceM),
79 maxSpeedKmh(maxSpeedKmh) {
91 maxSteeringAngle.getValue(
AngleUnit::DEG),
95
96
97
98
99
100
101
102
105 pidSpeed_.begin(0.0f, minOut, maxOut, kp, ki, kd);
109
110
111
112
113
114
115
118 pidSteering_.begin(0.0f, minOut, maxOut, kp, ki, kd);
122
123
124 void setPath(Path<Coordinate<DistanceM>> path) {
this->path = path; }
127
128
130 this->path.addWaypoint(target);
134
135
136
140
141
144 has_distance =
false;
146 updateStartTimeMs = 0;
148 dtSetFromUpdates =
false;
150 if (!hasStartCoordinate) {
151 startCoordinate = motionStateSource.getPosition();
152 hasStartCoordinate =
true;
158
159
160 void end() { is_active =
false; }
163
164
165
167 if (!is_active)
return;
171 Coordinate<DistanceM> currentPos = motionStateSource.getPosition();
173 float currentHeading =
174 motionStateSource.getHeading().getValue(
AngleUnit::DEG);
175 float desiredHeading = 0;
176 if (!handleWaypoint(currentPos, targetPos, distanceToTargetM,
182 float currentSpeedKmh =
183 motionStateSource.getSpeed().getValue(
SpeedUnit::KPH);
184 float distanceFromStartM =
185 startCoordinate.distance(currentPos, DistanceUnit::M);
191 resultThrottlePercent = getThrottlePercent(currentSpeedKmh);
194 float pidInput = !isInverted ? -headingError : headingError;
195 resultSteeringAngleDeg = pidSteering_.calculate(0.0f, pidInput);
199 "MotionController2D: distanceToTarget=%.2f m, desiredHeading=%.1f deg, "
200 "currentHeading=%.1f deg, headingError=%.1f deg, steeringAngle=%.1f "
201 "deg, desiredSpeed=%.2f km/h, currentSpeed=%.2f km/h, throttle=%.1f%%",
202 distanceToTargetM, desiredHeading, currentHeading, headingError,
203 resultSteeringAngleDeg, desiredSpeedKmh, currentSpeedKmh,
204 resultThrottlePercent);
214
215
216
217
219 onGoalCallback = callback;
220 if (ref !=
nullptr) onGoalRef = ref;
224
225
228
229
232
233
239
240
241
242
243
249
250
252 if (!has_distance)
return false;
253 return distanceToTargetM < targetAccuracyM;
257
258
259
260
264
265
266
267
268
273 Path<Coordinate<DistanceM>> path;
274 bool is_active =
false;
275 bool has_distance =
false;
276 float accelDistanceM = 2.0f;
277 float maxSpeedKmh = 10.0f;
278 float desiredSpeedKmh = 0.0f;
279 float maxSteeringAngleDeg = 30.0f;
283 bool hasStartCoordinate =
false;
284 float targetAccuracyM = 0.10f;
285 float distanceToTargetM = 0;
286 bool (*onGoalCallback)(
void*) =
nullptr;
287 void* onGoalRef =
this;
288 float resultThrottlePercent = 0.0f;
289 float resultSteeringAngleDeg = 0.0f;
293 unsigned long updateStartTimeMs = 0;
294 unsigned long updateEndTimeMs = 0;
295 bool dtSetFromUpdates =
false;
296 bool isInverted =
false;
299 float getDesiredSpeed(
float distanceFromStartM,
float distanceToTarget,
300 float currentSpeedKmh) {
302 float minSpeedKmh = 0.0f;
303 float desiredSpeedKmh =
305 if (distanceToTarget < accelDistanceM) {
306 if (distanceToTarget < targetAccuracyM) {
307 desiredSpeedKmh = 0.0f;
309 desiredSpeedKmh = maxSpeedKmh * (distanceToTarget / accelDistanceM);
312 if (distanceFromStartM < accelDistanceM) {
314 if (distanceFromStartM == 0)
315 distanceFromStartM = 0.1f;
316 desiredSpeedKmh = maxSpeedKmh * (distanceFromStartM / accelDistanceM);
318 if (desiredSpeedKmh > maxSpeedKmh) desiredSpeedKmh = maxSpeedKmh;
319 if (desiredSpeedKmh < minSpeedKmh) desiredSpeedKmh = minSpeedKmh;
320 return desiredSpeedKmh;
326 float& distanceToTargetM,
float& desiredHeadingDeg) {
328 distanceToTargetM = 0.0f;
329 while (distanceToTargetM < targetAccuracyM) {
330 desiredHeadingDeg = currentPos.bearing(targetPos, AngleUnit::DEG);
331 distanceToTargetM = currentPos.distance(targetPos, DistanceUnit::M);
332 if (distanceToTargetM < targetAccuracyM) {
334 "Reached waypoint at (%.2f m, %.2f m), distance to target: %.2f m",
335 targetPos.x, targetPos.y, distanceToTargetM);
337 if (path.isEmpty()) {
338 TRLogger.info(
"Reached final goal!");
339 if (onGoalCallback) {
340 onGoalCallback(onGoalRef);
353 float headingError = desiredHeading - currentHeading;
354 while (headingError > 180) headingError -= 360;
355 while (headingError < -180) headingError += 360;
361 if (maxSpeedKmh <= 0.0f)
return 0.0f;
362 return 100.0f * desiredSpeedKmh / maxSpeedKmh;
367 float speedError = desiredSpeedKmh - currentSpeedKmh;
369 float fb = pidSpeed_.calculate(0.0f, -speedError);
370 float throttlePercent = 0.0f;
373 throttlePercent = ff;
376 throttlePercent = fb;
380 throttlePercent = ff + fb;
384 if (throttlePercent > 100.0f) throttlePercent = 100.0f;
385 if (throttlePercent < 0.0f) throttlePercent = 0.0f;
386 if (throttlePercent > 0.0f && throttlePercent < 1.0f)
387 throttlePercent = 1.0f;
388 return throttlePercent;
394 msg.origin = MessageOrigin::Autonomy;
395 msg.content = MessageContent::Throttle;
396 msg.unit = Unit::Percent;
397 msg.value = throttlePercent;
400 msg.content = MessageContent::SteeringAngle;
401 msg.unit = Unit::AngleDegree;
402 msg.value = steeringAngleDeg;
408 unsigned long nowMs = millis();
409 if (dtSetFromUpdates)
return true;
410 if (updateCount == 0) {
411 updateStartTimeMs = nowMs;
414 if (updateCount == 11) {
415 updateEndTimeMs = nowMs;
416 float avgMs = (updateEndTimeMs - updateStartTimeMs) / 10.0f;
417 float dt = avgMs / 1000.0f;
420 pidSteering_.setDt(dt);
421 dtSetFromUpdates =
true;
422 TRLogger.info(
"Initialized PID dt from updates: %.3f seconds (%.1f ms)",
425 return dtSetFromUpdates;
Represents an angle with unit conversion and wrap-around support.
Definition: Angle.h:42
A generic 3D coordinate class for robotics, navigation, and spatial calculations.
Definition: Coordinate.h:57
Represents a distance measurement with unit conversion support.
Definition: Distance.h:40
Interface for representing the navigation state of a robot in 2D space.
Definition: MotionState2D.h:35
Base class for message sources in the TinyRobotics communication framework.
Definition: MessageSource.h:35
2D motion controller for path following and vehicle control.
Definition: MotionController2D.h:72
void setThrottleMode(ThrottleMode mode)
Set the throttle control mode.
Definition: MotionController2D.h:261
void addWaypoint(Coordinate< DistanceM > target)
Adds a single waypoint to the path (appended to the end)
Definition: MotionController2D.h:129
void setInvertedSteering(bool inverted)
Set the Inverted Steering logic: by default we use ROS logic for steering correction (positive angle ...
Definition: MotionController2D.h:269
void configureSteeringPID(float minOut, float maxOut, float kp, float ki, float kd)
Configure the PID controller for steering.
Definition: MotionController2D.h:116
float getSteeringAngleDeg() const
Get the last computed steering angle in degrees.
Definition: MotionController2D.h:230
Angle getSteeringAngle() const
Get the last computed steering angle as an Angle object.
Definition: MotionController2D.h:234
bool isGoalReached() const
Returns true if the goal has been reached.
Definition: MotionController2D.h:251
void setTargetAccuracy(float accuracyM)
Set the target accuracy (meters) for reaching waypoints.
Definition: MotionController2D.h:137
bool begin()
Start the controller and initialize state.
Definition: MotionController2D.h:142
ThrottleMode throttleMode
Throttle control mode.
Definition: MotionController2D.h:291
float getDesiredSpeed(float distanceFromStartM, float distanceToTarget, float currentSpeedKmh)
Calculate desired speed based on distance to target and start.
Definition: MotionController2D.h:299
void configureSpeedPID(float minOut, float maxOut, float kp, float ki, float kd)
Configure the PID controller for speed-to-throttle mapping.
Definition: MotionController2D.h:103
float getThrottlePercent(float currentSpeedKmh)
Compute the throttle percent based on the selected mode.
Definition: MotionController2D.h:366
bool initializeDtFromUpdates()
Handles dt initialization from first 10 updates.
Definition: MotionController2D.h:407
bool handleWaypoint(const Coordinate< DistanceM > ¤tPos, Coordinate< DistanceM > &targetPos, float &distanceToTargetM, float &desiredHeadingDeg)
Handle the current waypoint: calculate distance and desired heading.
Definition: MotionController2D.h:324
void end()
Stop the controller and the vehicle.
Definition: MotionController2D.h:160
float getThrottlePercent() const
Get the last computed throttle percent.
Definition: MotionController2D.h:226
void update()
Main control loop to be called periodically (e.g., in a timer or main loop)
Definition: MotionController2D.h:166
void sendControlMessages(float throttlePercent, float steeringAngleDeg)
Send control messages for throttle and steering angle.
Definition: MotionController2D.h:392
float getTargetAccuracy(DistanceUnit unit) const
Get the Target Accuracy object.
Definition: MotionController2D.h:244
float computeHeadingError(float desiredHeading, float currentHeading)
Compute angle difference range [-180, 180].
Definition: MotionController2D.h:352
void setOnGoalCallback(bool(*callback)(void *), void *ref=nullptr)
Set a custom callback to be called when reaching the goal. The callback should return true if it hand...
Definition: MotionController2D.h:218
float feedforwardThrottle(float desiredSpeedKmh) const
Model-based feedforward throttle estimate: 100% throttle = maxSpeedKmh.
Definition: MotionController2D.h:360
void setPath(Path< Coordinate< DistanceM > > path)
Defines the path to follow as a sequence of waypoints.
Definition: MotionController2D.h:124
Implements a simple, header-only Proportional-Integral-Derivative (PID) controller.
Definition: PIDController.h:56
Represents a speed measurement with unit conversion support.
Definition: Speed.h:40
ThrottleMode
Throttle control strategy selection.
Definition: MotionController2D.h:16
@ FeedforwardOnly
Model-based (open-loop) throttle.
@ Combined
Feedforward + PID feedback.
@ PIDOnly
Feedback (closed-loop) throttle.
DistanceUnit
Supported distance units for conversion and representation.
Definition: Distance.h:10
AngleUnit
Supported angle units for conversion and representation.
Definition: Angle.h:11
SpeedUnit
Supported speed units for conversion and representation.
Definition: Speed.h:10
Generic message structure for communication, parameterized by value type.
Definition: Message.h:72