SMACC2
Loading...
Searching...
No Matches
signal_detector.cpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft 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: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
21#include <signal.h>
22#include <limits>
23#include <memory>
24#include <thread>
25#include <vector>
26
27#include <lttng/tracepoint.h>
32
33namespace smacc2
34{
35using namespace std::chrono_literals;
36
43: schedulerThread(nullptr),
44 signalDetectorLoop(nullptr),
45 signalDetector(nullptr),
46 scheduler1(nullptr)
47{
48}
49
51{
52 static SmExecution instance;
53 return instance;
54}
55
62{
63 scheduler_ = scheduler;
64 loop_rate_hz = 20.0;
65 end_ = false;
66 initialized_ = false;
67 rosInitialized_ = false;
68 executionModel_ = executionModel;
69}
70
71rclcpp::Node::SharedPtr SignalDetector::getNode() { return this->smaccStateMachine_->getNode(); }
72
79{
80 smaccStateMachine_ = stateMachine;
81 lastState_ = std::numeric_limits<int64_t>::quiet_NaN();
83 this->getNode()->declare_parameter("signal_detector_loop_freq", this->loop_rate_hz);
84
85 auto context = getNode()->get_node_base_interface()->get_context();
86 context->on_shutdown(
87 [this]()
88 {
89 this->stop();
90 this->terminateScheduler();
91 });
92
93 initialized_ = true;
94}
95
102{
103 this->updatableClients_.clear();
104 for (auto pair : this->smaccStateMachine_->getOrthogonals())
105 {
106 auto & orthogonal = pair.second;
107 auto & clients = orthogonal->getClients();
108
109 for (auto & client : clients)
110 {
111 // updatable client components
112 auto updatableClient = dynamic_cast<ISmaccUpdatable *>(client.get());
113
114 if (updatableClient != nullptr)
115 {
116 RCLCPP_DEBUG_STREAM(
117 getLogger(), "Adding updatable client: " << demangleType(typeid(updatableClient)));
118 this->updatableClients_.push_back(updatableClient);
119 }
120
121 // updatable client components
122 std::vector<std::shared_ptr<ISmaccComponent>> components;
123 client->getComponents(components);
124 for (auto & componententry : components)
125 {
126 auto updatableComponent = dynamic_cast<ISmaccUpdatable *>(componententry.get());
127 if (updatableComponent != nullptr)
128 {
129 RCLCPP_DEBUG_STREAM(
130 getLogger(),
131 "Adding updatable component: " << demangleType(typeid(*updatableComponent)));
132 this->updatableClients_.push_back(updatableComponent);
133 }
134 }
135 }
136 }
137}
138
145{
146 updatableStateElements_.push_back(std::vector<ISmaccUpdatable *>());
147
148 auto & updatableElements = updatableStateElements_.back();
149
150 for (auto pair : this->smaccStateMachine_->getOrthogonals())
151 {
152 auto & orthogonal = pair.second;
153 auto & behaviors = orthogonal->getClientBehaviors().back();
154
155 for (auto & currentBehavior : behaviors)
156 {
157 ISmaccUpdatable * updatableClientBehavior =
158 dynamic_cast<ISmaccUpdatable *>(currentBehavior.get());
159
160 if (updatableClientBehavior != nullptr)
161 {
162 RCLCPP_DEBUG_STREAM(
163 getLogger(),
164 "Adding updatable behavior: " << demangleType(typeid(updatableClientBehavior)));
165 updatableElements.push_back(updatableClientBehavior);
166 }
167 }
168 }
169
170 auto updatableState = dynamic_cast<ISmaccUpdatable *>(currentState);
171 if (updatableState != nullptr)
172 {
173 updatableElements.push_back(updatableState);
174 }
175
176 auto statereactors = currentState->getStateReactors();
177 for (auto & sr : statereactors)
178 {
179 ISmaccUpdatable * updatableStateReactor = dynamic_cast<ISmaccUpdatable *>(sr.get());
180 if (updatableStateReactor != nullptr)
181 {
182 RCLCPP_DEBUG_STREAM(
183 getLogger(),
184 "Adding updatable stateReactorr: " << demangleType(typeid(updatableStateReactor)));
185 updatableElements.push_back(updatableStateReactor);
186 }
187 }
188
189 auto eventgenerators = currentState->getEventGenerators();
190 for (auto & eg : eventgenerators)
191 {
192 ISmaccUpdatable * updatableEventGenerator = dynamic_cast<ISmaccUpdatable *>(eg.get());
193 if (updatableEventGenerator != nullptr)
194 {
195 RCLCPP_DEBUG_STREAM(
196 getLogger(),
197 "Adding updatable eventGenerator: " << demangleType(typeid(updatableEventGenerator)));
198 updatableElements.push_back(updatableEventGenerator);
199 }
200 }
201}
202
204{
205 this->findUpdatableStateElements(currentState);
206}
207
209{
210 this->updatableStateElements_.pop_back();
211}
212
214{
215 RCLCPP_INFO(getLogger(), "[SignalDetector] ROS initialization complete, enabling polling loop");
216 rosInitialized_ = true;
217}
218
224void SignalDetector::setProcessorHandle(SmaccFifoScheduler::processor_handle processorHandle)
225{
226 processorHandle_ = processorHandle;
227}
228
235{
236 signalDetectorThread_ = boost::thread(boost::bind(&SignalDetector::pollingLoop, this));
237}
238
245
251void SignalDetector::stop() { end_ = true; }
252
259{
260 if (scheduler_)
261 {
262 RCLCPP_INFO(getLogger(), "[SignalDetector] Terminating scheduler...");
263 scheduler_->terminate();
264 }
265}
266
273{
274 // precondition: smaccStateMachine_ != nullptr
275
276 TRACETOOLS_TRACEPOINT(spinOnce);
277
278 std::lock_guard<std::recursive_mutex> lock(smaccStateMachine_->m_mutex_);
279 try
280 {
282 RCLCPP_DEBUG_STREAM(getLogger(), "Updatable clients: " << this->updatableClients_.size());
283
284 if (this->updatableClients_.size())
285 {
286 auto node = getNode();
287 for (auto * updatableClient : this->updatableClients_)
288 {
289 auto updatableElementNameStr = demangleType(typeid(*updatableClient));
290 auto updatableElementName = updatableElementNameStr.c_str();
291 try
292 {
293 RCLCPP_DEBUG_STREAM(
294 node->get_logger(),
295 "[PollOnce] update client call: " << demangleType(typeid(*updatableClient)));
296
297 TRACETOOLS_TRACEPOINT(smacc2_state_update_start, updatableElementName);
298 updatableClient->executeUpdate(smaccStateMachine_->getNode());
299 TRACETOOLS_TRACEPOINT(smacc2_state_update_start, updatableElementName);
300 }
301 catch (const std::exception & e)
302 {
303 RCLCPP_ERROR_STREAM(
304 node->get_logger(),
305 "Error in updatable elemnent " << updatableElementName << ": " << e.what() << '\n');
306 }
307 }
308 }
309
310 // STATE UPDATABLE ELEMENTS
311 if (
314 this->smaccStateMachine_->stateMachineCurrentAction !=
316 this->smaccStateMachine_->stateMachineCurrentAction !=
318 this->smaccStateMachine_->stateMachineCurrentAction !=
320 {
321 RCLCPP_DEBUG_STREAM(
322 getLogger(), "Updatable states: " << this->updatableStateElements_.size());
323
324 for (auto stateElement : this->updatableStateElements_)
325 {
326 for (auto * udpatableStateElement : stateElement)
327 {
328 std::string updatableElementName = demangleType(typeid(*udpatableStateElement));
329 auto updatableElementNameCstr = updatableElementName.c_str();
330
331 RCLCPP_DEBUG_STREAM(
332 getLogger(), "pollOnce update client behavior call: "
333 << demangleType(typeid(*udpatableStateElement)));
334 TRACETOOLS_TRACEPOINT(smacc2_state_update_start, updatableElementNameCstr);
335
336 udpatableStateElement->executeUpdate(smaccStateMachine_->getNode());
337 TRACETOOLS_TRACEPOINT(smacc2_state_update_start, updatableElementNameCstr);
338 }
339 }
340 }
341 }
342 catch (std::exception & ex)
343 {
344 RCLCPP_ERROR(getLogger(), "Exception during Signal Detector update loop. %s.", ex.what());
345 }
346}
347
354{
355 // rclcpp::Node::SharedPtr nh("~"); // use node name as root of the parameter server
356 rclcpp::Node::SharedPtr _;
357 rclcpp::Rate r0(20);
358
359 // Wait for both SignalDetector::initialize() (called from ISmaccStateMachine constructor)
360 // and ROS initialization to complete (initializeROS called from initiate_impl).
361 // This ensures orthogonals, clients, and ROS objects are fully initialized
362 // before we start polling and accessing them.
363 while ((!initialized_ || !rosInitialized_) && rclcpp::ok() && !end_)
364 {
365 r0.sleep();
366 }
367
368 if (!rclcpp::ok() || end_)
369 {
370 RCLCPP_INFO(getLogger(), "[SignalDetector] Shutdown requested before initialization completed");
371 return;
372 }
373
374 auto nh = getNode();
375
376 if (!nh->get_parameter("signal_detector_loop_freq", this->loop_rate_hz))
377 {
378 RCLCPP_WARN(
379 getLogger(),
380 "Signal Detector frequency (ros param signal_detector_loop_freq) was not set, using default "
381 "frequency: "
382 "%lf",
383 this->loop_rate_hz);
384 }
385 else
386 {
387 RCLCPP_WARN(
388 getLogger(), "Signal Detector frequency (ros param signal_detector_loop_freq): %lf",
389 this->loop_rate_hz);
390 }
391
392 nh->set_parameter(rclcpp::Parameter("signal_detector_loop_freq", this->loop_rate_hz));
393
394 RCLCPP_INFO_STREAM(getLogger(), "[SignalDetector] Loop rate hz:" << loop_rate_hz);
395
397 {
398 RCLCPP_INFO_STREAM(getLogger(), "[SignalDetector] Running in single-threaded mode.");
399
400 rclcpp::Rate r(loop_rate_hz);
401 while (rclcpp::ok() && !end_)
402 {
403 RCLCPP_INFO_STREAM_THROTTLE(
404 getLogger(), *getNode()->get_clock(), 10000, "[SignalDetector] Heartbeat");
405 pollOnce();
406 rclcpp::spin_some(nh);
407 r.sleep();
408 }
409 }
410 else
411 {
412 RCLCPP_INFO_STREAM(getLogger(), "[SignalDetector] Running in multi-threaded mode.");
413
414 rclcpp::executors::MultiThreadedExecutor executor;
415 executor.add_node(nh);
416 executor.spin();
417 }
418}
419
420void onSignalShutdown(int /*sig*/)
421{
422 // IMPORTANT: Signal handlers can only call async-signal-safe functions
423 // We must NOT call complex C++ methods here (like terminateScheduler)
424 // as they may use mutexes/condition variables which are not signal-safe
425
426 // Stop the signal detector loop (atomic operation - signal-safe)
427 SmExecution & smExecution = SmExecution::getInstance();
428 if (smExecution.signalDetector)
429 {
430 smExecution.signalDetector->stop();
431 }
432
433 // Trigger ROS2 shutdown (this handles its own signal safety)
434 rclcpp::shutdown();
435
436 // Note: Scheduler termination will happen from main thread after polling loop exits
437}
438
439void onSigQuit(int)
440{
441 RCLCPP_INFO(rclcpp::get_logger("SMACC"), "SignalDetector: SIGQUIT received.");
442 exit(0);
443}
444
445} // namespace smacc2
StateMachineInternalAction stateMachineCurrentAction
rclcpp::Node::SharedPtr getNode()
const std::map< std::string, std::shared_ptr< smacc2::ISmaccOrthogonal > > & getOrthogonals() const
std::vector< std::shared_ptr< StateReactor > > & getStateReactors()
std::vector< std::shared_ptr< SmaccEventGenerator > > & getEventGenerators()
SmaccFifoScheduler * scheduler_
void notifyStateExited(ISmaccState *currentState)
void initialize(ISmaccStateMachine *stateMachine)
std::vector< std::vector< ISmaccUpdatable * > > updatableStateElements_
std::atomic< bool > rosInitialized_
rclcpp::Node::SharedPtr getNode()
SmaccFifoScheduler::processor_handle processorHandle_
SignalDetector(SmaccFifoScheduler *scheduler, ExecutionModel executionModel=ExecutionModel::SINGLE_THREAD_SPINNER)
std::atomic< int64_t > lastState_
std::vector< ISmaccUpdatable * > updatableClients_
void setProcessorHandle(SmaccFifoScheduler::processor_handle processorHandle)
void findUpdatableStateElements(ISmaccState *currentState)
ISmaccStateMachine * smaccStateMachine_
void notifyStateConfigured(ISmaccState *currentState)
std::atomic< bool > initialized_
static SmExecution & getInstance()
std::string demangleType(const std::type_info *tinfo)
void onSigQuit(int sig)
void onSignalShutdown(int sig)
boost::statechart::fifo_scheduler< SmaccFifoWorker, SmaccAllocator > SmaccFifoScheduler
smacc2_state_update_start