00001 /** 00002 \file Robots/LoBot/control/LoExtricate.C 00003 \brief This file defines the non-inline member functions of the 00004 lobot::Extricate class. 00005 */ 00006 00007 // //////////////////////////////////////////////////////////////////// // 00008 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00009 // by the University of Southern California (USC) and the iLab at USC. // 00010 // See http://iLab.usc.edu for information about this project. // 00011 // //////////////////////////////////////////////////////////////////// // 00012 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00013 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00014 // in Visual Environments, and Applications'' by Christof Koch and // 00015 // Laurent Itti, California Institute of Technology, 2001 (patent // 00016 // pending; application number 09/912,225 filed July 23, 2001; see // 00017 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00018 // //////////////////////////////////////////////////////////////////// // 00019 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00020 // // 00021 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00022 // redistribute it and/or modify it under the terms of the GNU General // 00023 // Public License as published by the Free Software Foundation; either // 00024 // version 2 of the License, or (at your option) any later version. // 00025 // // 00026 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00027 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00028 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00029 // PURPOSE. See the GNU General Public License for more details. // 00030 // // 00031 // You should have received a copy of the GNU General Public License // 00032 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00033 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00034 // Boston, MA 02111-1307 USA. // 00035 // //////////////////////////////////////////////////////////////////// // 00036 // 00037 // Primary maintainer for this file: mviswana usc edu 00038 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/control/LoExtricate.C $ 00039 // $Id: LoExtricate.C 13840 2010-08-27 22:28:12Z mviswana $ 00040 // 00041 00042 //------------------------------ HEADERS -------------------------------- 00043 00044 // lobot headers 00045 #include "Robots/LoBot/control/LoExtricate.H" 00046 #include "Robots/LoBot/control/LoMetrics.H" 00047 #include "Robots/LoBot/control/LoTurnArbiter.H" 00048 #include "Robots/LoBot/control/LoSpinArbiter.H" 00049 #include "Robots/LoBot/control/LoSpeedArbiter.H" 00050 00051 #include "Robots/LoBot/LoApp.H" 00052 #include "Robots/LoBot/slam/LoMap.H" 00053 #include "Robots/LoBot/io/LoDangerZone.H" 00054 #include "Robots/LoBot/config/LoConfigHelpers.H" 00055 #include "Robots/LoBot/thread/LoUpdateLock.H" 00056 00057 #include "Robots/LoBot/misc/LoExcept.H" 00058 #include "Robots/LoBot/misc/LoRegistry.H" 00059 #include "Robots/LoBot/misc/singleton.hh" 00060 00061 #include "Robots/LoBot/util/LoGL.H" 00062 #include "Robots/LoBot/util/LoMath.H" 00063 00064 // OpenGL headers 00065 #ifdef INVT_HAVE_LIBGL 00066 #include <GL/gl.h> 00067 #endif 00068 00069 // Standard C++ headers 00070 #include <iomanip> 00071 #include <sstream> 00072 #include <algorithm> 00073 #include <memory> 00074 00075 //----------------------------- NAMESPACE ------------------------------- 00076 00077 namespace lobot { 00078 00079 //-------------------------- KNOB TWIDDLING ----------------------------- 00080 00081 namespace { 00082 00083 // Retrieve settings from extricate section of config file 00084 template<typename T> 00085 static inline T conf(const std::string& key, const T& default_value) 00086 { 00087 return get_conf<T>(LOBE_EXTRICATE, key, default_value) ; 00088 } 00089 00090 /// This local class encapsulates various parameters that can be used to 00091 /// tweak different aspects of the extricate behaviour. 00092 class Params : public singleton<Params> { 00093 /// Users may specify what speed they would like the extrication to 00094 /// occur. 00095 /// 00096 /// CAUTION: It would be unwise to make this speed too high. 00097 float m_extricate_speed ; 00098 00099 /// In case the RPM sensor is configured to be off, we will need to 00100 /// specify the extricate "speed" in terms of PWM values as well. 00101 /// 00102 /// NOTE: All speed related behaviours should specify both a speed 00103 /// in m/s and a PWM value. 00104 /// 00105 /// CAUTION: For the extricate behaviour, it would be unwise to 00106 /// make the extrication PWM too high. 00107 int m_extricate_pwm ; 00108 00109 /// When lobot is running the LGMD-based avoidance behaviours, it can 00110 /// come to a complete halt due to the emergency stop behaviour and 00111 /// then not restart because the lack of motion will result in the 00112 /// LGMD spikes becoming dormant. To work around this problem, we can 00113 /// run the LRF-based extricate behaviour in addition to the 00114 /// LGMD-based extricate behaviours to restart the robot so as to 00115 /// start the spiking activity again. 00116 /// 00117 /// However, in this capacity, it would be best to reduce the 00118 /// frequency at which the LRF-based extricate behaviour runs. 00119 /// Furthermore, we would also want this behaviour to only perform its 00120 /// extrication actions if the robot is actually stopped. 00121 /// 00122 /// This setting is a flag that indicates whether the extricate 00123 /// behaviour should operate in "restart mode" as described above. By 00124 /// default, it is off, i.e., we assume this behaviour is the only 00125 /// active extricate behaviour and that it should perform extrication 00126 /// whenever the danger zone is penetrated, regardless of the robot's 00127 /// current motion state. 00128 bool m_restart_mode ; 00129 00130 /// Usually, steering control is effected using the turn arbiter, 00131 /// which veers the robot in different directions while it moves, 00132 /// i.e., smooth car-like turns. However, the lgmd_extricate_tti 00133 /// behaviour also supports spin-style steering, i.e., momentarily 00134 /// stopping the robot and then turning it cw/ccw in-place. This flag 00135 /// turns on spin-style steering. By default, the behaviour uses the 00136 /// normal car-like steering mode. 00137 bool m_spin_style_steering ; 00138 00139 /// The number of milliseconds between successive iterations of this 00140 /// behaviour. 00141 /// 00142 /// WARNING: The ability to change a behaviour's update frequency is a 00143 /// very powerful feature whose misuse or abuse can wreak havoc! Be 00144 /// sure to use reasonable values for this setting. 00145 int m_update_delay ; 00146 00147 /// The location and size (within the Robolocust main window) of the 00148 /// goal behaviour's visualization. 00149 typedef Drawable::Geometry Geom ; // just for properly aligning accessors 00150 Geom m_geometry ; 00151 00152 /// Private constructor because this is a singleton. 00153 Params() ; 00154 friend class singleton<Params> ; 00155 00156 public: 00157 /// Accessing the various parameters 00158 //@{ 00159 static float extricate_speed() {return instance().m_extricate_speed ;} 00160 static int extricate_pwm() {return instance().m_extricate_pwm ;} 00161 static bool restart_mode() {return instance().m_restart_mode ;} 00162 static bool spin_style_steering(){return instance().m_spin_style_steering;} 00163 static int update_delay() {return instance().m_update_delay ;} 00164 static Geom geometry() {return instance().m_geometry ;} 00165 //@} 00166 } ; 00167 00168 // Parameter initialization 00169 Params::Params() 00170 : m_extricate_speed(clamp(conf("extricate_speed", 0.15f), 0.1f, 0.75f)), 00171 m_extricate_pwm(clamp(conf("extricate_pwm", 25), 10, 50)), 00172 m_restart_mode(conf("restart_mode", false)), 00173 m_spin_style_steering(conf("spin_style_steering", false)), 00174 m_update_delay(clamp(conf("update_delay", 1750), 500, 10000)), 00175 m_geometry(conf<std::string>("geometry", "0 0 10 10")) 00176 {} 00177 00178 } // end of local anonymous namespace encapsulating above helpers 00179 00180 //-------------------------- INITIALIZATION ----------------------------- 00181 00182 Extricate::Extricate() 00183 : base(Params::update_delay(), LOBE_EXTRICATE, Params::geometry()) 00184 { 00185 start(LOBE_EXTRICATE) ; 00186 } 00187 00188 Extricate::Command::Command() 00189 : drive(0), turn(0) 00190 {} 00191 00192 void Extricate::pre_run() 00193 { 00194 if (! App::robot()) 00195 throw behavior_error(MOTOR_SYSTEM_MISSING) ; 00196 } 00197 00198 //---------------------- THE BEHAVIOUR'S ACTION ------------------------- 00199 00200 // Helper function object to compute the attractive and repulsive forces 00201 // for a block and maintain a running total. 00202 namespace { 00203 00204 // DEVNOTE: This function object is meant to be used with the STL 00205 // for_each algorithm. The goal is to compute the total attractive and 00206 // repulsive forces for all of the DangerZone's blocks one block at a 00207 // time. Since std::for_each() passes its function object parameter by 00208 // value, the call site will not get back the force vectors computed by 00209 // this function object unless special measures are taken to facilitate 00210 // the return of these vectors. 00211 // 00212 // One possibility is to use object references. That is, the att and rep 00213 // data members defined below can be of type Vector& and, in 00214 // Extricate::action(), we can create the result vectors and pass them to 00215 // this function object as references (via the constructor). Thus, when 00216 // std::for_each() is done, Extricate::action() will have the results. 00217 // 00218 // Alternatively, recalling that the STL for_each algorithm returns the 00219 // function object passed to it at the end of the for_each() function, we 00220 // can have the caller not discard for_each's return value (as is 00221 // typically done) and, instead, use the function's return value to 00222 // retrieve the force vectors computed for all the DangerZone blocks. 00223 // 00224 // This second approach is the one adopted here. 00225 class compute_forces { 00226 const LRFData& lrf ; 00227 mutable Vector att ; // attractive force 00228 mutable Vector rep ; // repulsive force 00229 public: 00230 compute_forces(const LRFData&) ; 00231 void operator()(const DangerZone::Block&) const ; 00232 00233 const Vector& attractive() const {return att ;} 00234 const Vector& repulsive() const {return rep ;} 00235 } ; 00236 00237 compute_forces::compute_forces(const LRFData& L) 00238 : lrf(L) 00239 {} 00240 00241 // Each DangerZone::Block contains a distance threshold. When the LRF 00242 // returns a distance measurement less than this threshold, it means that 00243 // particular reading indicates something inside the danger zone. When 00244 // the distance measurement is greater than the block's distance 00245 // threshold, the object being sensed lies outside the danger zone. 00246 // 00247 // If we think of a block's distance threshold as a kind of membrane, 00248 // then the difference between a distance reading and the distance 00249 // threshold for the block to which that reading belongs is a measure of 00250 // the amount by which the object being sensed has penetrated the 00251 // membrane (if the difference is negative; if positive, then it tells us 00252 // how far away the object is from the membrane). 00253 // 00254 // This difference, or distance "error", can be used to size the force 00255 // vectors. The greater the error, the greater the magnitude of the 00256 // force. 00257 // 00258 // Since the distance thresholds for a block will be fairly small 00259 // relative to the total distance range that can be sensed by the laser 00260 // range finder, the magnitudes of negative distance errors (i.e., when 00261 // an object has penetrated the danger zone) will be much smaller than 00262 // the magnitudes of positive errors (especially for objects far away on 00263 // one side of the robot). For example, the Hokuyo URG-LX-01 can sense 00264 // distances in the range 60mm to 5600mm; and distance thresholds for the 00265 // robot are usually in the range of 250-450mm. So, if the robot is near 00266 // a wall on its right but has a clear path to its left and in front, the 00267 // total magnitude of the attractive force will be quite high while that 00268 // of the repulsive force will be rather low. 00269 // 00270 // To even out this lopsidedness, we square the errors for repulsive 00271 // forces but not for attractive forces. The force vector is then 00272 // computed simply by multiplying this scalar value and the unit vector 00273 // for each LRF reading's angle. 00274 void compute_forces::operator()(const DangerZone::Block& B) const 00275 { 00276 for (int angle = B.start(); angle <= B.end(); ++angle) 00277 { 00278 int distance = lrf[angle] ; 00279 if (distance < 0) // bad reading along this direction 00280 continue ; 00281 float e = distance - B.danger_zone() ; // distance "error" 00282 Vector f = Vector(cos(angle), sin(angle)) ; 00283 if (e < 0) // distance reading is inside danger zone 00284 rep += sqr(e) * f ; 00285 else // distance reading is outside danger zone 00286 att += e * f ; 00287 } 00288 } 00289 00290 } // end of local namespace encapsulating above helpers 00291 00292 // The extricate behaviour monitors the robot's danger zone and issues 00293 // appropriate turn and drive commands to get the robot unstuck, which 00294 // happens when things have gotten too close (thereby triggering the 00295 // robot's emergency stop response). 00296 // 00297 // This behaviour works by assigning a repulsive force to each distance 00298 // reading that lies within the danger zone and an attractive force to 00299 // each reading outside it. The total force vector is then used to 00300 // determine suitable motor commands. 00301 void Extricate::action() 00302 { 00303 DangerZone::Blocks blocks ; 00304 std::auto_ptr<LRFData> lrf ; 00305 00306 // Copy the danger zone blocks and corresponding LRF data so as to not 00307 // hold the update lock for too long. 00308 UpdateLock::begin_read() ; 00309 bool danger_zone_penetrated = DangerZone::penetrated() ; 00310 if (danger_zone_penetrated) { 00311 blocks.reserve(DangerZone::num_blocks()) ; 00312 std::copy(DangerZone::begin(), DangerZone::end(), 00313 std::back_inserter(blocks)) ; 00314 lrf.reset(new LRFData(DangerZone::lrf_data())) ; 00315 } 00316 bool stopped = App::robot()->stopped() ; 00317 UpdateLock::end_read() ; 00318 00319 // If the danger zone has been penetrated, determine the attractive 00320 // and repulsive forces and the corresponding motor commands. In 00321 // restart mode, also check if the robot is actually stopped before 00322 // commencing extrication. 00323 // 00324 // DEVNOTE: To understand the logic of the if statement below, let: 00325 // D = Boolean variable indicating that danger zone is penetrated 00326 // M = Boolean variable indicating beh. is configured for restart mode 00327 // S = Boolean variable indicating robot is stopped 00328 // and E = Boolean variable indicating extrication should be performed 00329 // 00330 // In normal mode, i.e., when R is false, we only need check D. In 00331 // restart mode, we have to check both D and S. Thus, utilizing the 00332 // symbols of Boolean algebra, we have: 00333 // E = ~MD + MSD 00334 // 00335 // Simplifying the above, gives us: 00336 // E = D(~M + MS) = D(~M + S) 00337 Command C ; 00338 Vector A, R, F ; 00339 if (danger_zone_penetrated && (! Params::restart_mode() || stopped)) 00340 { 00341 compute_forces force_field = 00342 std::for_each(blocks.begin(), blocks.end(), compute_forces(*lrf)) ; 00343 A = force_field.attractive() ; 00344 R = force_field.repulsive() ; 00345 F = A - R ; 00346 00347 const int T = random(TurnArbiter::turn_step(), TurnArbiter::turn_max()); 00348 switch (quadrant(direction(F))) 00349 { 00350 case 1: 00351 C.drive = 1 ; C.turn = T ; 00352 break ; 00353 case 2: 00354 C.drive = -1 ; C.turn = -T ; 00355 break ; 00356 case 3: 00357 C.drive = -1 ; C.turn = T ; 00358 break ; 00359 case 4: 00360 C.drive = 1 ; C.turn = -T ; 00361 break ; 00362 default: // hunh?!? quadrant() shouldn't return anything outside [1,4] 00363 throw misc_error(LOGIC_ERROR) ; 00364 } 00365 00366 if (Params::spin_style_steering()) 00367 SpinArbiter::instance().vote(base::name, 00368 new SpinArbiter::Vote(C.turn)) ; 00369 else 00370 { 00371 SpeedArbiter::instance().vote(base::name, 00372 new SpeedArbiter::Vote(C.drive * Params::extricate_speed(), 00373 C.drive * Params::extricate_pwm())) ; 00374 TurnArbiter::instance().vote(base::name, 00375 new TurnArbiter::Vote(turn_vote_centered_at(C.turn))) ; 00376 } 00377 00378 Metrics::Log log ; 00379 log << std::setw(Metrics::opw()) << std::left << base::name ; 00380 Map* M = App::map() ; 00381 if (M) 00382 log << M->current_pose() ; 00383 } 00384 00385 // Record stuff for visualization 00386 viz_lock() ; 00387 m_attractive = A ; 00388 m_repulsive = R ; 00389 m_total_force = F ; 00390 00391 m_cmd = C ; 00392 viz_unlock() ; 00393 } 00394 00395 //--------------------------- VISUALIZATION ----------------------------- 00396 00397 #ifdef INVT_HAVE_LIBGL 00398 00399 // Helper function to convert the magnitude of a vector into a string, 00400 // prefixing the supplied single character label to the mag string. 00401 static std::string mag(char label, const Vector& v) 00402 { 00403 using namespace std ; 00404 00405 std::ostringstream v_str ; 00406 v_str << label << ": " 00407 << setw(8) << fixed << setprecision(1) << magnitude(v) ; 00408 return v_str.str() ; 00409 } 00410 00411 // The visualization callback 00412 void Extricate::render_me() 00413 { 00414 // Make local copies so that extricate thread isn't held up waiting 00415 // for visualization thread to complete. 00416 viz_lock() ; 00417 Command C = m_cmd ; 00418 Vector A = m_attractive ; 00419 Vector R = m_repulsive ; 00420 Vector T = m_total_force ; 00421 viz_unlock() ; 00422 00423 // Draw principal axes (to make it easier to understand what's going 00424 // on with the force vectors and turn command). 00425 unit_view_volume() ; 00426 glColor3f(1, 1, 1) ; 00427 glBegin(GL_LINES) ; 00428 glVertex2i(0, -1) ; 00429 glVertex2i(0, 1) ; 00430 glVertex2i(-1, 0) ; 00431 glVertex2i( 1, 0) ; 00432 glEnd() ; 00433 00434 // Render the extrication decision's visualization 00435 if (C.drive == 0 && C.turn == 0) 00436 ; // no extrication took place 00437 else 00438 { 00439 glColor3f(0, 1, 0) ; 00440 draw_vector(normalized(A)) ; 00441 00442 glColor3f(1, 0, 0) ; 00443 draw_vector(normalized(R)) ; 00444 00445 glColor3f(0.15f, 0.75f, 0.85f) ; 00446 draw_vector(normalized(T)) ; 00447 00448 glColor3f(1, 1, 1) ; 00449 draw_vector(Vector(0.75f * C.drive * cos(C.turn), 0.75f * sin(C.turn))) ; 00450 } 00451 00452 // Label the visualization so that it is easy to tell which behaviour 00453 // is being visualized. Also show the magnitudes of the forces. 00454 restore_view_volume() ; 00455 text_view_volume() ; 00456 glColor3f(0, 1, 1) ; 00457 draw_label(3, 12, "Extricate") ; 00458 00459 glColor3f(0, 1, 0) ; 00460 draw_label(3, m_geometry.height - 28, mag('A', A).c_str()) ; 00461 00462 glColor3f(1, 0, 0) ; 00463 draw_label(3, m_geometry.height - 16, mag('R', R).c_str()) ; 00464 00465 glColor3f(0.15f, 0.75f, 0.85f) ; 00466 draw_label(3, m_geometry.height - 4, mag('T', T).c_str()) ; 00467 restore_view_volume() ; 00468 } 00469 00470 #endif 00471 00472 //----------------------------- CLEAN-UP -------------------------------- 00473 00474 Extricate::~Extricate(){} 00475 00476 //----------------------------------------------------------------------- 00477 00478 } // end of namespace encapsulating this file's definitions 00479 00480 /* So things look consistent in everyone's emacs... */ 00481 /* Local Variables: */ 00482 /* indent-tabs-mode: nil */ 00483 /* End: */