LP-500 HoTT Bridge Module ported from TauLabs
[librepilot.git] / flight / modules / PathFollower / fixedwingflycontroller.cpp
blob1dcbd08d9a6bacaf9f0fb52782629bc2edda915f
1 /*
2 ******************************************************************************
4 * @file FixedWingFlyController.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2015.
7 * @brief Fixed wing fly controller implementation
8 * @see The GNU Public License (GPL) Version 3
10 * @addtogroup LibrePilot LibrePilotModules Modules PathFollower Navigation
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 extern "C" {
30 #include <openpilot.h>
32 #include <pid.h>
33 #include <sin_lookup.h>
34 #include <pathdesired.h>
35 #include <paths.h>
36 #include <fixedwingpathfollowersettings.h>
37 #include <fixedwingpathfollowerstatus.h>
38 #include <flightstatus.h>
39 #include <pathstatus.h>
40 #include <positionstate.h>
41 #include <velocitystate.h>
42 #include <velocitydesired.h>
43 #include <stabilizationdesired.h>
44 #include <airspeedstate.h>
45 #include <attitudestate.h>
46 #include <systemsettings.h>
49 // C++ includes
50 #include "fixedwingflycontroller.h"
52 // Private constants
54 // pointer to a singleton instance
55 FixedWingFlyController *FixedWingFlyController::p_inst = 0;
57 FixedWingFlyController::FixedWingFlyController()
58 : fixedWingSettings(NULL), mActive(false), mMode(0), indicatedAirspeedStateBias(0.0f)
61 // Called when mode first engaged
62 void FixedWingFlyController::Activate(void)
64 if (!mActive) {
65 mActive = true;
66 SettingsUpdated();
67 resetGlobals();
68 mMode = pathDesired->Mode;
69 lastAirspeedUpdate = 0;
73 uint8_t FixedWingFlyController::IsActive(void)
75 return mActive;
78 uint8_t FixedWingFlyController::Mode(void)
80 return mMode;
83 // Objective updated in pathdesired
84 void FixedWingFlyController::ObjectiveUpdated(void)
87 void FixedWingFlyController::Deactivate(void)
89 if (mActive) {
90 mActive = false;
91 resetGlobals();
96 void FixedWingFlyController::SettingsUpdated(void)
98 // fixed wing PID only
99 pid_configure(&PIDposH[0], fixedWingSettings->HorizontalPosP, 0.0f, 0.0f, 0.0f);
100 pid_configure(&PIDposH[1], fixedWingSettings->HorizontalPosP, 0.0f, 0.0f, 0.0f);
101 pid_configure(&PIDposV, fixedWingSettings->VerticalPosP, 0.0f, 0.0f, 0.0f);
103 pid_configure(&PIDcourse, fixedWingSettings->CoursePI.Kp, fixedWingSettings->CoursePI.Ki, 0.0f, fixedWingSettings->CoursePI.ILimit);
104 pid_configure(&PIDspeed, fixedWingSettings->SpeedPI.Kp, fixedWingSettings->SpeedPI.Ki, 0.0f, fixedWingSettings->SpeedPI.ILimit);
105 pid_configure(&PIDpower, fixedWingSettings->PowerPI.Kp, fixedWingSettings->PowerPI.Ki, 0.0f, fixedWingSettings->PowerPI.ILimit);
109 * Initialise the module, called on startup
110 * \returns 0 on success or -1 if initialisation failed
112 int32_t FixedWingFlyController::Initialize(FixedWingPathFollowerSettingsData *ptr_fixedWingSettings)
114 PIOS_Assert(ptr_fixedWingSettings);
116 fixedWingSettings = ptr_fixedWingSettings;
118 resetGlobals();
120 return 0;
124 * reset integrals
126 void FixedWingFlyController::resetGlobals()
128 pid_zero(&PIDposH[0]);
129 pid_zero(&PIDposH[1]);
130 pid_zero(&PIDposV);
131 pid_zero(&PIDcourse);
132 pid_zero(&PIDspeed);
133 pid_zero(&PIDpower);
134 pathStatus->path_time = 0.0f;
137 void FixedWingFlyController::UpdateAutoPilot()
139 uint8_t result = updateAutoPilotFixedWing();
141 if (result) {
142 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE, SYSTEMALARMS_ALARM_OK);
143 } else {
144 pathStatus->Status = PATHSTATUS_STATUS_CRITICAL;
145 AlarmsSet(SYSTEMALARMS_ALARM_GUIDANCE, SYSTEMALARMS_ALARM_WARNING);
148 PathStatusSet(pathStatus);
152 * fixed wing autopilot:
153 * straight forward:
154 * 1. update path velocity for limited motion crafts
155 * 2. update attitude according to default fixed wing pathfollower algorithm
157 uint8_t FixedWingFlyController::updateAutoPilotFixedWing()
159 updatePathVelocity(fixedWingSettings->CourseFeedForward, true);
160 return updateFixedDesiredAttitude();
164 * Compute desired velocity from the current position and path
166 void FixedWingFlyController::updatePathVelocity(float kFF, bool limited)
168 PositionStateData positionState;
170 PositionStateGet(&positionState);
171 VelocityStateData velocityState;
172 VelocityStateGet(&velocityState);
173 VelocityDesiredData velocityDesired;
175 const float dT = fixedWingSettings->UpdatePeriod / 1000.0f;
177 // look ahead kFF seconds
178 float cur[3] = { positionState.North + (velocityState.North * kFF),
179 positionState.East + (velocityState.East * kFF),
180 positionState.Down + (velocityState.Down * kFF) };
181 struct path_status progress;
182 path_progress(pathDesired, cur, &progress, true);
184 // calculate velocity - can be zero if waypoints are too close
185 velocityDesired.North = progress.path_vector[0];
186 velocityDesired.East = progress.path_vector[1];
187 velocityDesired.Down = progress.path_vector[2];
189 if (limited &&
190 // if a plane is crossing its desired flightpath facing the wrong way (away from flight direction)
191 // it would turn towards the flightpath to get on its desired course. This however would reverse the correction vector
192 // once it crosses the flightpath again, which would make it again turn towards the flightpath (but away from its desired heading)
193 // leading to an S-shape snake course the wrong way
194 // this only happens especially if HorizontalPosP is too high, as otherwise the angle between velocity desired and path_direction won't
195 // turn steep unless there is enough space complete the turn before crossing the flightpath
196 // in this case the plane effectively needs to be turned around
197 // indicators:
198 // difference between correction_direction and velocitystate >90 degrees and
199 // difference between path_direction and velocitystate >90 degrees ( 4th sector, facing away from everything )
200 // fix: ignore correction, steer in path direction until the situation has become better (condition doesn't apply anymore)
201 // calculating angles < 90 degrees through dot products
202 (vector_lengthf(progress.path_vector, 2) > 1e-6f) &&
203 ((progress.path_vector[0] * velocityState.North + progress.path_vector[1] * velocityState.East) < 0.0f) &&
204 ((progress.correction_vector[0] * velocityState.North + progress.correction_vector[1] * velocityState.East) < 0.0f)) {
206 } else {
207 // calculate correction
208 velocityDesired.North += pid_apply(&PIDposH[0], progress.correction_vector[0], dT);
209 velocityDesired.East += pid_apply(&PIDposH[1], progress.correction_vector[1], dT);
211 velocityDesired.Down += pid_apply(&PIDposV, progress.correction_vector[2], dT);
213 // update pathstatus
214 pathStatus->error = progress.error;
215 pathStatus->fractional_progress = progress.fractional_progress;
216 pathStatus->path_direction_north = progress.path_vector[0];
217 pathStatus->path_direction_east = progress.path_vector[1];
218 pathStatus->path_direction_down = progress.path_vector[2];
220 pathStatus->correction_direction_north = progress.correction_vector[0];
221 pathStatus->correction_direction_east = progress.correction_vector[1];
222 pathStatus->correction_direction_down = progress.correction_vector[2];
225 VelocityDesiredSet(&velocityDesired);
230 * Compute desired attitude from the desired velocity for fixed wing craft
232 uint8_t FixedWingFlyController::updateFixedDesiredAttitude()
234 uint8_t result = 1;
235 bool cutThrust = false;
236 bool hasAirspeed = true;
238 const float dT = fixedWingSettings->UpdatePeriod / 1000.0f;
240 VelocityDesiredData velocityDesired;
241 VelocityStateData velocityState;
242 StabilizationDesiredData stabDesired;
243 AttitudeStateData attitudeState;
244 FixedWingPathFollowerStatusData fixedWingPathFollowerStatus;
245 AirspeedStateData airspeedState;
246 SystemSettingsData systemSettings;
248 float groundspeedProjection;
249 float indicatedAirspeedState;
250 float indicatedAirspeedDesired;
251 float airspeedError = 0.0f;
253 float pitchCommand;
255 float descentspeedDesired;
256 float descentspeedError;
257 float powerCommand;
259 float airspeedVector[2];
260 float fluidMovement[2];
261 float courseComponent[2];
262 float courseError;
263 float courseCommand;
265 FixedWingPathFollowerStatusGet(&fixedWingPathFollowerStatus);
267 VelocityStateGet(&velocityState);
268 StabilizationDesiredGet(&stabDesired);
269 VelocityDesiredGet(&velocityDesired);
270 AttitudeStateGet(&attitudeState);
271 AirspeedStateGet(&airspeedState);
272 SystemSettingsGet(&systemSettings);
276 * Compute speed error and course
279 // check for airspeed sensor
280 fixedWingPathFollowerStatus.Errors.AirspeedSensor = 0;
281 if (fixedWingSettings->UseAirspeedSensor == FIXEDWINGPATHFOLLOWERSETTINGS_USEAIRSPEEDSENSOR_FALSE) {
282 // fallback algo triggered voluntarily
283 hasAirspeed = false;
284 fixedWingPathFollowerStatus.Errors.AirspeedSensor = 1;
285 } else if (PIOS_DELAY_GetuSSince(lastAirspeedUpdate) > 1000000) {
286 // no airspeed update in one second, assume airspeed sensor failure
287 hasAirspeed = false;
288 result = 0;
289 fixedWingPathFollowerStatus.Errors.AirspeedSensor = 1;
293 if (hasAirspeed) {
294 // missing sensors for airspeed-direction we have to assume within
295 // reasonable error that measured airspeed is actually the airspeed
296 // component in forward pointing direction
297 // airspeedVector is normalized
298 airspeedVector[0] = cos_lookup_deg(attitudeState.Yaw);
299 airspeedVector[1] = sin_lookup_deg(attitudeState.Yaw);
301 // current ground speed projected in forward direction
302 groundspeedProjection = velocityState.North * airspeedVector[0] + velocityState.East * airspeedVector[1];
304 // note that airspeedStateBias is ( calibratedAirspeed - groundspeedProjection ) at the time of measurement,
305 // but thanks to accelerometers, groundspeedProjection reacts faster to changes in direction
306 // than airspeed and gps sensors alone
307 indicatedAirspeedState = groundspeedProjection + indicatedAirspeedStateBias;
309 // fluidMovement is a vector describing the aproximate movement vector of
310 // the surrounding fluid in 2d space (aka wind vector)
311 fluidMovement[0] = velocityState.North - (indicatedAirspeedState * airspeedVector[0]);
312 fluidMovement[1] = velocityState.East - (indicatedAirspeedState * airspeedVector[1]);
314 // calculate the movement vector we need to fly to reach velocityDesired -
315 // taking fluidMovement into account
316 courseComponent[0] = velocityDesired.North - fluidMovement[0];
317 courseComponent[1] = velocityDesired.East - fluidMovement[1];
319 indicatedAirspeedDesired = boundf(sqrtf(courseComponent[0] * courseComponent[0] + courseComponent[1] * courseComponent[1]),
320 fixedWingSettings->HorizontalVelMin,
321 fixedWingSettings->HorizontalVelMax);
323 // if we could fly at arbitrary speeds, we'd just have to move towards the
324 // courseComponent vector as previously calculated and we'd be fine
325 // unfortunately however we are bound by min and max air speed limits, so
326 // we need to recalculate the correct course to meet at least the
327 // velocityDesired vector direction at our current speed
328 // this overwrites courseComponent
329 bool valid = correctCourse(courseComponent, (float *)&velocityDesired.North, fluidMovement, indicatedAirspeedDesired);
331 // Error condition: wind speed too high, we can't go where we want anymore
332 fixedWingPathFollowerStatus.Errors.Wind = 0;
333 if ((!valid) &&
334 fixedWingSettings->Safetymargins.Wind > 0.5f) { // alarm switched on
335 fixedWingPathFollowerStatus.Errors.Wind = 1;
336 result = 0;
339 // Airspeed error
340 airspeedError = indicatedAirspeedDesired - indicatedAirspeedState;
342 // Error condition: plane too slow or too fast
343 fixedWingPathFollowerStatus.Errors.Highspeed = 0;
344 fixedWingPathFollowerStatus.Errors.Lowspeed = 0;
345 if (indicatedAirspeedState > systemSettings.AirSpeedMax * fixedWingSettings->Safetymargins.Overspeed) {
346 fixedWingPathFollowerStatus.Errors.Overspeed = 1;
347 result = 0;
349 if (indicatedAirspeedState > fixedWingSettings->HorizontalVelMax * fixedWingSettings->Safetymargins.Highspeed) {
350 fixedWingPathFollowerStatus.Errors.Highspeed = 1;
351 result = 0;
352 cutThrust = true;
354 if (indicatedAirspeedState < fixedWingSettings->HorizontalVelMin * fixedWingSettings->Safetymargins.Lowspeed) {
355 fixedWingPathFollowerStatus.Errors.Lowspeed = 1;
356 result = 0;
358 if (indicatedAirspeedState < systemSettings.AirSpeedMin * fixedWingSettings->Safetymargins.Stallspeed) {
359 fixedWingPathFollowerStatus.Errors.Stallspeed = 1;
360 result = 0;
362 if (indicatedAirspeedState < fixedWingSettings->HorizontalVelMin * fixedWingSettings->Safetymargins.Lowspeed - fixedWingSettings->SafetyCutoffLimits.MaxDecelerationDeltaMPS) {
363 cutThrust = true;
364 result = 0;
368 // Vertical speed error
369 descentspeedDesired = boundf(
370 velocityDesired.Down,
371 -fixedWingSettings->VerticalVelMax,
372 fixedWingSettings->VerticalVelMax);
373 descentspeedError = descentspeedDesired - velocityState.Down;
376 * Compute desired thrust command
379 // Compute the cross feed from vertical speed to pitch, with saturation
380 float speedErrorToPowerCommandComponent = 0.0f;
381 if (hasAirspeed) {
382 speedErrorToPowerCommandComponent = boundf(
383 (airspeedError / fixedWingSettings->HorizontalVelMin) * fixedWingSettings->AirspeedToPowerCrossFeed.Kp,
384 -fixedWingSettings->AirspeedToPowerCrossFeed.Max,
385 fixedWingSettings->AirspeedToPowerCrossFeed.Max
389 // Compute final thrust response
390 powerCommand = pid_apply(&PIDpower, -descentspeedError, dT) +
391 speedErrorToPowerCommandComponent;
393 // Output internal state to telemetry
394 fixedWingPathFollowerStatus.Error.Power = descentspeedError;
395 fixedWingPathFollowerStatus.ErrorInt.Power = PIDpower.iAccumulator;
396 fixedWingPathFollowerStatus.Command.Power = powerCommand;
398 // set thrust
399 stabDesired.Thrust = boundf(fixedWingSettings->ThrustLimit.Neutral + powerCommand,
400 fixedWingSettings->ThrustLimit.Min,
401 fixedWingSettings->ThrustLimit.Max);
403 // Error condition: plane cannot hold altitude at current speed.
404 fixedWingPathFollowerStatus.Errors.Lowpower = 0;
405 if (fixedWingSettings->ThrustLimit.Neutral + powerCommand >= fixedWingSettings->ThrustLimit.Max && // thrust at maximum
406 velocityState.Down > 0.0f && // we ARE going down
407 descentspeedDesired < 0.0f && // we WANT to go up
408 airspeedError > 0.0f) { // we are too slow already
409 fixedWingPathFollowerStatus.Errors.Lowpower = 1;
411 if (fixedWingSettings->Safetymargins.Lowpower > 0.5f) { // alarm switched on
412 result = 0;
415 // Error condition: plane keeps climbing despite minimum thrust (opposite of above)
416 fixedWingPathFollowerStatus.Errors.Highpower = 0;
417 if (fixedWingSettings->ThrustLimit.Neutral + powerCommand <= fixedWingSettings->ThrustLimit.Min && // thrust at minimum
418 velocityState.Down < 0.0f && // we ARE going up
419 descentspeedDesired > 0.0f && // we WANT to go down
420 airspeedError < 0.0f) { // we are too fast already
421 // this alarm is often switched off because of false positives, however we still want to cut throttle if it happens
422 cutThrust = true;
423 fixedWingPathFollowerStatus.Errors.Highpower = 1;
425 if (fixedWingSettings->Safetymargins.Highpower > 0.5f) { // alarm switched on
426 result = 0;
431 * Compute desired pitch command
433 if (hasAirspeed) {
434 // Compute the cross feed from vertical speed to pitch, with saturation
435 float verticalSpeedToPitchCommandComponent = boundf(-descentspeedError * fixedWingSettings->VerticalToPitchCrossFeed.Kp,
436 -fixedWingSettings->VerticalToPitchCrossFeed.Max,
437 fixedWingSettings->VerticalToPitchCrossFeed.Max
440 // Compute the pitch command as err*Kp + errInt*Ki + X_feed.
441 pitchCommand = -pid_apply(&PIDspeed, airspeedError, dT) + verticalSpeedToPitchCommandComponent;
443 fixedWingPathFollowerStatus.Error.Speed = airspeedError;
444 fixedWingPathFollowerStatus.ErrorInt.Speed = PIDspeed.iAccumulator;
445 fixedWingPathFollowerStatus.Command.Speed = pitchCommand;
447 stabDesired.Pitch = boundf(fixedWingSettings->PitchLimit.Neutral + pitchCommand,
448 fixedWingSettings->PitchLimit.Min,
449 fixedWingSettings->PitchLimit.Max);
451 // Error condition: high speed dive
452 fixedWingPathFollowerStatus.Errors.Pitchcontrol = 0;
453 if (fixedWingSettings->PitchLimit.Neutral + pitchCommand >= fixedWingSettings->PitchLimit.Max && // pitch demand is full up
454 velocityState.Down > 0.0f && // we ARE going down
455 descentspeedDesired < 0.0f && // we WANT to go up
456 airspeedError < 0.0f && // we are too fast already
457 fixedWingSettings->Safetymargins.Pitchcontrol > 0.5f) { // alarm switched on
458 fixedWingPathFollowerStatus.Errors.Pitchcontrol = 1;
459 result = 0;
460 cutThrust = true;
462 } else {
463 // no airspeed sensor means we fly with constant pitch, like for landing pathfollower
464 stabDesired.Pitch = fixedWingSettings->LandingPitch;
467 // Error condition: pitch way out of wack
468 if (fixedWingSettings->Safetymargins.Pitchcontrol > 0.5f &&
469 (attitudeState.Pitch < fixedWingSettings->PitchLimit.Min - fixedWingSettings->SafetyCutoffLimits.PitchDeg ||
470 attitudeState.Pitch > fixedWingSettings->PitchLimit.Max + fixedWingSettings->SafetyCutoffLimits.PitchDeg)) {
471 fixedWingPathFollowerStatus.Errors.Pitchcontrol = 1;
472 result = 0;
473 cutThrust = true;
478 * Compute desired roll command
480 if (hasAirspeed) {
481 courseError = RAD2DEG(atan2f(courseComponent[1], courseComponent[0])) - attitudeState.Yaw;
482 } else {
483 // fallback based on effective movement direction when in fallback mode, hope that airspeed > wind velocity, or we will never get home
484 courseError = RAD2DEG(atan2f(velocityDesired.East, velocityDesired.North)) - RAD2DEG(atan2f(velocityState.East, velocityState.North));
487 if (courseError < -180.0f) {
488 courseError += 360.0f;
490 if (courseError > 180.0f) {
491 courseError -= 360.0f;
494 // overlap calculation. Theres a dead zone behind the craft where the
495 // counter-yawing of some craft while rolling could render a desired right
496 // turn into a desired left turn. Making the turn direction based on
497 // current roll angle keeps the plane committed to a direction once chosen
498 if (courseError < -180.0f + (fixedWingSettings->ReverseCourseOverlap * 0.5f)
499 && attitudeState.Roll > 0.0f) {
500 courseError += 360.0f;
502 if (courseError > 180.0f - (fixedWingSettings->ReverseCourseOverlap * 0.5f)
503 && attitudeState.Roll < 0.0f) {
504 courseError -= 360.0f;
507 courseCommand = pid_apply(&PIDcourse, courseError, dT);
509 fixedWingPathFollowerStatus.Error.Course = courseError;
510 fixedWingPathFollowerStatus.ErrorInt.Course = PIDcourse.iAccumulator;
511 fixedWingPathFollowerStatus.Command.Course = courseCommand;
513 stabDesired.Roll = boundf(fixedWingSettings->RollLimit.Neutral +
514 courseCommand,
515 fixedWingSettings->RollLimit.Min,
516 fixedWingSettings->RollLimit.Max);
518 // Error condition: roll way out of wack
519 fixedWingPathFollowerStatus.Errors.Rollcontrol = 0;
520 if (fixedWingSettings->Safetymargins.Rollcontrol > 0.5f &&
521 (attitudeState.Roll < fixedWingSettings->RollLimit.Min - fixedWingSettings->SafetyCutoffLimits.RollDeg ||
522 attitudeState.Roll > fixedWingSettings->RollLimit.Max + fixedWingSettings->SafetyCutoffLimits.RollDeg)) {
523 fixedWingPathFollowerStatus.Errors.Rollcontrol = 1;
524 result = 0;
525 cutThrust = true;
530 * Compute desired yaw command
532 // TODO implement raw control mode for yaw and base on Accels.Y
533 stabDesired.Yaw = 0.0f;
535 // safety cutoff condition
536 if (cutThrust) {
537 stabDesired.Thrust = 0.0f;
540 stabDesired.StabilizationMode.Roll = STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE;
541 stabDesired.StabilizationMode.Pitch = STABILIZATIONDESIRED_STABILIZATIONMODE_ATTITUDE;
542 stabDesired.StabilizationMode.Yaw = STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL;
543 stabDesired.StabilizationMode.Thrust = STABILIZATIONDESIRED_STABILIZATIONMODE_MANUAL;
545 StabilizationDesiredSet(&stabDesired);
547 FixedWingPathFollowerStatusSet(&fixedWingPathFollowerStatus);
549 return result;
554 * Function to calculate course vector C based on airspeed s, fluid movement F
555 * and desired movement vector V
556 * parameters in: V,F,s
557 * parameters out: C
558 * returns true if a valid solution could be found for V,F,s, false if not
559 * C will be set to a best effort attempt either way
561 bool FixedWingFlyController::correctCourse(float *C, float *V, float *F, float s)
563 // Approach:
564 // Let Sc be a circle around origin marking possible movement vectors
565 // of the craft with airspeed s (all possible options for C)
566 // Let Vl be a line through the origin along movement vector V where fr any
567 // point v on line Vl v = k * (V / |V|) = k' * V
568 // Let Wl be a line parallel to Vl where for any point v on line Vl exists
569 // a point w on WL with w = v - F
570 // Then any intersection between circle Sc and line Wl represents course
571 // vector which would result in a movement vector
572 // V' = k * ( V / |V|) = k' * V
573 // If there is no intersection point, S is insufficient to compensate
574 // for F and we can only try to fly in direction of V (thus having wind drift
575 // but at least making progress orthogonal to wind)
577 s = fabsf(s);
578 float f = vector_lengthf(F, 2);
580 // normalize Cn=V/|V|, |V| must be >0
581 float v = vector_lengthf(V, 2);
582 if (v < 1e-6f) {
583 // if |V|=0, we aren't supposed to move, turn into the wind
584 // (this allows hovering)
585 C[0] = -F[0];
586 C[1] = -F[1];
587 // if desired airspeed matches fluidmovement a hover is actually
588 // intended so return true
589 return fabsf(f - s) < 1e-3f;
591 float Vn[2] = { V[0] / v, V[1] / v };
593 // project F on V
594 float fp = F[0] * Vn[0] + F[1] * Vn[1];
596 // find component Fo of F that is orthogonal to V
597 // (which is exactly the distance between Vl and Wl)
598 float Fo[2] = { F[0] - (fp * Vn[0]), F[1] - (fp * Vn[1]) };
599 float fo2 = Fo[0] * Fo[0] + Fo[1] * Fo[1];
601 // find k where k * Vn = C - Fo
602 // |C|=s is the hypothenuse in any rectangular triangle formed by k * Vn and Fo
603 // so k^2 + fo^2 = s^2 (since |Vn|=1)
604 float k2 = s * s - fo2;
605 if (k2 <= -1e-3f) {
606 // there is no solution, we will be drifted off either way
607 // fallback: fly stupidly in direction of V and hope for the best
608 C[0] = V[0];
609 C[1] = V[1];
610 return false;
611 } else if (k2 <= 1e-3f) {
612 // there is exactly one solution: -Fo
613 C[0] = -Fo[0];
614 C[1] = -Fo[1];
615 return true;
617 // we have two possible solutions k positive and k negative as there are
618 // two intersection points between Wl and Sc
619 // which one is better? two criteria:
620 // 1. we MUST move in the right direction, if any k leads to -v its invalid
621 // 2. we should minimize the speed error
622 float k = sqrt(k2);
623 float C1[2] = { -k * Vn[0] - Fo[0], -k * Vn[1] - Fo[1] };
624 float C2[2] = { k *Vn[0] - Fo[0], k * Vn[1] - Fo[1] };
625 // project C+F on Vn to find signed resulting movement vector length
626 float vp1 = (C1[0] + F[0]) * Vn[0] + (C1[1] + F[1]) * Vn[1];
627 float vp2 = (C2[0] + F[0]) * Vn[0] + (C2[1] + F[1]) * Vn[1];
628 if (vp1 >= 0.0f && fabsf(v - vp1) < fabsf(v - vp2)) {
629 // in this case the angle between course and resulting movement vector
630 // is greater than 90 degrees - so we actually fly backwards
631 C[0] = C1[0];
632 C[1] = C1[1];
633 return true;
635 C[0] = C2[0];
636 C[1] = C2[1];
637 if (vp2 >= 0.0f) {
638 // in this case the angle between course and movement vector is less than
639 // 90 degrees, but we do move in the right direction
640 return true;
641 } else {
642 // in this case we actually get driven in the opposite direction of V
643 // with both solutions for C
644 // this might be reached in headwind stronger than maximum allowed
645 // airspeed.
646 return false;
651 void FixedWingFlyController::AirspeedStateUpdatedCb(__attribute__((unused)) UAVObjEvent *ev)
653 AirspeedStateData airspeedState;
654 VelocityStateData velocityState;
656 AirspeedStateGet(&airspeedState);
657 VelocityStateGet(&velocityState);
658 float airspeedVector[2];
659 float yaw;
660 AttitudeStateYawGet(&yaw);
661 airspeedVector[0] = cos_lookup_deg(yaw);
662 airspeedVector[1] = sin_lookup_deg(yaw);
663 // vector projection of groundspeed on airspeed vector to handle both forward and backwards movement
664 float groundspeedProjection = velocityState.North * airspeedVector[0] + velocityState.East * airspeedVector[1];
666 indicatedAirspeedStateBias = airspeedState.CalibratedAirspeed - groundspeedProjection;
667 // note - we do fly by Indicated Airspeed (== calibrated airspeed) however
668 // since airspeed is updated less often than groundspeed, we use sudden
669 // changes to groundspeed to offset the airspeed by the same measurement.
670 // This has a side effect that in the absence of any airspeed updates, the
671 // pathfollower will fly using groundspeed.
673 lastAirspeedUpdate = PIOS_DELAY_GetuS();