SMACC2
Loading...
Searching...
No Matches
cb_wait_for_heading_stable.cpp
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
17
18#include <chrono>
19#include <deque>
20#include <utility>
21
22namespace cl_px4_mr
23{
24
25namespace
26{
27float wrapPi(float a)
28{
29 while (a > M_PI) a -= 2.0f * M_PI;
30 while (a < -M_PI) a += 2.0f * M_PI;
31 return a;
32}
33} // namespace
34
36
38{
39 if (localPosition_ == nullptr)
40 {
41 RCLCPP_ERROR(getLogger(), "CbWaitForHeadingStable: no CpVehicleLocalPosition");
42 this->postPx4Failure();
43 return;
44 }
45 const bool checkExpected = std::isfinite(params_.expectedHeadingRad);
46 RCLCPP_INFO(
47 getLogger(),
48 "CbWaitForHeadingStable: waiting for the heading to hold within %.2f deg/s over %.0f s%s "
49 "(timeout %.0f s)",
50 params_.maxDriftRadS * 180.0 / M_PI, params_.windowS,
51 checkExpected ? " and match the parked heading" : "", params_.timeoutS);
52
53 using clock = std::chrono::steady_clock;
54 const auto start = clock::now();
55 std::deque<std::pair<double, float>> samples; // (t since start, heading)
56 rclcpp::Rate rate(10.0);
57 double lastReport = -10.0;
58
59 while (!this->isShutdownRequested())
60 {
61 const double t = std::chrono::duration<double>(clock::now() - start).count();
63 {
64 samples.emplace_back(t, localPosition_->getHeading());
65 }
66 while (!samples.empty() && t - samples.front().first > params_.windowS)
67 {
68 samples.pop_front();
69 }
70
71 bool windowFull =
72 !samples.empty() && samples.back().first - samples.front().first >= 0.9 * params_.windowS;
73 float drift = 0.0f, offset = 0.0f;
74 bool stable = false, aligned = true;
75 if (windowFull)
76 {
77 // unwrap over the window: max excursion from the first sample
78 float lo = 0.0f, hi = 0.0f;
79 for (const auto & s : samples)
80 {
81 const float d = wrapPi(s.second - samples.front().second);
82 lo = std::min(lo, d);
83 hi = std::max(hi, d);
84 }
85 drift = hi - lo;
86 stable = drift <= static_cast<float>(params_.maxDriftRadS * params_.windowS);
87 if (checkExpected)
88 {
89 offset = wrapPi(samples.back().second - params_.expectedHeadingRad);
90 aligned = std::fabs(offset) <= params_.headingTolRad;
91 }
92 }
93
94 if (windowFull && stable && aligned)
95 {
96 RCLCPP_INFO(
97 getLogger(),
98 "CbWaitForHeadingStable: heading %.1f deg stable (%.2f deg over %.0f s%s) - posting "
99 "success",
100 samples.back().second * 180.0 / M_PI, drift * 180.0 / M_PI, params_.windowS,
101 checkExpected ? (", offset " + std::to_string(offset * 180.0 / M_PI) + " deg").c_str()
102 : "");
103 this->postPx4Success();
104 return;
105 }
106
107 if (t - lastReport >= 2.0)
108 {
109 lastReport = t;
110 if (!windowFull)
111 {
112 RCLCPP_INFO(
113 getLogger(), "CbWaitForHeadingStable: collecting (%zu samples, position valid=%d)",
114 samples.size(), static_cast<int>(localPosition_->isValid()));
115 }
116 else
117 {
118 RCLCPP_WARN(
119 getLogger(),
120 "CbWaitForHeadingStable: heading %.1f deg NOT ready: drift %.2f deg/s%s - the EKF "
121 "yaw is not trustworthy for takeoff",
122 samples.back().second * 180.0 / M_PI, drift / params_.windowS * 180.0 / M_PI,
123 checkExpected
124 ? (", offset from parked heading " + std::to_string(offset * 180.0 / M_PI) + " deg")
125 .c_str()
126 : "");
127 }
128 }
129
130 if (t > params_.timeoutS)
131 {
132 RCLCPP_ERROR(
133 getLogger(),
134 "CbWaitForHeadingStable: timeout (%.0f s): heading %.1f deg, drift %.2f deg/s. "
135 "Refusing to arm; restart the estimator / SITL",
136 params_.timeoutS, samples.empty() ? 0.0 : samples.back().second * 180.0 / M_PI,
137 drift / params_.windowS * 180.0 / M_PI);
138 this->postPx4Failure();
139 return;
140 }
141 rate.sleep();
142 }
143 RCLCPP_WARN(getLogger(), "CbWaitForHeadingStable: shutdown requested");
144 this->postPx4Failure();
145}
146
147} // namespace cl_px4_mr
CbWaitForHeadingStable(HeadingStableParams params={})
virtual rclcpp::Logger getLogger() const
bool isShutdownRequested()
onEntry is executed in a new thread. However the current state cannot be left until the onEntry threa...
float wrapPi(float angle)