SMACC2
Loading...
Searching...
No Matches
cp_tunnel_centering.hpp
Go to the documentation of this file.
1// Copyright 2026 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*****************************************************************************************************************
16 *
17 * Authors: Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
21#pragma once
22
23#include <array>
24#include <atomic>
25#include <chrono>
26#include <mutex>
27
28#include <px4_msgs/msg/vehicle_attitude.hpp>
29#include <rclcpp/rclcpp.hpp>
30#include <sensor_msgs/msg/point_cloud2.hpp>
32#include <smacc2/smacc.hpp>
33
34namespace cl_px4_mr
35{
36
38{
39 // returns closer than this are the vehicle's own arms and rotors, not the cave;
40 // without this cut the floor and ceiling both read 0 m and the vertical trim
41 // is a constant +maxVerticalM climb (sm_cl_px4_mr_test_5 runs 16-21)
42 float minRangeM = 0.6f;
43 // slab ahead of the vehicle (level frame, metres) the walls are measured in
44 float lookAheadMinM = 1.5f;
45 float lookAheadMaxM = 6.0f;
46 // half-height of the band, about the sensor, used for the wall measurement
47 float wallBandHalfHeightM = 0.6f;
48 // half-width of the column, about the heading, used for floor/ceiling; the
49 // column runs from floorLookBackM BEHIND the sensor (what is under and beside
50 // the vehicle counts, e.g. a ledge it hovers over) to lookAheadMaxM. Keep it
51 // near the airframe's half-width: in a 5 m passage a wider column reaches the
52 // walls, whose lower slopes then read as a floor. Points inside the wall band
53 // (|z| <= wallBandHalfHeightM) are never floor or ceiling.
54 float columnHalfWidthM = 1.0f;
55 float floorLookBackM = 1.5f;
56 float lateralSearchM = 8.0f; // walls farther than this are "not seen"
57 float verticalSearchM = 8.0f; // floor/ceiling farther than this are "not seen"
58 // desired height above the floor and minimum gap below the ceiling
59 float floorClearanceM = 1.75f;
60 float ceilingClearanceM = 1.0f;
61 // The vertical correction never takes the vehicle closer to the measured floor
62 // than this, whatever the overhead reading says. Rising terrain ahead (a rock
63 // pile reaching sensor height) reads as a low "ceiling"; diving under it means
64 // flying into the floor - that case is an obstacle for the guard, not a height
65 // to trim to.
66 float minFloorClearanceM = 1.0f;
67 // corrections are clamped ...
68 float maxLateralM = 2.0f;
69 float maxVerticalM = 1.0f;
70 // ... and the lateral one fades to zero when the nearer wall is farther than this
71 float wallFadeM = 6.0f;
72 int minWallPoints = 15; // per side, else that side is "not seen"
74 float smoothing = 0.6f; // EMA weight of the previous value (0 = none)
75 bool levelFrame = true; // rotate the cloud by roll/pitch (needs /fmu/out/vehicle_attitude)
76};
77
78// Keeps the vehicle in the middle of whatever passage it is flying through.
79// Each cloud (sensor frame, rotated level) is reduced to the nearest left wall,
80// right wall, floor and ceiling in a slab ahead; from those it derives a
81// lateral offset (toward the midpoint between the walls) and a vertical one
82// (floorClearanceM above the floor, but ceilingClearanceM below the ceiling),
83// clamped and smoothed. A path follower with useTunnelCentering set adds them
84// to every carrot it commands (see CbPx4PathFollowerBase). No map, no memory:
85// a purely reactive local planner for corridors, tunnels and caves.
86//
87// Requires a CpTopicSubscriber<sensor_msgs::msg::PointCloud2> on the same
88// client; the sensor must be mounted level with the body (x forward, z up).
89// onCloud runs on the ROS executor; consumers read atomics.
91{
92public:
94 virtual ~CpTunnelCentering();
95
96 void onInitialize() override;
97
98 // offsets in the level heading frame: lateral > 0 = move LEFT, vertical > 0 = climb
99 float lateralOffsetM() const { return lateral_.load(); }
100 float verticalOffsetM() const { return vertical_.load(); }
101 // true while clouds keep arriving (fresher than 1 s)
102 bool valid() const;
103
104 // last measurement (metres from the sensor, +inf when not seen)
105 float leftWallM() const { return left_.load(); }
106 float rightWallM() const { return right_.load(); }
107 float floorM() const { return floor_.load(); }
108 float ceilingM() const { return ceiling_.load(); }
109
110 const TunnelCenteringParams & params() const { return params_; }
111
112private:
113 void onCloud(const sensor_msgs::msg::PointCloud2 & msg);
114 void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg);
115
118 nullptr;
119 rclcpp::Subscription<px4_msgs::msg::VehicleAttitude>::SharedPtr attitudeSub_;
120 mutable std::mutex attitudeMutex_;
121 std::array<float, 4> attitudeQ_{{1.0f, 0.0f, 0.0f, 0.0f}};
122 bool haveAttitude_ = false;
123
124 std::atomic<float> lateral_{0.0f};
125 std::atomic<float> vertical_{0.0f};
126 std::atomic<float> left_{0.0f};
127 std::atomic<float> right_{0.0f};
128 std::atomic<float> floor_{0.0f};
129 std::atomic<float> ceiling_{0.0f};
130 std::atomic<int64_t> lastCloudNs_{0};
131 std::chrono::steady_clock::time_point lastLog_;
132};
133
134} // namespace cl_px4_mr
std::chrono::steady_clock::time_point lastLog_
std::atomic< int64_t > lastCloudNs_
CpTunnelCentering(TunnelCenteringParams params={})
rclcpp::Subscription< px4_msgs::msg::VehicleAttitude >::SharedPtr attitudeSub_
void onCloud(const sensor_msgs::msg::PointCloud2 &msg)
void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg)
smacc2::client_core_components::CpTopicSubscriber< sensor_msgs::msg::PointCloud2 > * subscriber_
const TunnelCenteringParams & params() const