OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_drv_n0183_serial.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Implement comm_drv_n0183_serial.h -- serial Nmea 0183 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 <mutex> // std::mutex
34#include <queue> // std::queue
35#include <thread>
36#include <vector>
37
38#include <wx/event.h>
39#include <wx/log.h>
40#include <wx/string.h>
41#include <wx/utils.h>
42
43#include "comm_drv_n0183_serial.h"
44#include "comm_navmsg_bus.h"
45#include "comm_drv_registry.h"
46
47#ifndef __ANDROID__
48#include "serial/serial.h"
49#endif
50
51#ifdef __ANDROID__
52#include "androidUTIL.h"
53#endif
54
55typedef enum DS_ENUM_BUFFER_STATE {
56 DS_RX_BUFFER_EMPTY,
57 DS_RX_BUFFER_FULL
58} _DS_ENUM_BUFFER_STATE;
59
60class CommDriverN0183Serial; // fwd
61
62#define MAX_OUT_QUEUE_MESSAGE_LENGTH 100
63
64template <typename T>
66public:
67 size_t size() {
68 std::lock_guard<std::mutex> lock(m_mutex);
69 return m_queque.size();
70 }
71
72 bool empty() {
73 std::lock_guard<std::mutex> lock(m_mutex);
74 return m_queque.empty();
75 }
76
77 const T& front() {
78 std::lock_guard<std::mutex> lock(m_mutex);
79 return m_queque.front();
80 }
81
82 void push(const T& value) {
83 std::lock_guard<std::mutex> lock(m_mutex);
84 m_queque.push(value);
85 }
86
87 void pop() {
88 std::lock_guard<std::mutex> lock(m_mutex);
89 m_queque.pop();
90 }
91
92private:
93 std::queue<T> m_queque;
94 mutable std::mutex m_mutex;
95};
96
97#define OUT_QUEUE_LENGTH 20
98#define MAX_OUT_QUEUE_MESSAGE_LENGTH 100
99
100class CommDriverN0183SerialEvent; // fwd
101
104public:
106 const wxString& PortName,
107 const wxString& strBaudRate);
108
110 void* Entry();
111 bool SetOutMsg(const wxString& msg);
112 void OnExit(void);
113
114private:
115#ifndef __ANDROID__
116 serial::Serial m_serial;
117#endif
118 void ThreadMessage(const wxString& msg);
119 bool OpenComPortPhysical(const wxString& com_name, int baud_rate);
120 void CloseComPortPhysical();
121 size_t WriteComPortPhysical(char* msg);
122 size_t WriteComPortPhysical(const wxString& string);
123
124 CommDriverN0183Serial* m_pParentDriver;
125 wxString m_PortName;
126 wxString m_FullPortName;
127
128 int m_baud;
129
131
132};
133
134template <class T>
136public:
137 explicit circular_buffer(size_t size)
138 : buf_(std::unique_ptr<T[]>(new T[size])), max_size_(size) {}
139
140 void reset();
141 size_t capacity() const;
142 size_t size() const;
143
144 bool empty() const {
145 // if head and tail are equal, we are empty
146 return (!full_ && (head_ == tail_));
147 }
148
149 bool full() const {
150 // If tail is ahead the head by 1, we are full
151 return full_;
152 }
153
154 void put(T item) {
155 std::lock_guard<std::mutex> lock(mutex_);
156 buf_[head_] = item;
157 if (full_) tail_ = (tail_ + 1) % max_size_;
158
159 head_ = (head_ + 1) % max_size_;
160
161 full_ = head_ == tail_;
162 }
163
164 T get() {
165 std::lock_guard<std::mutex> lock(mutex_);
166
167 if (empty()) return T();
168
169 // Read data and advance the tail (we now have a free space)
170 auto val = buf_[tail_];
171 full_ = false;
172 tail_ = (tail_ + 1) % max_size_;
173
174 return val;
175 }
176
177private:
178 std::mutex mutex_;
179 std::unique_ptr<T[]> buf_;
180 size_t head_ = 0;
181 size_t tail_ = 0;
182 const size_t max_size_;
183 bool full_ = 0;
184};
185
187wxDECLARE_EVENT(wxEVT_COMMDRIVER_N0183_SERIAL, CommDriverN0183SerialEvent);
188
190class CommDriverN0183SerialEvent : public wxEvent {
191public:
193 wxEventType commandType = wxEVT_COMMDRIVER_N0183_SERIAL, int id = 0)
194 : wxEvent(id, commandType){};
196
197 // accessors
198 void SetPayload(std::shared_ptr<std::vector<unsigned char>> data) {
199 m_payload = data;
200 }
201 std::shared_ptr<std::vector<unsigned char>> GetPayload() { return m_payload; }
202
203 // required for sending with wxPostEvent()
204 wxEvent* Clone() const {
207 newevent->m_payload = this->m_payload;
208 return newevent;
209 };
210
211private:
212 std::shared_ptr<std::vector<unsigned char>> m_payload;
213};
214
215//========================================================================
216/* commdriverN0183Serial implementation
217 * */
218wxDEFINE_EVENT(wxEVT_COMMDRIVER_N0183_SERIAL, CommDriverN0183SerialEvent);
219
220CommDriverN0183Serial::CommDriverN0183Serial(const ConnectionParams* params,
221 DriverListener& listener)
222 : CommDriverN0183(NavAddr::Bus::N0183,
223 ((ConnectionParams*)params)->GetStrippedDSPort()),
224 m_Thread_run_flag(-1),
225 m_bok(false),
226 m_portstring(params->GetDSPort()),
227 m_pSecondary_Thread(NULL),
228 m_params(*params),
229 m_listener(listener) {
230 m_BaudRate = wxString::Format("%i", params->Baudrate), SetSecThreadInActive();
231
232 // Prepare the wxEventHandler to accept events from the actual hardware thread
233 Bind(wxEVT_COMMDRIVER_N0183_SERIAL, &CommDriverN0183Serial::handle_N0183_MSG,
234 this);
235
236 Open();
237}
238
239CommDriverN0183Serial::~CommDriverN0183Serial() { Close(); }
240
241bool CommDriverN0183Serial::Open() {
242 wxString comx;
243 comx = m_params.GetDSPort().AfterFirst(':'); // strip "Serial:"
244
245 // strip off any description provided by Windows
246 comx = comx.BeforeFirst(' ');
247
248 // Kick off the RX thread
249 SetSecondaryThread(new CommDriverN0183SerialThread(this, comx, m_BaudRate));
250 SetThreadRunFlag(1);
251 std::thread t(&CommDriverN0183SerialThread::Entry, GetSecondaryThread());
252 t.detach();
253 return true;
254}
255
256void CommDriverN0183Serial::Close() {
257 wxLogMessage(
258 wxString::Format(_T("Closing NMEA Driver %s"), m_portstring.c_str()));
259
260 // FIXME (dave)
261 // If port is opened, and then closed immediately,
262 // the secondary thread may not stop quickly enough.
263 // It can then crash trying to send an event to its "parent".
264
265 Unbind(wxEVT_COMMDRIVER_N0183_SERIAL, &CommDriverN0183Serial::handle_N0183_MSG,
266 this);
267
268 // Kill off the Secondary RX Thread if alive
269 if (m_pSecondary_Thread) {
270
271 if (m_bsec_thread_active) // Try to be sure thread object is still alive
272 {
273 wxLogMessage(_T("Stopping Secondary Thread"));
274
275 m_Thread_run_flag = 0;
276
277 int tsec = 10;
278 while ((m_Thread_run_flag >= 0) && (tsec--)) wxSleep(1);
279
280 wxString msg;
281 if (m_Thread_run_flag < 0)
282 msg.Printf(_T("Stopped in %d sec."), 10 - tsec);
283 else
284 msg.Printf(_T("Not Stopped after 10 sec."));
285 wxLogMessage(msg);
286 }
287
288 delete m_pSecondary_Thread;
289 m_pSecondary_Thread = NULL;
290 m_bsec_thread_active = false;
291 }
292
293 // FIXME Kill off the Garmin handler, if alive
294 // if (m_GarminHandler) {
295 // m_GarminHandler->Close();
296 // delete m_GarminHandler;
297 // }
298}
299
301 CommDriverRegistry::GetInstance().Activate(shared_from_this());
302 // TODO: Read input data.
303}
304
305bool CommDriverN0183Serial::SendMessage(std::shared_ptr<const NavMsg> msg,
306 std::shared_ptr<const NavAddr> addr) {
307
308 auto msg_0183 = std::dynamic_pointer_cast<const Nmea0183Msg>(msg);
309 wxString sentence(msg_0183->payload.c_str());
310
311#ifdef __ANDROID__
312 wxString payload = sentence;
313 if( !sentence.EndsWith(_T("\r\n")) )
314 payload += _T("\r\n");
315
316
317 wxString port = m_params.GetStrippedDSPort(); //GetPort().AfterFirst(':');
318 androidWriteSerial( port, payload );
319 return true;
320#endif
321 if( GetSecondaryThread() ) {
322 if( IsSecThreadActive() )
323 {
324 int retry = 10;
325 while( retry ) {
326 if( GetSecondaryThread()->SetOutMsg( sentence ))
327 return true;
328 else
329 retry--;
330 }
331 return false; // could not send after several tries....
332 }
333 else
334 return false;
335 }
336 return true;
337}
338
339
340
341void CommDriverN0183Serial::handle_N0183_MSG(
343 auto p = event.GetPayload();
344 std::vector<unsigned char>* payload = p.get();
345
346 // Extract the NMEA0183 sentence
347 std::string full_sentence = std::string(payload->begin(), payload->end());
348
349 if ((full_sentence[0] == '$') || (full_sentence[0] == '!')) { // Sanity check
350 std::string identifier;
351 // We notify based on full message, including the Talker ID
352 identifier = full_sentence.substr(1, 5);
353
354 // notify message listener and also "ALL" N0183 messages, to support plugin
355 // API using original talker id
356 auto msg = std::make_shared<const Nmea0183Msg>(identifier, full_sentence,
357 GetAddress());
358 auto msg_all = std::make_shared<const Nmea0183Msg>(*msg, "ALL");
359
360 if (m_params.SentencePassesFilter(full_sentence, FILTER_INPUT))
361 m_listener.Notify(std::move(msg));
362
363 m_listener.Notify(std::move(msg_all));
364 }
365}
366
367#ifndef __ANDROID__
368
369#define DS_RX_BUFFER_SIZE 4096
370
371CommDriverN0183SerialThread::CommDriverN0183SerialThread(
372 CommDriverN0183Serial* Launcher, const wxString& PortName,
373 const wxString& strBaudRate) {
374 m_pParentDriver = Launcher; // This thread's immediate "parent"
375
376 m_PortName = PortName;
377 m_FullPortName = _T("Serial:") + PortName;
378
379 m_baud = 4800; // default
380 long lbaud;
381 if (strBaudRate.ToLong(&lbaud)) m_baud = (int)lbaud;
382}
383
384CommDriverN0183SerialThread::~CommDriverN0183SerialThread(void) {}
385
386void CommDriverN0183SerialThread::OnExit(void) {}
387
388bool CommDriverN0183SerialThread::OpenComPortPhysical(const wxString& com_name,
389 int baud_rate) {
390 try {
391 m_serial.setPort(com_name.ToStdString());
392 m_serial.setBaudrate(baud_rate);
393 m_serial.open();
394 m_serial.setTimeout(250, 250, 0, 250, 0);
395 } catch (std::exception& e) {
396 // std::cerr << "Unhandled Exception while opening serial port: " <<
397 // e.what() << std::endl;
398 }
399 return m_serial.isOpen();
400}
401
402void CommDriverN0183SerialThread::CloseComPortPhysical() {
403 try {
404 m_serial.close();
405 } catch (std::exception& e) {
406 // std::cerr << "Unhandled Exception while closing serial port: " <<
407 // e.what() << std::endl;
408 }
409}
410
411bool CommDriverN0183SerialThread::SetOutMsg(const wxString &msg)
412{
413 if(out_que.size() < OUT_QUEUE_LENGTH){
414 wxCharBuffer buf = msg.ToUTF8();
415 if(buf.data()){
416 char *qmsg = (char *)malloc(strlen(buf.data()) +1);
417 strcpy(qmsg, buf.data());
418 out_que.push(qmsg);
419 return true;
420 }
421 }
422
423 return false;
424}
425
426void CommDriverN0183SerialThread::ThreadMessage(const wxString& msg) {
427 // Signal the main program thread
428 // OCPN_ThreadMessageEvent event(wxEVT_OCPN_THREADMSG, 0);
429 // event.SetSString(std::string(msg.mb_str()));
430 // if (gFrame) gFrame->GetEventHandler()->AddPendingEvent(event);
431}
432
433size_t CommDriverN0183SerialThread::WriteComPortPhysical(char* msg) {
434 if (m_serial.isOpen()) {
435 ssize_t status;
436 try {
437 status = m_serial.write((uint8_t*)msg, strlen(msg));
438 } catch (std::exception& e) {
439 // std::cerr << "Unhandled Exception while writing to serial port: "
440 // << e.what() << std::endl;
441 return -1;
442 }
443 return status;
444 } else {
445 return -1;
446 }
447}
448
449void* CommDriverN0183SerialThread::Entry() {
450 bool not_done = true;
451 m_pParentDriver->SetSecThreadActive(); // I am alive
452 int nl_found = 0;
453 wxString msg;
454 circular_buffer<uint8_t> circle(DS_RX_BUFFER_SIZE);
455
456 // Request the com port from the comm manager
457 if (!OpenComPortPhysical(m_PortName, m_baud)) {
458 wxString msg(_T("NMEA input device open failed: "));
459 msg.Append(m_PortName);
460 ThreadMessage(msg);
461 // goto thread_exit; // This means we will not be trying to connect = The
462 // device must be connected when the thread is created. Does not seem to be
463 // needed/what we want as the reconnection logic is able to pick it up
464 // whenever it actually appears (Of course given it appears with the
465 // expected device name).
466 }
467
468
469 // The main loop
470 static size_t retries = 0;
471
472 while ((not_done) && (m_pParentDriver->m_Thread_run_flag > 0)) {
473 if ( m_pParentDriver->m_Thread_run_flag == 0) goto thread_exit;
474
475 uint8_t next_byte = 0;
476 unsigned int newdata = 0;
477 uint8_t rdata[2000];
478
479 if (m_serial.isOpen()) {
480 try {
481 newdata = m_serial.read(rdata, 200);
482 } catch (std::exception& e) {
483 // std::cerr << "Serial read exception: " << e.what() <<
484 // std::endl;
485 if (10 < retries++) {
486 // We timed out waiting for the next character 10 times, let's close
487 // the port so that the reconnection logic kicks in and tries to fix
488 // our connection.
489 CloseComPortPhysical();
490 retries = 0;
491 }
492 }
493 } else {
494 // Reconnection logic. Let's try to reopen the port while waiting longer
495 // every time (until we simply keep trying every 2.5 seconds)
496 // std::cerr << "Serial port seems closed." << std::endl;
497 wxMilliSleep(250 * retries);
498 CloseComPortPhysical();
499 if (OpenComPortPhysical(m_PortName, m_baud))
500 retries = 0;
501 else if (retries < 10)
502 retries++;
503 }
504
505 if (newdata > 0) {
506 nl_found = 0;
507 for (unsigned int i = 0; i < newdata; i++) {
508 circle.put(rdata[i]);
509 if (0x0a == rdata[i]) nl_found++;
510 }
511
512 // Found a NL char, thus end of message?
513 if (nl_found) {
514 bool done = false;
515 while (!done) {
516 if (circle.empty()) {
517 done = true;
518 break;
519 }
520
521 // Copy the message into a vector for tranmittal upstream
522 auto buffer = std::make_shared<std::vector<unsigned char>>();
523 std::vector<unsigned char>* vec = buffer.get();
524
525 uint8_t take_byte = circle.get();
526 while ((take_byte != 0x0a) && !circle.empty()) {
527 vec->push_back(take_byte);
528 take_byte = circle.get();
529 }
530
531 if (take_byte == 0x0a) {
532 vec->push_back(take_byte);
533
534 if ( m_pParentDriver->m_Thread_run_flag == 0) goto thread_exit;
535
536 // Message is ready to parse and send out
537 // Messages may be coming in as <blah blah><lf><cr>.
538 // One example device is KVH1000 heading sensor.
539 // If that happens, the first character of a new captured message
540 // will the <cr>, and we need to discard it. This is out of spec,
541 // but we should handle it anyway
542 if (vec->at(0) == '\r') vec->erase(vec->begin());
543
544 CommDriverN0183SerialEvent Nevent(wxEVT_COMMDRIVER_N0183_SERIAL, 0);
545 Nevent.SetPayload(buffer);
546 m_pParentDriver->AddPendingEvent(Nevent);
547
548 } else {
549 done = true;
550 }
551 }
552 } // if nl
553 } // if newdata > 0
554
555 // Check for any pending output message
556
557 bool b_qdata = !out_que.empty();
558
559 while (b_qdata) {
560 // Take a copy of message
561 char* qmsg = out_que.front();
562 out_que.pop();
563 // m_outCritical.Leave();
564 char msg[MAX_OUT_QUEUE_MESSAGE_LENGTH];
565 strncpy(msg, qmsg, MAX_OUT_QUEUE_MESSAGE_LENGTH - 1);
566 free(qmsg);
567
568 if (static_cast<size_t>(-1) == WriteComPortPhysical(msg) &&
569 10 < retries++) {
570 // We failed to write the port 10 times, let's close the port so that
571 // the reconnection logic kicks in and tries to fix our connection.
572 retries = 0;
573 CloseComPortPhysical();
574 }
575
576 b_qdata = !out_que.empty();
577 } // while b_qdata
578 }
579
580thread_exit:
581 CloseComPortPhysical();
582 m_pParentDriver->SetSecThreadInActive(); // I am dead
583 m_pParentDriver->m_Thread_run_flag = -1;
584
585 return 0;
586}
587
588#endif // Android
Internal event worker thread -> driver main code.
Driver for NMEA0183 over serial connections.
void Activate() override
Register driver and possibly do other post-ctor steps.
Abstract NMEA0183 drivers common parts.
void Activate(DriverPtr driver)
Add driver to list of active drivers.
Interface implemented by transport layer and possible other parties like test code which should handl...
Definition: comm_driver.h:47
virtual void Notify(std::shared_ptr< const NavMsg > message)=0
Handle a received message.
Where messages are sent to or received from.
Definition: comm_navmsg.h:136