ros2_canopen master
C++ ROS CANopen Library
Loading...
Searching...
No Matches
node_canopen_base_driver_impl.hpp
Go to the documentation of this file.
1// Copyright 2022 Christoph Hellmann Santos
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#ifndef NODE_CANOPEN_BASE_DRIVER_IMPL
16#define NODE_CANOPEN_BASE_DRIVER_IMPL
19
20using namespace ros2_canopen::node_interfaces;
21
22template <class NODETYPE>
24: ros2_canopen::node_interfaces::NodeCanopenDriver<NODETYPE>(node),
25 diagnostic_enabled_(false),
26 diagnostic_collector_(new DiagnosticsCollector())
27{
28}
29
30template <class NODETYPE>
31void NodeCanopenBaseDriver<NODETYPE>::init(bool called_from_base)
32{
33}
34
35template <>
37{
38 try
39 {
40 this->non_transmit_timeout_ =
41 std::chrono::milliseconds(this->config_["non_transmit_timeout"].as<int>());
42 }
43 catch (...)
44 {
45 }
46 RCLCPP_INFO_STREAM(
47 this->node_->get_logger(),
48 "Non transmit timeout" << static_cast<int>(this->non_transmit_timeout_.count()) << "ms");
49
50 try
51 {
52 polling_ = this->config_["polling"].as<bool>();
53 }
54 catch (...)
55 {
56 RCLCPP_WARN(this->node_->get_logger(), "Could not polling from config, setting to true.");
57 polling_ = true;
58 }
59 if (polling_)
60 {
61 try
62 {
63 period_ms_ = this->config_["period"].as<std::uint32_t>();
64 }
65 catch (...)
66 {
67 RCLCPP_WARN(this->node_->get_logger(), "Could not read period from config, setting to 10ms");
68 period_ms_ = 10;
69 }
70 }
71
72 // Diagnostic components
73 try
74 {
75 diagnostic_enabled_ = this->config_["diagnostics"]["enable"].as<bool>();
76 }
77 catch (...)
78 {
79 RCLCPP_WARN(
80 this->node_->get_logger(),
81 "Could not read enable diagnostics from config, setting to false.");
82 diagnostic_enabled_ = false;
83 }
84 if (diagnostic_enabled_.load())
85 {
86 try
87 {
88 diagnostic_period_ms_ = this->config_["diagnostics"]["period"].as<std::uint32_t>();
89 }
90 catch (...)
91 {
92 RCLCPP_ERROR(
93 this->node_->get_logger(),
94 "Could not read diagnostics period from config, setting to 1000ms");
95 diagnostic_period_ms_ = 1000;
96 }
97
98 diagnostic_updater_ = std::make_shared<diagnostic_updater::Updater>(this->node_);
99 diagnostic_updater_->setHardwareID(std::to_string(this->node_id_));
100 }
101
102 std::optional<int> sdo_timeout_ms;
103 try
104 {
105 sdo_timeout_ms = std::optional(this->config_["sdo_timeout_ms"].as<int>());
106 }
107 catch (...)
108 {
109 }
110 sdo_timeout_ms_ = sdo_timeout_ms.value_or(20);
111
112 std::optional<int> boot_timeout_ms;
113 try
114 {
115 boot_timeout_ms = std::optional(this->config_["boot_timeout_ms"].as<int>());
116 }
117 catch (...)
118 {
119 }
120 boot_timeout_ms_ = boot_timeout_ms.value_or(20);
121}
122template <>
123void NodeCanopenBaseDriver<rclcpp::Node>::configure(bool called_from_base)
124{
125 try
126 {
127 this->non_transmit_timeout_ =
128 std::chrono::milliseconds(this->config_["non_transmit_timeout"].as<int>());
129 }
130 catch (...)
131 {
132 }
133 RCLCPP_INFO_STREAM(
134 this->node_->get_logger(),
135 "Non transmit timeout" << static_cast<int>(this->non_transmit_timeout_.count()) << "ms");
136
137 try
138 {
139 polling_ = this->config_["polling"].as<bool>();
140 }
141 catch (...)
142 {
143 RCLCPP_WARN(this->node_->get_logger(), "Could not polling from config, setting to true.");
144 polling_ = true;
145 }
146 if (polling_)
147 {
148 try
149 {
150 period_ms_ = this->config_["period"].as<std::uint32_t>();
151 }
152 catch (...)
153 {
154 RCLCPP_WARN(this->node_->get_logger(), "Could not read period from config, setting to 10ms");
155 period_ms_ = 10;
156 }
157 }
158
159 // Diagnostic components
160 try
161 {
162 diagnostic_enabled_ = this->config_["diagnostics"]["enable"].as<bool>();
163 }
164 catch (...)
165 {
166 RCLCPP_WARN(
167 this->node_->get_logger(),
168 "Could not read enable diagnostics from config, setting to false.");
169 diagnostic_enabled_ = false;
170 }
171 if (diagnostic_enabled_.load())
172 {
173 try
174 {
175 diagnostic_period_ms_ = this->config_["diagnostics"]["period"].as<std::uint32_t>();
176 }
177 catch (...)
178 {
179 RCLCPP_WARN(
180 this->node_->get_logger(),
181 "Could not read diagnostics period from config, setting to 1000ms");
182 diagnostic_period_ms_ = 1000;
183 }
184
185 diagnostic_updater_ = std::make_shared<diagnostic_updater::Updater>(this->node_);
186 diagnostic_updater_->setHardwareID(std::to_string(this->node_id_));
187 }
188
189 std::optional<int> sdo_timeout_ms;
190 try
191 {
192 sdo_timeout_ms = std::optional(this->config_["sdo_timeout_ms"].as<int>());
193 }
194 catch (...)
195 {
196 }
197 sdo_timeout_ms_ = sdo_timeout_ms.value_or(20);
198
199 std::optional<int> boot_timeout_ms;
200 try
201 {
202 boot_timeout_ms = std::optional(this->config_["boot_timeout_ms"].as<int>());
203 }
204 catch (...)
205 {
206 }
207 boot_timeout_ms_ = boot_timeout_ms.value_or(20);
208}
209
210template <class NODETYPE>
212{
213 nmt_state_publisher_thread_ =
214 std::thread(std::bind(&NodeCanopenBaseDriver<NODETYPE>::nmt_listener, this));
215 emcy_queue_ = this->lely_driver_->get_emcy_queue();
216 rpdo_queue_ = this->lely_driver_->get_rpdo_queue();
217 if (polling_)
218 {
219 RCLCPP_INFO(this->node_->get_logger(), "Starting with polling mode.");
220 poll_timer_ = this->node_->create_wall_timer(
221 std::chrono::milliseconds(period_ms_),
222 std::bind(&NodeCanopenBaseDriver<NODETYPE>::poll_timer_callback, this), this->timer_cbg_);
223 }
224 else
225 {
226 RCLCPP_INFO(this->node_->get_logger(), "Starting with event mode.");
227 this->lely_driver_->set_sync_function(
229 }
230
231 if (diagnostic_enabled_.load())
232 {
233 RCLCPP_INFO(this->node_->get_logger(), "Starting with diagnostics enabled.");
234 diagnostic_updater_->add(
235 "diagnostic updater", this, &NodeCanopenBaseDriver<NODETYPE>::diagnostic_callback);
236 }
237}
238
239template <class NODETYPE>
241{
242 nmt_state_publisher_thread_.join();
243 poll_timer_->cancel();
244 emcy_queue_.reset();
245 rpdo_queue_.reset();
246 if (diagnostic_enabled_.load())
247 {
248 diagnostic_updater_->removeByName("diagnostic updater");
249 }
250}
251
252template <class NODETYPE>
254{
255 NodeCanopenDriver<NODETYPE>::cleanup(called_from_base);
256 // this->lely_driver_->unset_sync_function();
257}
258
259template <class NODETYPE>
261{
262}
263
264template <class NODETYPE>
266{
267 RCLCPP_INFO(this->node_->get_logger(), "eds file %s", this->eds_.c_str());
268 RCLCPP_INFO(this->node_->get_logger(), "bin file %s", this->bin_.c_str());
269 std::shared_ptr<std::promise<std::shared_ptr<ros2_canopen::LelyDriverBridge>>> prom;
270 prom = std::make_shared<std::promise<std::shared_ptr<ros2_canopen::LelyDriverBridge>>>();
271 std::future<std::shared_ptr<ros2_canopen::LelyDriverBridge>> f = prom->get_future();
272 this->exec_->post(
273 [this, prom]()
274 {
275 std::scoped_lock<std::mutex> lock(this->driver_mutex_);
276 this->lely_driver_ = std::make_shared<ros2_canopen::LelyDriverBridge>(
277 *(this->exec_), *(this->master_), this->node_id_, this->node_->get_name(), this->eds_,
278 this->bin_, std::chrono::milliseconds(this->sdo_timeout_ms_),
279 std::chrono::milliseconds(this->boot_timeout_ms_));
280 this->driver_ = std::static_pointer_cast<lely::canopen::BasicDriver>(this->lely_driver_);
281 prom->set_value(lely_driver_);
282 });
283
284 auto future_status = f.wait_for(this->non_transmit_timeout_);
285 if (future_status != std::future_status::ready)
286 {
287 RCLCPP_ERROR(this->node_->get_logger(), "Adding timed out.");
288 throw DriverException("add_to_master: adding timed out");
289 }
290 this->lely_driver_ = f.get();
291 this->driver_ = std::static_pointer_cast<lely::canopen::BasicDriver>(this->lely_driver_);
292 if (!this->lely_driver_->IsReady())
293 {
294 bool boot_success = false;
295 int boot_attempts = 0;
296 const int max_boot_attempts = 3; // 1 retry allowed
297 RCLCPP_WARN(this->node_->get_logger(), "Wait for device to boot...");
298 while (!boot_success && boot_attempts < max_boot_attempts)
299 {
300 boot_attempts++;
301 try
302 {
303 this->lely_driver_->wait_for_boot(); // This will block and throw on failure
304 boot_success = true;
305 RCLCPP_INFO(this->node_->get_logger(), "Driver booted and ready.");
306 }
307 catch (const std::exception & e)
308 {
309 RCLCPP_ERROR(
310 this->node_->get_logger(), "Boot attempt %d failed: %s", boot_attempts, e.what());
311
312 if (boot_attempts < max_boot_attempts)
313 {
314 RCLCPP_INFO(this->node_->get_logger(), "Sending NMT reset before retrying boot");
315 this->lely_driver_->nmt_command(canopen::NmtCommand::RESET_NODE);
316 this->lely_driver_->Boot(); // Trigger boot again
317 RCLCPP_WARN(this->node_->get_logger(), "Retrying boot configuration...");
318 }
319 else
320 {
321 RCLCPP_ERROR(this->node_->get_logger(), "Boot failed after %d attempts", boot_attempts);
322 }
323 }
324 }
325 if (!boot_success)
326 {
327 throw DriverException(
328 std::string("Boot failed after ") + std::to_string(boot_attempts) + " attempts");
329 }
330 }
331
332 if (diagnostic_enabled_.load())
333 {
334 diagnostic_collector_->updateAll(
335 diagnostic_msgs::msg::DiagnosticStatus::OK, "Device ready", "DEVICE", "Added to master.");
336 }
337}
338
339template <class NODETYPE>
341{
342 std::shared_ptr<std::promise<void>> prom = std::make_shared<std::promise<void>>();
343 auto f = prom->get_future();
344 this->exec_->post(
345 [this, prom]()
346 {
347 this->driver_.reset();
348 this->lely_driver_.reset();
349 prom->set_value();
350 });
351
352 auto future_status = f.wait_for(this->non_transmit_timeout_);
353 if (future_status != std::future_status::ready)
354 {
355 throw DriverException("remove_from_master: removing timed out");
356 }
357 if (diagnostic_enabled_.load())
358 {
359 diagnostic_collector_->updateAll(
360 diagnostic_msgs::msg::DiagnosticStatus::ERROR, "Device removed", "DEVICE",
361 "Removed from master.");
362 }
363}
364template <class NODETYPE>
366{
367 while (rclcpp::ok())
368 {
369 std::future<lely::canopen::NmtState> f;
370 {
371 std::scoped_lock<std::mutex> lock(this->driver_mutex_);
372 f = this->lely_driver_->async_request_nmt();
373 }
374 while (f.wait_for(this->non_transmit_timeout_) != std::future_status::ready)
375 {
376 if (!this->activated_.load()) return;
377 }
378 try
379 {
380 auto state = f.get();
381 if (nmt_state_cb_)
382 {
383 nmt_state_cb_(state, this->lely_driver_->get_id());
384 }
385 on_nmt(state);
386 }
387 catch (const std::future_error & e)
388 {
389 break;
390 }
391 }
392}
393template <class NODETYPE>
394void NodeCanopenBaseDriver<NODETYPE>::on_nmt(canopen::NmtState nmt_state)
395{
396}
397
398template <class NODETYPE>
402
403template <class NODETYPE>
405{
406 diagnostic_collector_->summary(
407 diagnostic_msgs::msg::DiagnosticStatus::ERROR, "Emergency message received");
408 std::string emcy_msg = "Emergency message: ";
409 emcy_msg.append("eec: ");
410 emcy_msg.append(std::to_string(emcy.eec));
411 emcy_msg.append(" er: ");
412 emcy_msg.append(std::to_string(emcy.er));
413 emcy_msg.append(" msef: ");
414 for (auto & msef : emcy.msef)
415 {
416 emcy_msg.append(std::to_string(msef));
417 emcy_msg.append(" ");
418 }
419 diagnostic_collector_->add("EMCY", emcy_msg);
420}
421
422template <class NODETYPE>
424{
425 RCLCPP_INFO(this->node_->get_logger(), "Starting RPDO Listener");
426 auto q = lely_driver_->get_rpdo_queue();
427 while (rclcpp::ok())
428 {
430 while (!q->wait_and_pop_for(this->non_transmit_timeout_, rpdo))
431 {
432 if (!this->activated_.load()) return;
433 }
434 try
435 {
436 if (rpdo_cb_)
437 {
438 rpdo_cb_(rpdo, this->lely_driver_->get_id());
439 }
440 on_rpdo(rpdo);
441 }
442 catch (const std::exception & e)
443 {
444 RCLCPP_ERROR_STREAM(this->node_->get_logger(), "RPDO Listener error: " << e.what());
445 break;
446 }
447 }
448}
449template <class NODETYPE>
451{
452 for (int i = 0; i < 10; i++)
453 {
454 auto opt = emcy_queue_->try_pop();
455 if (!opt.has_value())
456 {
457 break;
458 }
459 try
460 {
461 if (emcy_cb_)
462 {
463 emcy_cb_(opt.value(), this->lely_driver_->get_id());
464 }
465 on_emcy(opt.value());
466 }
467 catch (const std::exception & e)
468 {
469 RCLCPP_ERROR_STREAM(this->node_->get_logger(), "EMCY poll error: " << e.what());
470 break;
471 }
472 }
473 for (int i = 0; i < 10; i++)
474 {
475 auto opt = rpdo_queue_->try_pop();
476 if (!opt.has_value())
477 {
478 break;
479 }
480 try
481 {
482 if (rpdo_cb_)
483 {
484 rpdo_cb_(opt.value(), this->lely_driver_->get_id());
485 }
486 on_rpdo(opt.value());
487 }
488 catch (const std::exception & e)
489 {
490 RCLCPP_ERROR_STREAM(this->node_->get_logger(), "RPDO Poll error: " << e.what());
491 break;
492 }
493 }
494}
495
496template <class NODETYPE>
498{
499 RCLCPP_INFO(this->node_->get_logger(), "Starting EMCY Listener");
500 auto q = lely_driver_->get_emcy_queue();
501 while (rclcpp::ok())
502 {
504 while (!q->wait_and_pop_for(this->non_transmit_timeout_, emcy))
505 {
506 if (!this->activated_.load()) return;
507 }
508 try
509 {
510 if (emcy_cb_)
511 {
512 emcy_cb_(emcy, this->lely_driver_->get_id());
513 }
514 on_emcy(emcy);
515 }
516 catch (const std::exception & e)
517 {
518 RCLCPP_ERROR_STREAM(this->node_->get_logger(), "EMCY Listener error: " << e.what());
519 break;
520 }
521 }
522}
523
524template <class NODETYPE>
526 diagnostic_updater::DiagnosticStatusWrapper & stat)
527{
528}
529
530#endif
A class to collect diagnostic information.
Definition diagnostic_collector.hpp:34
Driver Exception.
Definition driver_error.hpp:31
Definition node_canopen_base_driver.hpp:34
void rdpo_listener()
Definition node_canopen_base_driver_impl.hpp:423
virtual void add_to_master()
Add the driver to master.
Definition node_canopen_base_driver_impl.hpp:265
void nmt_listener()
Definition node_canopen_base_driver_impl.hpp:365
virtual void on_rpdo(COData data)
Definition node_canopen_base_driver_impl.hpp:399
virtual void poll_timer_callback()
Definition node_canopen_base_driver_impl.hpp:450
NodeCanopenBaseDriver(NODETYPE *node)
Definition node_canopen_base_driver_impl.hpp:23
virtual void diagnostic_callback(diagnostic_updater::DiagnosticStatusWrapper &stat)
Definition node_canopen_base_driver_impl.hpp:525
virtual void remove_from_master()
Remove the driver from master.
Definition node_canopen_base_driver_impl.hpp:340
void emcy_listener()
Definition node_canopen_base_driver_impl.hpp:497
virtual void on_nmt(canopen::NmtState nmt_state)
Definition node_canopen_base_driver_impl.hpp:394
virtual void on_emcy(COEmcy emcy)
Definition node_canopen_base_driver_impl.hpp:404
Node Canopen Driver.
Definition node_canopen_driver.hpp:60
void shutdown()
Shutdown the driver.
Definition node_canopen_driver.hpp:344
void deactivate()
Deactivate the driver.
Definition node_canopen_driver.hpp:265
void cleanup()
Cleanup the driver.
Definition node_canopen_driver.hpp:305
void activate()
Activate the driver.
Definition node_canopen_driver.hpp:223
void init()
Initialise the driver.
Definition node_canopen_driver.hpp:127
void configure()
Configure the driver.
Definition node_canopen_driver.hpp:175
Definition node_canopen_driver.hpp:46
Definition configuration_manager.hpp:28
Definition exchange.hpp:26
Definition exchange.hpp:34
uint16_t eec
Definition exchange.hpp:36
uint8_t er
Definition exchange.hpp:37
uint8_t msef[5]
Definition exchange.hpp:38