1 /***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: NMEA Data Stream Object
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2010 by David S. Register *
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 ***************************************************************************
27 * Parts of this file were adapted from source code found in *
28 * John F. Waers (jfwaers@csn.net) public domain program MacGPS45 *
29 ***************************************************************************
30 *
31 */
32
33 #ifdef __MINGW32__
34 #undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
35 #include <windows.h>
36 #include <ws2tcpip.h>
37 #endif
38
39 #include "wx/wxprec.h"
40
41 #ifndef WX_PRECOMP
42 #include "wx/wx.h"
43 #endif //precompiled headers
44
45 #include <wx/datetime.h>
46
47 #include <stdlib.h>
48 #include <math.h>
49 #include <time.h>
50
51 #ifndef __WXMSW__
52 #include <arpa/inet.h>
53 #include <netinet/tcp.h>
54 #endif
55
56 #include "config.h"
57
58 #include "dychart.h"
59
60 #include "datastream.h"
61 #include "NetworkDataStream.h"
62 #include "SerialDataStream.h"
63 #include "SignalKDataStream.h"
64
65 #include "OCPN_DataStreamEvent.h"
66 #include "OCP_DataStreamInput_Thread.h"
67 #include "nmea0183.h"
68
69 #ifdef USE_GARMINHOST
70 #include "garmin_wrapper.h"
71 #endif
72
73 #include "GarminProtocolHandler.h"
74
75 #ifdef __OCPN__ANDROID__
76 #include "androidUTIL.h"
77 #endif
78
79 #include <vector>
80 #include <wx/socket.h>
81 #include <wx/log.h>
82 #include <wx/memory.h>
83 #include <wx/chartype.h>
84 #include <wx/wx.h>
85 #include <wx/sckaddr.h>
86
87 #if !defined(NAN)
88 static const long long lNaN = 0xfff8000000000000;
89 #define NAN (*(double*)&lNaN)
90 #endif
91
92 const wxEventType wxEVT_OCPN_DATASTREAM = wxNewEventType();
93
94 extern bool g_benableUDPNullHeader;
95
96 #define N_DOG_TIMEOUT 5
97
98 #ifdef __WXMSW__
99 // {2C9C45C2-8E7D-4C08-A12D-816BBAE722C0}
100 DEFINE_GUID(GARMIN_GUID1, 0x2c9c45c2L, 0x8e7d, 0x4c08, 0xa1, 0x2d, 0x81, 0x6b, 0xba, 0xe7, 0x22, 0xc0);
101 #endif
102
103 #ifdef __OCPN__ANDROID__
104 #include <netdb.h>
gethostbyaddr_r(const char *,int,int,struct hostent *,char *,size_t,struct hostent **,int *)105 int gethostbyaddr_r(const char *, int, int, struct hostent *, char *, size_t, struct hostent **, int *)
106 {
107 wxLogMessage(_T("Called stub gethostbyaddr_r()"));
108 return 0;
109 }
110 #endif
111
112
CheckSumCheck(const std::string & sentence)113 bool CheckSumCheck(const std::string& sentence)
114 {
115 size_t check_start = sentence.find('*');
116 if(check_start == wxString::npos || check_start > sentence.size() - 3)
117 return false; // * not found, or it didn't have 2 characters following it.
118
119 std::string check_str = sentence.substr(check_start+1,2);
120 unsigned long checksum = strtol(check_str.c_str(), 0, 16);
121 if(checksum == 0L && check_str != "00")
122 return false;
123
124 unsigned char calculated_checksum = 0;
125 for(std::string::const_iterator i = sentence.begin()+1; i != sentence.end() && *i != '*'; ++i)
126 calculated_checksum ^= static_cast<unsigned char> (*i);
127
128 return calculated_checksum == checksum;
129
130 }
131
132
makeDataStream(wxEvtHandler * input_consumer,const ConnectionParams * params)133 DataStream* makeDataStream(wxEvtHandler *input_consumer, const ConnectionParams* params)
134 {
135 wxLogMessage( wxString::Format(_T("makeDataStream %s"),
136 params->GetDSPort().c_str()) );
137 switch (params->Type) {
138 case SERIAL:
139 return new SerialDataStream(input_consumer, params);
140 case NETWORK:
141 switch(params->NetProtocol) {
142 case SIGNALK:
143 return new SignalKDataStream(input_consumer, params);
144 default:
145 return new NetworkDataStream(input_consumer, params);
146 }
147 case INTERNAL_GPS:
148 return new InternalGPSDataStream(input_consumer, params);
149 case INTERNAL_BT:
150 return new InternalBTDataStream(input_consumer, params);
151 default:
152 return new NullDataStream(input_consumer, params);
153 }
154 }
155
156
157 //------------------------------------------------------------------------------
158 // DataStream Implementation
159 //------------------------------------------------------------------------------
160
161
162 // constructor
DataStream(wxEvtHandler * input_consumer,const ConnectionType conn_type,const wxString & Port,const wxString & BaudRate,dsPortType io_select,int priority,bool bGarmin,int EOS_type,int handshake_type)163 DataStream::DataStream(wxEvtHandler *input_consumer,
164 const ConnectionType conn_type,
165 const wxString& Port,
166 const wxString& BaudRate,
167 dsPortType io_select,
168 int priority,
169 bool bGarmin,
170 int EOS_type,
171 int handshake_type)
172 :
173 m_Thread_run_flag(-1),
174 m_bok(false),
175 m_consumer(input_consumer),
176 m_portstring(Port),
177 m_BaudRate(BaudRate),
178 m_io_select(io_select),
179 m_priority(priority),
180 m_handshake(handshake_type),
181 m_pSecondary_Thread(NULL),
182 m_connection_type(conn_type),
183 m_bGarmin_GRMN_mode(bGarmin),
184 m_GarminHandler(NULL),
185 m_params()
186 {
187 wxLogMessage( _T("Classic CTOR"));
188
189 SetSecThreadInActive();
190
191 Open();
192 }
193
DataStream(wxEvtHandler * input_consumer,const ConnectionParams * params)194 DataStream::DataStream(wxEvtHandler *input_consumer,
195 const ConnectionParams* params)
196 :
197 m_Thread_run_flag(-1),
198 m_bok(false),
199 m_consumer(input_consumer),
200 m_portstring(params->GetDSPort()),
201 m_io_select(params->IOSelect),
202 m_priority(params->Priority),
203 m_handshake(DS_HANDSHAKE_NONE),
204 m_pSecondary_Thread(NULL),
205 m_connection_type(params->Type),
206 m_bGarmin_GRMN_mode(params->Garmin),
207 m_GarminHandler(NULL),
208 m_params(*params)
209 {
210 m_BaudRate = wxString::Format(wxT("%i"), params->Baudrate),
211 SetSecThreadInActive();
212
213 wxLogMessage( _T("ConnectionParams CTOR"));
214
215 // Open();
216
217 SetInputFilter(params->InputSentenceList);
218 SetInputFilterType(params->InputSentenceListType);
219 SetOutputFilter(params->OutputSentenceList);
220 SetOutputFilterType(params->OutputSentenceListType);
221 SetChecksumCheck(params->ChecksumCheck);
222 }
223
Open(void)224 void DataStream::Open(void)
225 {
226 // Open a port
227 wxLogMessage( wxString::Format(_T("Opening NMEA Datastream %s"), m_portstring.c_str()) );
228 SetOk(false);
229 m_connect_time = wxDateTime::Now();
230 }
231
Open(void)232 void InternalBTDataStream::Open(void)
233 {
234 #ifdef __OCPN__ANDROID__
235 SetOk(androidStartBT(GetConsumer(), GetPortString() ));
236 #endif
237 }
238
SendSentence(const wxString & sentence)239 bool InternalBTDataStream::SendSentence( const wxString &sentence )
240 {
241 #ifdef __OCPN__ANDROID__
242 wxString payload = sentence;
243 if( !sentence.EndsWith(_T("\r\n")) )
244 payload += _T("\r\n");
245
246 if(IsOk()){
247 androidSendBTMessage( payload );
248 return IsOk();
249 }
250 else
251 #endif
252 return false;
253 }
254
255
Open(void)256 void InternalGPSDataStream::Open(void)
257 {
258 #ifdef __OCPN__ANDROID__
259 androidStartNMEA(GetConsumer());
260 SetOk(true);
261 #endif
262
263 }
264
265
266
~DataStream()267 DataStream::~DataStream()
268 {
269 Close();
270 }
271
Close()272 void DataStream::Close()
273 {
274 wxLogMessage( wxString::Format(_T("Closing NMEA Datastream %s"), m_portstring.c_str()) );
275
276 // Kill off the Secondary RX Thread if alive
277 if(m_pSecondary_Thread)
278 {
279 if(m_bsec_thread_active) // Try to be sure thread object is still alive
280 {
281 wxLogMessage(_T("Stopping Secondary Thread"));
282
283 m_Thread_run_flag = 0;
284 int tsec = 10;
285 while((m_Thread_run_flag >= 0) && (tsec--))
286 wxSleep(1);
287
288 wxString msg;
289 if(m_Thread_run_flag < 0)
290 msg.Printf(_T("Stopped in %d sec."), 10 - tsec);
291 else
292 msg.Printf(_T("Not Stopped after 10 sec."));
293 wxLogMessage(msg);
294 }
295
296 m_pSecondary_Thread = NULL;
297 m_bsec_thread_active = false;
298 }
299
300 // Kill off the Garmin handler, if alive
301 if(m_GarminHandler) {
302 m_GarminHandler->Close();
303 delete m_GarminHandler;
304 }
305
306
307 if(m_connection_type == INTERNAL_GPS){
308 #ifdef __OCPN__ANDROID__
309 androidStopNMEA();
310 #endif
311 }
312 else if(m_connection_type == INTERNAL_BT){
313 #ifdef __OCPN__ANDROID__
314 androidStopBT();
315 #endif
316 }
317
318 wxString port = m_portstring.AfterFirst(':'); // strip "Serial:"
319
320 #ifdef __OCPN__ANDROID__
321 if(port.Contains(_T("AUSBSerial"))){
322 androidStopUSBSerial(port);
323 SetOk(false);
324 }
325 #endif
326
327
328
329 }
330
331 #if 0 // moved to NetworkDataStream
332 <<<<<<< HEAD
333 void DataStream::OnSocketReadWatchdogTimer(wxTimerEvent& event)
334 {
335 m_dog_value--;
336 if( m_dog_value <= 0 ) { // No receive in n seconds, assume connection lost
337 wxLogMessage( wxString::Format(_T(" TCP Datastream watchdog timeout: %s"), m_portstring.c_str()) );
338
339 if(m_net_protocol == TCP ) {
340 wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(m_sock);
341 if(tcp_socket) {
342 tcp_socket->Close();
343 }
344 m_socket_timer.Start(5000, wxTIMER_ONE_SHOT); // schedule a reconnect
345 m_socketread_watchdog_timer.Stop();
346 }
347 }
348 }
349
350 void DataStream::OnTimerSocket(wxTimerEvent& event)
351 {
352 // Attempt a connection
353 wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(m_sock);
354 if(tcp_socket) {
355 if(tcp_socket->IsDisconnected() ) {
356 m_brx_connect_event = false;
357 tcp_socket->Connect(m_addr, FALSE);
358 m_socket_timer.Start(5000, wxTIMER_ONE_SHOT); // schedule another attempt
359 }
360 }
361 }
362
363
364 void DataStream::OnSocketEvent(wxSocketEvent& event)
365 {
366 //#define RD_BUF_SIZE 200
367 #define RD_BUF_SIZE 4096 // Allows handling of high volume data streams, such as a National AIS stream with 100s of msgs a second.
368
369 switch(event.GetSocketEvent())
370 {
371 case wxSOCKET_INPUT : // from gpsd Daemon
372 {
373 // TODO determine if the follwing SetFlags needs to be done at every socket event or only once when socket is created, it it needs to be done at all!
374 //m_sock->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK); // was (wxSOCKET_NOWAIT);
375
376 // We use wxSOCKET_BLOCK to avoid Yield() reentrancy problems
377 // if a long ProgressDialog is active, as in S57 SENC creation.
378
379
380 // Disable input event notifications to preclude re-entrancy on non-blocking socket
381 // m_sock->SetNotify(wxSOCKET_LOST_FLAG);
382
383 std::vector<char> data(RD_BUF_SIZE+1);
384 event.GetSocket()->Read(&data.front(),RD_BUF_SIZE);
385 if(!event.GetSocket()->Error())
386 {
387 size_t count = event.GetSocket()->LastCount();
388 if(count)
389 {
390 if(!g_benableUDPNullHeader){
391 data[count]=0;
392 m_sock_buffer += (&data.front());
393 }
394 else{
395 // XXX FIXME: is it reliable?
396 // copy all received bytes
397 // there's 0 in furuno UDP tags before NMEA sentences.
398 m_sock_buffer.append(&data.front(), count);
399 }
400 }
401 }
402
403 bool done = false;
404
405 while(!done){
406 int nmea_tail = 2;
407 size_t nmea_end = m_sock_buffer.find_first_of("*\r\n"); // detect the potential end of a NMEA string by finding the checkum marker or EOL
408
409 if (nmea_end == wxString::npos) // No termination characters: continue reading
410 break;
411
412 if (m_sock_buffer[nmea_end] != '*')
413 nmea_tail = -1;
414
415 if(nmea_end < m_sock_buffer.size() - nmea_tail){
416 nmea_end += nmea_tail + 1; // move to the char after the 2 checksum digits, if present
417 if ( nmea_end == 0 ) //The first character in the buffer is a terminator, skip it to avoid infinite loop
418 nmea_end = 1;
419 std::string nmea_line = m_sock_buffer.substr(0,nmea_end);
420
421 // If, due to some logic error, the {nmea_end} parameter is larger than the length of the
422 // socket buffer, then std::string::substr() will throw an exception.
423 // We don't want that, so test for it.
424 // If found, the simple solution is to clear the socket buffer, and carry on
425 // This has been seen on high volume TCP feeds, Windows only.
426 // Hard to catch.....
427 if(nmea_end > m_sock_buffer.size())
428 m_sock_buffer.clear();
429 else
430 m_sock_buffer = m_sock_buffer.substr(nmea_end);
431
432 size_t nmea_start = nmea_line.find_last_of("$!"); // detect the potential start of a NMEA string, skipping preceding chars that may look like the start of a string.
433 if(nmea_start != wxString::npos){
434 nmea_line = nmea_line.substr(nmea_start);
435 nmea_line += "\r\n"; // Add cr/lf, possibly superfluous
436 if( m_consumer && ChecksumOK(nmea_line)){
437 OCPN_DataStreamEvent Nevent(wxEVT_OCPN_DATASTREAM, 0);
438 if(nmea_line.size()) {
439 Nevent.SetNMEAString( nmea_line );
440 Nevent.SetStream( this );
441
442 m_consumer->AddPendingEvent(Nevent);
443 }
444 }
445 }
446 }
447 else
448 done = true;
449 }
450
451 // Prevent non-nmea junk from consuming to much memory by limiting carry-over buffer size.
452 if(m_sock_buffer.size()>RD_BUF_SIZE)
453 m_sock_buffer = m_sock_buffer.substr(m_sock_buffer.size()-RD_BUF_SIZE);
454
455 m_dog_value = N_DOG_TIMEOUT; // feed the dog
456 break;
457 }
458
459 case wxSOCKET_LOST:
460 {
461 // wxSocketError e = m_sock->LastError(); // this produces wxSOCKET_WOULDBLOCK.
462 if(m_net_protocol == TCP || m_net_protocol == GPSD) {
463 if (m_brx_connect_event)
464 wxLogMessage(wxString::Format(_T("Datastream connection lost: %s"), m_portstring.c_str()));
465 if (m_socket_server) {
466 m_sock->Destroy();
467 m_sock=0;
468 break;
469 }
470 wxDateTime now = wxDateTime::Now();
471 wxTimeSpan since_connect = now - m_connect_time;
472
473 int retry_time = 5000; // default
474
475 // If the socket has never connected, and it is a short interval since the connect request
476 // then stretch the time a bit. This happens on Windows if there is no dafault IP on any interface
477
478 if(!m_brx_connect_event && (since_connect.GetSeconds() < 5) )
479 retry_time = 10000; // 10 secs
480
481 m_socketread_watchdog_timer.Stop();
482 m_socket_timer.Start(retry_time, wxTIMER_ONE_SHOT); // Schedule a re-connect attempt
483
484 break;
485 }
486 }
487 // FALL THROUGH
488
489 case wxSOCKET_CONNECTION :
490 {
491 if(m_net_protocol == GPSD) {
492 // Sign up for watcher mode, Cooked NMEA
493 // Note that SIRF devices will be converted by gpsd into pseudo-NMEA
494 char cmd[] = "?WATCH={\"class\":\"WATCH\", \"nmea\":true}";
495 m_sock->Write(cmd, strlen(cmd));
496 }
497 else if(m_net_protocol == TCP) {
498 wxLogMessage( wxString::Format(_T("TCP Datastream connection established: %s"), m_portstring.c_str()) );
499 m_dog_value = N_DOG_TIMEOUT; // feed the dog
500 if (m_io_select != DS_TYPE_OUTPUT)
501 m_socketread_watchdog_timer.Start(1000);
502 if (m_io_select != DS_TYPE_INPUT && m_sock->IsOk())
503 (void) SetOutputSocketOptions(m_sock);
504 m_socket_timer.Stop();
505 m_brx_connect_event = true;
506 }
507
508 m_connect_time = wxDateTime::Now();
509 break;
510 }
511
512 default :
513 break;
514 }
515 }
516
517
518
519 void DataStream::OnServerSocketEvent(wxSocketEvent& event)
520 {
521
522 switch(event.GetSocketEvent())
523 {
524 case wxSOCKET_CONNECTION :
525 {
526 m_sock= m_socket_server->Accept(false);
527
528 if( m_sock) {
529 m_sock->SetTimeout(2);
530 m_sock->SetFlags( wxSOCKET_BLOCK );
531 m_sock->SetEventHandler(*this, DS_SOCKET_ID);
532 int notify_flags=(wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG );
533 if (m_io_select != DS_TYPE_INPUT) {
534 notify_flags |= wxSOCKET_OUTPUT_FLAG;
535 (void) SetOutputSocketOptions(m_sock);
536 }
537 if (m_io_select != DS_TYPE_OUTPUT)
538 notify_flags |= wxSOCKET_INPUT_FLAG;
539 m_sock->SetNotify(notify_flags);
540 m_sock->Notify(true);
541 }
542
543 break;
544 }
545
546 default :
547 break;
548 }
549 }
550
551 =======
552 >>>>>>> 1f7f17e0a7cd430bc7d73457a91958a3d01eecfa
553 #endif
554
SentencePassesFilter(const wxString & sentence,FilterDirection direction)555 bool DataStream::SentencePassesFilter(const wxString& sentence, FilterDirection direction)
556 {
557 wxArrayString filter;
558 bool listype = false;
559
560 if (direction == FILTER_INPUT)
561 {
562 filter = m_input_filter;
563 if (m_input_filter_type == WHITELIST)
564 listype = true;
565 }
566 else
567 {
568 filter = m_output_filter;
569 if (m_output_filter_type == WHITELIST)
570 listype = true;
571 }
572 if (filter.Count() == 0) //Empty list means everything passes
573 return true;
574
575 wxString fs;
576 for (size_t i = 0; i < filter.Count(); i++)
577 {
578 fs = filter[i];
579 switch (fs.Length())
580 {
581 case 2:
582 if (fs == sentence.Mid(1, 2))
583 return listype;
584 break;
585 case 3:
586 if (fs == sentence.Mid(3, 3))
587 return listype;
588 break;
589 case 5:
590 if (fs == sentence.Mid(1, 5))
591 return listype;
592 break;
593 }
594 }
595 return !listype;
596 }
597
ChecksumOK(const std::string & sentence)598 bool DataStream::ChecksumOK( const std::string &sentence )
599 {
600 if (!m_bchecksumCheck)
601 return true;
602
603 return CheckSumCheck(sentence);
604
605 }
606
607
SendSentence(const wxString & sentence)608 bool DataStream::SendSentence( const wxString &sentence )
609 {
610 wxString payload = sentence;
611 if( !sentence.EndsWith(_T("\r\n")) )
612 payload += _T("\r\n");
613
614
615 return true;
616 }
617