OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_drv_n2k_serial.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Implement comm_drv_n2k.h -- Nmea2000 serial driver.
5 * Author: David Register, Alec Leamas
6 *
7 ***************************************************************************
8 * Copyright (C) 2022 by David Register, Alec Leamas *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32
33#include <vector>
34#include <mutex> // std::mutex
35#include <queue> // std::queue
36
37#include <wx/log.h>
38
39#include "model/comm_drv_n2k_serial.h"
42#include "model/logger.h"
44
45#include <N2kMsg.h>
46
47/* Copied from canboat Project
48 * https://github.com/canboat/canboat
49 *
50 * The following startup command reverse engineered from Actisense NMEAreader.
51 * It instructs the NGT1 to clear its PGN message TX list, thus it starts
52 * sending all PGNs that it knows about.
53 */
54static unsigned char NGT_STARTUP_SEQ[] = {
55 0x11, /* msg byte 1, meaning ? */
56 0x02, /* msg byte 2, meaning ? */
57 0x00 /* msg byte 3, meaning ? */
58};
59
60std::vector<unsigned char> BufferToActisenseFormat(tN2kMsg& msg);
61
62template <typename T>
64public:
65 size_t size() {
66 std::lock_guard<std::mutex> lock(m_mutex);
67 return m_queque.size();
68 }
69
70 bool empty() {
71 std::lock_guard<std::mutex> lock(m_mutex);
72 return m_queque.empty();
73 }
74
75 const T& front() {
76 std::lock_guard<std::mutex> lock(m_mutex);
77 return m_queque.front();
78 }
79
80 void push(const T& value) {
81 std::lock_guard<std::mutex> lock(m_mutex);
82 m_queque.push(value);
83 }
84
85 void pop() {
86 std::lock_guard<std::mutex> lock(m_mutex);
87 m_queque.pop();
88 }
89
90private:
91 std::queue<T> m_queque;
92 mutable std::mutex m_mutex;
93};
94
95template <class T>
96class circular_buffer {
97public:
98 explicit circular_buffer(size_t size)
99 : buf_(std::unique_ptr<T[]>(new T[size])), max_size_(size) {}
100
101 void reset();
102 size_t capacity() const;
103 size_t size() const;
104
105 bool empty() const {
106 // if head and tail are equal, we are empty
107 return (!full_ && (head_ == tail_));
108 }
109
110 bool full() const {
111 // If tail is ahead the head by 1, we are full
112 return full_;
113 }
114
115 void put(T item) {
116 std::lock_guard<std::mutex> lock(mutex_);
117 buf_[head_] = item;
118 if (full_) tail_ = (tail_ + 1) % max_size_;
119
120 head_ = (head_ + 1) % max_size_;
121
122 full_ = head_ == tail_;
123 }
124
125 T get() {
126 std::lock_guard<std::mutex> lock(mutex_);
127
128 if (empty()) return T();
129
130 // Read data and advance the tail (we now have a free space)
131 auto val = buf_[tail_];
132 full_ = false;
133 tail_ = (tail_ + 1) % max_size_;
134
135 return val;
136 }
137
138private:
139 std::mutex mutex_;
140 std::unique_ptr<T[]> buf_;
141 size_t head_ = 0;
142 size_t tail_ = 0;
143 const size_t max_size_;
144 bool full_ = 0;
145};
146
147class CommDriverN2KSerialEvent; // fwd
148
149class CommDriverN2KSerialThread : public wxThread {
150public:
152 const wxString& PortName,
153 const wxString& strBaudRate);
154
156 void* Entry();
157 bool SetOutMsg(const std::vector<unsigned char>& load);
158 void OnExit(void);
159 DriverStats GetStats() const;
160
161private:
162#ifndef __ANDROID__
163 serial::Serial m_serial;
164#endif
165 void ThreadMessage(const wxString& msg);
166 bool OpenComPortPhysical(const wxString& com_name, int baud_rate);
167 void CloseComPortPhysical();
168 size_t WriteComPortPhysical(std::vector<unsigned char> msg);
169 size_t WriteComPortPhysical(unsigned char* msg, size_t length);
170 void SetGatewayOperationMode(void);
171
172 CommDriverN2KSerial* m_pParentDriver;
173 wxString m_PortName;
174 wxString m_FullPortName;
175
176 unsigned char* put_ptr;
177 unsigned char* tak_ptr;
178
179 unsigned char* rx_buffer;
180
181 int m_baud;
182 int m_n_timeout;
183
185 DriverStats m_driver_stats;
186 mutable std::mutex m_stats_mutex;
187#ifdef __WXMSW__
188 HANDLE m_hSerialComm;
189 bool m_nl_found;
190#endif
191};
192
194wxDECLARE_EVENT(wxEVT_COMMDRIVER_N2K_SERIAL, CommDriverN2KSerialEvent);
195
196class CommDriverN2KSerialEvent : public wxEvent {
197public:
198 CommDriverN2KSerialEvent(wxEventType commandType = wxEVT_NULL, int id = 0)
199 : wxEvent(id, commandType) {};
201
202 // accessors
203 void SetPayload(std::shared_ptr<std::vector<unsigned char>> data) {
204 m_payload = data;
205 }
206 std::shared_ptr<std::vector<unsigned char>> GetPayload() { return m_payload; }
207
208 // required for sending with wxPostEvent()
209 wxEvent* Clone() const {
211 newevent->m_payload = this->m_payload;
212 return newevent;
213 };
214
215private:
216 std::shared_ptr<std::vector<unsigned char>> m_payload;
217};
218
219//========================================================================
220/* commdriverN2KSerial implementation
221 * */
222
223wxDEFINE_EVENT(wxEVT_COMMDRIVER_N2K_SERIAL, CommDriverN2KSerialEvent);
224
225CommDriverN2KSerial::CommDriverN2KSerial(const ConnectionParams* params,
226 DriverListener& listener)
227 : CommDriverN2K(params->GetStrippedDSPort()),
228 m_Thread_run_flag(-1),
229 m_params(*params),
230 m_bok(false),
231 m_portstring(params->GetDSPort()),
232 m_pSecondary_Thread(NULL),
233 m_listener(listener),
234 m_stats_timer(*this, 2s),
235 m_closing(false) {
236 m_BaudRate = wxString::Format("%i", params->Baudrate), SetSecThreadInActive();
237 m_manufacturers_code = 0;
238 m_got_mfg_code = false;
239 this->attributes["canAddress"] = std::string("-1");
240 this->attributes["userComment"] = params->UserComment.ToStdString();
241 this->attributes["ioDirection"] = DsPortTypeToString(params->IOSelect);
242
243 // Prepare the wxEventHandler to accept events from the actual hardware thread
244 Bind(wxEVT_COMMDRIVER_N2K_SERIAL, &CommDriverN2KSerial::handle_N2K_SERIAL_RAW,
245 this);
246
247 // Dummy Driver Stats, may be polled before worker thread is active
248 m_driver_stats.driver_bus = NavAddr::Bus::N2000;
249 m_driver_stats.driver_iface = m_params.GetStrippedDSPort();
250 m_driver_stats.available = false;
251
252 Open();
253
254 wxMilliSleep(100);
255 GetMfgCode();
256
257 // Initialize the device clearing all rx/tx filterx
258 SendMgmtMsg(NGT_STARTUP_SEQ, sizeof(NGT_STARTUP_SEQ), 0x11, 0, NULL);
259
260#if 0
261 // Testing TX of Heartbeat
262 wxSleep(1);
263
264 tN2kMsg N2kMsg; // automatically sets destination 255
265 //SetHeartbeat(N2kMsg,2000,0);
266 //SetN2kPGN126993(N2kMsg, 2000, 0);
267 N2kMsg.SetPGN(126993L);
268 //N2kMsg.Priority=7;
269 N2kMsg.Source = 2;
270 N2kMsg.Destination = 133;
271 N2kMsg.Add2ByteUInt((uint16_t)(2000)); // Rate, msec
272
273 N2kMsg.AddByte(0); //Status
274 N2kMsg.AddByte(0xff); // Reserved
275 N2kMsg.Add4ByteUInt(0xffffffff); // Reserved
276
277 const std::vector<unsigned char> mv = BufferToActisenseFormat(N2kMsg);
278
279 size_t len = mv.size();
280
281 wxString comx = m_params.GetDSPort().AfterFirst(':');
282 std::string interface = comx.ToStdString();
283
284 N2kName source_name(1234);
285 auto source_address = std::make_shared<NavAddr2000>(interface, source_name);
286 auto dest_address = std::make_shared<NavAddr2000>(interface, N2kMsg.Destination);
287
288 auto message_to_send = std::make_shared<Nmea2000Msg>(126993L,
289 mv, source_address, 3);
290
291 for(size_t i=0; i< mv.size(); i++){
292 printf("%02X ", mv.at(i));
293 }
294 printf("\n\n");
295
296 SetTXPGN(126993);
297 wxSleep(1);
298
299 SendMessage(message_to_send, dest_address);
300
301 int yyp = 4;
302#endif
303}
304
305CommDriverN2KSerial::~CommDriverN2KSerial() { Close(); }
306
308 if (m_closing) return m_driver_stats;
309
310#ifndef ANDROID
311 if (m_pSecondary_Thread)
312 return m_pSecondary_Thread->GetStats();
313 else
314#endif
315 return m_driver_stats;
316}
317
318bool CommDriverN2KSerial::Open() {
319 wxString comx;
320 comx = m_params.GetDSPort().AfterFirst(':'); // strip "Serial:"
321
322 comx =
323 comx.BeforeFirst(' '); // strip off any description provided by Windows
324
325#ifndef ANDROID
326 // Kick off the RX thread
327 SetSecondaryThread(new CommDriverN2KSerialThread(this, comx, m_BaudRate));
328 SetThreadRunFlag(1);
329 GetSecondaryThread()->Run();
330#endif
331
332 return true;
333}
334
335void CommDriverN2KSerial::Close() {
336 wxLogMessage(
337 wxString::Format(_T("Closing N2K Driver %s"), m_portstring.c_str()));
338
339 m_stats_timer.Stop();
340 m_closing = true;
341
342 // Kill off the Secondary RX Thread if alive
343 if (m_pSecondary_Thread) {
344 if (m_bsec_thread_active) // Try to be sure thread object is still alive
345 {
346 wxLogMessage(_T("Stopping Secondary Thread"));
347
348 m_Thread_run_flag = 0;
349 int tsec = 10;
350 while ((m_Thread_run_flag >= 0) && (tsec--)) wxSleep(1);
351
352 wxString msg;
353 if (m_Thread_run_flag < 0)
354 msg.Printf(_T("Stopped in %d sec."), 10 - tsec);
355 else
356 msg.Printf(_T("Not Stopped after 10 sec."));
357 wxLogMessage(msg);
358 }
359
360 m_pSecondary_Thread = NULL;
361 m_bsec_thread_active = false;
362 }
363}
364static uint64_t PayloadToName(const std::vector<unsigned char> payload) {
365 uint64_t name;
366 memcpy(&name, reinterpret_cast<const void*>(payload.data()), sizeof(name));
367 return name;
368}
369
370bool CommDriverN2KSerial::SendMessage(std::shared_ptr<const NavMsg> msg,
371 std::shared_ptr<const NavAddr> addr) {
372 if (m_closing) return false;
373
374#ifndef ANDROID
375
376 auto msg_n2k = std::dynamic_pointer_cast<const Nmea2000Msg>(msg);
377 std::vector<uint8_t> load = msg_n2k->payload;
378
379 uint64_t _pgn = msg_n2k->PGN.pgn;
380 auto destination_address = std::static_pointer_cast<const NavAddr2000>(addr);
381
382 tN2kMsg N2kMsg; // automatically sets destination 255
383 N2kMsg.SetPGN(_pgn);
384 N2kMsg.Priority = msg_n2k->priority;
385 if (destination_address) N2kMsg.Destination = destination_address->address;
386
387 for (size_t i = 0; i < load.size(); i++) N2kMsg.AddByte(load.at(i)); // data
388
389 const std::vector<uint8_t> acti_pkg = BufferToActisenseFormat(N2kMsg);
390
391 // Create the internal message for all N2K listeners
392 std::vector<unsigned char> msg_payload;
393 for (size_t i = 2; i < acti_pkg.size() - 2; i++)
394 msg_payload.push_back(acti_pkg[i]);
395 auto name = PayloadToName(load);
396 auto msg_all =
397 std::make_shared<const Nmea2000Msg>(1, msg_payload, GetAddress(name));
398 auto msg_internal =
399 std::make_shared<const Nmea2000Msg>(_pgn, msg_payload, GetAddress(name));
400
401 // Notify listeners
402 m_listener.Notify(std::move(msg_internal));
403 m_listener.Notify(std::move(msg_all));
404
405 if (GetSecondaryThread()) {
406 if (IsSecThreadActive()) {
407 int retry = 10;
408 while (retry) {
409 if (GetSecondaryThread()->SetOutMsg(acti_pkg))
410 return true;
411 else
412 retry--;
413 }
414 return false; // could not send after several tries....
415 } else
416 return false;
417 }
418#endif
419 return true;
420}
421
422void CommDriverN2KSerial::ProcessManagementPacket(
423 std::vector<unsigned char>* payload) {
424 if (payload->at(2) != 0xF2) { // hearbeat
425 // printf(" pl ");
426 // for (unsigned int i = 0; i < payload->size(); i++)
427 // printf("%02X ", payload->at(i));
428 // printf("\n");
429 }
430
431 switch (payload->at(2)) {
432 case 0x47:
433 m_bmg47_resp = true;
434 break;
435 case 0x01:
436 m_bmg01_resp = true;
437 break;
438 case 0x4B:
439 m_bmg4B_resp = true;
440 break;
441 case 0x041: {
442 m_bmg41_resp = true;
443 if (payload->at(3) == 0x02) { // ASCII device_common_name
444 std::string device_common_name;
445 for (unsigned int i = 0; i < 32; i++) {
446 device_common_name += payload->at(i + 14);
447 }
448 device_common_name += '\0';
449 m_device_common_name = device_common_name;
450 }
451 break;
452 }
453 case 0x042: {
454 m_bmg42_resp = true;
455 unsigned char name[8];
456 for (unsigned int i = 0; i < 8; i++) name[i] = payload->at(i + 15);
457
458 memcpy((void*)&NAME, name, 8);
459 // Extract the manufacturers code
460 int* f1 = (int*)&NAME;
461 int f1d = *f1;
462 m_manufacturers_code = f1d >> 21;
463 break;
464 }
465
466 default:
467 break;
468 }
469}
470
471void CommDriverN2KSerial::handle_N2K_SERIAL_RAW(
473 auto p = event.GetPayload();
474
475 std::vector<unsigned char>* payload = p.get();
476
477 if (payload->at(0) == 0xA0) {
478 ProcessManagementPacket(payload);
479 return;
480 }
481
482 // If port INPUT is not set, filter the mesage here
483 if (m_params.IOSelect != DS_TYPE_OUTPUT) {
484 // extract PGN
485 uint64_t pgn = 0;
486 unsigned char* c = (unsigned char*)&pgn;
487 *c++ = payload->at(3);
488 *c++ = payload->at(4);
489 *c++ = payload->at(5);
490
491 auto name = PayloadToName(*payload);
492 auto msg =
493 std::make_shared<const Nmea2000Msg>(pgn, *payload, GetAddress(name));
494
495 m_listener.Notify(std::move(msg));
496 }
497}
498
499int CommDriverN2KSerial::GetMfgCode() {
500 unsigned char request_name[] = {0x42};
501 int ni = SendMgmtMsg(request_name, sizeof(request_name), 0x41, 2000,
502 &m_bmg42_resp);
503 if (ni) return ni; // Not responding, return error so upper retries.
504 m_got_mfg_code = true;
505 return 0;
506}
507
508int CommDriverN2KSerial::SendMgmtMsg(unsigned char* string, size_t string_size,
509 unsigned char cmd_code, int timeout_msec,
510 bool* response_flag) {
511#ifndef ANDROID
512 // Make a message
513 int byteSum = 0;
514 uint8_t CheckSum;
515 std::vector<unsigned char> msg;
516
517 msg.push_back(ESCAPE);
518 msg.push_back(STARTOFTEXT);
519 msg.push_back(0xA1);
520 byteSum += 0xA1;
521 msg.push_back(string_size); // payload length
522 byteSum += string_size;
523
524 for (unsigned int i = 0; i < string_size; i++) {
525 if (string[i] == ESCAPE) msg.push_back(string[i]);
526 msg.push_back(string[i]);
527 byteSum += string[i];
528 }
529
530 // checksum
531 byteSum %= 256;
532 CheckSum = (uint8_t)((byteSum == 0) ? 0 : (256 - byteSum));
533 msg.push_back(CheckSum);
534
535 msg.push_back(ESCAPE);
536 msg.push_back(ENDOFTEXT);
537
538 // send it out
539
540 if (response_flag) *response_flag = false; // prime the response detector
541
542 // Send the msg
543 bool bsent = false;
544 bool not_done = true;
545 int ntry_outer = 10;
546 while (not_done) {
547 if (GetSecondaryThread() && IsSecThreadActive()) {
548 int retry = 10;
549 while (retry) {
550 if (GetSecondaryThread()->SetOutMsg(msg)) {
551 bsent = true;
552 not_done = false;
553 break;
554 } else
555 retry--;
556 }
557 } else {
558 wxMilliSleep(100);
559 if (ntry_outer-- <= 0) not_done = false;
560 }
561 }
562
563 if (!bsent) return 1;
564
565 bool bOK = false;
566 if (timeout_msec) {
567 int timeout = timeout_msec;
568 while (timeout > 0) {
569 wxYieldIfNeeded();
570 wxMilliSleep(100);
571 if (response_flag) {
572 if (*response_flag) {
573 bOK = true;
574 break;
575 }
576 }
577 timeout -= 100;
578 }
579 } else
580 bOK = true;
581
582 if (!bOK) {
583 // printf( "***Err-1\n");
584 return 1;
585 }
586 // else
587 // printf("***OK-1 %d\n", timeout);
588#endif
589 return 0;
590}
591
592int CommDriverN2KSerial::SetTXPGN(int pgn) {
593 // Enable PGN message
594 unsigned char request_enable[] = {0x47, 0x00, 0x00, 0x00, // pgn
595 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF};
596
597 int PGN = 0;
598 unsigned char* c = (unsigned char*)&pgn;
599 request_enable[1] = c[0];
600 request_enable[2] = c[1];
601 request_enable[3] = c[2];
602
603 int aa = SendMgmtMsg(request_enable, sizeof(request_enable), 0x47, 2000,
604 &m_bmg47_resp);
605 if (aa) return 0; // Probably YDNU-02 device
606
607 // Commit message
608 unsigned char request_commit[] = {0x01};
609 int bb = SendMgmtMsg(request_commit, sizeof(request_commit), 0x01, 2000,
610 &m_bmg01_resp);
611
612 // Activate message
613 unsigned char request_activate[] = {0x4B};
614 int cc = SendMgmtMsg(request_activate, sizeof(request_activate), 0x4B, 2000,
615 &m_bmg4B_resp);
616
617 return 0;
618}
619void CommDriverN2KSerial::AddTxPGN(int pgn) {
620 auto it = std::find(pgn_tx_list.begin(), pgn_tx_list.end(), pgn);
621 if (it != pgn_tx_list.end())
622 return;
623 else {
624 SetTXPGN(pgn);
625 pgn_tx_list.push_back(pgn);
626 }
627}
628
629#ifndef __ANDROID__
630
637// Commonly used raw format is actually inherited from an old paketizing format:
638// <10><02><application data><CRC (1)><10><03>
639
640// Actisense application data, from NGT-1 to PC
641// <data code=93><length (1)><priority (1)><PGN (3)><destination(1)><source
642// (1)><time (4)><len (1)><data (len)>
643
644// As applied to a real application data element, after extraction from packet
645// format: 93 13 02 01 F8 01 FF 01 76 C2 52 00 08 08 70 EB 14 E8 8E 52 D2 BB 10
646
647// length (1): 0x13
648// priority (1); 0x02
649// PGN (3): 0x01 0xF8 0x01
650// destination(1): 0xFF
651// source (1): 0x01
652// time (4): 0x76 0xC2 0x52 0x00
653// len (1): 0x08
654// data (len): 08 70 EB 14 E8 8E 52 D2
655// packet CRC: 0xBB
656
657#define DS_RX_BUFFER_SIZE 4096
658
659CommDriverN2KSerialThread::CommDriverN2KSerialThread(
660 CommDriverN2KSerial* Launcher, const wxString& PortName,
661 const wxString& strBaudRate) {
662 m_pParentDriver = Launcher; // This thread's immediate "parent"
663
664 m_PortName = PortName;
665 m_FullPortName = _T("Serial:") + PortName;
666
667 rx_buffer = new unsigned char[DS_RX_BUFFER_SIZE + 1];
668
669 put_ptr = rx_buffer; // local circular queue
670 tak_ptr = rx_buffer;
671
672 m_baud = 9600; // default
673 long lbaud;
674 if (strBaudRate.ToLong(&lbaud)) m_baud = (int)lbaud;
675 {
676 std::lock_guard lock(m_stats_mutex);
677 m_driver_stats.driver_bus = NavAddr::Bus::N2000;
678 m_driver_stats.driver_iface = m_pParentDriver->m_params.GetStrippedDSPort();
679 m_driver_stats.available = false;
680 }
681
682 Create();
683}
684
685CommDriverN2KSerialThread::~CommDriverN2KSerialThread(void) {
686 delete[] rx_buffer;
687}
688
689void CommDriverN2KSerialThread::OnExit(void) {}
690
691DriverStats CommDriverN2KSerialThread::GetStats() const {
692 std::lock_guard lock(m_stats_mutex);
693 return m_driver_stats;
694}
695
696bool CommDriverN2KSerialThread::OpenComPortPhysical(const wxString& com_name,
697 int baud_rate) {
698 try {
699 m_serial.setPort(com_name.ToStdString());
700 m_serial.setBaudrate(baud_rate);
701 m_serial.open();
702 m_serial.setTimeout(250, 250, 0, 250, 0);
703 } catch (std::exception&) {
704 // std::cerr << "Unhandled Exception while opening serial port: " <<
705 // e.what() << std::endl;
706 }
707 return m_serial.isOpen();
708}
709
710void CommDriverN2KSerialThread::CloseComPortPhysical() {
711 try {
712 m_serial.close();
713 } catch (std::exception&) {
714 // std::cerr << "Unhandled Exception while closing serial port: " <<
715 // e.what() << std::endl;
716 }
717 std::lock_guard lock(m_stats_mutex);
718 m_driver_stats.available = false;
719}
720
721void CommDriverN2KSerialThread::SetGatewayOperationMode(void) {
722 // For YDNU-02 device
723 // From Device User Manual
724 // Set the mode to "N2K"
725 unsigned char config_string[] = {0x10, 0x02, 0xA1, 0x03, 0x11,
726 0x02, 0x00, 0x49, 0x10, 0x03};
727 // std::vector<byte>writeBuffer {DLE,STX,NGT_TX_CMD,0x03,0x11,0x02,0x00,0x49,
728 // DLE,ETX};
729
730 WriteComPortPhysical(config_string, 10);
731}
732
733void CommDriverN2KSerialThread::ThreadMessage(const wxString& msg) {
734 // Signal the main program thread
735 // OCPN_ThreadMessageEvent event(wxEVT_OCPN_THREADMSG, 0);
736 // event.SetSString(std::string(msg.mb_str()));
737 // if (gFrame) gFrame->GetEventHandler()->AddPendingEvent(event);
738}
739
740size_t CommDriverN2KSerialThread::WriteComPortPhysical(
741 std::vector<unsigned char> msg) {
742 return WriteComPortPhysical(msg.data(), msg.size());
743}
744
745size_t CommDriverN2KSerialThread::WriteComPortPhysical(unsigned char* msg,
746 size_t length) {
747 if (!m_serial.isOpen()) return 0;
748 try {
749 size_t status = m_serial.write((uint8_t*)msg, length);
750 if (status) m_serial.flushOutput();
751 return status;
752 } catch (std::exception& e) {
753 DEBUG_LOG << "Unhandled Exception while writing to serial port: "
754 << e.what();
755 }
756 return 0;
757}
758
759bool CommDriverN2KSerialThread::SetOutMsg(
760 const std::vector<unsigned char>& msg) {
761 if (out_que.size() < OUT_QUEUE_LENGTH) {
762 out_que.push(msg);
763 return true;
764 }
765 return false;
766}
767
768#ifndef __WXMSW__
769void* CommDriverN2KSerialThread::Entry() {
770 bool not_done = true;
771 bool nl_found = false;
772 wxString msg;
773 uint8_t rdata[2000];
774 circular_buffer<uint8_t> circle(DS_RX_BUFFER_SIZE);
775 int ib = 0;
776
777 // Request the com port from the comm manager
778 if (!OpenComPortPhysical(m_PortName, m_baud)) {
779 wxString msg(_T("NMEA input device open failed: "));
780 msg.Append(m_PortName);
781 ThreadMessage(msg);
782 std::lock_guard lock(m_stats_mutex);
783 m_driver_stats.available = false;
784
785 // goto thread_exit; // This means we will not be trying to connect = The
786 // device must be connected when the thread is created. Does not seem to be
787 // needed/what we want as the reconnection logic is able to pick it up
788 // whenever it actually appears (Of course given it appears with the
789 // expected device name).
790 } else {
791 wxMilliSleep(100);
792 std::lock_guard lock(m_stats_mutex);
793 m_driver_stats.available = true;
794 SetGatewayOperationMode();
795 }
796
797 m_pParentDriver->SetSecThreadActive(); // I am alive
798
799 // The main loop
800 static size_t retries = 0;
801
802 bool bInMsg = false;
803 bool bGotESC = false;
804 bool bGotSOT = false;
805
806 while ((not_done) && (m_pParentDriver->m_Thread_run_flag > 0)) {
807 if (TestDestroy()) not_done = false; // smooth exit
808
809 uint8_t next_byte = 0;
810 int newdata = 0;
811 if (m_serial.isOpen()) {
812 try {
813 newdata = m_serial.read(rdata, 1000);
814 } catch (std::exception& e) {
815 // std::cerr << "Serial read exception: " << e.what() << std::endl;
816 std::lock_guard lock(m_stats_mutex);
817 m_driver_stats.error_count++;
818
819 if (10 < retries++) {
820 // We timed out waiting for the next character 10 times, let's close
821 // the port so that the reconnection logic kicks in and tries to fix
822 // our connection.
823 CloseComPortPhysical();
824 retries = 0;
825 }
826 }
827 } else {
828 // Reconnection logic. Let's try to reopen the port while waiting longer
829 // every time (until we simply keep trying every 2.5 seconds)
830 // std::cerr << "Serial port seems closed." << std::endl;
831 wxMilliSleep(250 * retries);
832 CloseComPortPhysical();
833 if (OpenComPortPhysical(m_PortName, m_baud)) {
834 SetGatewayOperationMode();
835 std::lock_guard lock(m_stats_mutex);
836 m_driver_stats.available = true;
837 retries = 0;
838 } else if (retries < 10) {
839 std::lock_guard lock(m_stats_mutex);
840 m_driver_stats.available = false;
841 retries++;
842 }
843 }
844
845 if (newdata > 0) {
846 std::lock_guard lock(m_stats_mutex);
847 m_driver_stats.rx_count += newdata;
848
849 for (int i = 0; i < newdata; i++) {
850 circle.put(rdata[i]);
851 }
852 }
853
854 while (!circle.empty()) {
855 if (ib >= DS_RX_BUFFER_SIZE) ib = 0;
856 uint8_t next_byte = circle.get();
857
858 if (bInMsg) {
859 if (bGotESC) {
860 if (ESCAPE == next_byte) {
861 rx_buffer[ib++] = next_byte;
862 bGotESC = false;
863 }
864 }
865
866 if (bGotESC && (ENDOFTEXT == next_byte)) {
867 // Process packet
868 // Copy the message into a std::vector
869
870 auto buffer = std::make_shared<std::vector<unsigned char>>(
871 rx_buffer, rx_buffer + ib);
872 std::vector<unsigned char>* vec = buffer.get();
873
874 ib = 0;
875 // tak_ptr = tptr;
876 bInMsg = false;
877 bGotESC = false;
878
879 // printf("raw ");
880 // for (unsigned int i = 0; i < vec->size(); i++)
881 // printf("%02X ", vec->at(i));
882 // printf("\n");
883
884 // Message is finished
885 // Send the captured raw data vector pointer to the thread's "parent"
886 // thereby releasing the thread for further data capture
887 CommDriverN2KSerialEvent Nevent(wxEVT_COMMDRIVER_N2K_SERIAL, 0);
888 Nevent.SetPayload(buffer);
889 m_pParentDriver->AddPendingEvent(Nevent);
890
891 } else {
892 bGotESC = (next_byte == ESCAPE);
893
894 if (!bGotESC) {
895 rx_buffer[ib++] = next_byte;
896 }
897 }
898 }
899
900 else {
901 if (STARTOFTEXT == next_byte) {
902 bGotSOT = false;
903 if (bGotESC) {
904 bGotSOT = true;
905 }
906 } else {
907 bGotESC = (next_byte == ESCAPE);
908 if (bGotSOT) {
909 bGotSOT = false;
910 bInMsg = true;
911
912 rx_buffer[ib++] = next_byte;
913 }
914 }
915 }
916 } // if newdata > 0
917
918 // Check for any pending output message
919#if 1
920 bool b_qdata = !out_que.empty();
921
922 while (b_qdata) {
923 // Take a copy of message
924 std::vector<unsigned char> qmsg = out_que.front();
925 out_que.pop();
926
927 if (static_cast<size_t>(-1) == WriteComPortPhysical(qmsg) &&
928 10 < retries++) {
929 // We failed to write the port 10 times, let's close the port so that
930 // the reconnection logic kicks in and tries to fix our connection.
931 retries = 0;
932 CloseComPortPhysical();
933 }
934
935 b_qdata = !out_que.empty();
936 } // while b_qdata
937
938#endif
939 } // while ((not_done)
940
941 // thread_exit:
942 CloseComPortPhysical();
943 m_pParentDriver->SetSecThreadInActive(); // I am dead
944 m_pParentDriver->m_Thread_run_flag = -1;
945
946 std::lock_guard lock(m_stats_mutex);
947 m_driver_stats.available = false;
948
949 return 0;
950}
951
952#else
953void* CommDriverN2KSerialThread::Entry() {
954 bool not_done = true;
955 bool nl_found = false;
956 wxString msg;
957 circular_buffer<uint8_t> circle(DS_RX_BUFFER_SIZE);
958
959 // Request the com port from the comm manager
960 if (!OpenComPortPhysical(m_PortName, m_baud)) {
961 wxString msg(_T("NMEA input device open failed: "));
962 msg.Append(m_PortName);
963 ThreadMessage(msg);
964 std::lock_guard lock(m_stats_mutex);
965 m_driver_stats.available = false;
966
967 // goto thread_exit; // This means we will not be trying to connect = The
968 // device must be connected when the thread is created. Does not seem to be
969 // needed/what we want as the reconnection logic is able to pick it up
970 // whenever it actually appears (Of course given it appears with the
971 // expected device name).
972 } else {
973 SetGatewayOperationMode();
974 std::lock_guard lock(m_stats_mutex);
975 m_driver_stats.available = true;
976 }
977
978 m_pParentDriver->SetSecThreadActive(); // I am alive
979
980 // The main loop
981 static size_t retries = 0;
982
983 bool bInMsg = false;
984 bool bGotESC = false;
985 bool bGotSOT = false;
986
987 while ((not_done) && (m_pParentDriver->m_Thread_run_flag > 0)) {
988 if (TestDestroy()) not_done = false; // smooth exit
989
990 uint8_t next_byte = 0;
991 int newdata = -1;
992 uint8_t rdata[2000];
993
994 if (m_serial.isOpen()) {
995 try {
996 newdata = m_serial.read(rdata, 200);
997 } catch (std::exception& e) {
998 // std::cerr << "Serial read exception: " << e.what() << std::endl;
999 if (10 < retries++) {
1000 // We timed out waiting for the next character 10 times, let's close
1001 // the port so that the reconnection logic kicks in and tries to fix
1002 // our connection.
1003 CloseComPortPhysical();
1004 retries = 0;
1005 }
1006 }
1007 } else {
1008 // Reconnection logic. Let's try to reopen the port while waiting longer
1009 // every time (until we simply keep trying every 2.5 seconds)
1010 // std::cerr << "Serial port seems closed." << std::endl;
1011 wxMilliSleep(250 * retries);
1012 CloseComPortPhysical();
1013 if (OpenComPortPhysical(m_PortName, m_baud)) {
1014 std::lock_guard lock(m_stats_mutex);
1015 m_driver_stats.available = true;
1016 SetGatewayOperationMode();
1017 retries = 0;
1018 } else if (retries < 10)
1019 retries++;
1020 }
1021
1022 if (newdata > 0) {
1023 for (int i = 0; i < newdata; i++) {
1024 circle.put(rdata[i]);
1025 }
1026 }
1027
1028 while (!circle.empty()) {
1029 uint8_t next_byte = circle.get();
1030
1031 if (1) {
1032 if (bInMsg) {
1033 if (bGotESC) {
1034 if (ESCAPE == next_byte) {
1035 *put_ptr++ = next_byte;
1036 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1037 put_ptr = rx_buffer;
1038 bGotESC = false;
1039 } else if (ENDOFTEXT == next_byte) {
1040 // Process packet
1041 // Copy the message into a std::vector
1042
1043 auto buffer = std::make_shared<std::vector<unsigned char>>();
1044 std::vector<unsigned char>* vec = buffer.get();
1045
1046 unsigned char* tptr;
1047 tptr = tak_ptr;
1048
1049 while ((tptr != put_ptr)) {
1050 vec->push_back(*tptr++);
1051 if ((tptr - rx_buffer) > DS_RX_BUFFER_SIZE) tptr = rx_buffer;
1052 }
1053
1054 tak_ptr = tptr;
1055 bInMsg = false;
1056 bGotESC = false;
1057
1058 // Message is finished
1059 // Send the captured raw data vector pointer to the thread's
1060 // "parent"
1061 // thereby releasing the thread for further data capture
1062 CommDriverN2KSerialEvent Nevent(wxEVT_COMMDRIVER_N2K_SERIAL, 0);
1063 Nevent.SetPayload(buffer);
1064 m_pParentDriver->AddPendingEvent(Nevent);
1065 std::lock_guard lock(m_stats_mutex);
1066 m_driver_stats.rx_count += vec->size();
1067 } else if (next_byte == STARTOFTEXT) {
1068 put_ptr = rx_buffer;
1069 bGotESC = false;
1070 } else {
1071 put_ptr = rx_buffer;
1072 bInMsg = false;
1073 bGotESC = false;
1074 }
1075
1076 } else {
1077 bGotESC = (next_byte == ESCAPE);
1078
1079 if (!bGotESC) {
1080 *put_ptr++ = next_byte;
1081 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1082 put_ptr = rx_buffer;
1083 }
1084 }
1085 }
1086
1087 else {
1088 if (STARTOFTEXT == next_byte) {
1089 bGotSOT = false;
1090 if (bGotESC) {
1091 bGotSOT = true;
1092 }
1093 } else {
1094 bGotESC = (next_byte == ESCAPE);
1095 if (bGotSOT) {
1096 bGotSOT = false;
1097 bInMsg = true;
1098
1099 *put_ptr++ = next_byte;
1100 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1101 put_ptr = rx_buffer;
1102 }
1103 }
1104 }
1105 } // if newdata > 0
1106 } // while
1107
1108 // Check for any pending output message
1109 bool b_qdata = !out_que.empty();
1110
1111 while (b_qdata) {
1112 // Take a copy of message
1113 std::vector<unsigned char> qmsg = out_que.front();
1114 out_que.pop();
1115
1116 if (static_cast<size_t>(-1) == WriteComPortPhysical(qmsg) &&
1117 10 < retries++) {
1118 // We failed to write the port 10 times, let's close the port so that
1119 // the reconnection logic kicks in and tries to fix our connection.
1120 retries = 0;
1121 CloseComPortPhysical();
1122 }
1123 std::lock_guard lock(m_stats_mutex);
1124 m_driver_stats.tx_count += qmsg.size();
1125
1126 b_qdata = !out_que.empty();
1127 } // while b_qdata
1128 } // while ((not_done)
1129
1130 // thread_exit:
1131 CloseComPortPhysical();
1132 m_pParentDriver->SetSecThreadInActive(); // I am dead
1133 m_pParentDriver->m_Thread_run_flag = -1;
1134
1135 return 0;
1136}
1137
1138#endif // wxmsw Entry()
1139
1140#endif // Android
1141
1142//*****************************************************************************
1143// Actisense Format:
1144// <10><02><93><length (1)><priority (1)><PGN (3)><destination (1)><source
1145// (1)><time (4)><len (1)><data (len)><CRC (1)><10><03>
1146#define MaxActisenseMsgBuf 400
1147#define MsgTypeN2kTX 0x94
1148
1149void AddByteEscapedToBuf(unsigned char byteToAdd, uint8_t& idx,
1150 unsigned char* buf, int& byteSum);
1151
1152std::vector<unsigned char> BufferToActisenseFormat(tN2kMsg& msg) {
1153 unsigned long _PGN = msg.PGN;
1154 uint8_t msgIdx = 0;
1155 int byteSum = 0;
1156 uint8_t CheckSum;
1157 unsigned char ActisenseMsgBuf[MaxActisenseMsgBuf];
1158
1159 ActisenseMsgBuf[msgIdx++] = ESCAPE;
1160 ActisenseMsgBuf[msgIdx++] = STARTOFTEXT;
1161 AddByteEscapedToBuf(MsgTypeN2kTX, msgIdx, ActisenseMsgBuf, byteSum);
1162 AddByteEscapedToBuf(msg.DataLen + 6, msgIdx, ActisenseMsgBuf,
1163 byteSum); // length does not include escaped chars
1164
1165 AddByteEscapedToBuf(msg.Priority, msgIdx, ActisenseMsgBuf, byteSum);
1166 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1167 _PGN >>= 8;
1168 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1169 _PGN >>= 8;
1170 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1171 AddByteEscapedToBuf(msg.Destination, msgIdx, ActisenseMsgBuf, byteSum);
1172
1173#if 0
1174 // For TX through Actisense compatible gateway, we skip "source" byte and msg time fields
1175 // Source
1176 AddByteEscapedToBuf(msg.Source,msgIdx,ActisenseMsgBuf,byteSum);
1177 // Time
1178 int _MsgTime = 0;
1179 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1180 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1181 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1182 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum);
1183
1184#endif
1185
1186 AddByteEscapedToBuf(msg.DataLen, msgIdx, ActisenseMsgBuf, byteSum);
1187
1188 for (int i = 0; i < msg.DataLen; i++)
1189 AddByteEscapedToBuf(msg.Data[i], msgIdx, ActisenseMsgBuf, byteSum);
1190 byteSum %= 256;
1191
1192 CheckSum = (uint8_t)((byteSum == 0) ? 0 : (256 - byteSum));
1193 ActisenseMsgBuf[msgIdx++] = CheckSum;
1194 if (CheckSum == ESCAPE) ActisenseMsgBuf[msgIdx++] = CheckSum;
1195
1196 ActisenseMsgBuf[msgIdx++] = ESCAPE;
1197 ActisenseMsgBuf[msgIdx++] = ENDOFTEXT;
1198
1199 std::vector<unsigned char> rv;
1200 for (unsigned int i = 0; i < msgIdx; i++) rv.push_back(ActisenseMsgBuf[i]);
1201
1202 return rv;
1203}
DriverStats GetDriverStats() const override
Get the Driver Statistics.
Interface for handling incoming messages.
Definition comm_driver.h:52
virtual void Notify(std::shared_ptr< const NavMsg > message)=0
Handle a received message.
Class that provides a portable serial port interface.
Definition serial.h:147
size_t read(uint8_t *buffer, size_t size)
Read a given amount of bytes from the serial port into a given buffer.
void setPort(const std::string &port)
Sets the serial port identifier.
size_t write(const uint8_t *data, size_t size)
Write a string to the serial port.
void setBaudrate(uint32_t baudrate)
Sets the baudrate for the serial port.
void close()
Closes the serial port.
void setTimeout(Timeout &timeout)
Sets the timeout for reads and writes using the Timeout struct.
void flushOutput()
Flush only the output buffer.
void open()
Opens the serial port as long as the port is set and the port isn't already open.
bool isOpen() const
Gets the open status of the serial port.
Driver registration container, a singleton.
Communication statistics infrastructure.
Raw messages layer, supports sending and recieving navmsg messages.
Enhanced logging interface on top of wx/log.h.
Driver statistics report.
unsigned tx_count
Number of bytes sent since program start.
unsigned rx_count
Number of bytes received since program start.
unsigned error_count
Number of detected errors since program start.
N2k uses CAN which defines the basic properties of messages.
Definition comm_navmsg.h:70