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};
72
73// Watches a PointCloud2 (sensor frame) for returns inside a forward cone and
74// raises / clears an obstacle flag with hysteresis. "Forward" is the sensor's
75// +x axis: with the lidar mounted level and every leg flown in
76// YawMode::TANGENT, body x is the direction of travel while cruising. The
77// cone is not the velocity direction during vertical legs, holds, or the
78// 180-degree turn at the start of a retrace - keep the trigger range below
79// half the narrowest passage width so those sweeps do not trip it.
80//
81// Requires a CpTopicSubscriber<sensor_msgs::msg::PointCloud2> on the same
82// client (ClGenericSensor<PointCloud2> creates one). Subscribes to PX4's
83// /fmu/out/vehicle_attitude itself (level frame + yaw-rate gate). The sensor must be mounted level with the body (x
84// forward, z up). onCloud runs on the ROS executor; it only emits SmaccSignals.
86{
87public:
90
91 void onInitialize() override;
92
93 template <typename T>
94 smacc2::SmaccSignalConnection onObstacleTooClose(void (T::*callback)(), T * object)
95 {
96 return this->getStateMachine()->createSignalConnection(onObstacleTooClose_, callback, object);
97 }
98
99 template <typename T>
100 smacc2::SmaccSignalConnection onObstacleCleared(void (T::*callback)(), T * object)
101 {
102 return this->getStateMachine()->createSignalConnection(onObstacleCleared_, callback, object);
103 }
104
105 // forget the current flag and debounce state: a behavior arming itself calls
106 // this so a stale flag (e.g. the ground seen while sitting on the pad) does
107 // not fire the moment it connects; a real obstacle re-triggers within
108 // triggerClouds clouds
109 void reset();
110
111 bool isTooClose() const { return tooClose_.load(); }
112 float lastYawRate() const { return yawRate_.load(); }
113 float lastMinRange() const { return lastMinRange_.load(); }
114 int lastHits() const { return lastHits_.load(); }
115 const ForwardObstacleGuardParams & params() const { return params_; }
116
119
120private:
121 void onCloud(const sensor_msgs::msg::PointCloud2 & msg);
122 void onAttitude(const px4_msgs::msg::VehicleAttitude::SharedPtr msg);
123 // roll/pitch-only rotation (sensor -> level frame) from the latest attitude; false if none yet
124 bool levelRotation(std::array<float, 9> & r) const;
125
128 nullptr;
129 rclcpp::Subscription<px4_msgs::msg::VehicleAttitude>::SharedPtr attitudeSub_;
130 double lastYaw_ = 0.0;
131 uint64_t lastYawStampUs_ = 0;
132 mutable std::mutex attitudeMutex_;
133 std::array<float, 4> attitudeQ_{{1.0f, 0.0f, 0.0f, 0.0f}}; // PX4 order w, x, y, z
134 bool haveAttitude_ = false;
135 std::atomic<float> yawRate_{0.0f};
137 float cosHalfAngle_ = 1.0f;
140 std::atomic<bool> tooClose_{false};
141 std::atomic<float> lastMinRange_{std::numeric_limits<float>::infinity()};
142 std::atomic<int> lastHits_{0};
143};
144
145} // 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