2 ******************************************************************************
3 * @addtogroup LibrePilotModules LibrePilot Modules
5 * @addtogroup PathPlanner Path Planner Module
6 * @brief Executes a series of waypoints
10 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
11 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2015.
12 * @brief Executes a series of waypoints
14 * @see The GNU Public License (GPL) Version 3
16 *****************************************************************************/
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
28 * You should have received a copy of the GNU General Public License along
29 * with this program; if not, write to the Free Software Foundation, Inc.,
30 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include "openpilot.h"
35 #include "callbackinfo.h"
37 #include "flightstatus.h"
38 #include "airspeedstate.h"
39 #include "pathaction.h"
40 #include "pathdesired.h"
41 #include "pathstatus.h"
42 #include "positionstate.h"
43 #include "velocitystate.h"
45 #include "waypointactive.h"
46 #include "flightmodesettings.h"
47 #include <systemsettings.h>
50 #include <sanitycheck.h>
51 #include <vtolpathfollowersettings.h>
52 #include <manualcontrolcommand.h>
55 #define STACK_SIZE_BYTES 1024
56 #define TASK_PRIORITY CALLBACK_TASK_NAVIGATION
57 #define MAX_QUEUE_SIZE 2
58 #define PATH_PLANNER_UPDATE_RATE_MS 100 // can be slow, since we listen to status updates as well
63 static void pathPlannerTask();
64 static void commandUpdated(UAVObjEvent
*ev
);
65 static void statusUpdated(UAVObjEvent
*ev
);
66 static void updatePathDesired();
67 static void setWaypoint(uint16_t num
);
69 static uint8_t checkPathPlan();
70 static uint8_t pathConditionCheck();
71 static uint8_t conditionNone();
72 static uint8_t conditionTimeOut();
73 static uint8_t conditionDistanceToTarget();
74 static uint8_t conditionLegRemaining();
75 static uint8_t conditionBelowError();
76 static uint8_t conditionAboveAltitude();
77 static uint8_t conditionAboveSpeed();
78 static uint8_t conditionPointingTowardsNext();
79 static uint8_t conditionPythonScript();
80 static uint8_t conditionImmediate();
81 static void SettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
);
85 static DelayedCallbackInfo
*pathPlannerHandle
;
86 static DelayedCallbackInfo
*pathDesiredUpdaterHandle
;
87 static WaypointActiveData waypointActive
;
88 static WaypointData waypoint
;
89 static PathActionData pathAction
;
90 static bool pathplanner_active
= false;
91 static FrameType_t frameType
;
94 extern FrameType_t
GetCurrentFrameType();
97 * Module initialization
99 int32_t PathPlannerStart()
102 // when the active waypoint changes, update pathDesired
103 WaypointConnectCallback(commandUpdated
);
104 WaypointActiveConnectCallback(commandUpdated
);
105 PathActionConnectCallback(commandUpdated
);
106 PathStatusConnectCallback(statusUpdated
);
107 SettingsUpdatedCb(NULL
);
108 SystemSettingsConnectCallback(&SettingsUpdatedCb
);
109 VtolPathFollowerSettingsConnectCallback(&SettingsUpdatedCb
);
111 // Start main task callback
112 PIOS_CALLBACKSCHEDULER_Dispatch(pathPlannerHandle
);
118 * Module initialization
120 int32_t PathPlannerInitialize()
122 PathPlanInitialize();
123 PathActionInitialize();
124 PathStatusInitialize();
125 PathDesiredInitialize();
126 PositionStateInitialize();
127 AirspeedStateInitialize();
128 VelocityStateInitialize();
129 WaypointInitialize();
130 WaypointActiveInitialize();
132 pathPlannerHandle
= PIOS_CALLBACKSCHEDULER_Create(&pathPlannerTask
, CALLBACK_PRIORITY_REGULAR
, TASK_PRIORITY
, CALLBACKINFO_RUNNING_PATHPLANNER0
, STACK_SIZE_BYTES
);
133 pathDesiredUpdaterHandle
= PIOS_CALLBACKSCHEDULER_Create(&updatePathDesired
, CALLBACK_PRIORITY_CRITICAL
, TASK_PRIORITY
, CALLBACKINFO_RUNNING_PATHPLANNER1
, STACK_SIZE_BYTES
);
138 MODULE_INITCALL(PathPlannerInitialize
, PathPlannerStart
);
141 static void SettingsUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
143 VtolPathFollowerSettingsTreatCustomCraftAsOptions TreatCustomCraftAs
;
145 VtolPathFollowerSettingsTreatCustomCraftAsGet(&TreatCustomCraftAs
);
147 frameType
= GetCurrentFrameType();
149 if (frameType
== FRAME_TYPE_CUSTOM
) {
150 switch (TreatCustomCraftAs
) {
151 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_FIXEDWING
:
152 frameType
= FRAME_TYPE_FIXED_WING
;
154 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_VTOL
:
155 frameType
= FRAME_TYPE_MULTIROTOR
;
157 case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_GROUND
:
158 frameType
= FRAME_TYPE_GROUND
;
164 case FRAME_TYPE_GROUND
:
172 #define AUTOTAKEOFF_THROTTLE_LIMIT_TO_ALLOW_TAKEOFF_START 0.3f
177 static void pathPlannerTask()
179 PIOS_CALLBACKSCHEDULER_Schedule(pathPlannerHandle
, PATH_PLANNER_UPDATE_RATE_MS
, CALLBACK_UPDATEMODE_SOONER
);
181 bool endCondition
= false;
183 // check path plan validity early to raise alarm
184 // even if not in guided mode
185 uint8_t validPathPlan
= checkPathPlan();
187 FlightStatusData flightStatus
;
188 FlightStatusGet(&flightStatus
);
189 if (flightStatus
.ControlChain
.PathPlanner
!= FLIGHTSTATUS_CONTROLCHAIN_TRUE
) {
190 pathplanner_active
= false;
191 AlarmsClear(SYSTEMALARMS_ALARM_PATHPLAN
);
195 PathDesiredData pathDesired
;
196 PathDesiredGet(&pathDesired
);
198 static uint8_t failsafeRTHset
= 0;
199 if (!validPathPlan
) {
200 pathplanner_active
= false;
202 if (!failsafeRTHset
) {
204 plan_setup_positionHold();
206 AlarmsSet(SYSTEMALARMS_ALARM_PATHPLAN
, SYSTEMALARMS_ALARM_CRITICAL
);
212 AlarmsClear(SYSTEMALARMS_ALARM_PATHPLAN
);
214 WaypointActiveGet(&waypointActive
);
216 // with the introduction of takeoff, we allow for arming
217 // whilst in pathplanner mode. Previously it was just an assumption that
218 // a user never armed in pathplanner mode. This check allows a user to select
219 // pathplanner, to upload waypoints, and then arm in pathplanner.
220 if (!flightStatus
.Armed
) {
224 // the transition from pathplanner to another flightmode back to pathplanner
225 // triggers a reset back to 0 index in the waypoint list
226 if (pathplanner_active
== false) {
227 pathplanner_active
= true;
229 FlightModeSettingsFlightModeChangeRestartsPathPlanOptions restart
;
230 FlightModeSettingsFlightModeChangeRestartsPathPlanGet(&restart
);
231 if (restart
== FLIGHTMODESETTINGS_FLIGHTMODECHANGERESTARTSPATHPLAN_TRUE
) {
237 WaypointInstGet(waypointActive
.Index
, &waypoint
);
238 PathActionInstGet(waypoint
.Action
, &pathAction
);
239 PathStatusData pathStatus
;
240 PathStatusGet(&pathStatus
);
242 // delay next step until path follower has acknowledged the path mode
243 if (pathStatus
.UID
!= pathDesired
.UID
) {
247 // negative destinations DISABLE this feature
248 if (pathStatus
.Status
== PATHSTATUS_STATUS_CRITICAL
&& waypointActive
.Index
!= pathAction
.ErrorDestination
&& pathAction
.ErrorDestination
>= 0) {
249 setWaypoint(pathAction
.ErrorDestination
);
254 // check if condition has been met
255 endCondition
= pathConditionCheck();
257 switch (pathAction
.Command
) {
258 case PATHACTION_COMMAND_ONNOTCONDITIONNEXTWAYPOINT
:
259 endCondition
= !endCondition
;
260 case PATHACTION_COMMAND_ONCONDITIONNEXTWAYPOINT
:
262 setWaypoint(waypointActive
.Index
+ 1);
265 case PATHACTION_COMMAND_ONNOTCONDITIONJUMPWAYPOINT
:
266 endCondition
= !endCondition
;
267 case PATHACTION_COMMAND_ONCONDITIONJUMPWAYPOINT
:
269 if (pathAction
.JumpDestination
< 0) {
270 // waypoint ids <0 code relative jumps
271 setWaypoint(waypointActive
.Index
- pathAction
.JumpDestination
);
273 setWaypoint(pathAction
.JumpDestination
);
277 case PATHACTION_COMMAND_IFCONDITIONJUMPWAYPOINTELSENEXTWAYPOINT
:
279 if (pathAction
.JumpDestination
< 0) {
280 // waypoint ids <0 code relative jumps
281 setWaypoint(waypointActive
.Index
- pathAction
.JumpDestination
);
283 setWaypoint(pathAction
.JumpDestination
);
286 setWaypoint(waypointActive
.Index
+ 1);
292 // callback function when waypoints changed in any way, update pathDesired
293 void updatePathDesired()
295 // only ever touch pathDesired if pathplanner is enabled
296 if (!pathplanner_active
) {
300 // find out current waypoint
301 WaypointActiveGet(&waypointActive
);
302 WaypointInstGet(waypointActive
.Index
, &waypoint
);
304 PathActionInstGet(waypoint
.Action
, &pathAction
);
306 PathDesiredData pathDesired
;
308 pathDesired
.End
.North
= waypoint
.Position
.North
;
309 pathDesired
.End
.East
= waypoint
.Position
.East
;
310 pathDesired
.End
.Down
= waypoint
.Position
.Down
;
311 pathDesired
.EndingVelocity
= waypoint
.Velocity
;
312 pathDesired
.Mode
= pathAction
.Mode
;
313 pathDesired
.ModeParameters
[0] = pathAction
.ModeParameters
[0];
314 pathDesired
.ModeParameters
[1] = pathAction
.ModeParameters
[1];
315 pathDesired
.ModeParameters
[2] = pathAction
.ModeParameters
[2];
316 pathDesired
.ModeParameters
[3] = pathAction
.ModeParameters
[3];
317 pathDesired
.UID
= waypointActive
.Index
;
320 if (waypointActive
.Index
== 0) {
321 PositionStateData positionState
;
322 PositionStateGet(&positionState
);
323 // First waypoint has itself as start point (used to be home position but that proved dangerous when looping)
325 /*pathDesired.Start[PATHDESIRED_START_NORTH] = waypoint.Position[WAYPOINT_POSITION_NORTH];
326 pathDesired.Start[PATHDESIRED_START_EAST] = waypoint.Position[WAYPOINT_POSITION_EAST];
327 pathDesired.Start[PATHDESIRED_START_DOWN] = waypoint.Position[WAYPOINT_POSITION_DOWN];*/
328 // note: if certain flightmodes need to override Start, End or mode parameters, that should happen within
329 // the pathfollower as needed. This also holds if Start is replaced by current position for takeoff and landing
330 pathDesired
.Start
.North
= positionState
.North
;
331 pathDesired
.Start
.East
= positionState
.East
;
332 pathDesired
.Start
.Down
= positionState
.Down
;
333 pathDesired
.StartingVelocity
= pathDesired
.EndingVelocity
;
335 // Get previous waypoint as start point
336 WaypointData waypointPrev
;
337 WaypointInstGet(waypointActive
.Index
- 1, &waypointPrev
);
339 pathDesired
.Start
.North
= waypointPrev
.Position
.North
;
340 pathDesired
.Start
.East
= waypointPrev
.Position
.East
;
341 pathDesired
.Start
.Down
= waypointPrev
.Position
.Down
;
342 pathDesired
.StartingVelocity
= waypointPrev
.Velocity
;
345 PathDesiredSet(&pathDesired
);
349 // safety checks for path plan integrity
350 static uint8_t checkPathPlan()
353 uint16_t waypointCount
;
354 uint16_t actionCount
;
356 PathPlanData pathPlan
;
358 // WaypointData waypoint; // using global instead (?)
359 // PathActionData action; // using global instead (?)
361 PathPlanGet(&pathPlan
);
363 waypointCount
= pathPlan
.WaypointCount
;
364 if (waypointCount
== 0) {
365 // an empty path plan is invalid
368 actionCount
= pathPlan
.PathActionCount
;
370 // check count consistency
371 if (waypointCount
> UAVObjGetNumInstances(WaypointHandle())) {
372 // PIOS_DEBUGLOG_Printf("PathPlan : waypoint count error!");
375 if (actionCount
> UAVObjGetNumInstances(PathActionHandle())) {
376 // PIOS_DEBUGLOG_Printf("PathPlan : path action count error!");
382 for (i
= 0; i
< waypointCount
; i
++) {
383 pathCrc
= UAVObjUpdateCRC(WaypointHandle(), i
, pathCrc
);
385 for (i
= 0; i
< actionCount
; i
++) {
386 pathCrc
= UAVObjUpdateCRC(PathActionHandle(), i
, pathCrc
);
388 if (pathCrc
!= pathPlan
.Crc
) {
390 // PIOS_DEBUGLOG_Printf("PathPlan : bad CRC (%d / %d)!", pathCrc, pathPlan.Crc);
394 // waypoint consistency
395 for (i
= 0; i
< waypointCount
; i
++) {
396 WaypointInstGet(i
, &waypoint
);
397 if (waypoint
.Action
>= actionCount
) {
398 // path action id is out of range
403 // path action consistency
404 for (i
= 0; i
< actionCount
; i
++) {
405 PathActionInstGet(i
, &pathAction
);
406 if (pathAction
.ErrorDestination
>= waypointCount
) {
407 // waypoint id is out of range
410 if (pathAction
.JumpDestination
>= waypointCount
) {
411 // waypoint id is out of range
416 // path plan passed checks
421 // callback function when status changed, issue execution of state machine
422 void commandUpdated(__attribute__((unused
)) UAVObjEvent
*ev
)
424 PIOS_CALLBACKSCHEDULER_Dispatch(pathDesiredUpdaterHandle
);
427 // callback function when waypoints changed in any way, update pathDesired
428 void statusUpdated(__attribute__((unused
)) UAVObjEvent
*ev
)
430 PIOS_CALLBACKSCHEDULER_Dispatch(pathPlannerHandle
);
434 // helper function to go to a specific waypoint
435 static void setWaypoint(uint16_t num
)
437 PathPlanData pathPlan
;
439 PathPlanGet(&pathPlan
);
441 // here it is assumed that the path plan has been validated (waypoint count is consistent)
442 if (num
>= pathPlan
.WaypointCount
) {
443 // path plans wrap around
447 waypointActive
.Index
= num
;
448 WaypointActiveSet(&waypointActive
);
451 // execute the appropriate condition and report result
452 static uint8_t pathConditionCheck()
454 // i thought about a lookup table, but a switch is safer considering there could be invalid EndCondition ID's
455 switch (pathAction
.EndCondition
) {
456 case PATHACTION_ENDCONDITION_NONE
:
457 return conditionNone();
460 case PATHACTION_ENDCONDITION_TIMEOUT
:
461 return conditionTimeOut();
464 case PATHACTION_ENDCONDITION_DISTANCETOTARGET
:
465 return conditionDistanceToTarget();
468 case PATHACTION_ENDCONDITION_LEGREMAINING
:
469 return conditionLegRemaining();
472 case PATHACTION_ENDCONDITION_BELOWERROR
:
473 return conditionBelowError();
476 case PATHACTION_ENDCONDITION_ABOVEALTITUDE
:
477 return conditionAboveAltitude();
480 case PATHACTION_ENDCONDITION_ABOVESPEED
:
481 return conditionAboveSpeed();
484 case PATHACTION_ENDCONDITION_POINTINGTOWARDSNEXT
:
485 return conditionPointingTowardsNext();
488 case PATHACTION_ENDCONDITION_PYTHONSCRIPT
:
489 return conditionPythonScript();
492 case PATHACTION_ENDCONDITION_IMMEDIATE
:
493 return conditionImmediate();
497 // invalid endconditions are always true to prevent freezes
504 /* the None condition is always false */
505 static uint8_t conditionNone()
511 * the Timeout condition measures time this waypoint is active
512 * Parameter 0: timeout in seconds
514 static uint8_t conditionTimeOut()
516 static uint16_t toWaypoint
;
517 static uint32_t toStarttime
;
519 // reset timer if waypoint changed
520 if (waypointActive
.Index
!= toWaypoint
) {
521 toWaypoint
= waypointActive
.Index
;
522 toStarttime
= PIOS_DELAY_GetRaw();
524 if (PIOS_DELAY_DiffuS(toStarttime
) >= 1e6f
* pathAction
.ConditionParameters
[0]) {
525 // make sure we reinitialize even if the same waypoint comes twice
533 * the DistanceToTarget measures distance to a waypoint
534 * returns true if closer
535 * Parameter 0: distance in meters
536 * Parameter 1: flag: 0=2d 1=3d
538 static uint8_t conditionDistanceToTarget()
541 PositionStateData positionState
;
543 PositionStateGet(&positionState
);
544 if (pathAction
.ConditionParameters
[1] > 0.5f
) {
545 distance
= sqrtf(powf(waypoint
.Position
.North
- positionState
.North
, 2)
546 + powf(waypoint
.Position
.East
- positionState
.East
, 2)
547 + powf(waypoint
.Position
.Down
- positionState
.Down
, 2));
549 distance
= sqrtf(powf(waypoint
.Position
.North
- positionState
.North
, 2)
550 + powf(waypoint
.Position
.East
- positionState
.East
, 2));
553 if (distance
<= pathAction
.ConditionParameters
[0]) {
561 * the LegRemaining measures how far the pathfollower got on a linear path segment
562 * returns true if closer to destination (path more complete)
563 * Parameter 0: relative distance (0= complete, 1= just starting)
565 static uint8_t conditionLegRemaining()
567 PathStatusData pathStatus
;
569 PathStatusGet(&pathStatus
);
571 if (pathStatus
.fractional_progress
>= (1.0f
- pathAction
.ConditionParameters
[0])) {
578 * the BelowError measures the error on a path segment
579 * returns true if error is below margin
580 * Parameter 0: error margin (in m)
582 static uint8_t conditionBelowError()
584 PathDesiredData pathDesired
;
585 PositionStateData positionState
;
587 PathDesiredGet(&pathDesired
);
588 PositionStateGet(&positionState
);
590 float cur
[3] = { positionState
.North
, positionState
.East
, positionState
.Down
};
591 struct path_status progress
;
593 path_progress(&pathDesired
,
594 cur
, &progress
, mode3D
);
595 if (progress
.error
<= pathAction
.ConditionParameters
[0]) {
602 * the AboveAltitude measures the flight altitude relative to home position
603 * returns true if above critical altitude
604 * WARNING! Altitudes are always negative (down coordinate)
605 * Parameter 0: altitude in meters (negative!)
607 static uint8_t conditionAboveAltitude()
609 PositionStateData positionState
;
611 PositionStateGet(&positionState
);
613 if (positionState
.Down
<= pathAction
.ConditionParameters
[0]) {
620 * the AboveSpeed measures the movement speed (3d)
621 * returns true if above critical speed
622 * Parameter 0: speed in m/s
623 * Parameter 1: flag: 0=groundspeed 1=airspeed
625 static uint8_t conditionAboveSpeed()
627 VelocityStateData velocityState
;
629 VelocityStateGet(&velocityState
);
630 float velocity
= sqrtf(velocityState
.North
* velocityState
.North
+ velocityState
.East
* velocityState
.East
+ velocityState
.Down
* velocityState
.Down
);
632 // use airspeed if requested and available
633 if (pathAction
.ConditionParameters
[1] > 0.5f
) {
634 AirspeedStateData airspeed
;
635 AirspeedStateGet(&airspeed
);
636 velocity
= airspeed
.CalibratedAirspeed
;
639 if (velocity
>= pathAction
.ConditionParameters
[0]) {
647 * the PointingTowardsNext measures the horizontal movement vector direction relative to the next waypoint
648 * regardless whether this waypoint will ever be active (Command could jump to another one on true)
649 * This is useful for curve segments where the craft should stop circling when facing a certain way (usually the next waypoint)
650 * returns true if within a certain angular margin
651 * Parameter 0: degrees variation allowed
653 static uint8_t conditionPointingTowardsNext()
655 uint16_t nextWaypointId
= waypointActive
.Index
+ 1;
657 if (nextWaypointId
>= UAVObjGetNumInstances(WaypointHandle())) {
660 WaypointData nextWaypoint
;
661 WaypointInstGet(nextWaypointId
, &nextWaypoint
);
663 float angle1
= atan2f((nextWaypoint
.Position
.North
- waypoint
.Position
.North
), (nextWaypoint
.Position
.East
- waypoint
.Position
.East
));
665 VelocityStateData velocity
;
666 VelocityStateGet(&velocity
);
667 float angle2
= atan2f(velocity
.North
, velocity
.East
);
669 // calculate the absolute angular difference
670 angle1
= fabsf(RAD2DEG(angle1
- angle2
));
671 while (angle1
> 360) {
675 if (angle1
<= pathAction
.ConditionParameters
[0]) {
682 * the PythonScript is supposed to read the output of a PyMite program running at the same time
683 * and return based on its output, likely read out through some to be defined UAVObject
684 * TODO XXX NOT YET IMPLEMENTED
685 * returns always true until implemented
686 * Parameter 0-3: defined by user script
688 static uint8_t conditionPythonScript()
693 /* the immediate condition is always true */
694 static uint8_t conditionImmediate()