SMACC2
Loading...
Searching...
No Matches
cp_forward_obstacle_guard.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 <cmath>
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
37// state machine events, posted by CbObstacleGuard on the guard's signals
38template <typename TSource, typename TOrthogonal>
39struct EvObstacleTooClose : sc::event<EvObstacleTooClose<TSource, TOrthogonal>>
40{
41};
42
43template <typename TSource, typename TOrthogonal>
44struct EvObstacleCleared : sc::event<EvObstacleCleared<TSource, TOrthogonal>>
45{
46};
47
49{
50 float halfAngleRad = 0.349f; // 20 deg cone about the sensor +x axis
51 float triggerRangeM = 3.0f; // too close below this ...
52 float clearRangeM = 4.0f; // ... cleared again above this (hysteresis)
53 float minRangeM = 0.6f; // ignore the airframe's own returns
54 // Ignore returns more than this far below the sensor (metres, sensor frame):
55 // the floor of a passage flown at low altitude must not count as an obstacle
56 // ahead; anything reaching up to within this of the vehicle still does. 0 disables.
57 float floorCutoffM = 0.8f;
58 int minHits = 10; // cone points below triggerRangeM per cloud to count
59 int triggerClouds = 2; // consecutive triggering clouds before "too close"
60 int clearClouds = 10; // consecutive clear clouds before "cleared"
61 // Clouds captured while the vehicle yaws faster than this (rad/s, derived from
62 // successive /fmu/out/vehicle_attitude headings) are ignored: the cone is
63 // sweeping the walls, not looking along the direction of travel (e.g. the
64 // 180 deg turn at the start of a retrace). 0 disables the gate.
65 float maxYawRateRadS = 0.35f;
66 // Evaluate the cone in a level frame: each cloud is rotated by the vehicle's
67 // roll and pitch (from /fmu/out/vehicle_attitude) so the cone axis is the
68 // true heading and floorCutoffM is true height below the sensor. Without it
69 // a nose-down pitch tilts the floor up into the cone.
70 bool levelFrame = true;
71 // The cloud is in a camera OPTICAL frame (z forward, x right, y down - e.g. a
72 // depth_image_proc point cloud) rather than the sensor frame assumed above
73 // (x forward, y left, z up): remap every point before the cone test.
74 bool opticalFrameInput = false;
75};
76
77// Watches a PointCloud2 (sensor frame) for returns inside a forward cone and
78// raises / clears an obstacle flag with hysteresis. "Forward" is the sensor's
79// +x axis: with the lidar mounted level and every leg flown in
80// YawMode::TANGENT, body x is the direction of travel while cruising. The
81// cone is not the velocity direction during vertical legs, holds, or the
82// 180-degree turn at the start of a retrace - keep the trigger range below
83// half the narrowest passage width so those sweeps do not trip it.
84//
85// Requires a CpTopicSubscriber<sensor_msgs::msg::PointCloud2> on the same
86// client (ClGenericSensor<PointCloud2> creates one). Subscribes to PX4's
87// /fmu/out/vehicle_attitude itself (level frame + yaw-rate gate). The sensor must be mounted level with the body (x
88// forward, z up). onCloud runs on the ROS executor; it only emits SmaccSignals.
90{
91public:
94
95 void onInitialize() override;
96
97 template <typename T>
98 smacc2::SmaccSignalConnection onObstacleTooClose(void (T::*callback)(), T * object)
99 {
100 return this->getStateMachine()->createSignalConnection(onObstacleTooClose_, callback, object);
101 }
102
103 template <typename T>
104 smacc2::SmaccSignalConnection onObstacleCleared(void (T::*callback)(), T * object)
105 {
106 return this->getStateMachine()->createSignalConnection(onObstacleCleared_, callback, object);
107 }
108
109 // forget the current flag and debounce state: a behavior arming itself calls
110 // this so a stale flag (e.g. the ground seen while sitting on the pad) does
111 // not fire the moment it connects; a real obstacle re-triggers within
112 // triggerClouds clouds
113 void reset();
114
115 bool isTooClose() const { return tooClose_.load(); }
116 float lastYawRate() const { return yawRate_.load(); }
117 float lastMinRange() const { return lastMinRange_.load(); }
118 int lastHits() const { return lastHits_.load(); }
119 const ForwardObstacleGuardParams & params() const { return params_; }
120
123
124private:
125 void onCloud(const sensor_msgs::msg::PointCloud2 & msg);
126 void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg);
127 // roll/pitch-only rotation (sensor -> level frame) from the latest attitude; false if none yet
128 bool levelRotation(std::array<float, 9> & r) const;
129
132 nullptr;
133 rclcpp::Subscription<px4_msgs::msg::VehicleAttitude>::SharedPtr attitudeSub_;
134 double lastYaw_ = 0.0;
135 uint64_t lastYawStampUs_ = 0;
136 mutable std::mutex attitudeMutex_;
137 std::array<float, 4> attitudeQ_{{1.0f, 0.0f, 0.0f, 0.0f}}; // PX4 order w, x, y, z
138 bool haveAttitude_ = false;
139 std::atomic<float> yawRate_{0.0f};
141 float cosHalfAngle_ = 1.0f;
144 std::atomic<bool> tooClose_{false};
145 std::atomic<float> lastMinRange_{std::numeric_limits<float>::infinity()};
146 std::atomic<int> lastHits_{0};
147};
148
149} // namespace cl_px4_mr
smacc2::client_core_components::CpTopicSubscriber< sensor_msgs::msg::PointCloud2 > * subscriber_
CpForwardObstacleGuard(ForwardObstacleGuardParams params={})
void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg)
smacc2::SmaccSignalConnection onObstacleTooClose(void(T::*callback)(), T *object)
const ForwardObstacleGuardParams & params() const
bool levelRotation(std::array< float, 9 > &r) const
void onCloud(const sensor_msgs::msg::PointCloud2 &msg)
smacc2::SmaccSignalConnection onObstacleCleared(void(T::*callback)(), T *object)
smacc2::SmaccSignal< void()> onObstacleCleared_
rclcpp::Subscription< px4_msgs::msg::VehicleAttitude >::SharedPtr attitudeSub_
smacc2::SmaccSignal< void()> onObstacleTooClose_
ISmaccStateMachine * getStateMachine()
smacc2::SmaccSignalConnection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)
boost::signals2::connection SmaccSignalConnection