SMACC2
Loading...
Searching...
No Matches
cb_px4_client_behavior_base.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 <atomic>
24#include <chrono>
25#include <optional>
26
34
35namespace cl_px4_mr
36{
37
38// Base for cl_px4_mr client behaviors. Encodes the async-thread discipline
39// (see the locking rule in CLAUDE.md): the shared components are resolved and
40// completion signals wired in onStateOrthogonalAllocation, on the state
41// machine thread - never from the asynchronous onEntry thread, where
42// requiresComponent/createSignalConnection contend for the state machine
43// mutex and can deadlock against a concurrent transition.
44//
45// Derived behaviors:
46// - override wireCompletionSignals() to connect their completion signal
47// (goal checker reached, vehicle disarmed, ...) - it is invoked on the
48// state machine thread during allocation
49// - keep onEntry() as pure command issuance (setpoints, goals, mode switches)
50// - finish through postPx4Success()/postPx4Failure(), which latch completion
51// so the timeout watchdog and a late signal can never double-post
52//
53// Timeout watchdog: setTimeout() arms a deadline checked from update() (the
54// SignalDetector thread, ~20 Hz). On expiry the behavior posts failure -
55// previously no signal-driven PX4 behavior had ANY failure path, so a stuck
56// goal checker or a lost vehicle meant waiting forever. Poll-driven behaviors
57// that override update() must chain CbPx4ClientBehaviorBase::update() to keep
58// the watchdog.
61{
62public:
63 template <typename TOrthogonal, typename TSourceObject>
78
80
81 // arm the completion watchdog (checked from update() on the SignalDetector
82 // thread); disabled when never called
83 void setTimeout(std::chrono::milliseconds timeout) { timeout_ = timeout; }
84
85 void update() override
86 {
87 if (!timeout_ || completed_)
88 {
89 return;
90 }
91
92 auto now = std::chrono::steady_clock::now();
93 if (!watchdogStart_)
94 {
95 watchdogStart_ = now;
96 return;
97 }
98
99 if (now - *watchdogStart_ > *timeout_)
100 {
101 RCLCPP_ERROR(
102 getLogger(), "[%s] Timed out after %ld ms without completing - posting failure",
103 getName().c_str(), static_cast<long>(timeout_->count()));
104 this->postPx4Failure();
105 }
106 }
107
108protected:
109 // connect completion signals here (invoked on the state machine thread
110 // during state allocation)
111 virtual void wireCompletionSignals() {}
112
114 {
115 if (!completed_.exchange(true))
116 {
117 this->postSuccessEvent();
118 }
119 }
120
122 {
123 if (!completed_.exchange(true))
124 {
125 this->postFailureEvent();
126 }
127 }
128
135
136private:
137 std::atomic<bool> completed_{false};
138 std::optional<std::chrono::milliseconds> timeout_;
139 std::optional<std::chrono::steady_clock::time_point> watchdogStart_;
140};
141
142} // namespace cl_px4_mr
std::optional< std::chrono::steady_clock::time_point > watchdogStart_
std::optional< std::chrono::milliseconds > timeout_
void setTimeout(std::chrono::milliseconds timeout)
virtual rclcpp::Logger getLogger() const
void requiresComponent(SmaccComponentType *&storage, ComponentRequirement requirementType=ComponentRequirement::SOFT)