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"] = std::string("IN/OUT");
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
307DriverStats CommDriverN2KSerial::GetDriverStats() const {
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 // extract PGN
483 uint64_t pgn = 0;
484 unsigned char* c = (unsigned char*)&pgn;
485 *c++ = payload->at(3);
486 *c++ = payload->at(4);
487 *c++ = payload->at(5);
488 // memcpy(&v, &data[3], 1);
489 // printf(" %ld\n", pgn);
490
491 auto name = PayloadToName(*payload);
492 auto msg =
493 std::make_shared<const Nmea2000Msg>(pgn, *payload, GetAddress(name));
494 auto msg_all =
495 std::make_shared<const Nmea2000Msg>(1, *payload, GetAddress(name));
496
497 m_listener.Notify(std::move(msg));
498 m_listener.Notify(std::move(msg_all));
499
500#if 0 // Debug output
501 size_t packetLength = (size_t)payload->at(1);
502 size_t vector_length = payload->size();
503
504
505 //if(pgn > 120000)
506 {
507 printf("Payload Length: %ld\n", vector_length);
508
509 printf("PGN: %ld\n", pgn);
510
511 for(size_t i=0; i< vector_length ; i++){
512 printf("%02X ", payload->at(i));
513 }
514 printf("\n\n");
515 }
516#endif
517}
518
519int CommDriverN2KSerial::GetMfgCode() {
520 unsigned char request_name[] = {0x42};
521 int ni = SendMgmtMsg(request_name, sizeof(request_name), 0x41, 2000,
522 &m_bmg42_resp);
523 if (ni) return ni; // Not responding, return error so upper retries.
524 m_got_mfg_code = true;
525 return 0;
526}
527
528int CommDriverN2KSerial::SendMgmtMsg(unsigned char* string, size_t string_size,
529 unsigned char cmd_code, int timeout_msec,
530 bool* response_flag) {
531#ifndef ANDROID
532 // Make a message
533 int byteSum = 0;
534 uint8_t CheckSum;
535 std::vector<unsigned char> msg;
536
537 msg.push_back(ESCAPE);
538 msg.push_back(STARTOFTEXT);
539 msg.push_back(0xA1);
540 byteSum += 0xA1;
541 msg.push_back(string_size); // payload length
542 byteSum += string_size;
543
544 for (unsigned int i = 0; i < string_size; i++) {
545 if (string[i] == ESCAPE) msg.push_back(string[i]);
546 msg.push_back(string[i]);
547 byteSum += string[i];
548 }
549
550 // checksum
551 byteSum %= 256;
552 CheckSum = (uint8_t)((byteSum == 0) ? 0 : (256 - byteSum));
553 msg.push_back(CheckSum);
554
555 msg.push_back(ESCAPE);
556 msg.push_back(ENDOFTEXT);
557
558 // send it out
559
560 if (response_flag) *response_flag = false; // prime the response detector
561
562 // Send the msg
563 bool bsent = false;
564 bool not_done = true;
565 int ntry_outer = 10;
566 while (not_done) {
567 if (GetSecondaryThread() && IsSecThreadActive()) {
568 int retry = 10;
569 while (retry) {
570 if (GetSecondaryThread()->SetOutMsg(msg)) {
571 bsent = true;
572 not_done = false;
573 break;
574 } else
575 retry--;
576 }
577 } else {
578 wxMilliSleep(100);
579 if (ntry_outer-- <= 0) not_done = false;
580 }
581 }
582
583 if (!bsent) return 1;
584
585 bool bOK = false;
586 if (timeout_msec) {
587 int timeout = timeout_msec;
588 while (timeout > 0) {
589 wxYieldIfNeeded();
590 wxMilliSleep(100);
591 if (response_flag) {
592 if (*response_flag) {
593 bOK = true;
594 break;
595 }
596 }
597 timeout -= 100;
598 }
599 } else
600 bOK = true;
601
602 if (!bOK) {
603 // printf( "***Err-1\n");
604 return 1;
605 }
606 // else
607 // printf("***OK-1 %d\n", timeout);
608#endif
609 return 0;
610}
611
612int CommDriverN2KSerial::SetTXPGN(int pgn) {
613 // Enable PGN message
614 unsigned char request_enable[] = {0x47, 0x00, 0x00, 0x00, // pgn
615 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF};
616
617 int PGN = 0;
618 unsigned char* c = (unsigned char*)&pgn;
619 request_enable[1] = c[0];
620 request_enable[2] = c[1];
621 request_enable[3] = c[2];
622
623 int aa = SendMgmtMsg(request_enable, sizeof(request_enable), 0x47, 2000,
624 &m_bmg47_resp);
625 if (aa) return 0; // Probably YDNU-02 device
626
627 // Commit message
628 unsigned char request_commit[] = {0x01};
629 int bb = SendMgmtMsg(request_commit, sizeof(request_commit), 0x01, 2000,
630 &m_bmg01_resp);
631
632 // Activate message
633 unsigned char request_activate[] = {0x4B};
634 int cc = SendMgmtMsg(request_activate, sizeof(request_activate), 0x4B, 2000,
635 &m_bmg4B_resp);
636
637 return 0;
638}
639void CommDriverN2KSerial::AddTxPGN(int pgn) {
640 auto it = std::find(pgn_tx_list.begin(), pgn_tx_list.end(), pgn);
641 if (it != pgn_tx_list.end())
642 return;
643 else {
644 SetTXPGN(pgn);
645 pgn_tx_list.push_back(pgn);
646 }
647}
648
649#ifndef __ANDROID__
650
657// Commonly used raw format is actually inherited from an old paketizing format:
658// <10><02><application data><CRC (1)><10><03>
659
660// Actisense application data, from NGT-1 to PC
661// <data code=93><length (1)><priority (1)><PGN (3)><destination(1)><source
662// (1)><time (4)><len (1)><data (len)>
663
664// As applied to a real application data element, after extraction from packet
665// format: 93 13 02 01 F8 01 FF 01 76 C2 52 00 08 08 70 EB 14 E8 8E 52 D2 BB 10
666
667// length (1): 0x13
668// priority (1); 0x02
669// PGN (3): 0x01 0xF8 0x01
670// destination(1): 0xFF
671// source (1): 0x01
672// time (4): 0x76 0xC2 0x52 0x00
673// len (1): 0x08
674// data (len): 08 70 EB 14 E8 8E 52 D2
675// packet CRC: 0xBB
676
677#define DS_RX_BUFFER_SIZE 4096
678
679CommDriverN2KSerialThread::CommDriverN2KSerialThread(
680 CommDriverN2KSerial* Launcher, const wxString& PortName,
681 const wxString& strBaudRate) {
682 m_pParentDriver = Launcher; // This thread's immediate "parent"
683
684 m_PortName = PortName;
685 m_FullPortName = _T("Serial:") + PortName;
686
687 rx_buffer = new unsigned char[DS_RX_BUFFER_SIZE + 1];
688
689 put_ptr = rx_buffer; // local circular queue
690 tak_ptr = rx_buffer;
691
692 m_baud = 9600; // default
693 long lbaud;
694 if (strBaudRate.ToLong(&lbaud)) m_baud = (int)lbaud;
695 {
696 std::lock_guard lock(m_stats_mutex);
697 m_driver_stats.driver_bus = NavAddr::Bus::N2000;
698 m_driver_stats.driver_iface = m_pParentDriver->m_params.GetStrippedDSPort();
699 m_driver_stats.available = false;
700 }
701
702 Create();
703}
704
705CommDriverN2KSerialThread::~CommDriverN2KSerialThread(void) {
706 delete[] rx_buffer;
707}
708
709void CommDriverN2KSerialThread::OnExit(void) {}
710
711DriverStats CommDriverN2KSerialThread::GetStats() const {
712 std::lock_guard lock(m_stats_mutex);
713 return m_driver_stats;
714}
715
716bool CommDriverN2KSerialThread::OpenComPortPhysical(const wxString& com_name,
717 int baud_rate) {
718 try {
719 m_serial.setPort(com_name.ToStdString());
720 m_serial.setBaudrate(baud_rate);
721 m_serial.open();
722 m_serial.setTimeout(250, 250, 0, 250, 0);
723 } catch (std::exception&) {
724 // std::cerr << "Unhandled Exception while opening serial port: " <<
725 // e.what() << std::endl;
726 }
727 return m_serial.isOpen();
728}
729
730void CommDriverN2KSerialThread::CloseComPortPhysical() {
731 try {
732 m_serial.close();
733 } catch (std::exception&) {
734 // std::cerr << "Unhandled Exception while closing serial port: " <<
735 // e.what() << std::endl;
736 }
737 std::lock_guard lock(m_stats_mutex);
738 m_driver_stats.available = false;
739}
740
741void CommDriverN2KSerialThread::SetGatewayOperationMode(void) {
742 // For YDNU-02 device
743 // From Device User Manual
744 // Set the mode to "N2K"
745 unsigned char config_string[] = {0x10, 0x02, 0xA1, 0x03, 0x11,
746 0x02, 0x00, 0x49, 0x10, 0x03};
747 // std::vector<byte>writeBuffer {DLE,STX,NGT_TX_CMD,0x03,0x11,0x02,0x00,0x49,
748 // DLE,ETX};
749
750 WriteComPortPhysical(config_string, 10);
751}
752
753void CommDriverN2KSerialThread::ThreadMessage(const wxString& msg) {
754 // Signal the main program thread
755 // OCPN_ThreadMessageEvent event(wxEVT_OCPN_THREADMSG, 0);
756 // event.SetSString(std::string(msg.mb_str()));
757 // if (gFrame) gFrame->GetEventHandler()->AddPendingEvent(event);
758}
759
760size_t CommDriverN2KSerialThread::WriteComPortPhysical(
761 std::vector<unsigned char> msg) {
762 return WriteComPortPhysical(msg.data(), msg.size());
763}
764
765size_t CommDriverN2KSerialThread::WriteComPortPhysical(unsigned char* msg,
766 size_t length) {
767 if (!m_serial.isOpen()) return 0;
768 try {
769 size_t status = m_serial.write((uint8_t*)msg, length);
770 if (status) m_serial.flushOutput();
771 return status;
772 } catch (std::exception& e) {
773 DEBUG_LOG << "Unhandled Exception while writing to serial port: "
774 << e.what();
775 }
776 return 0;
777}
778
779bool CommDriverN2KSerialThread::SetOutMsg(
780 const std::vector<unsigned char>& msg) {
781 if (out_que.size() < OUT_QUEUE_LENGTH) {
782 out_que.push(msg);
783 return true;
784 }
785 return false;
786}
787
788#ifndef __WXMSW__
789void* CommDriverN2KSerialThread::Entry() {
790 bool not_done = true;
791 bool nl_found = false;
792 wxString msg;
793 uint8_t rdata[2000];
794 circular_buffer<uint8_t> circle(DS_RX_BUFFER_SIZE);
795 int ib = 0;
796
797 // Request the com port from the comm manager
798 if (!OpenComPortPhysical(m_PortName, m_baud)) {
799 wxString msg(_T("NMEA input device open failed: "));
800 msg.Append(m_PortName);
801 ThreadMessage(msg);
802 std::lock_guard lock(m_stats_mutex);
803 m_driver_stats.available = false;
804
805 // goto thread_exit; // This means we will not be trying to connect = The
806 // device must be connected when the thread is created. Does not seem to be
807 // needed/what we want as the reconnection logic is able to pick it up
808 // whenever it actually appears (Of course given it appears with the
809 // expected device name).
810 } else {
811 wxMilliSleep(100);
812 std::lock_guard lock(m_stats_mutex);
813 m_driver_stats.available = true;
814 SetGatewayOperationMode();
815 }
816
817 m_pParentDriver->SetSecThreadActive(); // I am alive
818
819 // The main loop
820 static size_t retries = 0;
821
822 bool bInMsg = false;
823 bool bGotESC = false;
824 bool bGotSOT = false;
825
826 while ((not_done) && (m_pParentDriver->m_Thread_run_flag > 0)) {
827 if (TestDestroy()) not_done = false; // smooth exit
828
829 uint8_t next_byte = 0;
830 int newdata = 0;
831 if (m_serial.isOpen()) {
832 try {
833 newdata = m_serial.read(rdata, 1000);
834 } catch (std::exception& e) {
835 // std::cerr << "Serial read exception: " << e.what() << std::endl;
836 std::lock_guard lock(m_stats_mutex);
837 m_driver_stats.error_count++;
838
839 if (10 < retries++) {
840 // We timed out waiting for the next character 10 times, let's close
841 // the port so that the reconnection logic kicks in and tries to fix
842 // our connection.
843 CloseComPortPhysical();
844 retries = 0;
845 }
846 }
847 } else {
848 // Reconnection logic. Let's try to reopen the port while waiting longer
849 // every time (until we simply keep trying every 2.5 seconds)
850 // std::cerr << "Serial port seems closed." << std::endl;
851 wxMilliSleep(250 * retries);
852 CloseComPortPhysical();
853 if (OpenComPortPhysical(m_PortName, m_baud)) {
854 SetGatewayOperationMode();
855 std::lock_guard lock(m_stats_mutex);
856 m_driver_stats.available = true;
857 retries = 0;
858 } else if (retries < 10) {
859 std::lock_guard lock(m_stats_mutex);
860 m_driver_stats.available = false;
861 retries++;
862 }
863 }
864
865 if (newdata > 0) {
866 std::lock_guard lock(m_stats_mutex);
867 m_driver_stats.rx_count += newdata;
868
869 for (int i = 0; i < newdata; i++) {
870 circle.put(rdata[i]);
871 }
872 }
873
874 while (!circle.empty()) {
875 if (ib >= DS_RX_BUFFER_SIZE) ib = 0;
876 uint8_t next_byte = circle.get();
877
878 if (bInMsg) {
879 if (bGotESC) {
880 if (ESCAPE == next_byte) {
881 rx_buffer[ib++] = next_byte;
882 bGotESC = false;
883 }
884 }
885
886 if (bGotESC && (ENDOFTEXT == next_byte)) {
887 // Process packet
888 // Copy the message into a std::vector
889
890 auto buffer = std::make_shared<std::vector<unsigned char>>(
891 rx_buffer, rx_buffer + ib);
892 std::vector<unsigned char>* vec = buffer.get();
893
894 ib = 0;
895 // tak_ptr = tptr;
896 bInMsg = false;
897 bGotESC = false;
898
899 // printf("raw ");
900 // for (unsigned int i = 0; i < vec->size(); i++)
901 // printf("%02X ", vec->at(i));
902 // printf("\n");
903
904 // Message is finished
905 // Send the captured raw data vector pointer to the thread's "parent"
906 // thereby releasing the thread for further data capture
907 CommDriverN2KSerialEvent Nevent(wxEVT_COMMDRIVER_N2K_SERIAL, 0);
908 Nevent.SetPayload(buffer);
909 m_pParentDriver->AddPendingEvent(Nevent);
910
911 } else {
912 bGotESC = (next_byte == ESCAPE);
913
914 if (!bGotESC) {
915 rx_buffer[ib++] = next_byte;
916 }
917 }
918 }
919
920 else {
921 if (STARTOFTEXT == next_byte) {
922 bGotSOT = false;
923 if (bGotESC) {
924 bGotSOT = true;
925 }
926 } else {
927 bGotESC = (next_byte == ESCAPE);
928 if (bGotSOT) {
929 bGotSOT = false;
930 bInMsg = true;
931
932 rx_buffer[ib++] = next_byte;
933 }
934 }
935 }
936 } // if newdata > 0
937
938 // Check for any pending output message
939#if 1
940 bool b_qdata = !out_que.empty();
941
942 while (b_qdata) {
943 // Take a copy of message
944 std::vector<unsigned char> qmsg = out_que.front();
945 out_que.pop();
946
947 if (static_cast<size_t>(-1) == WriteComPortPhysical(qmsg) &&
948 10 < retries++) {
949 // We failed to write the port 10 times, let's close the port so that
950 // the reconnection logic kicks in and tries to fix our connection.
951 retries = 0;
952 CloseComPortPhysical();
953 }
954
955 b_qdata = !out_que.empty();
956 } // while b_qdata
957
958#endif
959 } // while ((not_done)
960
961 // thread_exit:
962 CloseComPortPhysical();
963 m_pParentDriver->SetSecThreadInActive(); // I am dead
964 m_pParentDriver->m_Thread_run_flag = -1;
965
966 std::lock_guard lock(m_stats_mutex);
967 m_driver_stats.available = false;
968
969 return 0;
970}
971
972#else
973void* CommDriverN2KSerialThread::Entry() {
974 bool not_done = true;
975 bool nl_found = false;
976 wxString msg;
977 circular_buffer<uint8_t> circle(DS_RX_BUFFER_SIZE);
978
979 // Request the com port from the comm manager
980 if (!OpenComPortPhysical(m_PortName, m_baud)) {
981 wxString msg(_T("NMEA input device open failed: "));
982 msg.Append(m_PortName);
983 ThreadMessage(msg);
984 // goto thread_exit; // This means we will not be trying to connect = The
985 // device must be connected when the thread is created. Does not seem to be
986 // needed/what we want as the reconnection logic is able to pick it up
987 // whenever it actually appears (Of course given it appears with the
988 // expected device name).
989 } else {
990 SetGatewayOperationMode();
991 }
992
993 m_pParentDriver->SetSecThreadActive(); // I am alive
994
995 // The main loop
996 static size_t retries = 0;
997
998 bool bInMsg = false;
999 bool bGotESC = false;
1000 bool bGotSOT = false;
1001
1002 while ((not_done) && (m_pParentDriver->m_Thread_run_flag > 0)) {
1003 if (TestDestroy()) not_done = false; // smooth exit
1004
1005 uint8_t next_byte = 0;
1006 int newdata = -1;
1007 uint8_t rdata[2000];
1008
1009 if (m_serial.isOpen()) {
1010 try {
1011 newdata = m_serial.read(rdata, 200);
1012 } catch (std::exception& e) {
1013 // std::cerr << "Serial read exception: " << e.what() << std::endl;
1014 if (10 < retries++) {
1015 // We timed out waiting for the next character 10 times, let's close
1016 // the port so that the reconnection logic kicks in and tries to fix
1017 // our connection.
1018 CloseComPortPhysical();
1019 retries = 0;
1020 }
1021 }
1022 } else {
1023 // Reconnection logic. Let's try to reopen the port while waiting longer
1024 // every time (until we simply keep trying every 2.5 seconds)
1025 // std::cerr << "Serial port seems closed." << std::endl;
1026 wxMilliSleep(250 * retries);
1027 CloseComPortPhysical();
1028 if (OpenComPortPhysical(m_PortName, m_baud)) {
1029 SetGatewayOperationMode();
1030 retries = 0;
1031 } else if (retries < 10)
1032 retries++;
1033 }
1034
1035 if (newdata > 0) {
1036 for (int i = 0; i < newdata; i++) {
1037 circle.put(rdata[i]);
1038 }
1039 }
1040
1041 while (!circle.empty()) {
1042 uint8_t next_byte = circle.get();
1043
1044 if (1) {
1045 if (bInMsg) {
1046 if (bGotESC) {
1047 if (ESCAPE == next_byte) {
1048 *put_ptr++ = next_byte;
1049 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1050 put_ptr = rx_buffer;
1051 bGotESC = false;
1052 } else if (ENDOFTEXT == next_byte) {
1053 // Process packet
1054 // Copy the message into a std::vector
1055
1056 auto buffer = std::make_shared<std::vector<unsigned char>>();
1057 std::vector<unsigned char>* vec = buffer.get();
1058
1059 unsigned char* tptr;
1060 tptr = tak_ptr;
1061
1062 while ((tptr != put_ptr)) {
1063 vec->push_back(*tptr++);
1064 if ((tptr - rx_buffer) > DS_RX_BUFFER_SIZE) tptr = rx_buffer;
1065 }
1066
1067 tak_ptr = tptr;
1068 bInMsg = false;
1069 bGotESC = false;
1070
1071 // Message is finished
1072 // Send the captured raw data vector pointer to the thread's
1073 // "parent"
1074 // thereby releasing the thread for further data capture
1075 CommDriverN2KSerialEvent Nevent(wxEVT_COMMDRIVER_N2K_SERIAL, 0);
1076 Nevent.SetPayload(buffer);
1077 m_pParentDriver->AddPendingEvent(Nevent);
1078 } else if (next_byte == STARTOFTEXT) {
1079 put_ptr = rx_buffer;
1080 bGotESC = false;
1081 } else {
1082 put_ptr = rx_buffer;
1083 bInMsg = false;
1084 bGotESC = false;
1085 }
1086
1087 } else {
1088 bGotESC = (next_byte == ESCAPE);
1089
1090 if (!bGotESC) {
1091 *put_ptr++ = next_byte;
1092 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1093 put_ptr = rx_buffer;
1094 }
1095 }
1096 }
1097
1098 else {
1099 if (STARTOFTEXT == next_byte) {
1100 bGotSOT = false;
1101 if (bGotESC) {
1102 bGotSOT = true;
1103 }
1104 } else {
1105 bGotESC = (next_byte == ESCAPE);
1106 if (bGotSOT) {
1107 bGotSOT = false;
1108 bInMsg = true;
1109
1110 *put_ptr++ = next_byte;
1111 if ((put_ptr - rx_buffer) > DS_RX_BUFFER_SIZE)
1112 put_ptr = rx_buffer;
1113 }
1114 }
1115 }
1116 } // if newdata > 0
1117 } // while
1118
1119 // Check for any pending output message
1120 bool b_qdata = !out_que.empty();
1121
1122 while (b_qdata) {
1123 // Take a copy of message
1124 std::vector<unsigned char> qmsg = out_que.front();
1125 out_que.pop();
1126
1127 if (static_cast<size_t>(-1) == WriteComPortPhysical(qmsg) &&
1128 10 < retries++) {
1129 // We failed to write the port 10 times, let's close the port so that
1130 // the reconnection logic kicks in and tries to fix our connection.
1131 retries = 0;
1132 CloseComPortPhysical();
1133 }
1134
1135 b_qdata = !out_que.empty();
1136 } // while b_qdata
1137 } // while ((not_done)
1138
1139 // thread_exit:
1140 CloseComPortPhysical();
1141 m_pParentDriver->SetSecThreadInActive(); // I am dead
1142 m_pParentDriver->m_Thread_run_flag = -1;
1143
1144 return 0;
1145}
1146
1147#endif // wxmsw Entry()
1148
1149#endif // Android
1150
1151//*****************************************************************************
1152// Actisense Format:
1153// <10><02><93><length (1)><priority (1)><PGN (3)><destination (1)><source
1154// (1)><time (4)><len (1)><data (len)><CRC (1)><10><03>
1155#define MaxActisenseMsgBuf 400
1156#define MsgTypeN2kTX 0x94
1157
1158void AddByteEscapedToBuf(unsigned char byteToAdd, uint8_t& idx,
1159 unsigned char* buf, int& byteSum);
1160
1161std::vector<unsigned char> BufferToActisenseFormat(tN2kMsg& msg) {
1162 unsigned long _PGN = msg.PGN;
1163 uint8_t msgIdx = 0;
1164 int byteSum = 0;
1165 uint8_t CheckSum;
1166 unsigned char ActisenseMsgBuf[MaxActisenseMsgBuf];
1167
1168 ActisenseMsgBuf[msgIdx++] = ESCAPE;
1169 ActisenseMsgBuf[msgIdx++] = STARTOFTEXT;
1170 AddByteEscapedToBuf(MsgTypeN2kTX, msgIdx, ActisenseMsgBuf, byteSum);
1171 AddByteEscapedToBuf(msg.DataLen + 6, msgIdx, ActisenseMsgBuf,
1172 byteSum); // length does not include escaped chars
1173
1174 AddByteEscapedToBuf(msg.Priority, msgIdx, ActisenseMsgBuf, byteSum);
1175 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1176 _PGN >>= 8;
1177 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1178 _PGN >>= 8;
1179 AddByteEscapedToBuf(_PGN & 0xff, msgIdx, ActisenseMsgBuf, byteSum);
1180 AddByteEscapedToBuf(msg.Destination, msgIdx, ActisenseMsgBuf, byteSum);
1181
1182#if 0
1183 // For TX through Actisense compatible gateway, we skip "source" byte and msg time fields
1184 // Source
1185 AddByteEscapedToBuf(msg.Source,msgIdx,ActisenseMsgBuf,byteSum);
1186 // Time
1187 int _MsgTime = 0;
1188 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1189 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1190 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum); _MsgTime>>=8;
1191 AddByteEscapedToBuf(_MsgTime & 0xff,msgIdx,ActisenseMsgBuf,byteSum);
1192
1193#endif
1194
1195 AddByteEscapedToBuf(msg.DataLen, msgIdx, ActisenseMsgBuf, byteSum);
1196
1197 for (int i = 0; i < msg.DataLen; i++)
1198 AddByteEscapedToBuf(msg.Data[i], msgIdx, ActisenseMsgBuf, byteSum);
1199 byteSum %= 256;
1200
1201 CheckSum = (uint8_t)((byteSum == 0) ? 0 : (256 - byteSum));
1202 ActisenseMsgBuf[msgIdx++] = CheckSum;
1203 if (CheckSum == ESCAPE) ActisenseMsgBuf[msgIdx++] = CheckSum;
1204
1205 ActisenseMsgBuf[msgIdx++] = ESCAPE;
1206 ActisenseMsgBuf[msgIdx++] = ENDOFTEXT;
1207
1208 std::vector<unsigned char> rv;
1209 for (unsigned int i = 0; i < msgIdx; i++) rv.push_back(ActisenseMsgBuf[i]);
1210
1211 return rv;
1212}
Interface implemented by transport layer and possible other parties like test code which should handl...
Definition comm_driver.h:48
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 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