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 
43 namespace lely {
44 
45 namespace canopen {
46 
48 struct 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 
93 Node::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 
100 Node::~Node() = default;
101 
102 void
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 
113 void
115  impl_->lock();
116 }
117 void
119  impl_->unlock();
120 }
121 
122 void
123 Node::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 
136 CONMT*
137 Node::nmt() const noexcept {
138  return impl_->nmt.get();
139 }
140 
141 void
142 Node::Error(uint16_t eec, uint8_t er, const uint8_t msef[5]) {
143  impl_->nmt->onErr(eec, er, msef);
144 }
145 
146 void
147 Node::RpdoRtr(int num) {
148  if (num) {
149  impl_->RpdoRtr(num);
150  } else {
151  for (num = 1; num <= 512; num++) impl_->RpdoRtr(num);
152  }
153 }
154 
155 void
156 Node::TpdoEvent(int num) {
157  if (num) {
158  impl_->TpdoEvent(num);
159  } else {
160  for (num = 1; num <= 512; num++) impl_->TpdoEvent(num);
161  }
162 }
163 
164 Node::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__
181 Node::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 
191 void
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 
202 void
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 
213 void
214 Node::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 
241 void
242 Node::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 
251 void
252 Node::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 
261 void
262 Node::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 
268 void
269 Node::Impl_::OnRpdoErr(CORPDO* pdo, uint16_t eec, uint8_t er) noexcept {
270  self->OnRpdoError(pdo->getNum(), eec, er);
271 }
272 
273 void
274 Node::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 
279 void
280 Node::Impl_::OnSyncInd(CONMT*, uint8_t cnt) noexcept {
281  self->OnSync(cnt, Node::time_point::clock::now());
282 }
283 
284 void
285 Node::Impl_::OnSyncErr(COSync*, uint16_t eec, uint8_t er) noexcept {
286  self->OnSyncError(eec, er);
287 }
288 
289 void
290 Node::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 
297 void
298 Node::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 
303 void
304 Node::Impl_::RpdoRtr(int num) {
305  auto pdo = nmt->getRPDO(num);
306  if (pdo && pdo->rtr() == -1) throw_errc("RpdoRtr");
307 }
308 
309 void
310 Node::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 NMT error control event occurred.
Definition: nmt.h:80
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
void mtx_destroy(mtx_t *mtx)
Releases any resources used by the mutex at mtx.
virtual void unlock() override
Releases the lock held by the execution agent. Throws no exceptions.
Definition: node.cpp:203
CONMT * nmt() const noexcept
Returns a pointer to the internal CANopen NMT master/slave service from <lely/co/nmt.hpp>.
Definition: node.cpp:137
virtual void unlock() final override
Releases the lock held by the execution agent. Throws no exceptions.
Definition: node.cpp:118
An opaque CANopen device type.
Definition: dev.hpp:76
Indicates that the requested operation succeeded.
Definition: threads.h:121
A mutex type that supports neither timeout nor test and return.
Definition: threads.h:109
void OnCanState(CanState new_state, CanState old_state) noexcept override
Implements the default behavior for a CAN bus state change.
Definition: node.cpp:123
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
int mtx_unlock(mtx_t *mtx)
Unlocks the mutex at mtx.
CANNet * net() const noexcept
Returns a pointer to the internal CAN network interface from <lely/can/net.hpp>.
Definition: io_context.cpp:86
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 CANopen library; it contains the C++ interface of the device descript...
This header file is part of the C11 and POSIX compatibility library; it includes <threads.h>, if it exists, and defines any missing functionality.
void SetTime()
Update the CAN network time.
Definition: io_context.cpp:91
int mtx_lock(mtx_t *mtx)
Blocks until it locks the mutex at mtx.
pthread_mutex_t mtx_t
A complete object type that holds an identifier for a mutex.
Definition: threads.h:102
An opaque CANopen EMCY producer/consumer service type.
Definition: emcy.hpp:65
This header file is part of the CANopen library; it contains the C++ interface of the synchronization...
This header file is part of the CANopen library; it contains the C++ interface of the time stamp (TIM...
#define CO_NMT_CS_RESET_NODE
The NMT command specifier &#39;reset node&#39;.
Definition: nmt.h:49
SdoErrc
The SDO abort codes.
Definition: sdo_error.hpp:37
#define CO_NMT_CS_ENTER_PREOP
The NMT command specifier &#39;enter pre-operational&#39;.
Definition: nmt.h:46
An opaque CANopen Receive-PDO service type.
Definition: rpdo.hpp:65
virtual void lock() override
Blocks until a lock can be obtained for the current execution agent (thread, process, task).
Definition: node.cpp:192
An abstract interface conforming to the BasicLockable concept.
Definition: coapp.hpp:40
This header file is part of the CANopen library; it contains the C++ interface of the emergency (EMCY...
An NMT error control timeout event.
Definition: nmt.h:87
This is the internal header file of the C++ CANopen application library.
An opaque CANopen Transmit-PDO service type.
Definition: tpdo.hpp:65
void Reset()
(Re)starts the node.
Definition: node.cpp:103
::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, using lely::delete_c_type as the deleter.
Definition: c_type.hpp:103
This header file is part of the CANopen library; it contains the C++ interface of the Receive-PDO dec...
This header file is part of the CANopen library; it contains the C++ interface of the Transmit-PDO de...
void RpdoRtr(int num=0)
Requests the transmission of a PDO.
Definition: node.cpp:147
This header file is part of the C++ CANopen application library; it contains the CANopen node declara...
An opaque CANopen NMT master/slave service type.
Definition: nmt.hpp:70
CODev * dev() const noexcept
Returns a pointer to the internal CANopen device from <lely/co/dev.hpp>.
Definition: device.cpp:387
virtual void lock() final override
Blocks until a lock can be obtained for the current execution agent (thread, process, task).
Definition: node.cpp:114
The CANopen device description.
Definition: device.hpp:42
#define CO_NMT_CS_START
The NMT command specifier &#39;start&#39;.
Definition: nmt.h:40
Global namespace for the Lely Industries N.V. libraries.
Definition: buf.hpp:32
An opaque CAN network interface type.
Definition: net.hpp:83
An opaque CANopen SYNC producer/consumer service type.
Definition: sync.hpp:65
The base class for CANopen nodes.
Definition: node.hpp:109
An opaque CANopen TIME producer/consumer service type.
Definition: time.hpp:65
The internal implementation of the CANopen node.
Definition: node.cpp:48
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: ...
The I/O context.
Definition: io_context.hpp:42
void TpdoEvent(int num=0)
Triggers the transmission of an event-driven (asynchronous) PDO.
Definition: node.cpp:156