longer95479@home:~$

Wall Follower


github: github.com/Longer95479/Fast-Drone-XI35

Result:

How to make a drone follow a wall while flying?

Pesudocode of finding next waypoint to follow the wall is shown as the flollwing.

\begin{algorithm}
\caption{Find\_next\_waypoint\_for\_wall\_following}
\begin{algorithmic}

\REQUIRE pts\_end $^{body}$ are initialzed
\ENSURE some postconditions
\INPUT pts\_end $^{body}$, $\vec{p} \in$ pts\_end $^{world}$, $T_{body}^w$
\OUTPUT next\_way\_point
\FUNCTION{Find-next-waypoint-for-wall-following}{pts\_end $^{body}$}
    \IF{has\_reached\_waypoint}
    \FOR{$\mathbf{each}$ pt\_end$^{body}$ $\in$ pts\_end$^{body}$}
        \STATE pt\_end = $R_{body}^w \cdot$ pt\_end$^{body}$ $ + t_{body}^w$
        \STATE raycaster.\CALL {Set-input}{body\_position/resolution, pt\_end/resolution}
        \WHILE {raycaster. \CALL {step}{ray\_pt}}
            \IF {\CALL {is-known-occupied}{ray\_pt}}
                \STATE Occupied\_pts.\CALL{push-back}{ray\_pt}
            \ENDIF
        \ENDWHILE
    \ENDFOR
    \IF {Occupied\_pts.\CALL {Size}{} > certain\_threshold}
        \COMMENT{Wall existing}
        \STATE $\vec{p}, \vec{v}$ = \CALL{Plane-Fitting}{Occupied\_pts}
        \STATE next\_way\_point = $\vec{p} +$ \CALL {Sign}{(body\_position - $\vec{p}$) $\cdot \vec{v}$} $\cdot d_w  \frac{\vec{v}}{||\vec{v}||}$
        \IF{\CALL {is-known-occupied}{next\_way\_point}}
        \STATE next\_way\_point = \CALL {Ray-casting}{body\_position, next\_way\_point}
        \STATE next\_way\_point = body\_position + k $\cdot$ (next\_way\_point - body\_position)
        \COMMENT {0 < k < 1}
        \ENDIF
    \ELSE
        \COMMENT {Wall absent}
        \STATE next\_way\_point = \CALL{Move-right-forward}{$T_{body}^w$}
    \ENDIF
    \STATE Occupied\_pts.\CALL{clean}{}
    \RETURN next\_way\_point
    \ENDIF
\ENDFUNCTION

\end{algorithmic}
\end{algorithm}
\begin{algorithm}
\caption{Ray-casting}
\begin{algorithmic}

\FUNCTION{Ray-casting}{pt\_start, pt\_end}
\STATE to do
\ENDFUNCTION

\end{algorithmic}
\end{algorithm}
\begin{algorithm}
\caption{Plane-Fitting}
\begin{algorithmic}

\FUNCTION{Plane-Fitting}{pts}
\STATE to do
\ENDFUNCTION

\end{algorithmic}
\end{algorithm}

FOV points initialization

\begin{algorithm}
\caption{pts\_end\_body\_initialization}
\begin{algorithmic}

\FUNCTION {Pts-end-body-initialization}{f,deltaY,deltaZ,X}

width_idx = (int)std::ceil(f_/X_ * deltaY_)

\ENDFUNCTION

\end{algorithmic}
\end{algorithm}

In VINS-Fusion, in order to simplify the notation, author denotes the IMU as body's center (if the IMU isn't used, left camera is denoted as body's center).

NED (North-East-Down) convenstion is used as that of body frame in PX4, but NWU (North-West-Up) is denoted as body frame in VINS-Fusion and fast-drone-250.

In ego-planner (or fast-drone-250), planner node subscribes entrinsic topic and depthOdom synchronizer in order to compute the cam_r and cam_pos using entrinsic and odom (body frame) translation.

Modification in fast-drone source code

Wall follower class should be added in EGOPlannerManager class, rather than creating a new nodes, as GridMap::Ptr grid_map_ can only be observable in EGOPlannerManager class to be reused.

Three steps need to be done while adding a new package in the catkin workspace to develop a project

  1. create a new package, including writing package.xml, Cmakelists.txt, *.cpp, *.h files.
  2. Modify .cpp .h of the package which depend on the new one.
    • add #include into .h of the package using the new one
    • add initialization function of new package into .cpp of the package using the new one
  3. Modify package.xml Cmakelists.txt of the package using the new one

More details is shown as follow.

wall_follower.h and wall_follower.h under wall_follower dir

wall_follower.cpp:

  • Move ptsEndFovGeneration() from class WallFollower to WallFollower::PtsEndFov, and modify corsponding varias name (e.g. X_ to X = pts_end_fov_ptr->X_;)
  • add some param print
  • Move pts_end_fov_pub_ from constructor of WallFollower to that of WallFollower::PtsEndFov
  • fix a bug in the function publicPtsEndFov()
          void WallFollower::PtsEndFov::publicPtsEndFov()
      ... {
      327      for (auto& pt_end_world: pts_end_world) {
      328          pt.x = pt_end_world(0);
      329          pt.y = pt_end_world(1);
      330          pt.z = pt_end_world(2);
      331 +        cloud.push_back(pt);
      332      }
      ... }
    
    
  • fix the logic of projecting pts_end_body_ to pts_end_world_ by varify odom before using it.
      199 +    if (grid_map_ptr_->md_.has_odom_) {
      ...
      214 +        for (auto& pt_end: pts_end) {
      215 +            pt_end = camera_r_m * pt_end + camera_pos;
      216 +        }
      ...
      221 +        pts_end_fov_ptr_->pts_end_world_ = pts_end;
      222 +    }
    
  • add odom_sub_

wall_follower.h:

grid_map.h and grid_map.cpp under plan_env fir

All modification in this file is to make camera_pos_ and camera_r_m_ update and observable for projecting pts_end_body_ to pts_end_world_ in wall_follwer.cpp

grid_map.h:

  • make mp_ and md_ public
      9 -private:
      10    MappingParameters mp_;
      11   MappingData md_;
      12  
      13 +  private:
      14 +
    

grid_map.cpp:

  • add oritation update in void GridMap::odomCallback(const nav_msgs::OdometryConstPtr &odom) The reason why this change was reversed is that extrinsic of exp and sim are different. subscribing grid_map/odom in wall_follower.cpp directly instead of using grid_map_ptr_->md.camera_pos_ and grid_map_ptr_->md.camera_r_m_
      22 @@ -735,6 +735,11 @@ void GridMap::odomCallback(const nav_msgs::OdometryConstPtr &odom)
      {
      23    md_.camera_pos_(1) = odom->pose.pose.position.y;
      24    md_.camera_pos_(2) = odom->pose.pose.position.z;
      25  
      26 +  md_.camera_r_m_ = Eigen::Quaterniond(odom->pose.pose.orientation.w,
      27 +                                       odom->pose.pose.orientation.x,
      28 +                                       odom->pose.pose.orientation.y,
      29 +                                       odom->pose.pose.orientation.z).toRotationMatrix();
      30 +
      31    md_.has_odom_ = true;
      32  }
    

ego_replan_fsm.h and ego_replan_fsm.cpp under plan_manage dir

package.xml and CmakeLists.txt under plan_manage dir

Reference


Debug log

Invalid use of non-static data member

Nested classes are not connected to any instance of the outer class.

My case:

class WallFollower {
    struct PtsEndFov {
        ...
        void publicPtsEndFov();
    }

    GridMap::Ptr grid_map_ptr_;
}

grid_map_ptr_ is used in void publicPtsEndFov(); leading to error “Invalid use of non-static data member”.

stackoverflow: invalid use of non-static data member

No matching function for call to ‘ros::NodeHandle::param’

nh.param("wall_follower/f", f_, 100);

It fails on builds with the following error:

error: No matching function for call to ‘ros::NodeHandle::param’

f_ is double type and 100 is int type. They are not matching. The correction is shown as below:

nh.param("wall_follower/f", f_, 100.0);

Why catkin_make make nothing change after I edited the source code

Answer in ROS Answers:

I understand it as both your src folder contains files with the same names and same CMakeList but only some lines of code differs between the two. That being said when you run catkin_make after swapping your folders catkin doesn’t notice any change in your files (I don’t really know how, probably CMake related, but if someone can enlighten me on this part) so if nothing has changed catkin won’t rebuild everything as usual.

To avoid that you have two options :

  • Delete the build and devel folder each time you change the src folder
  • Change a little thing in a file so that catkin notice a change and rebuild the package

My problem has been solved following “Delete the build and devel folder each time you change the src folder”.

`WallFollower::~WallFollower()’ is defined multiple times

I wrote deconstruct function outside the class in .h file, which should be enclosed by class that it belong to.

static member function

class members should not be used in static member function, as there is no instance while using a static member function.

std::vector can’t be access the element whose index out of range

if there are not elements pushed back (i.e. vector is clear), any elements can’t be accessed using index.

If no occupied points, it shouldn’t use planeFitting()

rand()%occupied_pts.size(); //will be rand()%0, wrong! 

Invalid argument passed to canTransform argument source_frame in tf2 frame_ids cannot be empty

%0 is no define

Just change the order of a variable

next_waypoint need to be initialized

void WallFollower::findWayPointCallback(const ros::TimerEvent& /*event*/)
{
    if (have_odom_) {
        if (!is_next_waypoint_initialized_) {
            next_way_point_ = body_pos_;
            is_next_waypoint_initialized_ = true;
        }
        // std::cout << "MY_DEBUG: dist = " << (body_pos_-next_way_point_).norm() << std::endl;
        if ((body_pos_-next_way_point_).norm() < 0.5) {
            findNextWayPoint();
        }
    }
}

fix most forward point in plane

    /* get the point of most front in the best plane */
    double max_temp = 0;
    for (auto &pt: occupied_pts) {
        double d = plane_fitter_ptr_->solveDistance(pt, last_best_plane);
        if (d < plane_fitter_ptr_->sigma_) {
            // double temp = body_pos_.normalization().transpose() * pt;
            double x_local = (body_r_m_.transpose() * (pt - body_pos_))(0);
            if (x_local > max_temp) {
                max_temp = x_local;
                last_best_plane.p_ = pt;
            }
        }
    }


Write and render peseudocode in the blogs


The way to write and render pseudocode in personal site will be introduced in this blog.

I. Using MathJax 3.x to render formula

Include the following in the <head> of your page:

<script>
    MathJax = {
        tex: {
            inlineMath: [['$','$'], ['\\(','\\)']],
            displayMath: [['$$','$$'], ['\\[','\\]']],
            processEscapes: true,
            processEnvironments: true,
        }
    }
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-chtml-full.js"
        integrity="sha256-kbAFUDxdHwlYv01zraGjvjNZayxKtdoiJ38bDTFJtaQ="
        crossorigin="anonymous">
</script>

II. Grab pseudocode.js

Include the following in the <head> of your page:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.css">
<script src="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.js">
</script>

III. Write your pseudocode inside a <pre>

We assume the pseudocode to be rendered is in a <pre> DOM element. Here is an example that illustrates a quicksort algorithm:

<pre id="quicksort" class="pseudocode">
    % This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
    \begin{algorithm}
    \caption{Quicksort}
    \begin{algorithmic}
    \PROCEDURE{Quicksort}{$A, p, r$}
        \IF{$p < r$} 
            \STATE $q = $ \CALL{Partition}{$A, p, r$}
            \STATE \CALL{Quicksort}{$A, p, q - 1$}
            \STATE \CALL{Quicksort}{$A, q + 1, r$}
        \ENDIF
    \ENDPROCEDURE
    \PROCEDURE{Partition}{$A, p, r$}
        \STATE $x = A[r]$
        \STATE $i = p - 1$
        \FOR{$j = p$ \TO $r - 1$}
            \IF{$A[j] < x$}
                \STATE $i = i + 1$
                \STATE exchange
                $A[i]$ with $A[j]$
            \ENDIF
            \STATE exchange $A[i]$ with $A[r]$
        \ENDFOR
    \ENDPROCEDURE
    \end{algorithmic}
    \end{algorithm}
</pre>

IV. Render the element or class using pseudocode.js

Insert the following Javascript snippet at the end of your document:

  • Render the element using pseudocode.js
      <script>
          pseudocode.renderElement(document.getElementById("quicksort"));
      </script>
    
  • Render all elements of the class using pseudocode.js
      <script>
          pseudocode.renderClass("pseudocode");
      </script>
    

Example

    % This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
    \begin{algorithm}
    \caption{Quicksort}
    \begin{algorithmic}
    \PROCEDURE{Quicksort}{$A, p, r$}
        \IF{$p < r$} 
            \STATE $q = $ \CALL{Partition}{$A, p, r$}
            \STATE \CALL{Quicksort}{$A, p, q - 1$}
            \STATE \CALL{Quicksort}{$A, q + 1, r$}
        \ENDIF
    \ENDPROCEDURE
    \PROCEDURE{Partition}{$A, p, r$}
        \STATE $x = A[r]$
        \STATE $i = p - 1$
        \FOR{$j = p$ \TO $r - 1$}
            \IF{$A[j] < x$}
                \STATE $i = i + 1$
                \STATE exchange
                $A[i]$ with $A[j]$
            \ENDIF
            \STATE exchange $A[i]$ with $A[r]$
        \ENDFOR
    \ENDPROCEDURE
    \end{algorithmic}
    \end{algorithm}
\begin{algorithm}
\caption{Test text-style}
\begin{algorithmic}
\REQUIRE some preconditions
\ENSURE some postconditions
\INPUT some inputs
\OUTPUT some outputs
\PROCEDURE{Test-Declarations}{}
    \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
    \STATE font weights: {normal weight, \bfseries bold, \mdseries
    medium, \lfseries lighter. }
    \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
    \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
    footnotesize \small small \normalsize normal \large large \Large Large
    \LARGE LARGE \huge huge \Huge Huge \normalsize
\ENDPROCEDURE
\PROCEDURE{Test-Commands}{}
    \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
    \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
    \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
    \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
\ENDPROCEDURE
\PROCEDURE{Test-Colors}{}
% feature not implemented
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}

\begin{algorithm}
\caption{Test atoms}
\begin{algorithmic}
\STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
\STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
\STATE \textbf{Carriage return:} first line \\ second line
\STATE \textbf{Text-symbols:} \textbackslash
\STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
\STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
\END{ALGORITHMIC}
\END{ALGORITHM}

Reference



ego planner roslaunch


以下是 advanced_param.xml 的简略版本:

<launch>
  <arg name="map_size_x_"/>
  <arg name="map_size_y_"/>
  <arg name="map_size_z_"/>

  <!-- main node -->
  <!-- <node pkg="ego_planner" name="ego_planner_node" type="ego_planner_node" output="screen" launch-prefix="valgrind"> -->
  <node pkg="ego_planner" name="drone_$(arg drone_id)_ego_planner_node" type="ego_planner_node" output="screen">

    <remap from="~grid_map/odom" to="/drone_$(arg drone_id)_$(arg odometry_topic)"/>
    <remap from="~grid_map/cloud" to="/drone_$(arg drone_id)_$(arg cloud_topic)"/>
    <remap from="~grid_map/pose"   to = "/drone_$(arg drone_id)_$(arg camera_pose_topic)"/> 
    <remap from="~grid_map/depth" to = "/drone_$(arg drone_id)_$(arg depth_topic)"/>
    

    <!-- planning fsm -->
    <param name="fsm/flight_type" value="$(arg flight_type)" type="int"/>
    <param name="fsm/thresh_replan_time" value="1.0" type="double"/>
    <param name="fsm/thresh_no_replan_meter" value="1.0" type="double"/>
    <param name="fsm/planning_horizon" value="$(arg planning_horizon)" type="double"/>


  </node>
</launch>
  • 对于 </node> 标签,type= 指明 可执行文件name = 用作 node 运行后使用 rosnode list 列出的节点的名字
  • remap 中的 from 是 node 自己以为订阅的,to 是实际上订阅的
  • "~grid_map/odom" 中的波浪线表示节点的私有命名空间,因此等价于 "drone_$(arg drone_id)_ego_planner_node/grid_map/odom"
    • 实例:/drone_0_ego_planner_node/grid_map/depth
    • 对应于 grid_map.cpp 中的源代码
        depth_sub_.reset(new message_filters::Subscriber<sensor_msgs::Image>(node_, "grid_map/depth", 50));
      

      由于 grid_map/depth 没有以 / 开头,所以默认加上节点的命名空间?

  • arg标签 是用于将参数从命令行传递到launch文件中,param标签 是用于将参数从launch文件传递到ROS节点代码中,从而可以在 c++ 代码里通过 node.param("param_name", var, default_val) 获取到该参数(ROS学习笔记(四)- ROS的launch文件

在 ROS(Robot Operating System)中,确保包的正确编译顺序是非常重要的,特别是当一个包依赖另一个包时。要确保包的编译顺序,可以使用以下几种方法:

1. 使用 find_packagecatkin_package

在每个 ROS 包的 CMakeLists.txt 文件中,通过使用 find_packagecatkin_package 声明依赖关系,可以确保 ROS 编译系统知道包之间的依赖性。

示例:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  your_dependency_package  # 依赖的包
)

catkin_package(
  CATKIN_DEPENDS roscpp std_msgs your_dependency_package  # 依赖的包
)

这会确保 your_dependency_package 在编译当前包之前被正确编译。

2. 修改 package.xml 声明依赖

package.xml 文件中也需要声明依赖关系。使用 <build_depend><exec_depend> 来声明构建和运行时依赖。

示例:

<build_depend>your_dependency_package</build_depend>
<exec_depend>your_dependency_package</exec_depend>

确保依赖的包被声明为 build_depend,以便在编译时可以自动按照正确的顺序构建。

3. 使用 catkin_makecatkin build

  • 如果使用 catkin_make,ROS 会根据 CMakeLists.txtpackage.xml 文件中的依赖关系,自动确定包的编译顺序。
  • 如果使用 catkin_toolscatkin build,编译时也会自动根据依赖性来调整包的编译顺序,推荐使用这个工具,因为它可以更高效地处理依赖关系。

4. 使用 rosdep 安装依赖

在编译之前,使用 rosdep install --from-paths src --ignore-src -r -y 来安装所有依赖包。rosdep 会检查依赖关系并安装缺失的包,以避免编译时找不到依赖包的错误。

5. 确保包路径顺序正确

如果你在工作空间中有多个包,确保 CMakeLists.txtpackage.xml 的依赖声明是正确的,并且 catkin_makecatkin build 能识别所有路径。你可以通过检查 ROS_PACKAGE_PATH 环境变量,确保 ROS 能找到依赖的包。

6. 在工作空间内进行分步构建

如果某些包的依赖关系复杂,无法自动解析,你可以分两步构建:

  1. 先编译依赖包:
    catkin_make --only-pkg-with-deps <your_dependency_package>
    
  2. 然后再编译依赖这些包的目标包:
    catkin_make --only-pkg-with-deps <your_target_package>
    

通过这些方法,可以保证 ROS 包的编译顺序,避免 Could not find a package configuration file provided by 这类错误。



Total views. cowboys. Hits