canserial-esd.cpp
Go to the documentation of this file.
1 //======================================================================
28 //======================================================================
29 
30 // on cygwin ntcan.h includes windef.h which defines macros named max/min which the compiler confuses with max/min templates
31 // but defining NOMINMAX prevents those evil macros from being defined
32 #ifdef OSNAME_CYGWIN
33 //# define NOMINMAX
34 #endif
35 #include "ntcan.h"
36 
37 #include "sdhlibrary_settings.h"
38 
39 //----------------------------------------------------------------------
40 // System Includes - include with <>
41 //----------------------------------------------------------------------
42 
43 #include <fcntl.h>
44 //#include <termios.h>
45 #include <stdio.h>
46 #if ! SDH_USE_VCC
47 # include <unistd.h>
48 #endif
49 //#include <errno.h>
50 //#include <string.h>
51 //#include <sys/select.h>
52 //#include <sys/ioctl.h>
53 
54 #include <iostream>
55 #include <exception>
56 #include <stdarg.h>
57 #include <assert.h>
58 
59 //----------------------------------------------------------------------
60 // Project Includes - include with ""
61 //----------------------------------------------------------------------
62 
63 #include "canserial-esd.h"
64 #include "simpletime.h"
65 #include "util.h"
66 
67 
68 /*
69  The Linux version of the ESD ntcan.h file differ a little from the
70  Windows (and Cygwin) version.
71  So define some macros for mapping/defining names to make the code compile
72  on Linux systems too.
73 */
74 #ifdef OSNAME_LINUX
75 
76 // Linux ntcan.h maps its internal error number to std unix error numbers, so include their definitions:
77 # include <errno.h>
78 
79 
80 // Old Linux ntcan.h used HANDLE where Windows ntcan uses NTCAN_HANDLE
81 // so we had to make the following define:
82 //# define NTCAN_HANDLE HANDLE
83 // But newer ESD drivers seem to use NTCAN_HANDLE for both Linux and Windows
84 // and give deprecation warnings on the use of HANDLE
85 
86 # ifndef NTCAN_BAUD_1000
87  // Some Linux ntcan.h do not define any baudrate codes. The following ones were taken from
88  // the Windows version in the hope that they are the same for Linux. Lets see...:
89 # define NTCAN_BAUD_1000 0
90 # define NTCAN_BAUD_800 14
91 # define NTCAN_BAUD_500 2
92 # define NTCAN_BAUD_250 4
93 # define NTCAN_BAUD_125 6
94 # define NTCAN_BAUD_100 7
95 # define NTCAN_BAUD_50 9
96 # define NTCAN_BAUD_20 11
97 # define NTCAN_BAUD_10 13
98 # endif
99 #endif
100 
101 //----------------------------------------------------------------------
102 // Defines, enums, unions, structs,
103 //----------------------------------------------------------------------
104 
106 
108 
114 {
115 public:
117  NTCAN_HANDLE ntcan_handle; /* remark: if you get a compiler error here
118  (e.g. "error: NTCAN_HANDLE does not name a type")
119  then please consider updating your ESD CAN
120  driver (and hence ntcan.h). See also the
121  comment on NTCAN_HANDLE on the beginning
122  of this file.
123  */
124 
125  int32_t timeout_ms;
126 
133  CMSG m_cmsg;
136 
137  NTCAN_RESULT rc;
138 
139 };
141 
142 
144 
154 #define SDH_CANSERIAL_ESD_DEBUG 1
155 
160 #if SDH_CANSERIAL_ESD_DEBUG
161 # define DBG( ... ) \
162  do { \
163  __VA_ARGS__; \
164  } while (0)
165 #else
166 # define DBG( ... )
167 #endif
168 
169 
170 //----------------------------------------------------------------------
171 // Global variables
172 //----------------------------------------------------------------------
173 
174 
175 //----------------------------------------------------------------------
176 // Function and class member implementation (function definitions)
177 //----------------------------------------------------------------------
178 
179 using namespace std;
180 
181 
182 cCANSerial_ESD::cCANSerial_ESD( int _net, unsigned long _baudrate, double _timeout, int _id_read, int _id_write )
183 {
184  pimpl = NULL;
185  assert( sizeof( NTCAN_HANDLE ) == sizeof( tDeviceHandle) ); // if this fails, please adjust the tDeviceHandle type
186 
187  if ( _timeout < 0.0 )
188  throw new cCANSerial_ESDException( cMsg( "Invalid timeout %f (must be >= 0)", _timeout ) );
189 
190  pimpl = new cCANSerial_ESD_Internal();
191  pimpl->ntcan_handle = NTCAN_HANDLE(NTCAN_INVALID_HANDLE);
192  net = _net;
193  baudrate = _baudrate;
194  SetTimeout( _timeout );
195  id_read = _id_read;
196  id_write = _id_write;
197 
198  ungetch_valid = false;
199 }
200 //----------------------------------------------------------------------
201 
202 cCANSerial_ESD::cCANSerial_ESD( tDeviceHandle _ntcan_handle, double _timeout, int _id_read, int _id_write )
203 {
204  pimpl = NULL;
205  assert( sizeof( NTCAN_HANDLE ) == sizeof( tDeviceHandle) ); // if this fails, please adjust the tDeviceHandle type
206 
207  if ( _timeout < 0.0 )
208  throw new cCANSerial_ESDException( cMsg( "Invalid timeout %f (must be >= 0)", _timeout ) );
209 
210  if ( _ntcan_handle == tDeviceHandle(NTCAN_HANDLE(NTCAN_INVALID_HANDLE)) )
211  throw new cCANSerial_ESDException( cMsg( "Cannot reuse invalid ESD CAN handle" ) );
212 
213  pimpl = new cCANSerial_ESD_Internal();
214  pimpl->ntcan_handle = *(NTCAN_HANDLE*)(_ntcan_handle);
215  net = -1;
216  baudrate = 0;
217  SetTimeout( _timeout );
218  id_read = _id_read;
219  id_write = _id_write;
220 
221  ungetch_valid = false;
222 }
223 //----------------------------------------------------------------------
224 
226 {
227  if ( pimpl )
228  delete pimpl;
229 }
230 //----------------------------------------------------------------------
231 
232 char const* ESD_strerror( NTCAN_RESULT rc )
233 {
234  switch (rc)
235  {
236  DEFINE_TO_CASECOMMAND( NTCAN_SUCCESS );
237  DEFINE_TO_CASECOMMAND( NTCAN_RX_TIMEOUT );
238  DEFINE_TO_CASECOMMAND( NTCAN_TX_TIMEOUT );
239  DEFINE_TO_CASECOMMAND( NTCAN_TX_ERROR );
240  DEFINE_TO_CASECOMMAND( NTCAN_CONTR_OFF_BUS );
241  DEFINE_TO_CASECOMMAND( NTCAN_CONTR_BUSY );
242  DEFINE_TO_CASECOMMAND( NTCAN_CONTR_WARN );
243  DEFINE_TO_CASECOMMAND( NTCAN_NO_ID_ENABLED );
244  DEFINE_TO_CASECOMMAND( NTCAN_ID_ALREADY_ENABLED );
245  DEFINE_TO_CASECOMMAND( NTCAN_ID_NOT_ENABLED );
246 
247  DEFINE_TO_CASECOMMAND( NTCAN_INVALID_FIRMWARE );
248  DEFINE_TO_CASECOMMAND( NTCAN_MESSAGE_LOST );
249  DEFINE_TO_CASECOMMAND( NTCAN_INVALID_HARDWARE );
250 
251  DEFINE_TO_CASECOMMAND( NTCAN_PENDING_WRITE );
252  DEFINE_TO_CASECOMMAND( NTCAN_PENDING_READ );
253  DEFINE_TO_CASECOMMAND( NTCAN_INVALID_DRIVER );
254 
255  DEFINE_TO_CASECOMMAND( NTCAN_SOCK_CONN_TIMEOUT );
256  DEFINE_TO_CASECOMMAND( NTCAN_SOCK_CMD_TIMEOUT );
257  DEFINE_TO_CASECOMMAND( NTCAN_SOCK_HOST_NOT_FOUND );
258 
259  DEFINE_TO_CASECOMMAND( NTCAN_INVALID_PARAMETER );
260  DEFINE_TO_CASECOMMAND( NTCAN_INVALID_HANDLE );
261 #ifndef OSNAME_LINUX
262 // these errors are for Windows only ;-)
263  DEFINE_TO_CASECOMMAND( NTCAN_IO_INCOMPLETE );
264  DEFINE_TO_CASECOMMAND( NTCAN_IO_PENDING );
265 #endif
266  DEFINE_TO_CASECOMMAND( NTCAN_HANDLE_FORCED_CLOSE );
267  DEFINE_TO_CASECOMMAND( NTCAN_NOT_IMPLEMENTED );
268  DEFINE_TO_CASECOMMAND( NTCAN_NOT_SUPPORTED );
269  DEFINE_TO_CASECOMMAND( NTCAN_NET_NOT_FOUND );
270  DEFINE_TO_CASECOMMAND( NTCAN_INSUFFICIENT_RESOURCES );
271 
272  DEFINE_TO_CASECOMMAND( NTCAN_OPERATION_ABORTED );
273  DEFINE_TO_CASECOMMAND( NTCAN_WRONG_DEVICE_STATE );
274  DEFINE_TO_CASECOMMAND( NTCAN_CONTR_ERR_PASSIVE );
275 // DEFINE_TO_CASECOMMAND( NTCAN_ERROR_NO_BAUDRATE );
276 // DEFINE_TO_CASECOMMAND( NTCAN_ERROR_LOM );
277  default:
278  return "unknown";
279  }
280 }
281 //-----------------------------------------------------------------
282 
283 
285 {
286  if ( pimpl->ntcan_handle == NTCAN_HANDLE(NTCAN_INVALID_HANDLE) )
287  {
288  // only open if we're not reusing an existing handle:
289 
290  //cerr << "opening can with timeout = " << timeout << ", in ms " << int32_t( timeout * 1000.0 ) << "\n";
291  DBG( dbg << "Opening ESD CAN net: " << net << ", baudrate: " << baudrate << ", id_read: 0x" << std::hex << id_read << ", id_write: 0x" << id_write << std::dec << "\n" );
292  pimpl->rc = canOpen( net,
293  0, // flags
294  CAN_ESD_TXQUEUESIZE, // txquesize
295  CAN_ESD_RXQUEUESIZE, // rxquesize
296  pimpl->timeout_ms,
297  pimpl->timeout_ms,
298  &(pimpl->ntcan_handle) );
299 
300  if (pimpl->rc != NTCAN_SUCCESS)
301  {
302  // open failed, so ensure that pimpl->ntcan_handle is invalid
303  pimpl->ntcan_handle = NTCAN_HANDLE(NTCAN_INVALID_HANDLE);
304  throw new cCANSerial_ESDException( cMsg( "Could not open ESD CAN net %d: %s", net, GetLastErrorMessage() ) );
305  }
306 
307  pimpl->rc = canSetBaudrate( pimpl->ntcan_handle, BaudrateToBaudrateCode( baudrate ) );
308  if (pimpl->rc != NTCAN_SUCCESS)
309  throw new cCANSerial_ESDException( cMsg( "Could not set baudrate to %lu on ESD CAN net %d: %s", baudrate, net, GetLastErrorMessage() ) );
310  }
311 
312  pimpl->rc = canIdAdd( pimpl->ntcan_handle, id_read );
313  if (pimpl->rc != NTCAN_SUCCESS)
314  throw new cCANSerial_ESDException( cMsg( "Could not add CAN ID 0x%03x on ESD CAN net %d: %s", (unsigned int) id_read, net, GetLastErrorMessage() ) );
315 
316  // (re)init member data:
317  pimpl->m_cmsg.len = 0;
318  pimpl->m_cmsg.msg_lost = 0;
319  pimpl->m_cmsg_next = 0;
320 }
321 //----------------------------------------------------------------------
322 
323 
325 throw()
326 {
327  return ( pimpl->ntcan_handle != NTCAN_HANDLE(NTCAN_INVALID_HANDLE) );
328 }
329 //----------------------------------------------------------------------
330 
331 
333 {
334  if ( pimpl->ntcan_handle == NTCAN_HANDLE(NTCAN_INVALID_HANDLE) )
335  throw new cCANSerial_ESDException( cMsg( "Could not close un-opened device" ) );
336 
337  canClose( pimpl->ntcan_handle );
338  pimpl->ntcan_handle = NTCAN_HANDLE(NTCAN_INVALID_HANDLE);
339 }
340 //----------------------------------------------------------------------
341 
342 unsigned int cCANSerial_ESD::BaudrateToBaudrateCode( unsigned long baudrate )
343 {
344  switch (baudrate)
345  {
346  case 1000000: return NTCAN_BAUD_1000;
347  case 800000: return NTCAN_BAUD_800;
348  case 500000: return NTCAN_BAUD_500;
349  case 250000: return NTCAN_BAUD_250;
350  case 125000: return NTCAN_BAUD_125;
351  case 100000: return NTCAN_BAUD_100;
352  case 50000: return NTCAN_BAUD_50;
353  case 20000: return NTCAN_BAUD_20;
354  case 10000: return NTCAN_BAUD_10;
355  }
356 
357  throw new cCANSerial_ESDException( cMsg( "Invalid baudrate %ld", baudrate ) );
358 }
359 //----------------------------------------------------------------------
360 
361 int cCANSerial_ESD::write( char const *ptr, int len )
362 {
363  assert( pimpl->ntcan_handle != NTCAN_HANDLE(NTCAN_INVALID_HANDLE) );
364 
365  //cerr << "in cCANSerial_ESD::write\n"; cerr.flush();
366  if ( len == 0 )
367  len = int( strlen( ptr ) );
368 
369  //cerr << "sending " << len << " bytes <" << ptr << "> to CAN net\n"; cerr.flush();
370 
371  // calculate number of CMSGS needed (max 8 data bytes per CMSG)
372  int len_cmsgs = len/8 + (((len%8)!=0) ? 1 : 0);
373 
374  if ( len_cmsgs > CAN_ESD_TXQUEUESIZE )
375  throw new cCANSerial_ESDException( cMsg( "len_cmsgs = %d > %d, please adjust CAN_ESD_TXQUEUESIZE!", (int) len_cmsgs, CAN_ESD_TXQUEUESIZE ) );
376 
377  //---------------------
378  // prepare CMSGs to send:
379 #if SDH_USE_VCC
380  // VCC cannot create variable size arrays on the stack; so use the heap
381  CMSG* cmsg = new CMSG[ len_cmsgs ];
382 #else
383  CMSG cmsg[ len_cmsgs ];
384 #endif
385  for ( int i=0; i < len_cmsgs; i++)
386  {
387  cmsg[i].id = id_write;
388  cmsg[i].len = min( 8, len-i*8 );
389  for ( int j=0; j<cmsg[i].len; j++ )
390  cmsg[i].data[ j ] = *(ptr++);
391 
392  DBG( dbg << "cCANSerial_ESD::write writing CAN frame id:0x" << std::hex << cmsg[i].id << " len=" << int(cmsg[i].len) << " DATA (hex):" << cHexByteString( (char const*) cmsg[i].data, cmsg[i].len ) << " bytes_written:" << (i*8+cmsg[i].len) << "/" << len << "\n" );
393 
394  }
395  //---------------------
396 
397  //---------------------
398  // now send the cmsgs and check return values (pimpl->rc and len_cmsgs)
399  int len_cmsgs_save = len_cmsgs;
400  pimpl->rc = canWrite( pimpl->ntcan_handle, cmsg, (int32_t*) &len_cmsgs, NULL );
401 #if SDH_USE_VCC
402  // the code above cannot throw exceptions, so this delete[] will be reached in any case
403  delete[] cmsg;
404 #endif
405  if (pimpl->rc != NTCAN_SUCCESS)
406  throw new cCANSerial_ESDException( cMsg( "Could not write %d CMSGs on ESD CAN net %d: %s", (int)len_cmsgs, net, GetLastErrorMessage() ) );
407 
408  if ( len_cmsgs != len_cmsgs_save )
409  throw new cCANSerial_ESDException( cMsg( "Could only send %d/%d CMSGs on ESD CAN net %d", (int)len_cmsgs, (int)len_cmsgs_save, net ) );
410 
411  return len;
412 }
413 //----------------------------------------------------------------------
414 
415 
416 ssize_t cCANSerial_ESD::Read( void *_data, ssize_t size, long timeout_us, bool return_on_less_data )
417 {
418  assert( pimpl->ntcan_handle != NTCAN_HANDLE(NTCAN_INVALID_HANDLE) );
419 
420  char* data = (char*) _data;
421 
422 
423 #ifndef OSNAME_LINUX
424  // This timeout adjust seems unnesscary on Linux and when the timeout is
425  // changed to zero, the process pegs the CPU with nonblocking read
426  // attempts until it gets a message. -Neil Dantam, 2013-11-06
427  //---------------------
428  // adjust rx timeout if necessary
429  if ( long(pimpl->timeout_ms) * 1000L != timeout_us )
430  {
431  SetTimeout( double(timeout_us) / 1E6 );
432  }
433 #endif
434  //---------------------
435 
436  //---------------------
437  int bytes_read = 0;
438 
439  do
440  {
441  // copy remaining, not yet returned bytes from a previous canRead call to data
442  for ( ; pimpl->m_cmsg_next < pimpl->m_cmsg.len && bytes_read < size; pimpl->m_cmsg_next++, bytes_read++ )
443  *data++ = pimpl->m_cmsg.data[ pimpl->m_cmsg_next ];
444 
445  if ( bytes_read < size )
446  {
447  // if necessary read one more CMSGs with blocking call
448  int len_cmsgs = 1;
449  pimpl->m_cmsg.len = 0;
450  pimpl->m_cmsg_next = 0;
451  if ( timeout_us == 0 )
452  pimpl->rc = canTake( pimpl->ntcan_handle, &(pimpl->m_cmsg), (int32_t*) &len_cmsgs );
453  else
454  pimpl->rc = canRead( pimpl->ntcan_handle, &(pimpl->m_cmsg), (int32_t*) &len_cmsgs, NULL );
455 
456  if (pimpl->rc != NTCAN_SUCCESS)
457  throw new cCANSerial_ESDException( cMsg( "Could not read CAN messages from ESD CAN net %d: %s", net, GetLastErrorMessage() ) );
458 
459  DBG( dbg << "cCANSerial_ESD::Read read CAN frame id:0x" << std::hex << pimpl->m_cmsg.id << " len=" << int(pimpl->m_cmsg.len) << " data (hex):" << cHexByteString( (char const*) pimpl->m_cmsg.data, pimpl->m_cmsg.len ) << " bytes_read:" << bytes_read << "/" << size << "\n" );
460  //DBG( dbg << "cCANSerial_ESD::Read read CAN frame id:0x" << std::hex << pimpl->m_cmsg.id << " len=" << int(pimpl->m_cmsg.len) << " data (hex):" << cHexByteString( (char const*) pimpl->m_cmsg.data, pimpl->m_cmsg.len ) << " bytes_read:" << bytes_read << "/" << size << " us_elapsed:" << start_time.Elapsed_us() << "\n );
461 
462  if ( len_cmsgs != 1 && timeout_us != 0 )
463  throw new cCANSerial_ESDException( cMsg( "Could only read %d/%d CMSGs from ESD CAN net %d", int(len_cmsgs), 1, net ) );
464  if ( len_cmsgs > 0 && pimpl->m_cmsg.id != id_read )
465  throw new cCANSerial_ESDException( cMsg( "Invalid CAN ID 0x%03x received, expected 0x%03x", (unsigned int) pimpl->m_cmsg.id, (unsigned int) id_read ) );
466 
467  for ( ; pimpl->m_cmsg_next < pimpl->m_cmsg.len && bytes_read < size; pimpl->m_cmsg_next++, bytes_read++ )
468  *data++ = pimpl->m_cmsg.data[ pimpl->m_cmsg_next ];
469  }
470  }
471  while ( bytes_read < size && !return_on_less_data );
472 
473  return bytes_read;
474 }
475 //----------------------------------------------------------------------
476 
477 
478 void cCANSerial_ESD::SetTimeout( double _timeout )
479 {
480  if ( _timeout < 0.0 )
481  _timeout = 0.0;
482 
483  cSerialBase::SetTimeout( _timeout );
484  pimpl->timeout_ms = int32_t(_timeout * 1000.0);
485 
486  if ( pimpl->ntcan_handle != NTCAN_HANDLE(NTCAN_INVALID_HANDLE) )
487  {
488  // we already have a handle, so we must forward the timeout to the driver also:
489  //cerr << "adjusting timeout = " << _timeout << ", in ms " << pimpl->timeout_ms << "\n";
490 
491  pimpl->rc = canIoctl( pimpl->ntcan_handle, NTCAN_IOCTL_SET_RX_TIMEOUT, &(pimpl->timeout_ms) );
492 
493  if ( pimpl->rc != NTCAN_SUCCESS )
494  throw new cCANSerial_ESDException( cMsg( "Could not set new rx timeout for ESD CAN net %d: %s", net, GetLastErrorMessage() ) );
495  }
496 }
497 //----------------------------------------------------------------------
498 
500 {
501  static char return_msg[512];
502 
503  snprintf( return_msg, 511, "error 0x%x = %d = \"%s\"", dw, dw, ESD_strerror( (NTCAN_RESULT) dw ) );
504  return return_msg;
505 
506 }
507 //----------------------------------------------------------------------
508 
510 {
511  return (tErrorCode) pimpl->rc;
512 }
513 //----------------------------------------------------------------------
514 
516 {
517  return tDeviceHandle( &pimpl->ntcan_handle );
518 }
519 //----------------------------------------------------------------------
520 
521 //======================================================================
522 /*
523  Here are some settings for the emacs/xemacs editor (and can be safely ignored):
524  (e.g. to explicitely set C++ mode for *.h header files)
525 
526  Local Variables:
527  mode:C++
528  mode:ELSE
529  End:
530  */
531 //======================================================================
int write(char const *ptr, int len=0)
Write data to a previously opened port.
Derived exception class for low-level CAN ESD related exceptions.
Definition: canserial-esd.h:75
Interface of auxilliary utility functions for SDHLibrary-CPP.
ssize_t Read(void *data, ssize_t size, long timeout_us, bool return_on_less_data)
int m_cmsg_next
index of next received data byte to return to user in m_cmsg
~cCANSerial_ESD()
destructor: clean up
#define NULL
Definition: getopt1.c:56
void Close(void)
Close the previously opened CAN ESD interface port.
void SetTimeout(double _timeout)
set the timeout for next readline() calls (negative value means: no timeout, wait for ever) ...
Interface of class #SDH::cCANSerial_ESD, class to access CAN bus via ESD card on cygwin/linux.
dummy class for (debug) stream output of bytes as list of hex values
Definition: dbg.h:329
char const * ESD_strerror(NTCAN_RESULT rc)
virtual void SetTimeout(double _timeout)
set the timeout for next readline() calls (negative value means: no timeout, wait for ever) ...
Definition: serialbase.h:151
unsigned int BaudrateToBaudrateCode(unsigned long baudrate)
Translate a baudrate given as unsigned long into a baudrate code for struct termios.
internal hardware specific implementation details of the lowlevel ESD CAN interface ...
int tErrorCode
type of the error code, DWORD on windows and int on Linux/cygwin
Definition: serialbase.h:233
#define NAMESPACE_SDH_START
tDeviceHandle GetHandle()
return the internally used NTCAN_HANDLE cast to a tDeviceHandle
Interface of auxilliary utility functions for SDHLibrary-CPP.
#define CAN_ESD_TXQUEUESIZE
transmit queue size for CAN frames
Definition: canserial-esd.h:54
#define USING_NAMESPACE_SDH
UInt16 size
Definition: dsa.h:269
#define CAN_ESD_RXQUEUESIZE
receive queue size for CAN frames
Definition: canserial-esd.h:57
virtual char const * GetErrorMessage(tErrorCode dw)
#define NAMESPACE_SDH_END
This file contains settings to make the SDHLibrary compile on differen systems:
NTCAN_HANDLE ntcan_handle
the internal handle to the driver
Class for short, fixed maximum length text messages.
Definition: sdhexception.h:77
#define DEFINE_TO_CASECOMMAND(_c)
Definition: util.h:103
#define DBG(...)
bool IsOpen(void)
Return true if interface to CAN ESD is open.
virtual tErrorCode GetErrorNumber()
NAMESPACE_SDH_START typedef void * tDeviceHandle
generic device handle for CAN devices
Definition: serialbase.h:64
cCANSerial_ESD(cCANSerial_ESD const &other)
private copy constructor without implementation, since copying of cCANSerial_ESD objects makes no sen...


sdhlibrary_cpp
Author(s): Dirk Osswald
autogenerated on Mon Feb 28 2022 23:41:51