LCOV - code coverage report
Current view: top level - AC_Avoidance - AP_OAPathPlanner.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 142 197 72.1 %
Date: 2026-09-09 11:02:08 Functions: 7 8 87.5 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AC_Avoidance_config.h"
      17             : 
      18             : #if AP_OAPATHPLANNER_ENABLED
      19             : 
      20             : #include "AP_OAPathPlanner.h"
      21             : #include <AP_Math/AP_Math.h>
      22             : #include <AP_AHRS/AP_AHRS.h>
      23             : #include <AC_Fence/AC_Fence.h>
      24             : #include <AP_Logger/AP_Logger.h>
      25             : #include "AP_OABendyRuler.h"
      26             : #include "AP_OADijkstra.h"
      27             : 
      28             : extern const AP_HAL::HAL &hal;
      29             : 
      30             : // parameter defaults
      31             : static constexpr float OA_MARGIN_MAX_DEFAULT = 5;
      32             : static constexpr int16_t OA_OPTIONS_DEFAULT = 1;
      33             : 
      34             : static constexpr int16_t OA_UPDATE_MS = 1000;      // path planning updates run at 1hz
      35             : static constexpr int16_t OA_TIMEOUT_MS = 3000;     // results over 3 seconds old are ignored
      36             : 
      37             : const AP_Param::GroupInfo AP_OAPathPlanner::var_info[] = {
      38             : 
      39             :     // @Param: TYPE
      40             :     // @DisplayName: Object Avoidance Path Planning algorithm to use
      41             :     // @Description: Enabled/disable path planning around obstacles
      42             :     // @Values: 0:Disabled,1:BendyRuler,2:Dijkstra,3:Dijkstra with BendyRuler
      43             :     // @User: Standard
      44             :     AP_GROUPINFO_FLAGS("TYPE", 1,  AP_OAPathPlanner, _type, OA_PATHPLAN_DISABLED, AP_PARAM_FLAG_ENABLE),
      45             : 
      46             :     // Note: Do not use Index "2" for any new parameter
      47             :     //       It was being used by _LOOKAHEAD which was later moved to AP_OABendyRuler 
      48             : 
      49             :     // @Param: MARGIN_MAX
      50             :     // @DisplayName: Object Avoidance wide margin distance
      51             :     // @Description: Minimum distance in meters that the vehicle will try to stay away from obstacles and fences. BendyRuler only accepts a candidate path whose clearance exceeds this, and Dijkstra uses it as the margin held from fences. This is not a detection range: use OA_BR_LOOKAHEAD to limit how far ahead BendyRuler looks for obstacles.
      52             :     // @Units: m
      53             :     // @Range: 0.1 100
      54             :     // @Increment: 1
      55             :     // @User: Standard
      56             :     AP_GROUPINFO("MARGIN_MAX", 3, AP_OAPathPlanner, _margin_max, OA_MARGIN_MAX_DEFAULT),
      57             : 
      58             :     // @Group: DB_
      59             :     // @Path: AP_OADatabase.cpp
      60             :     AP_SUBGROUPINFO(_oadatabase, "DB_", 4, AP_OAPathPlanner, AP_OADatabase),
      61             : 
      62             :     // @Param: OPTIONS
      63             :     // @DisplayName: Options while recovering from Object Avoidance
      64             :     // @Description: Bitmask which will govern vehicles behaviour while recovering from Obstacle Avoidance (i.e Avoidance is turned off after the path ahead is clear).   
      65             :     // @Bitmask{Rover}: 0: Reset the origin of the waypoint to the present location, 1: log Dijkstra points
      66             :     // @Bitmask{Copter}: 1:log Dijkstra points, 2:Allow fast waypoints (Dijkastras only)
      67             :     // @User: Standard
      68             :     AP_GROUPINFO("OPTIONS", 5, AP_OAPathPlanner, _options, OA_OPTIONS_DEFAULT),
      69             : 
      70             :     // @Group: BR_
      71             :     // @Path: AP_OABendyRuler.cpp
      72             :     AP_SUBGROUPPTR(_oabendyruler, "BR_", 6, AP_OAPathPlanner, AP_OABendyRuler),
      73             : 
      74             :     AP_GROUPEND
      75             : };
      76             : 
      77             : /// Constructor
      78        1286 : AP_OAPathPlanner::AP_OAPathPlanner()
      79             : {
      80        1286 :     _singleton = this;
      81             : 
      82        1286 :     AP_Param::setup_object_defaults(this, var_info);
      83        1286 : }
      84             : 
      85             : // perform any required initialisation
      86        1284 : void AP_OAPathPlanner::init()
      87             : {
      88             :     // run background task looking for best alternative destination
      89        1284 :     switch (_type) {
      90        1275 :     case OA_PATHPLAN_DISABLED:
      91             :         // do nothing
      92        1275 :         return;
      93           2 :     case OA_PATHPLAN_BENDYRULER:
      94           2 :         if (_oabendyruler == nullptr) {
      95           2 :             _oabendyruler = NEW_NOTHROW AP_OABendyRuler();
      96           2 :             AP_Param::load_object_from_eeprom(_oabendyruler, AP_OABendyRuler::var_info);
      97             :         }
      98           2 :         break;
      99           7 :     case OA_PATHPLAN_DIJKSTRA:
     100             : #if AP_FENCE_ENABLED
     101           7 :         if (_oadijkstra == nullptr) {
     102           7 :             _oadijkstra = NEW_NOTHROW AP_OADijkstra(_options);
     103             :         }
     104             : #endif
     105           7 :         break;
     106           0 :     case OA_PATHPLAN_DJIKSTRA_BENDYRULER:
     107             : #if AP_FENCE_ENABLED
     108           0 :         if (_oadijkstra == nullptr) {
     109           0 :             _oadijkstra = NEW_NOTHROW AP_OADijkstra(_options);
     110             :         }
     111             : #endif
     112           0 :         if (_oabendyruler == nullptr) {
     113           0 :             _oabendyruler = NEW_NOTHROW AP_OABendyRuler();
     114           0 :             AP_Param::load_object_from_eeprom(_oabendyruler, AP_OABendyRuler::var_info);
     115             :         }
     116           0 :         break;
     117             :     }
     118             : 
     119           9 :     _oadatabase.init();
     120           9 :     start_thread();
     121             : }
     122             : 
     123             : // pre-arm checks that algorithms have been initialised successfully
     124      107942 : bool AP_OAPathPlanner::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const
     125             : {
     126             :     // check if initialisation has succeeded
     127      107942 :     switch (_type) {
     128      107401 :     case OA_PATHPLAN_DISABLED:
     129             :         // do nothing
     130      107401 :         break;
     131         174 :     case OA_PATHPLAN_BENDYRULER:
     132         174 :         if (_oabendyruler == nullptr) {
     133           6 :             hal.util->snprintf(failure_msg, failure_msg_len, "BendyRuler OA requires reboot");
     134           6 :             return false;
     135             :         }
     136         168 :         break;
     137         367 :     case OA_PATHPLAN_DIJKSTRA:
     138         367 :         if (_oadijkstra == nullptr) {
     139           7 :             hal.util->snprintf(failure_msg, failure_msg_len, "Dijkstra OA requires reboot");
     140           7 :             return false;
     141             :         }
     142         360 :         break;
     143           0 :     case OA_PATHPLAN_DJIKSTRA_BENDYRULER:
     144           0 :         if(_oadijkstra == nullptr || _oabendyruler == nullptr) {
     145           0 :             hal.util->snprintf(failure_msg, failure_msg_len, "OA requires reboot");
     146           0 :             return false;
     147             :         }
     148           0 :         break;
     149             :     }
     150      107929 :     return true;
     151             : }
     152             : 
     153           9 : bool AP_OAPathPlanner::start_thread()
     154             : {
     155           9 :     WITH_SEMAPHORE(_rsem);
     156             : 
     157           9 :     if (_thread_created) {
     158           0 :         return true;
     159             :     }
     160           9 :     if (_type == OA_PATHPLAN_DISABLED) {
     161           0 :         return false;
     162             :     }
     163             : 
     164             :     // create the avoidance thread as low priority. It should soak
     165             :     // up spare CPU cycles to fill in the avoidance_result structure based
     166             :     // on requests in avoidance_request
     167           9 :     if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_OAPathPlanner::avoidance_thread, void),
     168             :                                       "avoidance",
     169             :                                       8192, AP_HAL::Scheduler::PRIORITY_IO, -1)) {
     170           0 :         return false;
     171             :     }
     172           9 :     _thread_created = true;
     173           9 :     return true;
     174           9 : }
     175             : 
     176             : // helper function to map OABendyType to OAPathPlannerUsed
     177         296 : AP_OAPathPlanner::OAPathPlannerUsed AP_OAPathPlanner::map_bendytype_to_pathplannerused(AP_OABendyRuler::OABendyType bendy_type)
     178             : {
     179         296 :     switch (bendy_type) {
     180         296 :     case AP_OABendyRuler::OABendyType::OA_BENDY_HORIZONTAL:
     181         296 :         return OAPathPlannerUsed::BendyRulerHorizontal;
     182             : 
     183           0 :     case AP_OABendyRuler::OABendyType::OA_BENDY_VERTICAL:
     184           0 :         return OAPathPlannerUsed::BendyRulerVertical;
     185             : 
     186           0 :     default:
     187             :     case AP_OABendyRuler::OABendyType::OA_BENDY_DISABLED:
     188           0 :         return OAPathPlannerUsed::None;
     189             :     }
     190             : }
     191             : 
     192             : // provides an alternative target location if path planning around obstacles is required
     193             : // returns true and updates result_origin, result_destination, result_next_destination with an intermediate path
     194             : // result_dest_to_next_dest_clear is set to true if the path from result_destination to result_next_destination is clear (only supported by Dijkstras)
     195             : // path_planner_used updated with which path planner produced the result
     196     3616575 : AP_OAPathPlanner::OA_RetState AP_OAPathPlanner::mission_avoidance(const Location &current_loc,
     197             :                                          const Location &origin,
     198             :                                          const Location &destination,
     199             :                                          const Location &next_destination,
     200             :                                          Location &result_origin,
     201             :                                          Location &result_destination,
     202             :                                          Location &result_next_destination,
     203             :                                          bool &result_dest_to_next_dest_clear,
     204             :                                          OAPathPlannerUsed &path_planner_used)
     205             : {
     206             :     // exit immediately if disabled or thread is not running from a failed init
     207     3616575 :     if (_type == OA_PATHPLAN_DISABLED || !_thread_created) {
     208     3588220 :         return OA_NOT_REQUIRED;
     209             :     }
     210             : 
     211             :     // check if just activated to avoid initial timeout error
     212       28355 :     const uint32_t now = AP_HAL::millis();
     213       28355 :     if (now - _last_update_ms > 200) {
     214           3 :         _activated_ms = now;
     215             :     }
     216       28355 :     _last_update_ms = now;
     217             : 
     218       28355 :     WITH_SEMAPHORE(_rsem);
     219             : 
     220             :     // place new request for the thread to work on
     221       28355 :     avoidance_request.current_loc = current_loc;
     222       28355 :     avoidance_request.origin = origin;
     223       28355 :     avoidance_request.destination = destination;
     224       28355 :     avoidance_request.next_destination = next_destination;
     225       28355 :     avoidance_request.ground_speed_vec = AP::ahrs().groundspeed_vector();
     226       28355 :     avoidance_request.request_time_ms = now;
     227             : 
     228             :     // check result's destination and next_destination matches our request
     229             :     // e.g. check this result was using our current inputs and not from an old request
     230       28355 :     const bool destination_matches = destination.same_latlon_as(avoidance_result.destination);
     231       28355 :     const bool next_destination_matches = next_destination.same_latlon_as(avoidance_result.next_destination);
     232             : 
     233             :     // check results have not timed out
     234       28355 :     const bool timed_out = (now - avoidance_result.result_time_ms > OA_TIMEOUT_MS) && (now - _activated_ms > OA_TIMEOUT_MS);
     235             : 
     236             :     // return results from background thread's latest checks
     237       28355 :     if (destination_matches && next_destination_matches && !timed_out) {
     238             :         // we have a result from the thread
     239       28210 :         result_origin = avoidance_result.origin_new;
     240       28210 :         result_destination = avoidance_result.destination_new;
     241       28210 :         result_next_destination = avoidance_result.next_destination_new;
     242       28210 :         result_dest_to_next_dest_clear = avoidance_result.dest_to_next_dest_clear;
     243       28210 :         path_planner_used = avoidance_result.path_planner_used;
     244       28210 :         return avoidance_result.ret_state;
     245             :     }
     246             : 
     247             :     // if timeout then path planner is taking too long to respond
     248         145 :     if (timed_out) {
     249           0 :         return OA_ERROR;
     250             :     }
     251             : 
     252             :     // background thread is working on a new destination
     253         145 :     return OA_PROCESSING;
     254       28355 : }
     255             : 
     256             : // avoidance thread that continually updates the avoidance_result structure based on avoidance_request
     257           9 : void AP_OAPathPlanner::avoidance_thread()
     258             : {
     259             :     // require ekf origin to have been set
     260           9 :     bool origin_set = false;
     261         357 :     while (!origin_set) {
     262         348 :         hal.scheduler->delay(500);
     263         348 :         Location ekf_origin {};
     264             :         {
     265         348 :             WITH_SEMAPHORE(AP::ahrs().get_semaphore());
     266         348 :             origin_set = AP::ahrs().get_origin(ekf_origin);    
     267         348 :         }
     268             :     }
     269             : 
     270             :     while (true) {
     271             : 
     272             :         // if database queue needs attention, service it faster
     273       37017 :         if (_oadatabase.process_queue()) {
     274           1 :             hal.scheduler->delay(1);
     275             :         } else {
     276       37016 :             hal.scheduler->delay(20);
     277             :         }
     278             : 
     279       37008 :         const uint32_t now = AP_HAL::millis();
     280       37008 :         if (now - avoidance_latest_ms < OA_UPDATE_MS) {
     281       36442 :             continue;
     282             :         }
     283         780 :         avoidance_latest_ms = now;
     284             : 
     285         780 :         _oadatabase.update();
     286             : 
     287             :         // values returned by path planners
     288         780 :         Location origin_new;
     289         780 :         Location destination_new;
     290         780 :         Location next_destination_new;
     291         780 :         bool dest_to_next_dest_clear = false;
     292             :         {
     293         780 :             WITH_SEMAPHORE(_rsem);
     294         780 :             if (avoidance_request.request_time_ms == 0 ||
     295         571 :                 now - avoidance_request.request_time_ms > OA_TIMEOUT_MS) {
     296             :                 // no request has ever been posted, or this is a very old request, don't process it
     297         211 :                 continue;
     298             :             }
     299             : 
     300             :             // copy request to avoid conflict with main thread
     301         569 :             avoidance_request2 = avoidance_request;
     302             : 
     303             :             // store passed in origin, destination and next_destination so we can return it if object avoidance is not required
     304         569 :             origin_new = avoidance_request.origin;
     305         569 :             destination_new = avoidance_request.destination;
     306         569 :             next_destination_new = avoidance_request.next_destination;
     307         780 :         }
     308             : 
     309             :         // run background task looking for best alternative destination
     310         569 :         OA_RetState res = OA_NOT_REQUIRED;
     311         569 :         OAPathPlannerUsed path_planner_used = OAPathPlannerUsed::None;
     312         569 :         switch (_type) {
     313           3 :         case OA_PATHPLAN_DISABLED:
     314           3 :             continue;
     315         296 :         case OA_PATHPLAN_BENDYRULER: {
     316         296 :             if (_oabendyruler == nullptr) {
     317           0 :                 continue;
     318             :             }
     319         296 :             _oabendyruler->set_config(_margin_max);
     320             : 
     321             :             AP_OABendyRuler::OABendyType bendy_type;
     322         296 :             if (_oabendyruler->update(avoidance_request2.current_loc, avoidance_request2.destination, avoidance_request2.ground_speed_vec, origin_new, destination_new, bendy_type, false)) {
     323         199 :                 res = OA_SUCCESS;
     324             :             }
     325         296 :             path_planner_used = map_bendytype_to_pathplannerused(bendy_type);
     326         296 :             break;
     327             :         }
     328             : 
     329         270 :         case OA_PATHPLAN_DIJKSTRA: {
     330             : #if AP_FENCE_ENABLED
     331         270 :             if (_oadijkstra == nullptr) {
     332           0 :                 continue;
     333             :             }
     334         270 :             _oadijkstra->set_fence_margin(_margin_max);
     335         270 :             const AP_OADijkstra::AP_OADijkstra_State dijkstra_state = _oadijkstra->update(avoidance_request2.current_loc,
     336         270 :                                                                                           avoidance_request2.destination,
     337         270 :                                                                                           avoidance_request2.next_destination,
     338             :                                                                                           origin_new,
     339             :                                                                                           destination_new,
     340             :                                                                                           next_destination_new,
     341             :                                                                                           dest_to_next_dest_clear);
     342         270 :             switch (dijkstra_state) {
     343          43 :             case AP_OADijkstra::DIJKSTRA_STATE_NOT_REQUIRED:
     344          43 :                 res = OA_NOT_REQUIRED;
     345          43 :                 break;
     346           0 :             case AP_OADijkstra::DIJKSTRA_STATE_ERROR:
     347           0 :                 res = OA_ERROR;
     348           0 :                 break;
     349         227 :             case AP_OADijkstra::DIJKSTRA_STATE_SUCCESS:
     350         227 :                 res = OA_SUCCESS;
     351         227 :                 break;
     352             :             }
     353         270 :             path_planner_used = OAPathPlannerUsed::Dijkstras;
     354             : #endif
     355         270 :             break;
     356             :         }
     357             : 
     358           0 :         case OA_PATHPLAN_DJIKSTRA_BENDYRULER: {
     359           0 :             if ((_oabendyruler == nullptr) || _oadijkstra == nullptr) {
     360           0 :                 continue;
     361             :             } 
     362           0 :             _oabendyruler->set_config(_margin_max);
     363             :             AP_OABendyRuler::OABendyType bendy_type;
     364           0 :             if (_oabendyruler->update(avoidance_request2.current_loc, avoidance_request2.destination, avoidance_request2.ground_speed_vec, origin_new, destination_new, bendy_type, proximity_only)) {
     365             :                 // detected a obstacle by vehicle's proximity sensor. Switch avoidance to BendyRuler till obstacle is out of the way
     366           0 :                 proximity_only = false;
     367           0 :                 res = OA_SUCCESS;
     368           0 :                 path_planner_used = map_bendytype_to_pathplannerused(bendy_type);
     369           0 :                 break;
     370             :             } else {
     371             :                 // cleared all obstacles, trigger Dijkstra's to calculate path based on current deviated position  
     372             : #if AP_FENCE_ENABLED
     373           0 :                 if (proximity_only == false) {
     374           0 :                     _oadijkstra->recalculate_path();
     375             :                 }
     376             : #endif
     377             :                 // only use proximity avoidance now for BendyRuler
     378           0 :                 proximity_only = true;
     379             :             }
     380             : #if AP_FENCE_ENABLED
     381           0 :             _oadijkstra->set_fence_margin(_margin_max);
     382           0 :             const AP_OADijkstra::AP_OADijkstra_State dijkstra_state = _oadijkstra->update(avoidance_request2.current_loc,
     383           0 :                                                                                           avoidance_request2.destination,
     384           0 :                                                                                           avoidance_request2.next_destination,
     385             :                                                                                           origin_new,
     386             :                                                                                           destination_new,
     387             :                                                                                           next_destination_new,
     388             :                                                                                           dest_to_next_dest_clear);
     389           0 :             switch (dijkstra_state) {
     390           0 :             case AP_OADijkstra::DIJKSTRA_STATE_NOT_REQUIRED:
     391           0 :                 res = OA_NOT_REQUIRED;
     392           0 :                 break;
     393           0 :             case AP_OADijkstra::DIJKSTRA_STATE_ERROR:
     394           0 :                 res = OA_ERROR;
     395           0 :                 break;
     396           0 :             case AP_OADijkstra::DIJKSTRA_STATE_SUCCESS:
     397           0 :                 res = OA_SUCCESS;
     398           0 :                 break;
     399             :             }
     400           0 :             path_planner_used = OAPathPlannerUsed::Dijkstras;
     401             : #endif
     402           0 :             break;
     403             :         }
     404             : 
     405           3 :         } // switch
     406             : 
     407             :         {
     408             :             // give the main thread the avoidance result
     409         566 :             WITH_SEMAPHORE(_rsem);
     410             : 
     411             :             // place the destination and next destination used into the result (used by the caller to verify the result matches their request)
     412         566 :             avoidance_result.destination = avoidance_request2.destination;
     413         566 :             avoidance_result.next_destination = avoidance_request2.next_destination;
     414         566 :             avoidance_result.dest_to_next_dest_clear = dest_to_next_dest_clear;
     415             : 
     416             :             // fill the result structure with the intermediate path
     417         566 :             avoidance_result.origin_new = (res == OA_SUCCESS) ? origin_new : avoidance_result.origin_new;
     418         566 :             avoidance_result.destination_new = (res == OA_SUCCESS) ? destination_new : avoidance_result.destination;
     419         566 :             avoidance_result.next_destination_new = (res == OA_SUCCESS) ? next_destination_new : avoidance_result.next_destination;
     420             : 
     421             :             // create new avoidance result.dest_to_next_dest_clear field.  fill in with results from dijkstras or leave as unknown
     422         566 :             avoidance_result.result_time_ms = AP_HAL::millis();
     423         566 :             avoidance_result.path_planner_used = path_planner_used;
     424         566 :             avoidance_result.ret_state = res;
     425         566 :         }
     426       37008 :     }
     427             : }
     428             : 
     429             : // singleton instance
     430             : AP_OAPathPlanner *AP_OAPathPlanner::_singleton;
     431             : 
     432             : namespace AP {
     433             : 
     434           0 : AP_OAPathPlanner *ap_oapathplanner()
     435             : {
     436           0 :     return AP_OAPathPlanner::get_singleton();
     437             : }
     438             : 
     439             : }
     440             : 
     441             : #endif  // AP_OAPATHPLANNER_ENABLED

Generated by: LCOV version 1.14