OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_drv_n2k_net.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Implement comm_drv_n2k_net.h -- network nmea2K driver
5 * Author: David Register, Alec Leamas
6 *
7 ***************************************************************************
8 * Copyright (C) 2023 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#ifdef __MINGW32__
27#undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
28#include <ws2tcpip.h>
29#include <windows.h>
30#endif
31
32#ifdef __MSVC__
33#include "winsock2.h"
34#include <wx/msw/winundef.h>
35#include <ws2tcpip.h>
36#endif
37
38#include <wx/wxprec.h>
39
40#ifndef WX_PRECOMP
41#include <wx/wx.h>
42#endif // precompiled headers
43
44#include <wx/tokenzr.h>
45#include <wx/datetime.h>
46
47#include <stdlib.h>
48#include <math.h>
49#include <time.h>
50
51#include "model/sys_events.h"
52
53#ifndef __WXMSW__
54#include <arpa/inet.h>
55#include <netinet/tcp.h>
56#endif
57
58#include <vector>
59#include <wx/socket.h>
60#include <wx/log.h>
61#include <wx/memory.h>
62#include <wx/chartype.h>
63#include <wx/wx.h>
64#include <wx/sckaddr.h>
65
66#include "model/comm_drv_n2k_net.h"
68#include "model/idents.h"
70
71#define N_DOG_TIMEOUT 8
72
73static const int kNotFound = -1;
74
75class MrqContainer {
76public:
77 struct ip_mreq m_mrq;
78 void SetMrqAddr(unsigned int addr) {
79 m_mrq.imr_multiaddr.s_addr = addr;
80 m_mrq.imr_interface.s_addr = INADDR_ANY;
81 }
82};
83
84// circular_buffer implementation
85
86circular_buffer::circular_buffer(size_t size)
87 : buf_(std::unique_ptr<unsigned char[]>(new unsigned char[size])),
88 max_size_(size) {}
89
90// void circular_buffer::reset()
91//{}
92
93// size_t circular_buffer::capacity() const
94//{}
95
96// size_t circular_buffer::size() const
97//{}
98
99bool circular_buffer::empty() const {
100 // if head and tail are equal, we are empty
101 return (!full_ && (head_ == tail_));
102}
103
104bool circular_buffer::full() const {
105 // If tail is ahead the head by 1, we are full
106 return full_;
107}
108
109void circular_buffer::put(unsigned char item) {
110 std::lock_guard<std::mutex> lock(mutex_);
111 buf_[head_] = item;
112 if (full_) tail_ = (tail_ + 1) % max_size_;
113
114 head_ = (head_ + 1) % max_size_;
115
116 full_ = head_ == tail_;
117}
118
119unsigned char circular_buffer::get() {
120 std::lock_guard<std::mutex> lock(mutex_);
121
122 if (empty()) return 0;
123
124 // Read data and advance the tail (we now have a free space)
125 auto val = buf_[tail_];
126 full_ = false;
127 tail_ = (tail_ + 1) % max_size_;
128
129 return val;
130}
131
134 : priority('\0'), source('\0'), destination('\0'), pgn(-1) {};
135
136wxDEFINE_EVENT(wxEVT_COMMDRIVER_N2K_NET, CommDriverN2KNetEvent);
137
139wxDECLARE_EVENT(wxEVT_COMMDRIVER_N2K_NET, CommDriverN2KNetEvent);
140
141class CommDriverN2KNetEvent : public wxEvent {
142public:
143 CommDriverN2KNetEvent(wxEventType commandType = wxEVT_NULL, int id = 0)
144 : wxEvent(id, commandType) {};
146
147 // accessors
148 void SetPayload(std::shared_ptr<std::vector<unsigned char>> data) {
149 m_payload = data;
150 }
151 std::shared_ptr<std::vector<unsigned char>> GetPayload() { return m_payload; }
152
153 // required for sending with wxPostEvent()
154 wxEvent* Clone() const {
155 CommDriverN2KNetEvent* newevent = new CommDriverN2KNetEvent(*this);
156 newevent->m_payload = this->m_payload;
157 return newevent;
158 };
159
160private:
161 std::shared_ptr<std::vector<unsigned char>> m_payload;
162};
163
164static uint64_t PayloadToName(const std::vector<unsigned char> payload) {
165 uint64_t name;
166 memcpy(&name, reinterpret_cast<const void*>(payload.data()), sizeof(name));
167 return name;
168}
169
170//========================================================================
171/* commdriverN2KNet implementation
172 * */
173
174#define TIMER_SOCKET_N2KNET 7339
175
176BEGIN_EVENT_TABLE(CommDriverN2KNet, wxEvtHandler)
177EVT_TIMER(TIMER_SOCKET_N2KNET, CommDriverN2KNet::OnTimerSocket)
178EVT_SOCKET(DS_SOCKET_ID, CommDriverN2KNet::OnSocketEvent)
179EVT_SOCKET(DS_SERVERSOCKET_ID, CommDriverN2KNet::OnServerSocketEvent)
180EVT_TIMER(TIMER_SOCKET_N2KNET + 1, CommDriverN2KNet::OnSocketReadWatchdogTimer)
181END_EVENT_TABLE()
182
183// CommDriverN0183Net::CommDriverN0183Net() : CommDriverN0183() {}
184
186 DriverListener& listener)
187 : CommDriverN2K(params->GetStrippedDSPort()),
188 m_params(*params),
189 m_listener(listener),
190 m_net_port(wxString::Format("%i", params->NetworkPort)),
191 m_net_protocol(params->NetProtocol),
192 m_sock(NULL),
193 m_tsock(NULL),
194 m_socket_server(NULL),
195 m_is_multicast(false),
196 m_txenter(0),
197 m_portstring(params->GetDSPort()),
198 m_io_select(params->IOSelect),
199 m_connection_type(params->Type),
200 m_bok(false)
201
202{
203 m_addr.Hostname(params->NetworkAddress);
204 m_addr.Service(params->NetworkPort);
205
206 m_socket_timer.SetOwner(this, TIMER_SOCKET_N2KNET);
207 m_socketread_watchdog_timer.SetOwner(this, TIMER_SOCKET_N2KNET + 1);
208 this->attributes["netAddress"] = params->NetworkAddress.ToStdString();
209 char port_char[10];
210 sprintf(port_char, "%d", params->NetworkPort);
211 this->attributes["netPort"] = std::string(port_char);
212 this->attributes["userComment"] = params->UserComment.ToStdString();
213 this->attributes["ioDirection"] = std::string("IN/OUT");
214
215 // Prepare the wxEventHandler to accept events from the actual hardware thread
216 Bind(wxEVT_COMMDRIVER_N2K_NET, &CommDriverN2KNet::handle_N2K_MSG, this);
217
218 m_mrq_container = new MrqContainer;
219 m_ib = 0;
220 m_bInMsg = false;
221 m_bGotESC = false;
222 m_bGotSOT = false;
223 rx_buffer = new unsigned char[RX_BUFFER_SIZE_NET + 1];
224 m_circle = new circular_buffer(RX_BUFFER_SIZE_NET);
225
226 fast_messages = new FastMessageMap();
227 m_order = 0; // initialize the fast message order bits, for TX
228 m_n2k_format = N2KFormat_YD_RAW;
229
230 // Establish the power events response
231 resume_listener.Init(SystemEvents::GetInstance().evt_resume,
232 [&](ObservedEvt&) { HandleResume(); });
233
234 Open();
235}
236
237CommDriverN2KNet::~CommDriverN2KNet() {
238 delete m_mrq_container;
239 delete[] rx_buffer;
240 delete m_circle;
241
242 Close();
243}
244
245typedef struct {
246 std::string Model_ID;
247 char RT_flag;
249
250std::unordered_map<uint8_t, product_info> prod_info_map;
251
252bool CommDriverN2KNet::HandleMgntMsg(uint64_t pgn,
253 std::vector<unsigned char>& payload) {
254 // Process a few N2K network management messages
255 auto name = PayloadToName(payload);
256 auto msg =
257 std::make_shared<const Nmea2000Msg>(pgn, payload, GetAddress(name));
258
259 bool b_handled = false;
260 switch (pgn) {
261 case 126996: { // Product information
262 uint8_t src_addr = payload.at(7);
263 if (src_addr == 75) return false; // skip simulator mgnt messages
264 product_info pr_info;
265 pr_info.Model_ID = std::string((char*)&payload.data()[17], 32);
266 pr_info.RT_flag = m_TX_flag;
267
268 prod_info_map[src_addr] = pr_info;
269 b_handled = true;
270 break;
271 }
272 case 59904: { // ISO request
273 uint8_t src_addr = payload.at(7);
274 b_handled = true;
275 break;
276 }
277
278 default:
279 break;
280 }
281 return b_handled;
282}
283
284void CommDriverN2KNet::handle_N2K_MSG(CommDriverN2KNetEvent& event) {
285 auto p = event.GetPayload();
286 std::vector<unsigned char>* payload = p.get();
287
288 // extract PGN
289 uint64_t pgn = 0;
290 unsigned char* c = (unsigned char*)&pgn;
291 *c++ = payload->at(3);
292 *c++ = payload->at(4);
293 *c++ = payload->at(5);
294 // memcpy(&v, &data[3], 1);
295 // printf(" %ld\n", pgn);
296
297 auto name = PayloadToName(*payload);
298 auto msg =
299 std::make_shared<const Nmea2000Msg>(pgn, *payload, GetAddress(name));
300 auto msg_all =
301 std::make_shared<const Nmea2000Msg>(1, *payload, GetAddress(name));
302
303 m_listener.Notify(std::move(msg));
304 m_listener.Notify(std::move(msg_all));
305}
306
307void CommDriverN2KNet::Open(void) {
308#ifdef __UNIX__
309#if wxCHECK_VERSION(3, 0, 0)
310 in_addr_t addr =
311 ((struct sockaddr_in*)GetAddr().GetAddressData())->sin_addr.s_addr;
312#else
313 in_addr_t addr =
314 ((struct sockaddr_in*)GetAddr().GetAddress()->m_addr)->sin_addr.s_addr;
315#endif
316#else
317 unsigned int addr = inet_addr(GetAddr().IPAddress().mb_str());
318#endif
319 // Create the socket
320 switch (m_net_protocol) {
321 case TCP: {
322 OpenNetworkTCP(addr);
323 break;
324 }
325 case UDP: {
326 OpenNetworkUDP(addr);
327 break;
328 }
329 default:
330 break;
331 }
332 SetOk(true);
333}
334
335void CommDriverN2KNet::OpenNetworkUDP(unsigned int addr) {
336 if (GetPortType() != DS_TYPE_OUTPUT) {
337 // We need a local (bindable) address to create the Datagram receive socket
338 // Set up the receive socket
339 wxIPV4address conn_addr;
340 conn_addr.Service(GetNetPort());
341 conn_addr.AnyAddress();
342 SetSock(
343 new wxDatagramSocket(conn_addr, wxSOCKET_NOWAIT | wxSOCKET_REUSEADDR));
344
345 // Test if address is IPv4 multicast
346 if ((ntohl(addr) & 0xf0000000) == 0xe0000000) {
347 SetMulticast(true);
348 m_mrq_container->SetMrqAddr(addr);
349 GetSock()->SetOption(IPPROTO_IP, IP_ADD_MEMBERSHIP,
350 &m_mrq_container->m_mrq,
351 sizeof(m_mrq_container->m_mrq));
352 }
353
354 GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
355
356 GetSock()->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG |
357 wxSOCKET_LOST_FLAG);
358 GetSock()->Notify(TRUE);
359 GetSock()->SetTimeout(1); // Short timeout
360 }
361
362 // Set up another socket for transmit
363 if (GetPortType() != DS_TYPE_INPUT) {
364 wxIPV4address tconn_addr;
365 tconn_addr.Service(0); // use ephemeral out port
366 tconn_addr.AnyAddress();
367 SetTSock(
368 new wxDatagramSocket(tconn_addr, wxSOCKET_NOWAIT | wxSOCKET_REUSEADDR));
369 // Here would be the place to disable multicast loopback
370 // but for consistency with broadcast behaviour, we will
371 // instead rely on setting priority levels to ignore
372 // sentences read back that have just been transmitted
373 if ((!GetMulticast()) && (GetAddr().IPAddress().EndsWith(_T("255")))) {
374 int broadcastEnable = 1;
375 bool bam = GetTSock()->SetOption(
376 SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
377 }
378 }
379
380 // In case the connection is lost before acquired....
381 SetConnectTime(wxDateTime::Now());
382}
383
384void CommDriverN2KNet::OpenNetworkTCP(unsigned int addr) {
385 int isServer = ((addr == INADDR_ANY) ? 1 : 0);
386 wxLogMessage(wxString::Format(_T("Opening TCP Server %d"), isServer));
387
388 if (isServer) {
389 SetSockServer(new wxSocketServer(GetAddr(), wxSOCKET_REUSEADDR));
390 } else {
391 SetSock(new wxSocketClient());
392 }
393
394 if (isServer) {
395 GetSockServer()->SetEventHandler(*this, DS_SERVERSOCKET_ID);
396 GetSockServer()->SetNotify(wxSOCKET_CONNECTION_FLAG);
397 GetSockServer()->Notify(TRUE);
398 GetSockServer()->SetTimeout(1); // Short timeout
399 } else {
400 GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
401 int notify_flags = (wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG);
402 if (GetPortType() != DS_TYPE_INPUT) notify_flags |= wxSOCKET_OUTPUT_FLAG;
403 if (GetPortType() != DS_TYPE_OUTPUT) notify_flags |= wxSOCKET_INPUT_FLAG;
404 GetSock()->SetNotify(notify_flags);
405 GetSock()->Notify(TRUE);
406 GetSock()->SetTimeout(1); // Short timeout
407
408 SetBrxConnectEvent(false);
409 GetSocketTimer()->Start(100, wxTIMER_ONE_SHOT); // schedule a connection
410 }
411
412 // In case the connection is lost before acquired....
413 SetConnectTime(wxDateTime::Now());
414}
415
416void CommDriverN2KNet::OnSocketReadWatchdogTimer(wxTimerEvent& event) {
417 m_dog_value--;
418
419 if (m_dog_value <= 0) { // No receive in n seconds
420 if (GetParams().NoDataReconnect) {
421 // Reconnect on NO DATA is true, so try to reconnect now.
422 if (GetProtocol() == TCP) {
423 wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
424 if (tcp_socket) tcp_socket->Close();
425
426 int n_reconnect_delay = wxMax(N_DOG_TIMEOUT - 2, 2);
427 wxLogMessage(wxString::Format(" Reconnection scheduled in %d seconds.",
428 n_reconnect_delay));
429 GetSocketTimer()->Start(n_reconnect_delay * 1000, wxTIMER_ONE_SHOT);
430
431 // Stop DATA watchdog, will be restarted on successful connection.
432 GetSocketThreadWatchdogTimer()->Stop();
433 }
434 }
435 }
436}
437
438void CommDriverN2KNet::OnTimerSocket() {
439 // Attempt a connection
440 wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
441 if (tcp_socket) {
442 if (tcp_socket->IsDisconnected()) {
443 wxLogDebug(" Attempting reconnection...");
444 SetBrxConnectEvent(false);
445 // Stop DATA watchdog, may be restarted on successful connection.
446 GetSocketThreadWatchdogTimer()->Stop();
447 tcp_socket->Connect(GetAddr(), FALSE);
448
449 // schedule another connection attempt, in case this one fails
450 int n_reconnect_delay = N_DOG_TIMEOUT;
451 GetSocketTimer()->Start(n_reconnect_delay * 1000, wxTIMER_ONE_SHOT);
452 }
453 }
454}
455
456void CommDriverN2KNet::HandleResume() {
457 // Attempt a stop and restart of connection
458 wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
459 if (tcp_socket) {
460 GetSocketThreadWatchdogTimer()->Stop();
461
462 tcp_socket->Close();
463
464 // schedule reconnect attempt
465 int n_reconnect_delay = wxMax(N_DOG_TIMEOUT - 2, 2);
466 wxLogMessage(wxString::Format(" Reconnection scheduled in %d seconds.",
467 n_reconnect_delay));
468
469 GetSocketTimer()->Start(n_reconnect_delay * 1000, wxTIMER_ONE_SHOT);
470 }
471}
472
473bool CommDriverN2KNet::SendMessage(std::shared_ptr<const NavMsg> msg,
474 std::shared_ptr<const NavAddr> addr) {
475 auto msg_n2k = std::dynamic_pointer_cast<const Nmea2000Msg>(msg);
476 auto dest_addr_n2k = std::static_pointer_cast<const NavAddr2000>(addr);
477 return SendN2KNetwork(msg_n2k, dest_addr_n2k);
478}
479
480std::vector<unsigned char> CommDriverN2KNet::PushCompleteMsg(
481 const CanHeader header, int position, const can_frame frame) {
482 std::vector<unsigned char> data;
483 data.push_back(0x93);
484 data.push_back(0x13);
485 data.push_back(header.priority);
486 data.push_back(header.pgn & 0xFF);
487 data.push_back((header.pgn >> 8) & 0xFF);
488 data.push_back((header.pgn >> 16) & 0xFF);
489 data.push_back(header.destination);
490 data.push_back(header.source);
491 data.push_back(0xFF); // FIXME (dave) generate the time fields
492 data.push_back(0xFF);
493 data.push_back(0xFF);
494 data.push_back(0xFF);
495 data.push_back(CAN_MAX_DLEN); // nominally 8
496 for (size_t n = 0; n < CAN_MAX_DLEN; n++) data.push_back(frame.data[n]);
497 data.push_back(0x55); // CRC dummy, not checked
498 return data;
499}
500
501std::vector<unsigned char> CommDriverN2KNet::PushFastMsgFragment(
502 const CanHeader& header, int position) {
503 std::vector<unsigned char> data;
504 data.push_back(0x93);
505 data.push_back(fast_messages->entries[position].expected_length + 11);
506 data.push_back(header.priority);
507 data.push_back(header.pgn & 0xFF);
508 data.push_back((header.pgn >> 8) & 0xFF);
509 data.push_back((header.pgn >> 16) & 0xFF);
510 data.push_back(header.destination);
511 data.push_back(header.source);
512 data.push_back(0xFF); // FIXME (dave) Could generate the time fields
513 data.push_back(0xFF);
514 data.push_back(0xFF);
515 data.push_back(0xFF);
516 data.push_back(fast_messages->entries[position].expected_length);
517 for (size_t n = 0; n < fast_messages->entries[position].expected_length; n++)
518 data.push_back(fast_messages->entries[position].data[n]);
519 data.push_back(0x55); // CRC dummy
520 fast_messages->Remove(position);
521 return data;
522}
523
530void CommDriverN2KNet::HandleCanFrameInput(can_frame frame) {
531 int position = -1;
532 bool ready = true;
533
534 CanHeader header(frame);
535 if (header.IsFastMessage()) {
536 position = fast_messages->FindMatchingEntry(header, frame.data[0]);
537 if (position == kNotFound) {
538 // Not an existing fast message:
539 // If valid, create new entry and insert first frame
540 // First, sanity check the arriving frame.
541 // If it is not the first frame of a FastMessage, then discard it
542 // n.b. This should be considered a network error, or possibly a gateway
543 // error. Maybe as simple as a dropped starting frame....
544 if ((frame.data[0] & 0x1F) == 0) {
545 position = fast_messages->AddNewEntry();
546 ready = fast_messages->InsertEntry(header, frame.data, position);
547 } else
548 ready = false;
549 } else {
550 // An existing fast message entry is present, append the frame
551 ready = fast_messages->AppendEntry(header, frame.data, position);
552 }
553 }
554 if (ready) {
555 std::vector<unsigned char> vec;
556 if (position >= 0) {
557 // Re-assembled fast message
558 vec = PushFastMsgFragment(header, position);
559 } else {
560 // Single frame message
561 vec = PushCompleteMsg(header, position, frame);
562 }
563
564 // Intercept network management messages not used by OCPN navigation core.
565 if (HandleMgntMsg(header.pgn, vec)) return;
566
567 // Message is ready
568 CommDriverN2KNetEvent Nevent(wxEVT_COMMDRIVER_N2K_NET, 0);
569 auto payload = std::make_shared<std::vector<uint8_t>>(vec);
570 Nevent.SetPayload(payload);
571 AddPendingEvent(Nevent);
572 }
573}
574
575bool isASCII(std::vector<unsigned char> packet) {
576 for (unsigned char c : packet) {
577 if (!isascii(c)) return false;
578 }
579 return true;
580}
581
582N2K_Format CommDriverN2KNet::DetectFormat(std::vector<unsigned char> packet) {
583 // A simplistic attempt at identifying which of the various available
584 // on-wire (or air) formats being emitted by a configured
585 // Actisense N2k<->ethernet device.
586
587 if (isASCII(packet)) {
588 std::string payload = std::string(packet.begin(), packet.end());
589 if (payload.find("$PCDIN") != std::string::npos) {
590 return N2KFormat_SeaSmart;
591 } else if (payload.find("$MXPGN") != std::string::npos) {
592 // TODO: Due to the weird fragmentation observed with default settings of
593 // the wi-fi part, the payload does not always start with or even contain
594 // `$MXPGN`. We now lose the later.
595 return N2KFormat_MiniPlex;
596 } else if (std::find(packet.begin(), packet.end(), ':') != packet.end()) {
597 return N2KFormat_Actisense_RAW_ASCII;
598 } else {
599 return N2KFormat_Actisense_N2K_ASCII;
600 }
601 } else {
602 if (packet[2] == 0x95)
603 return N2KFormat_Actisense_RAW;
604 else if (packet[2] == 0xd0)
605 return N2KFormat_Actisense_N2K;
606 else if (packet[2] == 0x93)
607 return N2KFormat_Actisense_NGT;
608 }
609 return N2KFormat_Undefined;
610}
611
612bool CommDriverN2KNet::ProcessActisense_N2K(std::vector<unsigned char> packet) {
613 // 1002 d0 1500ff0401f80900684c1b00a074eb14f89052d288 1003
614
615 std::vector<unsigned char> data;
616 bool bInMsg = false;
617 bool bGotESC = false;
618 bool bGotSOT = false;
619
620 while (!m_circle->empty()) {
621 uint8_t next_byte = m_circle->get();
622
623 if (bInMsg) {
624 if (bGotESC) {
625 if (next_byte == ESCAPE) {
626 data.push_back(next_byte);
627 bGotESC = false;
628 } else if (next_byte == ENDOFTEXT) {
629 // Process packet
630 // first 3 bytes are: 1 byte for message type, 2 bytes for rest of
631 // message length
632 unsigned int msg_length =
633 (uint32_t)data[1] + ((uint32_t)data[2] << 8);
634
635 // As a sanity check, verify message length
636 if (msg_length == data.size() - 1) {
637 uint8_t destination = data[3];
638 uint8_t source = data[4];
639
640 uint8_t dprp = data[7];
641 uint8_t priority =
642 (dprp >> 2) & 7; // priority bits are 3,4,5th bit
643 uint8_t rAndDP = dprp & 3; // data page + reserved is first 2 bits
644
645 // PGN
646 uint8_t pduFormat = data[6]; // PF (PDU Format)
647 uint32_t pgn = (rAndDP << 16) + (pduFormat << 8);
648 if (pduFormat >=
649 240) // message is broadcast, PS contains group extension
650 pgn += data[5]; // +PS (PDU Specific)
651
652 // Create the OCPN payload
653 std::vector<uint8_t> o_payload;
654 o_payload.push_back(0x93);
655 o_payload.push_back(0x13);
656 o_payload.push_back(priority); // priority;
657 o_payload.push_back(pgn & 0xFF);
658 o_payload.push_back((pgn >> 8) & 0xFF);
659 o_payload.push_back((pgn >> 16) & 0xFF);
660 o_payload.push_back(destination); // destination;
661 o_payload.push_back(source); // source);
662 o_payload.push_back(0xFF); // FIXME (dave) generate the time fields
663 o_payload.push_back(0xFF);
664 o_payload.push_back(0xFF);
665 o_payload.push_back(0xFF);
666 o_payload.push_back(data.size());
667
668 // Data starts at offset 13
669 for (size_t n = 13; n < data.size() - 1; n++)
670 o_payload.push_back(data[n]);
671
672 o_payload.push_back(0x55); // CRC dummy, not checked
673
674 // Message is ready
675 CommDriverN2KNetEvent Nevent(wxEVT_COMMDRIVER_N2K_NET, 0);
676 auto n2k_payload =
677 std::make_shared<std::vector<uint8_t>>(o_payload);
678 Nevent.SetPayload(n2k_payload);
679 AddPendingEvent(Nevent);
680 }
681
682 // reset for next packet
683 bInMsg = false;
684 bGotESC = false;
685 data.clear();
686 } else if (next_byte == STARTOFTEXT) {
687 bGotESC = false;
688 data.clear();
689 } else {
690 data.clear();
691 bInMsg = false;
692 bGotESC = false;
693 }
694 } else {
695 bGotESC = (next_byte == ESCAPE);
696
697 if (!bGotESC) {
698 data.push_back(next_byte);
699 }
700 }
701 }
702
703 else {
704 if (STARTOFTEXT == next_byte) {
705 bGotSOT = false;
706 if (bGotESC) {
707 bGotSOT = true;
708 }
709 } else {
710 bGotESC = (next_byte == ESCAPE);
711 if (bGotSOT) {
712 bGotSOT = false;
713 bInMsg = true;
714
715 data.push_back(next_byte);
716 }
717 }
718 }
719 } // while
720
721 return true;
722}
723
724bool CommDriverN2KNet::ProcessActisense_RAW(std::vector<unsigned char> packet) {
725 // 1002 95 0e15870402f8094b fc e6 20 00 00 ff ff 6f 1003
726
727 can_frame frame;
728
729 std::vector<unsigned char> data;
730 bool bInMsg = false;
731 bool bGotESC = false;
732 bool bGotSOT = false;
733
734 while (!m_circle->empty()) {
735 uint8_t next_byte = m_circle->get();
736
737 if (bInMsg) {
738 if (bGotESC) {
739 if (next_byte == ESCAPE) {
740 data.push_back(next_byte);
741 bGotESC = false;
742 } else if (next_byte == ENDOFTEXT) {
743 // Process packet
744 // Create a can_frame, to assemble fast packets.
745
746 // As a sanity check, verify message length
747 if (data.size() >= 8) {
748 size_t dLen = data[1];
749
750 if (dLen + 3 == data.size()) {
751 // can_id
752 memcpy(&frame.can_id, &data.data()[4], 4);
753
754 // data
755 memcpy(&frame.data, &data.data()[8], 8);
756
757 HandleCanFrameInput(frame);
758
759 // reset for next packet
760 bInMsg = false;
761 bGotESC = false;
762 data.clear();
763 }
764 }
765 } else if (next_byte == STARTOFTEXT) {
766 bGotESC = false;
767 data.clear();
768 } else {
769 data.clear();
770 bInMsg = false;
771 bGotESC = false;
772 }
773 } else {
774 bGotESC = (next_byte == ESCAPE);
775
776 if (!bGotESC) {
777 data.push_back(next_byte);
778 }
779 }
780 }
781
782 else {
783 if (STARTOFTEXT == next_byte) {
784 bGotSOT = false;
785 if (bGotESC) {
786 bGotSOT = true;
787 }
788 } else {
789 bGotESC = (next_byte == ESCAPE);
790 if (bGotSOT) {
791 bGotSOT = false;
792 bInMsg = true;
793
794 data.push_back(next_byte);
795 }
796 }
797 }
798 } // while
799
800 return true;
801}
802
803bool CommDriverN2KNet::ProcessActisense_NGT(std::vector<unsigned char> packet) {
804 std::vector<unsigned char> data;
805 bool bInMsg = false;
806 bool bGotESC = false;
807 bool bGotSOT = false;
808
809 while (!m_circle->empty()) {
810 uint8_t next_byte = m_circle->get();
811
812 if (bInMsg) {
813 if (bGotESC) {
814 if (next_byte == ESCAPE) {
815 data.push_back(next_byte);
816 bGotESC = false;
817 } else if (next_byte == ENDOFTEXT) {
818 // Process packet
819 CommDriverN2KNetEvent Nevent(wxEVT_COMMDRIVER_N2K_NET, 0);
820 auto n2k_payload = std::make_shared<std::vector<uint8_t>>(data);
821 Nevent.SetPayload(n2k_payload);
822 AddPendingEvent(Nevent);
823
824 // reset for next packet
825 bInMsg = false;
826 bGotESC = false;
827 data.clear();
828 } else if (next_byte == STARTOFTEXT) {
829 bGotESC = false;
830 data.clear();
831 } else {
832 data.clear();
833 bInMsg = false;
834 bGotESC = false;
835 }
836 } else {
837 bGotESC = (next_byte == ESCAPE);
838
839 if (!bGotESC) {
840 data.push_back(next_byte);
841 }
842 }
843 }
844
845 else {
846 if (STARTOFTEXT == next_byte) {
847 bGotSOT = false;
848 if (bGotESC) {
849 bGotSOT = true;
850 }
851 } else {
852 bGotESC = (next_byte == ESCAPE);
853 if (bGotSOT) {
854 bGotSOT = false;
855 bInMsg = true;
856
857 data.push_back(next_byte);
858 }
859 }
860 }
861 } // while
862
863 return true;
864}
865
866bool CommDriverN2KNet::ProcessActisense_ASCII_RAW(
867 std::vector<unsigned char> packet) {
868 can_frame frame;
869
870 while (!m_circle->empty()) {
871 char b = m_circle->get();
872 if ((b != 0x0a) && (b != 0x0d)) {
873 m_sentence += b;
874 }
875 if (b == 0x0a) { // end of sentence
876
877 // Extract a can_frame from ASCII stream
878 // printf("%s\n", m_sentence.c_str());
879
880 wxString ss(m_sentence.c_str());
881 m_sentence.clear();
882 wxStringTokenizer tkz(ss, " ");
883
884 // Discard first token
885 wxString token = tkz.GetNextToken(); // time stamp
886
887 token = tkz.GetNextToken(); // R/T
888 // Record the R/T flag, for use in device detect logic
889 m_TX_flag = token[0];
890
891 // can_id;
892 token = tkz.GetNextToken();
893 long canID;
894 token.ToLong(&canID, 16);
895 frame.can_id = canID;
896
897 // 8 data bytes, if present, 0 otherwise
898 unsigned char bytes[8];
899 memset(bytes, 0, 8);
900 for (unsigned int i = 0; i < 8; i++) {
901 if (tkz.HasMoreTokens()) {
902 token = tkz.GetNextToken();
903 long tui;
904 token.ToLong(&tui, 16);
905 bytes[i] = (uint8_t)tui;
906 }
907 }
908 memcpy(&frame.data, bytes, 8);
909 HandleCanFrameInput(frame);
910 }
911 }
912 return true;
913}
914
915bool CommDriverN2KNet::ProcessActisense_ASCII_N2K(
916 std::vector<unsigned char> packet) {
917 // A001001.732 04FF6 1FA03 C8FBA80329026400
918 std::string sentence;
919
920 while (!m_circle->empty()) {
921 char b = m_circle->get();
922 if ((b != 0x0a) && (b != 0x0d)) {
923 sentence += b;
924 }
925 if (b == 0x0a) { // end of sentence
926
927 // Extract items
928 // printf("%s", sentence.c_str());
929
930 wxString ss(sentence.c_str());
931 wxStringTokenizer tkz(ss, " ");
932 sentence.clear(); // for next while loop
933
934 // skip timestamp
935 wxString time_header = tkz.GetNextToken();
936
937 wxString sprio_addr = tkz.GetNextToken();
938 long prio_addr;
939 sprio_addr.ToLong(&prio_addr, 16);
940 uint8_t priority = (uint8_t)prio_addr & 0X0F;
941 uint8_t destination = (uint8_t)(prio_addr >> 4) & 0X0FF;
942 uint8_t source = (uint8_t)(prio_addr >> 12) & 0X0FF;
943
944 // PGN
945 wxString sPGN = tkz.GetNextToken();
946 unsigned long PGN;
947 sPGN.ToULong(&PGN, 16);
948 // printf(" PGN: %ld\n", PGN);
949
950 // data field
951 wxString sdata = tkz.GetNextToken();
952 std::vector<uint8_t> data;
953 for (size_t i = 0; i < sdata.Length(); i += 2) {
954 long dv;
955 wxString stui = sdata.Mid(i, 2);
956 stui.ToLong(&dv, 16);
957 data.push_back((uint8_t)dv);
958 }
959
960 // Create the OCPN payload
961 std::vector<uint8_t> o_payload;
962 o_payload.push_back(0x93);
963 o_payload.push_back(0x13);
964 o_payload.push_back(priority); // priority;
965 o_payload.push_back(PGN & 0xFF);
966 o_payload.push_back((PGN >> 8) & 0xFF);
967 o_payload.push_back((PGN >> 16) & 0xFF);
968 o_payload.push_back(destination); // destination;
969 o_payload.push_back(source); // header.source);
970 o_payload.push_back(0xFF); // FIXME (dave) generate the time fields
971 o_payload.push_back(0xFF);
972 o_payload.push_back(0xFF);
973 o_payload.push_back(0xFF);
974 o_payload.push_back(data.size());
975 for (size_t n = 0; n < data.size(); n++) o_payload.push_back(data[n]);
976 o_payload.push_back(0x55); // CRC dummy, not checked
977
978 if (HandleMgntMsg(PGN, o_payload)) return false;
979
980 // Message is ready
981 CommDriverN2KNetEvent Nevent(wxEVT_COMMDRIVER_N2K_NET, 0);
982 auto n2k_payload = std::make_shared<std::vector<uint8_t>>(o_payload);
983 Nevent.SetPayload(n2k_payload);
984 AddPendingEvent(Nevent);
985 }
986 }
987 return true;
988}
989
990bool CommDriverN2KNet::ProcessSeaSmart(std::vector<unsigned char> packet) {
991 while (!m_circle->empty()) {
992 char b = m_circle->get();
993 if ((b != 0x0a) && (b != 0x0d)) {
994 m_sentence += b;
995 }
996 if (b == 0x0a) { // end of sentence
997
998 // Extract a can_frame from ASCII stream
999 // printf("%s\n", m_sentence.c_str());
1000
1001 wxString ss(m_sentence.c_str());
1002 m_sentence.clear();
1003 wxStringTokenizer tkz(ss, ",");
1004
1005 // Discard first token
1006 wxString token = tkz.GetNextToken(); // $PCDIN
1007 m_TX_flag = 'R';
1008
1009 token = tkz.GetNextToken(); // PGN
1010 unsigned long PGN;
1011 token.ToULong(&PGN, 16);
1012
1013 token = tkz.GetNextToken(); // Timestamp
1014 unsigned long timestamp;
1015 token.ToULong(&timestamp, 16);
1016
1017 token = tkz.GetNextToken(); // Source ID
1018 unsigned long source;
1019 token.ToULong(&source, 16);
1020
1021 token = tkz.GetNextToken(); // Payload + "*CRC_byte"
1022
1023 wxStringTokenizer datatkz(token, "*");
1024 wxString data = datatkz.GetNextToken();
1025
1026 // Create the OCPN payload
1027 std::vector<uint8_t> o_payload;
1028 o_payload.push_back(0x93);
1029 o_payload.push_back(0x13);
1030 o_payload.push_back(3); // priority hardcoded, missing in SeaSmart
1031 o_payload.push_back(PGN & 0xFF);
1032 o_payload.push_back((PGN >> 8) & 0xFF);
1033 o_payload.push_back((PGN >> 16) & 0xFF);
1034 o_payload.push_back(0xFF); // destination hardcoded, missing in SeaSmart
1035 o_payload.push_back((uint8_t)source); // header.source);
1036 o_payload.push_back(timestamp & 0xFF);
1037 o_payload.push_back((timestamp >> 8) & 0xFF);
1038 o_payload.push_back((timestamp >> 16) & 0xFF);
1039 o_payload.push_back((timestamp >> 24) & 0xFF);
1040 o_payload.push_back((uint8_t)data.Length() / 2);
1041 for (size_t i = 0; i < data.Length(); i += 2) {
1042 unsigned long dv;
1043 wxString sbyte = data.Mid(i, 2);
1044 sbyte.ToULong(&dv, 16);
1045 o_payload.push_back((uint8_t)dv);
1046 }
1047 o_payload.push_back(0x55); // CRC dummy, not checked
1048
1049 if (HandleMgntMsg(PGN, o_payload)) return false;
1050
1051 // Message is ready
1052 CommDriverN2KNetEvent Nevent(wxEVT_COMMDRIVER_N2K_NET, 0);
1053 auto n2k_payload = std::make_shared<std::vector<uint8_t>>(o_payload);
1054 Nevent.SetPayload(n2k_payload);
1055 AddPendingEvent(Nevent);
1056 }
1057 }
1058 return true;
1059}
1060
1061bool CommDriverN2KNet::ProcessMiniPlex(std::vector<unsigned char> packet) {
1062 /*
1063 $MXPGN – NMEA 2000 PGN Data
1064 This sentence transports NMEA 2000/CAN frames in NMEA 0183 format. The
1065 MiniPlex-3 will transmit this sentence with Talker ID “MX”. When sent to the
1066 MiniPlex-3, the Talker ID is ignored unless a routing entry exists for this
1067 sentence.
1068
1069 Format: $--PGN,pppppp,aaaa,c--c*hh<CR><LF>
1070
1071 pppppp: PGN of the NMEA 2000/CAN frame, 3-byte hexadecimal number. If the PGN
1072 is non-global, the lowest byte contains the destination address. aaaa:
1073 Attribute Word, a 16-bit hexadecimal number. This word contains the priority,
1074 the DLC code and then source/destination address of the frame, formatted as
1075 shown below:
1076
1077 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1078 ----------------------------------------------------------------
1079 | S | Priority | DLC | Address |
1080 ----------------------------------------------------------------
1081
1082 S: Send bit. When an NMEA 2000/CAN frame is received, this bit is 0.
1083 To use the $MXPGN sentence to send an NMEA 2000/CAN frame, this bit must be 1.
1084 Priority: Frame priority. A value between 0 and 7, a lower value means higher
1085 priority. DLC: Data Length Code field, contains the size of the frame in bytes
1086 (1..8) or a Class 2 Transmission ID (9..15). Address: Depending on the Send
1087 bit, this field contains the Source Address (S=0) or the Destination Address
1088 (S=1) of the frame. c--c: Data field of the NMEA 2000/CAN frame, organised as
1089 one large number in hexadecimal notation from MSB to LSB. This is in
1090 accordance with “NMEA 2000 Appendix D”, chapter D.1, “Data Placement within
1091 the CAN Frame”. The size of this field depends on the DLC value and can be 1
1092 to 8 bytes (2 to 16 hexadecimal characters).
1093
1094 NMEA 2000 Reception
1095
1096 When the MiniPlex-3 converts an NMEA 2000/CAN frame into an $MXPGN sentence,
1097 the S bit in the Attribute field will be 0 and the Address field contains the
1098 source address of the frame. The destination address of the frame is either
1099 global or contained in the lower byte of the PGN, in accordance with the NMEA
1100 2000/ISO specification.
1101
1102 Notes:
1103
1104 Multiple messages can be delivered in a single packet
1105 It is not guaranteed that the whole message will be delivered in a single
1106 packet, actually it is common that the last message is split "anywhere" and
1107 continues in the next packet.
1108
1109 packet 1 payload
1110
1111 "$MXPGN,01F119,3816,FFFAAF01A3FDE301*14\r\n
1112 $MXPGN,01F201,2816,C50E0A19A0001A40*66\r\n
1113 $MXPGN,01F201,2816,6B4C0039058D8A41*15\r\n
1114 $MXPGN,01F201,2816,FFFFFFFFFF007542*1D\r\n
1115 $MXPGN,01F201,2816,FF7F7F0000000A43*6F\r\n
1116 $MXPGN,01F209,2816,2D002400ED0009A0*18\r\n
1117 $MXPGN,01F209,2816,FFFFFFFF002C00A1*10\r\n
1118 $MXPGN,01F213,6816,00B4F512020106C0*6E\r\n
1119 $MXPGN,01F214,6816,01FFFF7FFF04F801*12\r\n
1120 $MXPGN,01F214,6816,7EFFFF0009056400*65\r\n
1121 $MXPGN,"
1122
1123 packet 2 payload
1124
1125 "01F212,6816,185B560101010BC0*62\r\n
1126 $MXPGN,01F212,6816,FFFFFFFF00D700C1*1E\r\n
1127 $MXPGN,01FD06,5816,FF03F6749570C101*67\r\n
1128 $MXPGN,01FD07,5816,03F635B672F20401*1B\r\n"
1129
1130 packet 1
1131
1132 "$MXPGN,01F114,3816,FFFFF000D20212FF*1E\r\n
1133 $MXPGN,01F905,6816,0001000300005BC0*14\r\n
1134 $MXPGN,01F905,6816,6142010EE00007C1*67\r\n
1135 $MXPGN,01F905,6816,68206F74206B63C2*6F\r\n
1136 $MXPGN,01F905,6816,0D0001FF656D6FC3*16\r\n
1137 $MXPGN,01F905,6816,20747261745301C4*62\r\n
1138 $MXPGN,01F905,6816,4600746E696F70C5*6E\r\n
1139 $MXPGN,01F905,6816,020C84588023C3C6*6E\r\n
1140 $MXPGN,01F905,6816,6E727554011200C7*11\r\n
1141 $MXPGN,01F905,6816,65726F666562"
1142
1143 packet 2 payload
1144
1145 "20C8*1A\r\n
1146 $MXPGN,01F905,6816,CCA06B636F7220C9*1F\r\n
1147 $MXPGN,01F905,6816,030C85DF2023C4CA*1B\r\n
1148 $MXPGN,01F905,6816,656D6F48010600CB*19\r\n
1149 $MXPGN,01F905,6816,8765C023C65340CC*1B\r\n
1150 $MXPGN,01F905,6816,FFFFFFFFFFFF0CCD*66\r\n
1151 $MXPGN,01F10D,2816,FFFF0369FC97F901*16\r\n
1152 $MXPGN,01F112,2816,FD03C0FDF49B1A00*11\r\n
1153 $MXPGN,01F200,2816,FFFF7FFFFF43F800*10\r\n
1154 $MXPGN,01F205,2816,FF050D3A1D4CFC00*19\r\n"
1155 */
1156 while (!m_circle->empty()) {
1157 char b = m_circle->get();
1158 if ((b != 0x0a) && (b != 0x0d)) {
1159 m_sentence += b;
1160 }
1161 if (b == 0x0a) { // end of sentence
1162
1163 // Extract a can_frame from ASCII stream
1164 // printf("%s\n", m_sentence.c_str());
1165
1166 wxString ss(m_sentence.c_str());
1167 m_sentence.clear();
1168 wxStringTokenizer tkz(ss, ",");
1169
1170 // Discard first token
1171 wxString token = tkz.GetNextToken(); // $MXPGN
1172 m_TX_flag = 'R';
1173
1174 token = tkz.GetNextToken(); // PGN
1175 unsigned long PGN;
1176 token.ToULong(&PGN, 16);
1177
1178 token = tkz.GetNextToken(); // Attribute compound field
1179 unsigned long attr;
1180 token.ToULong(&attr, 16);
1181 // Send Bit
1182 bool send_bit = (attr >> 15) != 0;
1183 // Priority
1184 uint8_t priority = (attr >> 12) & 0x07;
1185
1186 // dlc
1187 uint8_t dlc = (attr >> 8) & 0x0F;
1188
1189 // address
1190 uint8_t address = attr & 0xFF;
1191
1192 token = tkz.GetNextToken(); // Payload + "*CRC_byte"
1193
1194 wxStringTokenizer datatkz(token, "*");
1195 wxString data = datatkz.GetNextToken();
1196
1197 if (data.Length() >
1198 16) { // Payload can never exceed 8 bytes (=16 HEX characters)
1199 return false;
1200 }
1201
1202 can_frame frame;
1203 memset(&frame.data, 0, 8);
1204 for (size_t i = 0; i < data.Length(); i += 2) {
1205 unsigned long dv;
1206 wxString sbyte = data.Mid(data.Length() - i - 2, 2);
1207 sbyte.ToULong(&dv, 16);
1208 frame.data[i / 2] = ((uint8_t)dv);
1209 }
1210 frame.can_id = (uint32_t)BuildCanID(priority, address, 0xFF, PGN);
1211 HandleCanFrameInput(frame);
1212 }
1213 }
1214 return true;
1215}
1216
1217void CommDriverN2KNet::OnSocketEvent(wxSocketEvent& event) {
1218#define RD_BUF_SIZE 4096
1219 // can_frame frame;
1220
1221 switch (event.GetSocketEvent()) {
1222 case wxSOCKET_INPUT: {
1223 // TODO determine if the follwing SetFlags needs to be done at every
1224 // socket event or only once when socket is created, it it needs to be
1225 // done at all!
1226 // m_sock->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK); // was
1227 // (wxSOCKET_NOWAIT);
1228
1229 // We use wxSOCKET_BLOCK to avoid Yield() reentrancy problems
1230 // if a long ProgressDialog is active, as in S57 SENC creation.
1231
1232 // Disable input event notifications to preclude re-entrancy on
1233 // non-blocking socket
1234 // m_sock->SetNotify(wxSOCKET_LOST_FLAG);
1235
1236 std::vector<unsigned char> data(RD_BUF_SIZE + 1);
1237 int newdata = 0;
1238 uint8_t next_byte = 0;
1239
1240 event.GetSocket()->Read(&data.front(), RD_BUF_SIZE);
1241 if (!event.GetSocket()->Error()) {
1242 size_t count = event.GetSocket()->LastCount();
1243 if (count) {
1244 if (1 /*FIXME !g_benableUDPNullHeader*/) {
1245 data[count] = 0;
1246 newdata = count;
1247 } else {
1248 // XXX FIXME: is it reliable?
1249 }
1250 }
1251 }
1252
1253 bool done = false;
1254 if (newdata > 0) {
1255 for (int i = 0; i < newdata; i++) {
1256 m_circle->put(data[i]);
1257 // printf("%c", data.at(i));
1258 }
1259 }
1260
1261 m_n2k_format = DetectFormat(data);
1262
1263 switch (m_n2k_format) {
1264 case N2KFormat_Actisense_RAW_ASCII:
1265 ProcessActisense_ASCII_RAW(data);
1266 break;
1267 case N2KFormat_YD_RAW: // RX Byte compatible with Actisense ASCII RAW
1268 ProcessActisense_ASCII_RAW(data);
1269 break;
1270 case N2KFormat_Actisense_N2K_ASCII:
1271 ProcessActisense_ASCII_N2K(data);
1272 break;
1273 case N2KFormat_Actisense_N2K:
1274 ProcessActisense_N2K(data);
1275 break;
1276 case N2KFormat_Actisense_RAW:
1277 ProcessActisense_RAW(data);
1278 break;
1279 case N2KFormat_Actisense_NGT:
1280 ProcessActisense_NGT(data);
1281 break;
1282 case N2KFormat_SeaSmart:
1283 ProcessSeaSmart(data);
1284 break;
1285 case N2KFormat_MiniPlex:
1286 ProcessMiniPlex(data);
1287 break;
1288 case N2KFormat_Undefined:
1289 default:
1290 break;
1291 }
1292 // Check for any pending output message
1293 } // case
1294
1295 m_dog_value = N_DOG_TIMEOUT; // feed the dog
1296 break;
1297#if 1
1298
1299 case wxSOCKET_LOST: {
1300 if (GetProtocol() == TCP || GetProtocol() == GPSD) {
1301 if (GetBrxConnectEvent())
1302 wxLogMessage(wxString::Format(
1303 _T("NetworkDataStream connection lost: %s"), GetPort().c_str()));
1304 if (GetSockServer()) {
1305 GetSock()->Destroy();
1306 SetSock(NULL);
1307 break;
1308 }
1309 wxDateTime now = wxDateTime::Now();
1310 wxTimeSpan since_connect(
1311 0, 0, 10); // ten secs assumed, if connect time is uninitialized
1312 if (GetConnectTime().IsValid()) since_connect = now - GetConnectTime();
1313
1314 int retry_time = 5000; // default
1315
1316 // If the socket has never connected, and it is a short interval since
1317 // the connect request then stretch the time a bit. This happens on
1318 // Windows if there is no dafault IP on any interface
1319
1320 if (!GetBrxConnectEvent() && (since_connect.GetSeconds() < 5))
1321 retry_time = 10000; // 10 secs
1322
1323 GetSocketThreadWatchdogTimer()->Stop();
1324 GetSocketTimer()->Start(
1325 retry_time, wxTIMER_ONE_SHOT); // Schedule a re-connect attempt
1326 }
1327 break;
1328 }
1329
1330 case wxSOCKET_CONNECTION: {
1331 if (GetProtocol() == GPSD) {
1332 // Sign up for watcher mode, Cooked NMEA
1333 // Note that SIRF devices will be converted by gpsd into
1334 // pseudo-NMEA
1335 char cmd[] = "?WATCH={\"class\":\"WATCH\", \"nmea\":true}";
1336 GetSock()->Write(cmd, strlen(cmd));
1337 } else if (GetProtocol() == TCP) {
1338 wxLogMessage(wxString::Format(
1339 _T("TCP NetworkDataStream connection established: %s"),
1340 GetPort().c_str()));
1341 m_dog_value = N_DOG_TIMEOUT; // feed the dog
1342 if (GetPortType() != DS_TYPE_OUTPUT) {
1344 if (GetParams().NoDataReconnect)
1345 GetSocketThreadWatchdogTimer()->Start(1000);
1346 }
1347 if (GetPortType() != DS_TYPE_INPUT && GetSock()->IsOk())
1348 (void)SetOutputSocketOptions(GetSock());
1349 GetSocketTimer()->Stop();
1350 SetBrxConnectEvent(true);
1351 }
1352
1353 SetConnectTime(wxDateTime::Now());
1354 break;
1355 }
1356#endif
1357 default:
1358 break;
1359 }
1360}
1361
1362void CommDriverN2KNet::OnServerSocketEvent(wxSocketEvent& event) {
1363 switch (event.GetSocketEvent()) {
1364 case wxSOCKET_CONNECTION: {
1365 SetSock(GetSockServer()->Accept(false));
1366
1367 if (GetSock()) {
1368 GetSock()->SetTimeout(2);
1369 // GetSock()->SetFlags(wxSOCKET_BLOCK);
1370 GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
1371 int notify_flags = (wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG);
1372 if (GetPortType() != DS_TYPE_INPUT) {
1373 notify_flags |= wxSOCKET_OUTPUT_FLAG;
1374 (void)SetOutputSocketOptions(GetSock());
1375 }
1376 if (GetPortType() != DS_TYPE_OUTPUT)
1377 notify_flags |= wxSOCKET_INPUT_FLAG;
1378 GetSock()->SetNotify(notify_flags);
1379 GetSock()->Notify(true);
1380 }
1381
1382 break;
1383 }
1384
1385 default:
1386 break;
1387 }
1388}
1389
1390std::vector<unsigned char> MakeSimpleOutMsg(
1391 int data_format, int pgn, std::vector<unsigned char>& payload) {
1392 std::vector<unsigned char> out_vec;
1393
1394 switch (data_format) {
1395 case N2KFormat_YD_RAW:
1396 case N2KFormat_Actisense_RAW_ASCII: {
1397 // Craft the canID
1398 unsigned can_id = BuildCanID(6, 0xff, 0xff, pgn);
1399 wxString scan_id;
1400 scan_id.Printf("%08X", can_id);
1401 std::string sscan_id = scan_id.ToStdString();
1402 for (unsigned char s : sscan_id) out_vec.push_back(s);
1403 out_vec.push_back(' ');
1404
1405 // Data payload
1406 std::string sspl;
1407 char tv[4];
1408 for (unsigned char d : payload) {
1409 snprintf(tv, 4, "%02X ", d);
1410 sspl += tv;
1411 }
1412 for (unsigned char s : sspl) out_vec.push_back(s);
1413
1414 // terminate
1415 out_vec.push_back(0x0d);
1416 out_vec.push_back(0x0a);
1417 break;
1418 }
1419 case N2KFormat_Actisense_N2K_ASCII: {
1420 // Create the time field
1421 wxDateTime now = wxDateTime::Now();
1422 wxString stime = now.Format("%H%M%S");
1423 stime += ".000 ";
1424 std::string sstime = stime.ToStdString();
1425 out_vec.push_back('A');
1426 for (unsigned char s : sstime) out_vec.push_back(s);
1427
1428 // src/dest/prio field
1429 wxString sdp;
1430 sdp.Printf("%02X%02X%1X ",
1431 1, // source
1432 (unsigned char)0xFF, 0x6);
1433 std::string ssdp = sdp.ToStdString();
1434 for (unsigned char s : ssdp) out_vec.push_back(s);
1435
1436 // PGN field
1437 wxString spgn;
1438 spgn.Printf("%05X ", pgn);
1439 std::string sspgn = spgn.ToStdString();
1440 for (unsigned char s : sspgn) out_vec.push_back(s);
1441
1442 // Data payload
1443 std::string sspl;
1444 char tv[3];
1445 for (unsigned char d : payload) {
1446 snprintf(tv, 3, "%02X", d);
1447 sspl += tv;
1448 }
1449 for (unsigned char s : sspl) out_vec.push_back(s);
1450
1451 // terminator
1452 out_vec.push_back(0x0d);
1453 out_vec.push_back(0x0a);
1454 break;
1455 }
1456 case N2KFormat_MiniPlex: {
1457 out_vec.push_back('$');
1458 out_vec.push_back('M');
1459 out_vec.push_back('X');
1460 out_vec.push_back('P');
1461 out_vec.push_back('G');
1462 out_vec.push_back('N');
1463 out_vec.push_back(',');
1464 // PGN field
1465 wxString spgn;
1466 spgn.Printf("%06X,", pgn);
1467 std::string sspgn = spgn.ToStdString();
1468 for (unsigned char c : sspgn) {
1469 out_vec.push_back(c);
1470 }
1471 // Attribute word
1472 uint16_t attr = 0;
1473 uint8_t len = 8;
1474
1475 attr |= ((uint16_t)0x06) << 12;
1476 attr |= ((uint16_t)payload.size()) << 8;
1477 attr |= (uint16_t)0xFF;
1478 attr |= 0x8000; // S bit set to 1
1479
1480 wxString sattr;
1481 sattr.Printf("%04X,", attr);
1482 std::string ssattr = sattr.ToStdString();
1483 for (unsigned char c : ssattr) {
1484 out_vec.push_back(c);
1485 }
1486 // Data payload
1487 char tv[3];
1488 for (auto rit = payload.rbegin(); rit != payload.rend(); ++rit) {
1489 snprintf(tv, 3, "%02X", *rit);
1490 out_vec.push_back(tv[0]);
1491 out_vec.push_back(tv[1]);
1492 }
1493 // CRC
1494 uint8_t crc = 0;
1495 for (auto ci = ++out_vec.begin(); ci != out_vec.end(); ci++) {
1496 crc ^= *ci;
1497 }
1498 out_vec.push_back('*');
1499 snprintf(tv, 3, "%02X", crc);
1500 out_vec.push_back(tv[0]);
1501 out_vec.push_back(tv[1]);
1502
1503 // term
1504 out_vec.push_back(0x0d);
1505 out_vec.push_back(0x0a);
1506 // DBG: std::cout << std::string(out_vec.begin(), out_vec.end()) <<
1507 // std::endl << std::flush;
1508 break;
1509 }
1510 default:
1511 break;
1512 }
1513 return out_vec;
1514}
1515
1516std::vector<std::vector<unsigned char>> CommDriverN2KNet::GetTxVector(
1517 const std::shared_ptr<const Nmea2000Msg>& msg,
1518 std::shared_ptr<const NavAddr2000> dest_addr) {
1519 std::vector<std::vector<unsigned char>> tx_vector;
1520
1521 // Branch based on detected network data format currently in use
1522 switch (m_n2k_format) {
1523 case N2KFormat_YD_RAW:
1524 break;
1525 case N2KFormat_Actisense_RAW_ASCII: {
1526 // 00:34:02.718 R 15FD0800 FF 00 01 CA 6F FF FF FF
1527 if (!IsFastMessagePGN(msg->PGN.pgn) && msg->payload.size() < 8) {
1528 // Single packet
1529 } else {
1530 std::vector<unsigned char> header_vec;
1531 std::vector<unsigned char> out_vec;
1532
1533 // No Need to create a timestamp or frame R/T indicator
1534#if 0
1535 // time header
1536 wxDateTime now = wxDateTime::Now();
1537 wxString stime = now.Format("%H:%M:%S");
1538 stime += ".000 ";
1539 std::string sstime = stime.ToStdString();
1540 for (unsigned char s : sstime) header_vec.push_back(s);
1541
1542 // Tx indicator
1543 header_vec.push_back('T');
1544 header_vec.push_back(' ');
1545#endif
1546
1547 // Craft the canID
1548 // No need to specify the source address
1549 // The TX frame will adopt the gateway's claimed N2K address.
1550 unsigned long can_id =
1551 BuildCanID(msg->priority, 0, dest_addr->address, msg->PGN.pgn);
1552 wxString scan_id;
1553 scan_id.Printf("%08X", can_id);
1554 std::string sscan_id = scan_id.ToStdString();
1555 for (unsigned char s : sscan_id) header_vec.push_back(s);
1556 header_vec.push_back(' ');
1557
1558 // format the required number of short packets, in a loop
1559 int payload_size = msg->payload.size();
1560 unsigned char temp[8]; // {0,0,0,0,0,0,0,0};
1561 int cur = 0;
1562 int nframes =
1563 (payload_size > 6 ? (payload_size - 6 - 1) / 7 + 1 + 1 : 1);
1564 bool result = true;
1565 for (int i = 0; i < nframes && result; i++) {
1566 temp[0] = i | m_order; // frame counter
1567 if (i == 0) {
1568 temp[1] = msg->payload.size(); // total bytes in fast packet
1569 // send the first 6 bytes
1570 for (int j = 2; j < 8; j++) {
1571 temp[j] = msg->payload.data()[cur];
1572 cur++;
1573 }
1574 } else {
1575 int j = 1;
1576 // send the next 7 data bytes
1577 for (; j < 8 && cur < payload_size; j++) {
1578 temp[j] = msg->payload.data()[cur];
1579 cur++;
1580 }
1581 for (; j < 8; j++) {
1582 temp[j] = 0xff;
1583 }
1584 }
1585
1586 out_vec.clear();
1587
1588 // constant header
1589 for (unsigned char s : header_vec) out_vec.push_back(s);
1590
1591 // data, per packet
1592 std::string ssdata;
1593 for (unsigned int k = 0; k < 8; k++) {
1594 char tb[4];
1595 snprintf(tb, 4, "%02X ", temp[k]);
1596 ssdata += tb;
1597 }
1598 for (unsigned char s : ssdata) out_vec.push_back(s);
1599 out_vec.pop_back(); // drop the last space character
1600
1601 out_vec.push_back(0x0d); // terminate the string
1602 out_vec.push_back(0x0a);
1603
1604 // for (char s :out_vec)
1605 // printf( "%c", s);
1606
1607 tx_vector.push_back(out_vec);
1608 } // for loop
1609 }
1610 } break;
1611 case N2KFormat_Actisense_N2K_ASCII: {
1612 // Source: Actisense own documentation `NMEA 2000 ASCII Output
1613 // format.docx`
1614 //
1615 // Ahhmmss.ddd <SS><DD><P> <PPPPP> b0b1b2b3b4b5b6b7.....bn<CR><LF>
1616 // A = message is N2K or J1939 message
1617 // 173321.107 - time 17:33:21.107
1618 // <SS> - source address
1619 // <DD> - destination address
1620 // <P> - priority
1621 // <PPPPP> - PGN number
1622 // b0b1b2b3b4b5b6b7.....bn - data payload in hex. NB: ISO TP payload could
1623 // be up to 1786 bytes
1624 //
1625 // Example: `A173321.107 23FF7 1F513 012F3070002F30709F\n`
1626 // 1 2 3 4
1627
1628 std::vector<unsigned char> ovec;
1629
1630 // Create the time field
1631 wxDateTime now = wxDateTime::Now();
1632 wxString stime = now.Format("%H%M%S");
1633 stime += ".000 ";
1634 std::string sstime = stime.ToStdString();
1635 ovec.push_back('A');
1636 for (unsigned char s : sstime) ovec.push_back(s);
1637
1638 // src/dest/prio field
1639 wxString sdp;
1640 sdp.Printf("%02X%02X%1X ",
1641 1, // source
1642 (unsigned char)dest_addr->address,
1643 (unsigned char)msg->priority);
1644 std::string ssdp = sdp.ToStdString();
1645 for (unsigned char s : ssdp) ovec.push_back(s);
1646
1647 // PGN field
1648 wxString spgn;
1649 spgn.Printf("%05X ", (int)msg->PGN.pgn);
1650 std::string sspgn = spgn.ToStdString();
1651 for (unsigned char s : sspgn) ovec.push_back(s);
1652
1653 // Data payload
1654 std::string sspl;
1655 char tv[3];
1656 for (unsigned char d : msg->payload) {
1657 snprintf(tv, 3, "%02X", d);
1658 sspl += tv;
1659 }
1660 for (unsigned char s : sspl) ovec.push_back(s);
1661
1662 // term
1663 ovec.push_back(0x0d);
1664 ovec.push_back(0x0a);
1665
1666 // form the result
1667 tx_vector.push_back(ovec);
1668
1669 break;
1670 }
1671 case N2KFormat_MiniPlex: {
1672 std::vector<unsigned char> ovec;
1673 if (!IsFastMessagePGN(msg->PGN.pgn) && msg->payload.size() < 8) {
1674 // Single packet
1675 } else {
1676 size_t cur = 0;
1677 size_t nframes =
1678 (msg->payload.size() > 6 ? (msg->payload.size() - 6 - 1) / 7 + 1 + 1
1679 : 1);
1680 for (size_t i = 0; i < nframes; i++) {
1681 ovec.push_back('$');
1682 ovec.push_back('M');
1683 ovec.push_back('X');
1684 ovec.push_back('P');
1685 ovec.push_back('G');
1686 ovec.push_back('N');
1687 ovec.push_back(',');
1688 // PGN field
1689 wxString spgn;
1690 spgn.Printf("%06X,", (int)msg->PGN.pgn);
1691 std::string sspgn = spgn.ToStdString();
1692 for (unsigned char c : sspgn) {
1693 ovec.push_back(c);
1694 }
1695 // Attribute word
1696 uint16_t attr = 0;
1697 uint8_t len = 8;
1698 if (i == nframes - 1) {
1699 len = msg->payload.size() + 1 - 6 - (nframes - 2) * 7;
1700 }
1701 attr |= ((uint16_t)((uint8_t)msg->priority & 0x07)) << 12;
1702 attr |= ((uint16_t)len) << 8;
1703 attr |= (uint16_t)dest_addr->address;
1704 attr |= 0x8000; // S bit set to 1
1705
1706 wxString sattr;
1707 sattr.Printf("%04X,", attr);
1708 std::string ssattr = sattr.ToStdString();
1709 for (unsigned char c : ssattr) {
1710 ovec.push_back(c);
1711 }
1712 // Data payload
1713 char tv[3];
1714 uint8_t databytes = i == 0 ? len - 2 : len - 1;
1715 std::vector<unsigned char> payload;
1716 for (uint8_t j = 0; j < databytes; j++) {
1717 payload.push_back(msg->payload[cur]);
1718 cur++;
1719 }
1720 for (auto rit = payload.rbegin(); rit != payload.rend(); ++rit) {
1721 snprintf(tv, 3, "%02X", *rit);
1722 ovec.push_back(tv[0]);
1723 ovec.push_back(tv[1]);
1724 }
1725 if (i == 0) { // First frame contains the total payload length
1726 snprintf(tv, 3, "%02X", (uint8_t)msg->payload.size());
1727 ovec.push_back(tv[0]);
1728 ovec.push_back(tv[1]);
1729 }
1730 // frame counter
1731 snprintf(tv, 3, "%02X", (uint8_t)i | m_order);
1732 ovec.push_back(tv[0]);
1733 ovec.push_back(tv[1]);
1734
1735 // CRC
1736 uint8_t crc = 0;
1737 for (auto ci = ++ovec.begin(); ci != ovec.end(); ci++) {
1738 crc ^= *ci;
1739 }
1740 ovec.push_back('*');
1741 snprintf(tv, 3, "%02X", crc);
1742 ovec.push_back(tv[0]);
1743 ovec.push_back(tv[1]);
1744
1745 // term
1746 ovec.push_back(0x0d);
1747 ovec.push_back(0x0a);
1748
1749 // DBG: std::cout << std::string(ovec.begin(), ovec.end()) <<
1750 // std::endl << std::flush;
1751
1752 // form the result
1753 tx_vector.push_back(ovec);
1754 ovec.clear();
1755 }
1756 m_order += 16;
1757 break;
1758 }
1759 }
1760 case N2KFormat_Actisense_N2K:
1761 break;
1762 case N2KFormat_Actisense_RAW:
1763 break;
1764 case N2KFormat_Actisense_NGT:
1765 break;
1766 case N2KFormat_SeaSmart:
1767 break;
1768 default:
1769 break;
1770 }
1771
1772 m_order += 16; // update the fast message order bits
1773
1774 return tx_vector;
1775}
1776
1777bool CommDriverN2KNet::PrepareForTX() {
1778 // We need to determine several items before TX operations can commence.
1779 // 1. Is the gateway configured at my ip present, and if so, which of
1780 // the two supported gateways is it? (YDEN-type, or Actisense-type.
1781 // 2. If Actisense type, we need to infer the N2K source address it has
1782 // claimed, so that we can use that address for our TX operations.
1783
1784 // BASIC ASSUMPTION: There is (or has been) enough network traffic to
1785 // allow occurate determination of data format currently in use
1786
1787 bool b_found = false;
1788
1789 // Step 1.1
1790 // If the detected data format is N2KFormat_Actisense_N2K_ASCII,
1791 // then we are clearly connected to an actisense device.
1792 // Nothing else need be done.
1793
1794 if (m_n2k_format == N2KFormat_Actisense_N2K_ASCII) return true;
1795
1796 // Step 1.2
1797 // If the detected data format is N2KFormat_MiniPlex,
1798 // then we are clearly connected to a MiniPlex.
1799 // Nothing else need be done.
1800
1801 if (m_n2k_format == N2KFormat_MiniPlex) return true;
1802
1803 // Step 1.2
1804 // If the detected data format is N2KFormat_SeaSmart,
1805 // then we can't transmit.
1806 if (m_n2k_format == N2KFormat_SeaSmart) return false;
1807
1808 // Step 2
1809
1810 // Assume that the gateway is YDEN type, RAW mode. Verify if true.
1811 // Logic: Actisense gateway will not respond to TX_FORMAT_YDEN,
1812 // so if we get sensible response, the gw must be YDEN type.
1813
1814 prod_info_map.clear();
1815
1816 // Send a broadcast request for PGN 126996, Product Information
1817 std::vector<unsigned char> payload;
1818 payload.push_back(0x14);
1819 payload.push_back(0xF0);
1820 payload.push_back(0x01);
1821
1822 std::vector<std::vector<unsigned char>> out_data;
1823 std::vector<unsigned char> msg_vec =
1824 MakeSimpleOutMsg(N2KFormat_YD_RAW, 59904, payload);
1825 out_data.push_back(msg_vec);
1826 SendSentenceNetwork(out_data);
1827
1828 // Wait some time, and study results
1829 wxMilliSleep(200);
1830 wxYield();
1831
1832 // Check the results of the PGN 126996 capture
1833 for (const auto& [key, value] : prod_info_map) {
1834 auto prod_info = value;
1835 if (prod_info.Model_ID.find("YDEN") != std::string::npos) {
1836 // Found a YDEN device
1837 // If this configured port is actually connector to YDEN,
1838 // then the device will have marked the received TCP packet
1839 // with "T" indicator. Check it.
1840 if (prod_info.RT_flag == 'T') b_found = true;
1841 break;
1842 }
1843 }
1844
1845 if (b_found) return true;
1846
1847 // No acceptable TX device found
1848 return false;
1849}
1850
1851bool CommDriverN2KNet::SendN2KNetwork(std::shared_ptr<const Nmea2000Msg>& msg,
1852 std::shared_ptr<const NavAddr2000> addr) {
1853 PrepareForTX();
1854
1855 std::vector<std::vector<unsigned char>> out_data = GetTxVector(msg, addr);
1856 SendSentenceNetwork(out_data);
1857 return true;
1858};
1859
1860bool CommDriverN2KNet::SendSentenceNetwork(
1861 std::vector<std::vector<unsigned char>> payload) {
1862 if (m_txenter)
1863 return false; // do not allow recursion, could happen with non-blocking
1864 // sockets
1865 m_txenter++;
1866
1867 bool ret = true;
1868 wxDatagramSocket* udp_socket;
1869 switch (GetProtocol()) {
1870 case TCP:
1871 for (std::vector<unsigned char>& v : payload) {
1872 if (GetSock() && GetSock()->IsOk()) {
1873 // printf("---%s", v.data());
1874 GetSock()->Write(v.data(), v.size());
1875 if (GetSock()->Error()) {
1876 if (GetSockServer()) {
1877 GetSock()->Destroy();
1878 SetSock(NULL);
1879 } else {
1880 wxSocketClient* tcp_socket =
1881 dynamic_cast<wxSocketClient*>(GetSock());
1882 if (tcp_socket) tcp_socket->Close();
1883 if (!GetSocketTimer()->IsRunning())
1884 GetSocketTimer()->Start(
1885 5000, wxTIMER_ONE_SHOT); // schedule a reconnect
1886 GetSocketThreadWatchdogTimer()->Stop();
1887 }
1888 ret = false;
1889 }
1890 wxMilliSleep(200);
1891 } else
1892 ret = false;
1893 }
1894 break;
1895 case UDP:
1896#if 0
1897 udp_socket = dynamic_cast<wxDatagramSocket*>(GetTSock());
1898 if (udp_socket && udp_socket->IsOk()) {
1899 udp_socket->SendTo(GetAddr(), payload.mb_str(), payload.size());
1900 if (udp_socket->Error()) ret = false;
1901 } else
1902 ret = false;
1903#endif
1904 break;
1905
1906 case GPSD:
1907 default:
1908 ret = false;
1909 break;
1910 }
1911 m_txenter--;
1912 return ret;
1913}
1914
1915void CommDriverN2KNet::Close() {
1916 wxLogMessage(wxString::Format(_T("Closing NMEA NetworkDataStream %s"),
1917 GetNetPort().c_str()));
1918 // Kill off the TCP Socket if alive
1919 if (m_sock) {
1920 if (m_is_multicast)
1921 m_sock->SetOption(IPPROTO_IP, IP_DROP_MEMBERSHIP, &m_mrq_container->m_mrq,
1922 sizeof(m_mrq_container->m_mrq));
1923 m_sock->Notify(FALSE);
1924 m_sock->Destroy();
1925 }
1926
1927 if (m_tsock) {
1928 m_tsock->Notify(FALSE);
1929 m_tsock->Destroy();
1930 }
1931
1932 if (m_socket_server) {
1933 m_socket_server->Notify(FALSE);
1934 m_socket_server->Destroy();
1935 }
1936
1937 m_socket_timer.Stop();
1938 m_socketread_watchdog_timer.Stop();
1939}
1940
1941bool CommDriverN2KNet::SetOutputSocketOptions(wxSocketBase* tsock) {
1942 int ret;
1943
1944 // Disable nagle algorithm on outgoing connection
1945 // Doing this here rather than after the accept() is
1946 // pointless on platforms where TCP_NODELAY is
1947 // not inherited. However, none of OpenCPN's currently
1948 // supported platforms fall into that category.
1949
1950 int nagleDisable = 1;
1951 ret = tsock->SetOption(IPPROTO_TCP, TCP_NODELAY, &nagleDisable,
1952 sizeof(nagleDisable));
1953
1954 // Drastically reduce the size of the socket output buffer
1955 // so that when client goes away without properly closing, the stream will
1956 // quickly fill the output buffer, and thus fail the write() call
1957 // within a few seconds.
1958 unsigned long outbuf_size = 1024; // Smallest allowable value on Linux
1959 return (tsock->SetOption(SOL_SOCKET, SO_SNDBUF, &outbuf_size,
1960 sizeof(outbuf_size)) &&
1961 ret);
1962}
CAN v2.0 29 bit header as used by NMEA 2000.
CanHeader()
CAN v2.0 29 bit header as used by NMEA 2000.
bool IsFastMessage() const
Return true if header reflects a multipart fast message.
void OnSocketEvent(wxSocketEvent &event)
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.
Track fast message fragments eventually forming complete messages.
int AddNewEntry(void)
Allocate a new, fresh entry and return index to it.
void Remove(int pos)
Remove entry at pos.
bool AppendEntry(const CanHeader hdr, const unsigned char *data, int index)
Append fragment to existing multipart message.
int FindMatchingEntry(const CanHeader header, const unsigned char sid)
Setter.
bool InsertEntry(const CanHeader header, const unsigned char *data, int index)
Insert a new entry, first part of a multipart message.
Adds a std::shared<void> element to wxCommandEvent.
Driver registration container, a singleton.
Raw messages layer, supports sending and recieving navmsg messages.
Suspend/resume and new devices events exchange point.