4#include "TinyRobotics/control/MotionState3D.h"
5#include "TinyRobotics/control/PIDController.h"
6#include "TinyRobotics/coordinates/Coordinate.h"
7#include "TinyRobotics/coordinates/Orientation3D.h"
8#include "TinyRobotics/planning/Path.h"
9#include "TinyRobotics/units/AngularVelocity.h"
10#include "TinyRobotics/units/Speed.h"
12namespace tinyrobotics {
15enum class OnGoalAction { Stop, HoldPosition, Circle };
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
64 MotionController3D(
IMotionState3D& motionStateRef, OnGoalAction onGloal,
65 float positionToleranceM = 2.0)
66 : positionToleranceM(positionToleranceM),
67 onGoalAction(onGloal),
69 motionState(motionStateRef) {
71 pidX.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
72 pidY.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
73 pidZ.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
74 pidYaw.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
75 pidPitch.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
76 pidRoll.begin(0.1f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f);
79 void configurePositionPID(
float dt,
float maxOut,
float minOut,
float kp,
81 pidX.begin(dt, minOut, maxOut, kp, ki, kd);
82 pidY.begin(dt, minOut, maxOut, kp, ki, kd);
83 pidZ.begin(dt, minOut, maxOut, kp, ki, kd);
85 void configureOrientationPID(
float dt,
float minOut,
float maxOut,
float kp,
87 pidYaw.begin(dt, minOut, maxOut, kp, ki, kd);
88 pidPitch.begin(dt, minOut, maxOut, kp, ki, kd);
89 pidRoll.begin(dt, minOut, maxOut, kp, ki, kd);
92 void setPath(Path<Coordinate<DistanceM>> path) {
94 circleInitialized =
false;
98
99
100
103 updateStartTimeMs = 0;
105 dtSetFromUpdates =
false;
107 if (!path.isEmpty()) {
108 target = MotionState3D(
109 path[0], target.getOrientation(), Speed3D(0, 0, 0, SpeedUnit::MPS),
110 AngularVelocity3D(0, 0, 0, AngularVelocityUnit::RadPerSec));
116 void end() { is_active =
false; }
119
120
121
122
124 if (!is_active)
return false;
126 advanceWaypoint(positionToleranceM);
127 if (path.isEmpty()) {
132 pidX.calculate(target.getPosition().x, motionState.getPosition().x);
134 pidY.calculate(target.getPosition().y, motionState.getPosition().y);
136 pidZ.calculate(target.getPosition().z, motionState.getPosition().z);
138 pidYaw.calculate(normalizeAngleRad(target.getOrientation().yaw),
139 normalizeAngleRad(motionState.getOrientation().yaw));
140 float vpitch = pidPitch.calculate(
141 normalizeAngleRad(target.getOrientation().pitch),
142 normalizeAngleRad(motionState.getOrientation().pitch));
144 pidRoll.calculate(normalizeAngleRad(target.getOrientation().roll),
145 normalizeAngleRad(motionState.getOrientation().roll));
147 linearCmd = Speed3D(vx, vy, vz,
SpeedUnit::MPS);
154 Speed3D getLinearCommand()
const {
return linearCmd; }
160 bool isActive()
const {
return is_active; }
163
164
165
167 onGoalAction = action;
168 circleInitialized =
false;
172
173
174
175
177 onGoalCallback = callback;
178 if (ref !=
nullptr) onGoaldRef = ref;
182
183
185 circleRadius = radius;
186 circleInitialized =
false;
190
191
193 circleAngularSpeed = angularSpeed;
197 bool is_active =
false;
198 float positionToleranceM;
199 OnGoalAction onGoalAction;
200 Path<Coordinate<DistanceM>> path;
207 Speed3D linearCmd = Speed3D(0, 0, 0,
SpeedUnit::MPS);
210 bool (*onGoalCallback)(
void*) =
nullptr;
211 void* onGoaldRef =
this;
214 unsigned long updateStartTimeMs = 0;
215 unsigned long updateEndTimeMs = 0;
216 bool dtSetFromUpdates =
false;
221 unsigned long nowMs = millis();
222 if (dtSetFromUpdates)
return true;
223 if (updateCount == 0) {
224 updateStartTimeMs = nowMs;
227 if (updateCount == 11) {
228 updateEndTimeMs = nowMs;
229 float avgMs = (updateEndTimeMs - updateStartTimeMs) / 10.0f;
230 float dt = avgMs / 1000.0f;
238 dtSetFromUpdates =
true;
240 return dtSetFromUpdates;
243 bool advanceWaypoint(
float positionTolerance = 0.1f) {
244 if (path.isEmpty())
return false;
246 float dx = tgt.x - motionState.getPosition().x;
247 float dy = tgt.y - motionState.getPosition().y;
248 float dz = tgt.z - motionState.getPosition().z;
249 float dist = std::sqrt(dx * dx + dy * dy + dz * dz);
250 if (dist < positionTolerance) {
252 if (!path.isEmpty()) {
255 if (path.size() == 1) {
257 target = MotionState3D(
258 path[0], target.getOrientation(),
259 Speed3D(0, 0, 0, SpeedUnit::MPS),
260 AngularVelocity3D(0, 0, 0, AngularVelocityUnit::RadPerSec));
263 MotionState3D(path[0], target.getOrientation(), target.getSpeed(),
264 target.getAngularVelocity());
268 handleOnGoalAction();
275 float circlePhase = 0.0f;
276 float circleRadius = 5.0f;
277 float circleAngularSpeed = 0.5f;
279 bool circleInitialized =
false;
281 void handleOnGoalAction() {
283 if (onGoalCallback) {
284 if (onGoalCallback(onGoaldRef)) {
289 switch (onGoalAction) {
290 case OnGoalAction::Stop:
292 linearCmd = Speed3D(0, 0, 0,
SpeedUnit::MPS);
296 case OnGoalAction::HoldPosition:
299 case OnGoalAction::Circle:
301 if (!circleInitialized) {
302 circleCenter = motionState.getPosition();
304 circleInitialized =
true;
306 circlePhase += circleAngularSpeed;
307 if (circlePhase > 2.0f *
static_cast<
float>(M_PI))
308 circlePhase -= 2.0f *
static_cast<
float>(M_PI);
311 float x = circleCenter.x + circleRadius * std::cos(circlePhase);
312 float y = circleCenter.y + circleRadius * std::sin(circlePhase);
313 float z = circleCenter.z;
315 float yaw = std::atan2(y - circleCenter.y, x - circleCenter.x) +
316 static_cast<
float>(M_PI_2);
317 Orientation3D orientation(yaw, target.getOrientation().pitch,
318 target.getOrientation().roll);
319 target = MotionState3D(
320 Coordinate<DistanceM>(x, y, z), orientation,
321 Speed3D(0, 0, 0, SpeedUnit::MPS),
322 AngularVelocity3D(0, 0, 0, AngularVelocityUnit::RadPerSec));
Represents a 3D angular velocity vector with unit support.
Definition: AngularVelocity.h:134
A generic 3D coordinate class for robotics, navigation, and spatial calculations.
Definition: Coordinate.h:57
Interface for representing the motion state of a robot in 3D space.
Definition: MotionState3D.h:34
3D motion/path controller using PID for position and orientation.
Definition: MotionController3D.h:62
void setCircleAngularSpeed(float angularSpeed)
Set the angular speed for circular motion (radians per update).
Definition: MotionController3D.h:192
bool initializeDtFromUpdates()
Definition: MotionController3D.h:220
bool update()
Update the controller and compute new commands.
Definition: MotionController3D.h:123
void end()
Stop the controller and the vehicle.
Definition: MotionController3D.h:116
void begin()
Initialize controller and set target from first path coordinate if available.
Definition: MotionController3D.h:101
void setCircleRadius(float radius)
Set the radius for circular motion (meters).
Definition: MotionController3D.h:184
void setOnGoalAction(OnGoalAction action)
Set the OnGoalAction behavior (Stop, HoldPosition, Circle). Resets circle mode state if changed.
Definition: MotionController3D.h:166
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: MotionController3D.h:176
Represents the full 3D motion state of a robot or vehicle.
Definition: MotionState3D.h:53
Simple 3D orientation class (yaw, pitch, roll in radians)
Definition: Orientation3D.h:13
Implements a simple, header-only Proportional-Integral-Derivative (PID) controller.
Definition: PIDController.h:56
AngularVelocityUnit
Supported angular velocity units for conversion and representation.
Definition: AngularVelocity.h:11
SpeedUnit
Supported speed units for conversion and representation.
Definition: Speed.h:10