OpenCPN Partial API docs
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"
47 #include "model/comm_drv_registry.h"
49 #include "model/comm_drv_n0183_net.h"
50 #include "model/comm_drv_n0183_android_bt.h"
51 #include "model/comm_navmsg_bus.h"
52 
53 wxDEFINE_EVENT(EVT_N0183_MUX, ObservedEvt);
54 
55 wxDEFINE_EVENT(EVT_N2K_129029, ObservedEvt);
56 wxDEFINE_EVENT(EVT_N2K_129025, ObservedEvt);
57 wxDEFINE_EVENT(EVT_N2K_129026, ObservedEvt);
58 wxDEFINE_EVENT(EVT_N2K_127250, ObservedEvt);
59 wxDEFINE_EVENT(EVT_N2K_129540, ObservedEvt);
60 wxDEFINE_EVENT(EVT_N2K_ALL, ObservedEvt);
61 
62 Multiplexer* g_pMUX;
63 
64 #ifdef HAVE_READLINK
65 
66 static 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 
89 static 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 
97 static bool inline is_same_device(const char *port1, const char *port2) {
98  return strcmp(port1, port2) == 0;
99 }
100 
101 #endif // HAVE_READLINK
102 
103 Multiplexer::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 
121 Multiplexer::~Multiplexer() {}
122 
123 void Multiplexer::LogOutputMessageColor(const wxString &msg,
124  const wxString &stream_name,
125  const wxString &color) {
126  if (m_log_callbacks.log_is_active()) {
127  wxDateTime now = wxDateTime::Now();
128  wxString ss;
129 #ifndef __WXQT__ // Date/Time on Qt are broken, at least for android
130  ss = now.FormatISOTime();
131 #endif
132  ss.Prepend(_T("--> "));
133  ss.Append(_T(" ("));
134  ss.Append(stream_name);
135  ss.Append(_T(") "));
136  ss.Append(msg);
137  ss.Prepend(color);
138 
139  m_log_callbacks.log_message(ss.ToStdString());
140  }
141 }
142 
143 void Multiplexer::LogOutputMessage(const wxString &msg, wxString stream_name,
144  bool b_filter) {
145  if (b_filter)
146  LogOutputMessageColor(msg, stream_name, _T("<CORAL>"));
147  else
148  LogOutputMessageColor(msg, stream_name, _T("<BLUE>"));
149 }
150 
151 void Multiplexer::LogInputMessage(const wxString &msg,
152  const wxString &stream_name, bool b_filter,
153  bool b_error) {
154  if (m_log_callbacks.log_is_active()) {
155  wxDateTime now = wxDateTime::Now();
156  wxString ss;
157 #ifndef __WXQT__ // Date/Time on Qt are broken, at least for android
158  ss = now.FormatISOTime();
159 #endif
160  ss.Append(_T(" ("));
161  ss.Append(stream_name);
162  ss.Append(_T(") "));
163  ss.Append(msg);
164  if (b_error) {
165  ss.Prepend(_T("<RED>"));
166  } else {
167  if (b_filter)
168  if (m_legacy_input_filter_behaviour)
169  ss.Prepend(_T("<CORAL>"));
170  else
171  ss.Prepend(_T("<MAROON>"));
172  else
173  ss.Prepend(_T("<GREEN>"));
174  }
175  m_log_callbacks.log_message(ss.ToStdString());
176  }
177 }
178 
179 void Multiplexer::HandleN0183(std::shared_ptr<const Nmea0183Msg> n0183_msg) {
180  // Find the driver that originated this message
181 
182  const auto& drivers = CommDriverRegistry::GetInstance().GetDrivers();
183  auto source_driver = FindDriver(drivers, n0183_msg->source->iface);
184 
185  wxString fmsg;
186  bool bpass_input_filter = true;
187 
188  // Send to the Debug Window, if open
189  // Special formatting for non-printable characters helps debugging NMEA
190  // problems
191  if (m_log_callbacks.log_is_active()) {
192  std::string str = n0183_msg->payload;
193 
194  // Get the params for the driver sending this message
195  ConnectionParams params;
196  auto drv_serial =
197  std::dynamic_pointer_cast<CommDriverN0183Serial>(source_driver);
198  if (drv_serial) {
199  params = drv_serial->GetParams();
200  } else {
201  auto drv_net = std::dynamic_pointer_cast<CommDriverN0183Net>(source_driver);
202  if (drv_net) {
203  params = drv_net->GetParams();
204  }
205 #ifdef __ANDROID__
206  else {
207  auto drv_bluetooth = std::dynamic_pointer_cast<CommDriverN0183AndroidBT>(source_driver);
208  if (drv_bluetooth) {
209  params = drv_bluetooth->GetParams();
210  }
211  }
212 #endif
213  }
214 
215  // Check to see if the message passes the source's input filter
216  bpass_input_filter = params.SentencePassesFilter(n0183_msg->payload.c_str(),
217  FILTER_INPUT);
218 
219  bool b_error = false;
220  for (std::string::iterator it = str.begin(); it != str.end(); ++it) {
221  if (isprint(*it))
222  fmsg += *it;
223  else {
224  wxString bin_print;
225  bin_print.Printf(_T("<0x%02X>"), *it);
226  fmsg += bin_print;
227  if ((*it != 0x0a) && (*it != 0x0d)) b_error = true;
228  }
229  }
230 
231  // FIXME (dave) Flag checksum errors, but fix and process the sentence anyway
232  //std::string goodMessage(message);
233  //bool checksumOK = CheckSumCheck(event.GetNMEAString());
234  //if (!checksumOK) {
235  //goodMessage = stream->FixChecksum(goodMessage);
236  //goodEvent->SetNMEAString(goodMessage);
237  //}
238 
239 
240  wxString port(n0183_msg->source->iface);
241  LogInputMessage(fmsg, port, !bpass_input_filter, b_error);
242  }
243 
244  // Detect virtual driver, message comes from plugin API
245  // Set such source iface to "" for later test
246  std::string source_iface;
247  if (source_driver) // NULL for virtual driver
248  source_iface = source_driver->iface;
249 
250 
251  // Perform multiplexer output functions
252  for (auto& driver : drivers) {
253 
254  if (driver->bus == NavAddr::Bus::N0183) {
255  ConnectionParams params;
256  auto drv_serial =
257  std::dynamic_pointer_cast<CommDriverN0183Serial>(driver);
258  if (drv_serial) {
259  params = drv_serial->GetParams();
260  } else {
261  auto drv_net = std::dynamic_pointer_cast<CommDriverN0183Net>(driver);
262  if (drv_net) {
263  params = drv_net->GetParams();
264  }
265 #ifdef __ANDROID__
266  else {
267  auto drv_bluetooth = std::dynamic_pointer_cast<CommDriverN0183AndroidBT>(driver);
268  if (drv_bluetooth) {
269  params = drv_bluetooth->GetParams();
270  }
271  }
272 #endif
273  }
274 
275  if ((m_legacy_input_filter_behaviour && !bpass_input_filter) ||
276  bpass_input_filter) {
277 
278  // Allow re-transmit on same port (if type is SERIAL),
279  // or any any other NMEA0183 port supporting output
280  // But, do not echo to the source network interface. This will likely recurse...
281  if ((!params.DisableEcho && params.Type == SERIAL) || driver->iface != source_iface) {
282  if (params.IOSelect == DS_TYPE_INPUT_OUTPUT ||
283  params.IOSelect == DS_TYPE_OUTPUT)
284  {
285  bool bout_filter = true;
286  bool bxmit_ok = true;
287  if (params.SentencePassesFilter(n0183_msg->payload.c_str(),
288  FILTER_OUTPUT)) {
289  bxmit_ok = driver->SendMessage(n0183_msg,
290  std::make_shared<NavAddr0183>(driver->iface));
291  bout_filter = false;
292  }
293 
294  // Send to the Debug Window, if open
295  if (!bout_filter) {
296  if (bxmit_ok)
297  LogOutputMessageColor(fmsg, driver->iface, _T("<BLUE>"));
298  else
299  LogOutputMessageColor(fmsg, driver->iface, _T("<RED>"));
300  } else
301  LogOutputMessageColor(fmsg, driver->iface, _T("<CORAL>"));
302  }
303  }
304  }
305  }
306  }
307 }
308 
309 void Multiplexer::InitN2KCommListeners() {
310  // Initialize the comm listeners
311  auto& msgbus = NavMsgBus::GetInstance();
312 
313  // Create a series of N2K listeners
314  // to allow minimal N2K Debug window logging
315 
316  // All N2K
317  //----------------------------------
318  Nmea2000Msg n2k_msg_All(static_cast<uint64_t>(1));
319  listener_N2K_All.Listen(n2k_msg_All, this, EVT_N2K_ALL);
320  Bind(EVT_N2K_ALL, [&](ObservedEvt ev) {
321  HandleN2K_Log(UnpackEvtPointer<Nmea2000Msg>(ev));
322  });
323 }
324 
325 bool Multiplexer::HandleN2K_Log(std::shared_ptr<const Nmea2000Msg> n2k_msg) {
326  if (!m_log_callbacks.log_is_active())
327  return false;
328 
329  // extract PGN
330  unsigned int pgn = 0;
331  pgn += n2k_msg.get()->payload.at(3);
332  pgn += n2k_msg.get()->payload.at(4) << 8;
333  pgn += n2k_msg.get()->payload.at(5) << 16;
334 
335  // extract data source
336  std::string source = n2k_msg.get()->source->to_string();
337 
338  // extract source ID
339  unsigned char source_id = n2k_msg.get()->payload.at(7);
340  char ss[4];
341  sprintf(ss, "%d", source_id);
342  std::string ident = std::string(ss);
343 
344  if (pgn == last_pgn_logged) {
345  n_N2K_repeat++;
346  return false;
347  }
348  else {
349  if(n_N2K_repeat) {
350  wxString repeat_log_msg;
351  repeat_log_msg.Printf("...Repeated %d times\n", n_N2K_repeat);
352  LogInputMessage(repeat_log_msg, "N2000", false, false);
353  n_N2K_repeat = 0;
354  }
355  }
356 
357  wxString log_msg;
358  log_msg.Printf("PGN: %d Source: %s ID: %s Desc: %s\n", pgn, source,
359  ident,N2K_LogMessage_Detail(pgn, n2k_msg).c_str());
360 
361  LogInputMessage(log_msg, "N2000", false, false);
362 
363  last_pgn_logged = pgn;
364  return true;
365 }
366 
367 
368 std::string Multiplexer::N2K_LogMessage_Detail(unsigned int pgn, std::shared_ptr<const Nmea2000Msg> n2k_msg) {
369  std::string notused = "Not used by OCPN, maybe by Plugins";
370 
371  switch (pgn){
372  case 129029:
373  return "GNSS Position & DBoard: SAT System";
374  break;
375  case 129025:
376  return "Position rapid";
377  break;
378  case 129026:
379  return "COG/SOG rapid";
380  break;
381  case 129038:
382  return "AIS Class A position report";
383  break;
384  case 129039:
385  return "AIS Class B position report";
386  break;
387  case 129041:
388  return "AIS Aids to Navigation (AtoN) Report";
389  break;
390  case 129793:
391  return "AIS Base Station report";
392  break;
393  case 129794:
394  return "AIS static data class A";
395  break;
396  case 129809:
397  return "AIS static data class B part A";
398  break;
399  case 129810:
400  return "AIS static data class B part B";
401  break;
402  case 127250:
403  return "Heading rapid";
404  break;
405  case 129540:
406  return "GNSS Sats & DBoard: SAT Status";
407  break;
408  //>> Dashboard
409  case 127245:
410  return "DBoard: Rudder data";
411  break;
412  case 127257:
413  return "DBoard: Roll Pitch";
414  break;
415  case 128259:
416  return "DBoard: Speed through water";
417  break;
418  case 128267:
419  return "DBoard: Depth Data";
420  break;
421  case 128275:
422  return "DBoard: Distance log";
423  break;
424  case 130306:
425  return "DBoard: Wind data";
426  break;
427  case 130310:
428  return "DBoard: Envorinment data";
429  break;
430  // Not used PGNs
431  case 126992:
432  return "System time. " + notused;
433  break;
434  case 127233:
435  return "Man Overboard Notification. " + notused;
436  break;
437  case 127237:
438  return "Heading/Track control. " + notused;
439  break;
440  case 127251:
441  return "Rate of turn. " + notused;
442  break;
443  case 127258:
444  return "Magnetic variation. " + notused;
445  break;
446  case 127488:
447  return "Engine rapid param. " + notused;
448  break;
449  case 127489:
450  return "Engine parameters dynamic. " + notused;
451  break;
452  case 127493:
453  return "Transmission parameters dynamic. " + notused;
454  break;
455  case 127497:
456  return "Trip Parameters, Engine. " + notused;
457  break;
458  case 127501:
459  return "Binary status report. " + notused;
460  break;
461  case 127505:
462  return "Fluid level. " + notused;
463  break;
464  case 127506:
465  return "DC Detailed Status. " + notused;
466  break;
467  case 127507:
468  return "Charger Status. " + notused;
469  break;
470  case 127508:
471  return "Battery Status. " + notused;
472  break;
473  case 127513:
474  return "Battery Configuration Status. " + notused;
475  break;
476  case 128000:
477  return "Leeway. " + notused;
478  break;
479  case 128776:
480  return "Windlass Control Status. " + notused;
481  break;
482  case 128777:
483  return "Windlass Operating Status. " + notused;
484  break;
485  case 128778:
486  return "Windlass Monitoring Status. " + notused;
487  break;
488  case 129033:
489  return "Date,Time & Local offset. " + notused;
490  break;
491  case 129539:
492  return "GNSS DOP data. " + notused;
493  break;
494  case 129283:
495  return "Cross Track Error. " + notused;
496  break;
497  case 129284:
498  return "Navigation info. " + notused;
499  break;
500  case 129285:
501  return "Waypoint list. " + notused;
502  break;
503  case 129802:
504  return "AIS Safety Related Broadcast Message. " + notused;
505  break;
506  case 130074:
507  return "Waypoint list. " + notused;
508  break;
509  case 130311:
510  return "Environmental parameters. " + notused;
511  break;
512  case 130312:
513  return "Temperature. " + notused;
514  break;
515  case 130313:
516  return "Humidity. " + notused;
517  break;
518  case 130314:
519  return "Actual Pressure. " + notused;
520  break;
521  case 130315:
522  return "Set Pressure. " + notused;
523  break;
524  case 130316:
525  return "Temperature extended range. " + notused;
526  break;
527  case 130323:
528  return "Meteorological Station Data. " + notused;
529  break;
530  case 130576:
531  return "Trim Tab Position. " + notused;
532  break;
533  case 130577:
534  return "Direction Data. " + notused;
535  break;
536  default:
537  return "No description. Not used by OCPN, maybe passed to plugins";
538  }
539 }
const std::vector< DriverPtr > & GetDrivers()
static std::string MessageKey(const char *type="ALL")
Return key which should be used to listen to given message type.
Definition: comm_navmsg.h:281
See: https://github.com/OpenCPN/OpenCPN/issues/2729#issuecomment-1179506343.
Definition: comm_navmsg.h:220
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Definition: observable.cpp:98
Adds a std::shared<void> element to wxCommandEvent.
Definition: ocpn_plugin.h:1652
NMEA0183 serial driver.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.