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