Lely core libraries 1.9.2
node.cpp
Go to the documentation of this file.
1
24#include "coapp.hpp"
25#include <lely/aio/detail/timespec.hpp>
26#include <lely/coapp/node.hpp>
27
28#include <mutex>
29
30#include <cassert>
31
32#if !LELY_NO_THREADS && defined(__MINGW32__)
33#include <lely/libc/threads.h>
34#endif
35#include <lely/co/dev.hpp>
36#include <lely/co/emcy.hpp>
37#include <lely/co/nmt.hpp>
38#include <lely/co/rpdo.hpp>
39#include <lely/co/sync.hpp>
40#include <lely/co/time.hpp>
41#include <lely/co/tpdo.hpp>
42
43namespace lely {
44
45namespace canopen {
46
48struct Node::Impl_ : public BasicLockable {
49 Impl_(Node* self, CANNet* net, CODev* dev);
50
51#if !LELY_NO_THREADS
52#ifdef __MINGW32__
53 ~Impl_();
54#endif
55#endif
56
57 virtual void lock() override;
58 virtual void unlock() override;
59
60 void OnCsInd(CONMT* nmt, uint8_t cs) noexcept;
61 void OnHbInd(CONMT* nmt, uint8_t id, int state, int reason) noexcept;
62 void OnStInd(CONMT* nmt, uint8_t id, uint8_t st) noexcept;
63
64 void OnRpdoInd(CORPDO* pdo, uint32_t ac, const void* ptr, size_t n) noexcept;
65 void OnRpdoErr(CORPDO* pdo, uint16_t eec, uint8_t er) noexcept;
66
67 void OnTpdoInd(COTPDO* pdo, uint32_t ac, const void* ptr, size_t n) noexcept;
68
69 void OnSyncInd(CONMT* nmt, uint8_t cnt) noexcept;
70 void OnSyncErr(COSync* sync, uint16_t eec, uint8_t er) noexcept;
71
72 void OnTimeInd(COTime* time, const timespec* tp) noexcept;
73
74 void OnEmcyInd(COEmcy* emcy, uint8_t id, uint16_t ec, uint8_t er,
75 uint8_t msef[5]) noexcept;
76
77 void RpdoRtr(int num);
78 void TpdoEvent(int num);
79
80 Node* self{nullptr};
81
82#if !LELY_NO_THREADS
83#ifdef __MINGW32__
84 mtx_t mutex;
85#else
86 ::std::mutex mutex;
87#endif
88#endif
89
91};
92
93Node::Node(aio::TimerBase& timer, aio::CanBusBase& bus,
94 const ::std::string& dcf_txt, const ::std::string& dcf_bin,
95 uint8_t id)
96 : IoContext(timer, bus, this),
97 Device(dcf_txt, dcf_bin, id, this),
98 impl_(new Impl_(this, IoContext::net(), Device::dev())) {}
99
100Node::~Node() = default;
101
102void
104 ::std::lock_guard<Impl_> lock(*impl_);
105
106 // Update the CAN network time before resetting the node. In the case of a
107 // master, this ensures that SDO timeouts do not occur too soon.
108 SetTime();
109
110 if (impl_->nmt->csInd(CO_NMT_CS_RESET_NODE) == -1) throw_errc("Reset");
111}
112
113void
115 impl_->lock();
116}
117void
119 impl_->unlock();
120}
121
122void
123Node::OnCanState(CanState new_state, CanState old_state) noexcept {
124 assert(new_state != old_state);
125
126 // TODO: Clear EMCY in error active mode.
127 if (new_state == CanState::PASSIVE) {
128 // CAN in error passive mode.
129 Error(0x8120, 0x10);
130 } else if (old_state == CanState::BUSOFF) {
131 // Recovered from bus off.
132 Error(0x8140, 0x10);
133 }
134}
135
136CONMT*
137Node::nmt() const noexcept {
138 return impl_->nmt.get();
139}
140
141void
142Node::Error(uint16_t eec, uint8_t er, const uint8_t msef[5]) {
143 impl_->nmt->onErr(eec, er, msef);
144}
145
146void
148 if (num) {
149 impl_->RpdoRtr(num);
150 } else {
151 for (num = 1; num <= 512; num++) impl_->RpdoRtr(num);
152 }
153}
154
155void
157 if (num) {
158 impl_->TpdoEvent(num);
159 } else {
160 for (num = 1; num <= 512; num++) impl_->TpdoEvent(num);
161 }
162}
163
164Node::Impl_::Impl_(Node* self_, CANNet* net, CODev* dev)
165 : self(self_), nmt(make_unique_c<CONMT>(net, dev)) {
166#if !LELY_NO_THREADS
167#ifdef __MINGW32__
168 if (mtx_init(&mutex, mtx_plain) != thrd_success) throw_errc("mtx_init");
169#endif
170#endif
171
172 nmt->setCsInd<Impl_, &Impl_::OnCsInd>(this);
173 nmt->setHbInd<Impl_, &Impl_::OnHbInd>(this);
174 nmt->setStInd<Impl_, &Impl_::OnStInd>(this);
175
176 nmt->setSyncInd<Impl_, &Impl_::OnSyncInd>(this);
177}
178
179#if !LELY_NO_THREADS
180#ifdef __MINGW32__
181Node::Impl_::~Impl_() {
182 // Make sure no callback functions will be invoked after the mutex is
183 // destroyed.
184 nmt.reset();
185
186 mtx_destroy(&mutex);
187}
188#endif
189#endif
190
191void
193#if !LELY_NO_THREADS
194#ifdef __MINGW32__
195 if (mtx_lock(&mutex) != thrd_success) throw_errc("mtx_lock");
196#else
197 mutex.lock();
198#endif
199#endif
200}
201
202void
204#if !LELY_NO_THREADS
205#ifdef __MINGW32__
206 if (mtx_unlock(&mutex) != thrd_success) throw_errc("mtx_lock");
207#else
208 mutex.unlock();
209#endif
210#endif
211}
212
213void
214Node::Impl_::OnCsInd(CONMT* nmt, uint8_t cs) noexcept {
215 if (cs == CO_NMT_CS_START || cs == CO_NMT_CS_ENTER_PREOP) {
216 auto sync = nmt->getSync();
217 if (sync) sync->setErr<Impl_, &Impl_::OnSyncErr>(this);
218 auto time = nmt->getTime();
219 if (time) time->setInd<Impl_, &Impl_::OnTimeInd>(this);
220 auto emcy = nmt->getEmcy();
221 if (emcy) emcy->setInd<Impl_, &Impl_::OnEmcyInd>(this);
222 }
223
224 if (cs == CO_NMT_CS_START) {
225 for (int i = 1; i <= 512; i++) {
226 auto pdo = nmt->getRPDO(i);
227 if (pdo) {
228 pdo->setInd<Impl_, &Impl_::OnRpdoInd>(this);
229 pdo->setErr<Impl_, &Impl_::OnRpdoErr>(this);
230 }
231 }
232 for (int i = 1; i <= 512; i++) {
233 auto pdo = nmt->getTPDO(i);
234 if (pdo) pdo->setInd<Impl_, &Impl_::OnTpdoInd>(this);
235 }
236 }
237
238 self->OnCommand(static_cast<NmtCommand>(cs));
239}
240
241void
242Node::Impl_::OnHbInd(CONMT* nmt, uint8_t id, int state, int reason) noexcept {
243 // Invoke the default behavior before notifying the implementation.
244 nmt->onHb(id, state, reason);
245 // Only handle heartbeat timeout events. State changes are handled by OnSt().
246 if (reason != CO_NMT_EC_TIMEOUT) return;
247 // Notify the implementation.
248 self->OnHeartbeat(id, state == CO_NMT_EC_OCCURRED);
249}
250
251void
252Node::Impl_::OnStInd(CONMT* nmt, uint8_t id, uint8_t st) noexcept {
253 // Invoke the default behavior before notifying the implementation.
254 nmt->onSt(id, st);
255 // Ignore local state changes.
256 if (id == nmt->getDev()->getId()) return;
257 // Notify the implementation.
258 self->OnState(id, static_cast<NmtState>(st));
259}
260
261void
262Node::Impl_::OnRpdoInd(CORPDO* pdo, uint32_t ac, const void* ptr,
263 size_t n) noexcept {
264 int num = pdo->getNum();
265 self->OnRpdo(num, static_cast<SdoErrc>(ac), ptr, n);
266}
267
268void
269Node::Impl_::OnRpdoErr(CORPDO* pdo, uint16_t eec, uint8_t er) noexcept {
270 self->OnRpdoError(pdo->getNum(), eec, er);
271}
272
273void
274Node::Impl_::OnTpdoInd(COTPDO* pdo, uint32_t ac, const void* ptr,
275 size_t n) noexcept {
276 self->OnTpdo(pdo->getNum(), static_cast<SdoErrc>(ac), ptr, n);
277}
278
279void
280Node::Impl_::OnSyncInd(CONMT*, uint8_t cnt) noexcept {
281 self->OnSync(cnt, Node::time_point::clock::now());
282}
283
284void
285Node::Impl_::OnSyncErr(COSync*, uint16_t eec, uint8_t er) noexcept {
286 self->OnSyncError(eec, er);
287}
288
289void
290Node::Impl_::OnTimeInd(COTime*, const timespec* tp) noexcept {
291 assert(tp);
292 ::std::chrono::system_clock::time_point abs_time(
293 aio::detail::FromTimespec(*tp));
294 self->OnTime(abs_time);
295}
296
297void
298Node::Impl_::OnEmcyInd(COEmcy*, uint8_t id, uint16_t ec, uint8_t er,
299 uint8_t msef[5]) noexcept {
300 self->OnEmcy(id, ec, er, msef);
301}
302
303void
304Node::Impl_::RpdoRtr(int num) {
305 auto pdo = nmt->getRPDO(num);
306 if (pdo && pdo->rtr() == -1) throw_errc("RpdoRtr");
307}
308
309void
310Node::Impl_::TpdoEvent(int num) {
311 auto pdo = nmt->getTPDO(num);
312 if (pdo && pdo->event() == -1) throw_errc("TpdoEvent");
313}
314
315} // namespace canopen
316
317} // namespace lely
An opaque CAN network interface type.
Definition: net.hpp:83
An opaque CANopen device type.
Definition: dev.hpp:76
An opaque CANopen EMCY producer/consumer service type.
Definition: emcy.hpp:65
An opaque CANopen NMT master/slave service type.
Definition: nmt.hpp:70
An opaque CANopen Receive-PDO service type.
Definition: rpdo.hpp:65
An opaque CANopen SYNC producer/consumer service type.
Definition: sync.hpp:65
An opaque CANopen Transmit-PDO service type.
Definition: tpdo.hpp:65
An opaque CANopen TIME producer/consumer service type.
Definition: time.hpp:65
An abstract interface conforming to the BasicLockable concept.
Definition: coapp.hpp:40
The CANopen device description.
Definition: device.hpp:42
CODev * dev() const noexcept
Returns a pointer to the internal CANopen device from <lely/co/dev.hpp>.
Definition: device.cpp:387
The I/O context.
Definition: io_context.hpp:42
void SetTime()
Update the CAN network time.
Definition: io_context.cpp:91
CANNet * net() const noexcept
Returns a pointer to the internal CAN network interface from <lely/can/net.hpp>.
Definition: io_context.cpp:86
The base class for CANopen nodes.
Definition: node.hpp:109
void Reset()
(Re)starts the node.
Definition: node.cpp:103
void OnCanState(CanState new_state, CanState old_state) noexcept override
Implements the default behavior for a CAN bus state change.
Definition: node.cpp:123
void TpdoEvent(int num=0)
Triggers the transmission of an event-driven (asynchronous) PDO.
Definition: node.cpp:156
CONMT * nmt() const noexcept
Returns a pointer to the internal CANopen NMT master/slave service from <lely/co/nmt....
Definition: node.cpp:137
void RpdoRtr(int num=0)
Requests the transmission of a PDO.
Definition: node.cpp:147
virtual void unlock() final override
Releases the lock held by the execution agent. Throws no exceptions.
Definition: node.cpp:118
virtual void lock() final override
Blocks until a lock can be obtained for the current execution agent (thread, process,...
Definition: node.cpp:114
Node(aio::TimerBase &timer, aio::CanBusBase &bus, const ::std::string &dcf_txt, const ::std::string &dcf_bin="", uint8_t id=0xff)
Creates a new CANopen node.
Definition: node.cpp:93
This header file is part of the CANopen library; it contains the C++ interface of the device descript...
This header file is part of the CANopen library; it contains the C++ interface of the emergency (EMCY...
NmtState
The NMT states.
Definition: node.hpp:50
SdoErrc
The SDO abort codes.
Definition: sdo_error.hpp:37
NmtCommand
The NMT command specifiers.
Definition: node.hpp:36
Global namespace for the Lely Industries N.V. libraries.
Definition: buf.hpp:32
unique_c_ptr< T > make_unique_c(Args &&... args)
Creates an instance of a trivial, standard layout or incomplete C type and wraps it in a lely::unique...
Definition: c_type.hpp:111
::std::unique_ptr< T, delete_c_type< T > > unique_c_ptr
A specialization of std::unique_ptr for trivial, standard layout or incomplete C types,...
Definition: c_type.hpp:103
@ CO_NMT_EC_OCCURRED
An NMT error control event occurred.
Definition: nmt.h:80
#define CO_NMT_CS_START
The NMT command specifier 'start'.
Definition: nmt.h:40
#define CO_NMT_CS_ENTER_PREOP
The NMT command specifier 'enter pre-operational'.
Definition: nmt.h:46
#define CO_NMT_CS_RESET_NODE
The NMT command specifier 'reset node'.
Definition: nmt.h:49
@ CO_NMT_EC_TIMEOUT
An NMT error control timeout event.
Definition: nmt.h:87
This header file is part of the CANopen library; it contains the C++ interface of the network managem...
This header file is part of the C++ CANopen application library; it contains the CANopen node declara...
This header file is part of the CANopen library; it contains the C++ interface of the Receive-PDO dec...
This is the internal header file of the C++ CANopen application library.
The internal implementation of the CANopen node.
Definition: node.cpp:48
virtual void lock() override
Blocks until a lock can be obtained for the current execution agent (thread, process,...
Definition: node.cpp:192
virtual void unlock() override
Releases the lock held by the execution agent. Throws no exceptions.
Definition: node.cpp:203
A time type with nanosecond resolution.
Definition: time.h:83
This header file is part of the CANopen library; it contains the C++ interface of the synchronization...
This header file is part of the C11 and POSIX compatibility library; it includes <threads....
@ thrd_success
Indicates that the requested operation succeeded.
Definition: threads.h:121
int mtx_init(mtx_t *mtx, int type)
Creates a mutex object with properties indicated by type, which must have one of the four values:
int mtx_lock(mtx_t *mtx)
Blocks until it locks the mutex at mtx.
@ mtx_plain
A mutex type that supports neither timeout nor test and return.
Definition: threads.h:109
int mtx_unlock(mtx_t *mtx)
Unlocks the mutex at mtx.
void mtx_destroy(mtx_t *mtx)
Releases any resources used by the mutex at mtx.
This header file is part of the CANopen library; it contains the C++ interface of the time stamp (TIM...
This header file is part of the CANopen library; it contains the C++ interface of the Transmit-PDO de...