OpenCPN Partial API docs
Loading...
Searching...
No Matches
multiplexer.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: NMEA Data Multiplexer Object
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2010 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26#ifdef __MSVC__
27#include "winsock2.h"
28#include <wx/msw/winundef.h>
29#endif
30
31#include "config.h"
32
33#ifdef HAVE_LIBGEN_H
34#include <libgen.h>
35#endif
36
37#if defined(HAVE_READLINK) && !defined(HAVE_LIBGEN_H)
38#error Using readlink(3) requires libgen.h which cannot be found.
39#endif
40
41#include <wx/wx.h>
42
43#include "model/multiplexer.h"
44
45#include "model/config_vars.h"
46#include "model/conn_params.h"
50#include "model/comm_drv_n0183_android_bt.h"
52
53wxDEFINE_EVENT(EVT_N0183_MUX, ObservedEvt);
54
55wxDEFINE_EVENT(EVT_N2K_129029, ObservedEvt);
56wxDEFINE_EVENT(EVT_N2K_129025, ObservedEvt);
57wxDEFINE_EVENT(EVT_N2K_129026, ObservedEvt);
58wxDEFINE_EVENT(EVT_N2K_127250, ObservedEvt);
59wxDEFINE_EVENT(EVT_N2K_129540, ObservedEvt);
60wxDEFINE_EVENT(EVT_N2K_ALL, ObservedEvt);
61
62Multiplexer *g_pMUX;
63
64#ifdef HAVE_READLINK
65
66static std::string do_readlink(const char *link) {
67 // Strip possible Serial: or Usb: prefix:
68 const char *colon = strchr(link, ':');
69 const char *path = colon ? colon + 1 : link;
70
71 char target[PATH_MAX + 1] = {0};
72 int r = readlink(path, target, sizeof(target));
73 if (r == -1 && (errno == EINVAL || errno == ENOENT)) {
74 // Not a a symlink
75 return path;
76 }
77 if (r == -1) {
78 wxLogDebug("Error reading device link %s: %s", path, strerror(errno));
79 return path;
80 }
81 if (*target == '/') {
82 return target;
83 }
84 char buff[PATH_MAX + 1];
85 memcpy(buff, path, std::min(strlen(path) + 1, (size_t)PATH_MAX));
86 return std::string(dirname(buff)) + "/" + target;
87}
88
89static bool is_same_device(const char *port1, const char *port2) {
90 std::string dev1 = do_readlink(port1);
91 std::string dev2 = do_readlink(port2);
92 return dev1 == dev2;
93}
94
95#else // HAVE_READLINK
96
97static bool inline is_same_device(const char *port1, const char *port2) {
98 return strcmp(port1, port2) == 0;
99}
100
101#endif // HAVE_READLINK
102
103Multiplexer::Multiplexer(MuxLogCallbacks cb, bool &filter_behaviour)
104 : m_log_callbacks(cb), m_legacy_input_filter_behaviour(filter_behaviour) {
105 auto &msgbus = NavMsgBus::GetInstance();
106
107 m_listener_N0183_all.Listen(Nmea0183Msg::MessageKey("ALL"), this,
108 EVT_N0183_MUX);
109 Bind(EVT_N0183_MUX, [&](ObservedEvt ev) {
110 auto ptr = ev.GetSharedPtr();
111 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
112 HandleN0183(n0183_msg);
113 });
114
115 InitN2KCommListeners();
116 n_N2K_repeat = 0;
117
118 if (g_GPS_Ident.IsEmpty()) g_GPS_Ident = wxT("Generic");
119}
120
121Multiplexer::~Multiplexer() {}
122
123static const wxChar SYMBOL_INPUT = 0x2190; // ← LEFT ARROW
124static const wxChar SYMBOL_OUTPUT = 0x2192; // → RIGHT ARROW
125static const wxChar SYMBOL_ERROR = 0x2716; // ✖ MULTIPLICATION X
126static const wxChar SYMBOL_FILTERED = 0x269F; // ⚟ FALLING DIAGONAL
127static const wxChar SYMBOL_DROPPED = 0x2298; // ⊘ CIRCLED DIVISION SLASH
128static const wxChar SYMBOL_ACCEPTED = 0x2713; // ✓ CHECK MARK
129
130void Multiplexer::LogOutputMessageColor(const wxString &msg,
131 const wxString &stream_name,
132 const wxString &color) {
133 if (m_log_callbacks.log_is_active()) {
134 wxDateTime now = wxDateTime::Now();
135 wxString ss;
136#ifndef __WXQT__ // Date/Time on Qt are broken, at least for android
137 ss = now.FormatISOTime();
138#endif
139 ss.Append(" ").Append(SYMBOL_OUTPUT).Append(" ");
140 if (color == "<RED>") {
141 ss.Append(SYMBOL_ERROR);
142 } else if (color == "<CORAL>") {
143 ss.Append(SYMBOL_DROPPED);
144 } else {
145 ss.Append(SYMBOL_ACCEPTED);
146 }
147 ss.Append(" (");
148 ss.Append(stream_name);
149 ss.Append(") ");
150 ss.Append(msg);
151 ss.Prepend(color);
152
153 m_log_callbacks.log_message(ss);
154 }
155}
156
157void Multiplexer::LogOutputMessage(const wxString &msg, wxString stream_name,
158 bool b_filter) {
159 if (b_filter)
160 LogOutputMessageColor(msg, stream_name, _T("<CORAL>"));
161 else
162 LogOutputMessageColor(msg, stream_name, _T("<BLUE>"));
163}
164
165void Multiplexer::LogInputMessage(const wxString &msg,
166 const wxString &stream_name, bool b_filter,
167 bool b_error, const wxString error_msg) {
168 if (m_log_callbacks.log_is_active()) {
169 wxDateTime now = wxDateTime::Now();
170 wxString ss;
171#ifndef __WXQT__ // Date/Time on Qt are broken, at least for android
172 ss = now.FormatISOTime();
173#endif
174 ss.Append(" ").Append(SYMBOL_INPUT);
175 if (b_error) {
176 ss.Prepend("<RED>");
177 ss.Append(" ").Append(SYMBOL_ERROR);
178 } else {
179 if (b_filter) {
180 if (m_legacy_input_filter_behaviour) {
181 ss.Prepend("<CORAL>");
182 ss.Append(" ").Append(SYMBOL_FILTERED);
183 } else {
184 ss.Prepend("<MAROON>");
185 ss.Append(" ").Append(SYMBOL_DROPPED);
186 }
187 } else {
188 ss.Prepend("<GREEN>");
189 ss.Append(" ").Append(SYMBOL_ACCEPTED);
190 }
191 }
192 ss.Append(" (");
193 ss.Append(stream_name);
194 ss.Append(") ");
195 ss.Append(msg);
196 if (b_error) {
197 ss.Append(" - ");
198 if (!error_msg.IsEmpty())
199 ss.Append(error_msg);
200 else
201 ss.Append(_("Unknown error"));
202 }
203 m_log_callbacks.log_message(ss);
204 }
205}
206
207void Multiplexer::HandleN0183(std::shared_ptr<const Nmea0183Msg> n0183_msg) {
208 // Find the driver that originated this message
209
210 const auto &drivers = CommDriverRegistry::GetInstance().GetDrivers();
211 auto &source_driver = FindDriver(drivers, n0183_msg->source->iface);
212
213 wxString fmsg;
214 bool bpass_input_filter = true;
215
216 // Send to the Debug Window, if open
217 // Special formatting for non-printable characters helps debugging NMEA
218 // problems
219 if (m_log_callbacks.log_is_active()) {
220 std::string str = n0183_msg->payload;
221
222 // Get the params for the driver sending this message
223 ConnectionParams params;
224 auto drv_serial =
225 dynamic_cast<CommDriverN0183Serial *>(source_driver.get());
226 if (drv_serial) {
227 params = drv_serial->GetParams();
228 } else {
229 auto drv_net = dynamic_cast<CommDriverN0183Net *>(source_driver.get());
230 if (drv_net) {
231 params = drv_net->GetParams();
232 }
233#ifdef __ANDROID__
234 else {
235 auto drv_bluetooth =
236 dynamic_cast<CommDriverN0183AndroidBT *>(source_driver.get());
237
238 if (drv_bluetooth) {
239 params = drv_bluetooth->GetParams();
240 }
241 }
242#endif
243 }
244
245 // Check to see if the message passes the source's input filter
246 bpass_input_filter =
247 params.SentencePassesFilter(n0183_msg->payload.c_str(), FILTER_INPUT);
248
249 bool b_error = false;
250 wxString error_msg;
251 for (std::string::iterator it = str.begin(); it != str.end(); ++it) {
252 if (isprint(*it))
253 fmsg += *it;
254 else {
255 wxString bin_print;
256 bin_print.Printf(_T("<0x%02X>"), *it);
257 fmsg += bin_print;
258 if ((*it != 0x0a) && (*it != 0x0d)) {
259 b_error = true;
260 error_msg = _("Non-printable character in NMEA0183 message");
261 }
262 }
263 }
264
265 // FIXME (dave) Flag checksum errors, but fix and process the sentence
266 // anyway
267 // std::string goodMessage(message);
268 // bool checksumOK = CheckSumCheck(event.GetNMEAString());
269 // if (!checksumOK) {
270 // goodMessage = stream->FixChecksum(goodMessage);
271 // goodEvent->SetNMEAString(goodMessage);
272 //}
273
274 wxString port(n0183_msg->source->iface);
275 LogInputMessage(fmsg, port, !bpass_input_filter, b_error, error_msg);
276 }
277
278 // Detect virtual driver, message comes from plugin API
279 // Set such source iface to "" for later test
280 std::string source_iface;
281 if (source_driver) // NULL for virtual driver
282 source_iface = source_driver->iface;
283
284 // Perform multiplexer output functions
285 for (auto &driver : drivers) {
286 if (driver->bus == NavAddr::Bus::N0183) {
287 ConnectionParams params;
288 auto drv_serial = dynamic_cast<CommDriverN0183Serial *>(driver.get());
289 if (drv_serial) {
290 params = drv_serial->GetParams();
291 } else {
292 auto drv_net = dynamic_cast<CommDriverN0183Net *>(driver.get());
293 if (drv_net) {
294 params = drv_net->GetParams();
295 }
296#ifdef __ANDROID__
297 else {
298 auto drv_bluetooth =
299 dynamic_cast<CommDriverN0183AndroidBT *>(driver.get());
300 if (drv_bluetooth) {
301 params = drv_bluetooth->GetParams();
302 }
303 }
304#endif
305 }
306
307 if ((m_legacy_input_filter_behaviour && !bpass_input_filter) ||
308 bpass_input_filter) {
309 // Allow re-transmit on same port (if type is SERIAL),
310 // or any any other NMEA0183 port supporting output
311 // But, do not echo to the source network interface. This will likely
312 // recurse...
313 if ((!params.DisableEcho && params.Type == SERIAL) ||
314 driver->iface != source_iface) {
315 if (params.IOSelect == DS_TYPE_INPUT_OUTPUT ||
316 params.IOSelect == DS_TYPE_OUTPUT) {
317 bool bout_filter = true;
318 bool bxmit_ok = true;
319 if (params.SentencePassesFilter(n0183_msg->payload.c_str(),
320 FILTER_OUTPUT)) {
321 // Reset source address. It's const, so make a modified copy
322 std::string id("XXXXX");
323 unsigned comma_pos = n0183_msg->payload.find(",");
324 if (comma_pos != std::string::npos && comma_pos > 5)
325 id = n0183_msg->payload.substr(1, comma_pos - 1);
326 auto null_addr = std::make_shared<NavAddr>();
327 auto msg = std::make_shared<Nmea0183Msg>(id, n0183_msg->payload,
328 null_addr);
329 bxmit_ok = driver->SendMessage(msg, null_addr);
330 bout_filter = false;
331 }
332
333 // Send to the Debug Window, if open
334 if (!bout_filter) {
335 if (bxmit_ok)
336 LogOutputMessageColor(fmsg, driver->iface, _T("<BLUE>"));
337 else
338 LogOutputMessageColor(fmsg, driver->iface, _T("<RED>"));
339 } else
340 LogOutputMessageColor(fmsg, driver->iface, _T("<CORAL>"));
341 }
342 }
343 }
344 }
345 }
346}
347
348void Multiplexer::InitN2KCommListeners() {
349 // Initialize the comm listeners
350 auto &msgbus = NavMsgBus::GetInstance();
351
352 // Create a series of N2K listeners
353 // to allow minimal N2K Debug window logging
354
355 // All N2K
356 //----------------------------------
357 Nmea2000Msg n2k_msg_All(static_cast<uint64_t>(1));
358 listener_N2K_All.Listen(n2k_msg_All, this, EVT_N2K_ALL);
359 Bind(EVT_N2K_ALL, [&](ObservedEvt ev) {
360 HandleN2K_Log(UnpackEvtPointer<Nmea2000Msg>(ev));
361 });
362}
363
364bool Multiplexer::HandleN2K_Log(std::shared_ptr<const Nmea2000Msg> n2k_msg) {
365 if (!m_log_callbacks.log_is_active()) return false;
366
367 // extract PGN
368 unsigned int pgn = 0;
369 pgn += n2k_msg.get()->payload.at(3);
370 pgn += n2k_msg.get()->payload.at(4) << 8;
371 pgn += n2k_msg.get()->payload.at(5) << 16;
372
373 // extract data source
374 std::string source = n2k_msg.get()->source->to_string();
375
376 // extract source ID
377 unsigned char source_id = n2k_msg.get()->payload.at(7);
378 char ss[4];
379 sprintf(ss, "%d", source_id);
380 std::string ident = std::string(ss);
381
382 if (pgn == last_pgn_logged) {
383 n_N2K_repeat++;
384 return false;
385 } else {
386 if (n_N2K_repeat) {
387 wxString repeat_log_msg;
388 repeat_log_msg.Printf("...Repeated %d times\n", n_N2K_repeat);
389 LogInputMessage(repeat_log_msg, "N2000", false, false);
390 n_N2K_repeat = 0;
391 }
392 }
393
394 wxString log_msg;
395 log_msg.Printf("PGN: %d Source: %s ID: %s Desc: %s\n", pgn, source, ident,
396 N2K_LogMessage_Detail(pgn, n2k_msg).c_str());
397
398 LogInputMessage(log_msg, "N2000", false, false);
399
400 last_pgn_logged = pgn;
401 return true;
402}
403
404std::string Multiplexer::N2K_LogMessage_Detail(
405 unsigned int pgn, std::shared_ptr<const Nmea2000Msg> n2k_msg) {
406 std::string notused = "Not used by OCPN, maybe by Plugins";
407
408 switch (pgn) {
409 case 129029:
410 return "GNSS Position & DBoard: SAT System";
411 break;
412 case 129025:
413 return "Position rapid";
414 break;
415 case 129026:
416 return "COG/SOG rapid";
417 break;
418 case 129038:
419 return "AIS Class A position report";
420 break;
421 case 129039:
422 return "AIS Class B position report";
423 break;
424 case 129041:
425 return "AIS Aids to Navigation (AtoN) Report";
426 break;
427 case 129793:
428 return "AIS Base Station report";
429 break;
430 case 129794:
431 return "AIS static data class A";
432 break;
433 case 129809:
434 return "AIS static data class B part A";
435 break;
436 case 129810:
437 return "AIS static data class B part B";
438 break;
439 case 127250:
440 return "Heading rapid";
441 break;
442 case 129540:
443 return "GNSS Sats & DBoard: SAT Status";
444 break;
445 //>> Dashboard
446 case 127245:
447 return "DBoard: Rudder data";
448 break;
449 case 127257:
450 return "DBoard: Roll Pitch";
451 break;
452 case 128259:
453 return "DBoard: Speed through water";
454 break;
455 case 128267:
456 return "DBoard: Depth Data";
457 break;
458 case 128275:
459 return "DBoard: Distance log";
460 break;
461 case 130306:
462 return "DBoard: Wind data";
463 break;
464 case 130310:
465 return "DBoard: Envorinment data";
466 break;
467 // Not used PGNs
468 case 126992:
469 return "System time. " + notused;
470 break;
471 case 127233:
472 return "Man Overboard Notification. " + notused;
473 break;
474 case 127237:
475 return "Heading/Track control. " + notused;
476 break;
477 case 127251:
478 return "Rate of turn. " + notused;
479 break;
480 case 127258:
481 return "Magnetic variation. " + notused;
482 break;
483 case 127488:
484 return "Engine rapid param. " + notused;
485 break;
486 case 127489:
487 return "Engine parameters dynamic. " + notused;
488 break;
489 case 127493:
490 return "Transmission parameters dynamic. " + notused;
491 break;
492 case 127497:
493 return "Trip Parameters, Engine. " + notused;
494 break;
495 case 127501:
496 return "Binary status report. " + notused;
497 break;
498 case 127505:
499 return "Fluid level. " + notused;
500 break;
501 case 127506:
502 return "DC Detailed Status. " + notused;
503 break;
504 case 127507:
505 return "Charger Status. " + notused;
506 break;
507 case 127508:
508 return "Battery Status. " + notused;
509 break;
510 case 127513:
511 return "Battery Configuration Status. " + notused;
512 break;
513 case 128000:
514 return "Leeway. " + notused;
515 break;
516 case 128776:
517 return "Windlass Control Status. " + notused;
518 break;
519 case 128777:
520 return "Windlass Operating Status. " + notused;
521 break;
522 case 128778:
523 return "Windlass Monitoring Status. " + notused;
524 break;
525 case 129033:
526 return "Date,Time & Local offset. " + notused;
527 break;
528 case 129539:
529 return "GNSS DOP data. " + notused;
530 break;
531 case 129283:
532 return "Cross Track Error. " + notused;
533 break;
534 case 129284:
535 return "Navigation info. " + notused;
536 break;
537 case 129285:
538 return "Waypoint list. " + notused;
539 break;
540 case 129802:
541 return "AIS Safety Related Broadcast Message. " + notused;
542 break;
543 case 130074:
544 return "Waypoint list. " + notused;
545 break;
546 case 130311:
547 return "Environmental parameters. " + notused;
548 break;
549 case 130312:
550 return "Temperature. " + notused;
551 break;
552 case 130313:
553 return "Humidity. " + notused;
554 break;
555 case 130314:
556 return "Actual Pressure. " + notused;
557 break;
558 case 130315:
559 return "Set Pressure. " + notused;
560 break;
561 case 130316:
562 return "Temperature extended range. " + notused;
563 break;
564 case 130323:
565 return "Meteorological Station Data. " + notused;
566 break;
567 case 130576:
568 return "Trim Tab Position. " + notused;
569 break;
570 case 130577:
571 return "Direction Data. " + notused;
572 break;
573 default:
574 return "No description. Not used by OCPN, maybe passed to plugins";
575 }
576}
const std::vector< DriverPtr > & GetDrivers() const
void LogInputMessage(const wxString &msg, const wxString &stream_name, bool b_filter, bool b_error=false, const wxString error_msg=wxEmptyString)
Logs an input message with context information.
static std::string MessageKey(const char *type="ALL")
Return key which should be used to listen to given message type.
See: https://github.com/OpenCPN/OpenCPN/issues/2729#issuecomment-1179506343.
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Adds a std::shared<void> element to wxCommandEvent.
NMEA0183 over IP driver.
NMEA0183 serial driver.
DriverPtr & FindDriver(const std::vector< DriverPtr > &drivers, const std::string &iface, const NavAddr::Bus _bus)
Search list of drivers for a driver with given interface string.
Driver registration container, a singleton.
Raw messages layer, supports sending and recieving navmsg messages.