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#include "model/nmea_log.h"
53
54wxDEFINE_EVENT(EVT_N0183_MUX, ObservedEvt);
55
56wxDEFINE_EVENT(EVT_N2K_129029, ObservedEvt);
57wxDEFINE_EVENT(EVT_N2K_129025, ObservedEvt);
58wxDEFINE_EVENT(EVT_N2K_129026, ObservedEvt);
59wxDEFINE_EVENT(EVT_N2K_127250, ObservedEvt);
60wxDEFINE_EVENT(EVT_N2K_129540, ObservedEvt);
61wxDEFINE_EVENT(EVT_N2K_ALL, ObservedEvt);
62
63Multiplexer *g_pMUX;
64
65Multiplexer::Multiplexer(MuxLogCallbacks cb, bool &filter_behaviour)
66 : m_log_callbacks(cb), m_legacy_input_filter_behaviour(filter_behaviour) {
67 m_listener_N0183_all.Listen(Nmea0183Msg::MessageKey("ALL"), this,
68 EVT_N0183_MUX);
69 Bind(EVT_N0183_MUX, [&](ObservedEvt ev) {
70 auto ptr = ev.GetSharedPtr();
71 auto n0183_msg = std::static_pointer_cast<const Nmea0183Msg>(ptr);
72 HandleN0183(n0183_msg);
73 });
74
75 InitN2KCommListeners();
76 n_N2K_repeat = 0;
77
78 if (g_GPS_Ident.IsEmpty()) g_GPS_Ident = wxT("Generic");
79}
80
81Multiplexer::~Multiplexer() {}
82
83void Multiplexer::LogOutputMessage(const std::shared_ptr<const NavMsg> &msg,
84 NavmsgStatus ns) {
85 if (m_log_callbacks.log_is_active()) {
86 ns.direction = NavmsgStatus::Direction::kOutput;
87 Logline ll(msg, ns);
88 m_log_callbacks.log_message(ll);
89 }
90}
91
92void Multiplexer::LogInputMessage(const std::shared_ptr<const NavMsg> &msg,
93 bool is_filtered, bool is_error,
94 const wxString error_msg) {
95 if (m_log_callbacks.log_is_active()) {
96 NavmsgStatus ns;
97 ns.direction = NavmsgStatus::Direction::kReceived;
98 if (is_error) {
99 ns.status = NavmsgStatus::State::kChecksumError;
100 } else {
101 if (is_filtered) {
102 if (m_legacy_input_filter_behaviour) {
103 ns.accepted = NavmsgStatus::Accepted::kFilteredNoOutput;
104 } else {
105 ns.accepted = NavmsgStatus::Accepted::kFilteredDropped;
106 }
107 } else {
108 ns.accepted = NavmsgStatus::Accepted::kOk;
109 }
110 }
111 Logline ll(msg, ns);
112 ll.error_msg = error_msg;
113 m_log_callbacks.log_message(ll);
114 }
115}
116
117void Multiplexer::HandleN0183(std::shared_ptr<const Nmea0183Msg> n0183_msg) {
118 // Find the driver that originated this message
119
120 const auto &drivers = CommDriverRegistry::GetInstance().GetDrivers();
121 auto &source_driver = FindDriver(drivers, n0183_msg->source->iface);
122
123 wxString fmsg;
124 bool bpass_input_filter = true;
125
126 // Send to the Debug Window, if open
127 // Special formatting for non-printable characters helps debugging NMEA
128 // problems
129 std::string str = n0183_msg->payload;
130
131 // Get the params for the driver sending this message
132 ConnectionParams params;
133 auto drv_serial = dynamic_cast<CommDriverN0183Serial *>(source_driver.get());
134 if (drv_serial) {
135 params = drv_serial->GetParams();
136 } else {
137 auto drv_net = dynamic_cast<CommDriverN0183Net *>(source_driver.get());
138 if (drv_net) {
139 params = drv_net->GetParams();
140 }
141#ifdef __ANDROID__
142 else {
143 auto drv_bluetooth =
144 dynamic_cast<CommDriverN0183AndroidBT *>(source_driver.get());
145
146 if (drv_bluetooth) {
147 params = drv_bluetooth->GetParams();
148 }
149 }
150#endif
151 }
152
153 // Check to see if the message passes the source's input filter
154 bpass_input_filter =
155 params.SentencePassesFilter(n0183_msg->payload.c_str(), FILTER_INPUT);
156
157 bool b_error = false;
158 wxString error_msg;
159 for (std::string::iterator it = str.begin(); it != str.end(); ++it) {
160 if (isprint(*it))
161 fmsg += *it;
162 else {
163 wxString bin_print;
164 bin_print.Printf(_T("<0x%02X>"), *it);
165 fmsg += bin_print;
166 if ((*it != 0x0a) && (*it != 0x0d)) {
167 b_error = true;
168 error_msg = _("Non-printable character in NMEA0183 message");
169 }
170 }
171 }
172
173 // FIXME (dave) Flag checksum errors, but fix and process the sentence
174 // anyway
175 // std::string goodMessage(message);
176 // bool checksumOK = CheckSumCheck(event.GetNMEAString());
177 // if (!checksumOK) {
178 // goodMessage = stream->FixChecksum(goodMessage);
179 // goodEvent->SetNMEAString(goodMessage);
180 //}
181
182 wxString port(n0183_msg->source->iface);
183 LogInputMessage(n0183_msg, !bpass_input_filter, b_error, error_msg);
184
185 // Detect virtual driver, message comes from plugin API
186 // Set such source iface to "" for later test
187 std::string source_iface;
188 if (source_driver) // NULL for virtual driver
189 source_iface = source_driver->iface;
190
191 // Perform multiplexer output functions
192 for (auto &driver : drivers) {
193 if (driver->bus == NavAddr::Bus::N0183) {
194 ConnectionParams params;
195 auto drv_serial = dynamic_cast<CommDriverN0183Serial *>(driver.get());
196 if (drv_serial) {
197 params = drv_serial->GetParams();
198 } else {
199 auto drv_net = dynamic_cast<CommDriverN0183Net *>(driver.get());
200 if (drv_net) {
201 params = drv_net->GetParams();
202 }
203#ifdef __ANDROID__
204 else {
205 auto drv_bluetooth =
206 dynamic_cast<CommDriverN0183AndroidBT *>(driver.get());
207 if (drv_bluetooth) {
208 params = drv_bluetooth->GetParams();
209 }
210 }
211#endif
212 }
213
214 std::shared_ptr<const Nmea0183Msg> msg = n0183_msg;
215 if ((m_legacy_input_filter_behaviour && !bpass_input_filter) ||
216 bpass_input_filter) {
217 // Allow re-transmit on same port (if type is SERIAL),
218 // or any other NMEA0183 port supporting output
219 // But, do not echo to the source network interface. This will likely
220 // recurse...
221 if ((!params.DisableEcho && params.Type == SERIAL) ||
222 driver->iface != source_iface) {
223 if (params.IOSelect == DS_TYPE_INPUT_OUTPUT ||
224 params.IOSelect == DS_TYPE_OUTPUT) {
225 bool bout_filter = true;
226 bool bxmit_ok = true;
227 std::string id("XXXXX");
228 size_t comma_pos = n0183_msg->payload.find(",");
229 if (comma_pos != std::string::npos && comma_pos > 5)
230 id = n0183_msg->payload.substr(1, comma_pos - 1);
231 if (params.SentencePassesFilter(n0183_msg->payload.c_str(),
232 FILTER_OUTPUT)) {
233 // Reset source address. It's const, so make a modified copy
234
235 auto null_addr = std::make_shared<NavAddr>();
236 msg = std::make_shared<Nmea0183Msg>(id, n0183_msg->payload,
237 null_addr);
238 bxmit_ok = driver->SendMessage(msg, null_addr);
239 bout_filter = false;
240 }
241
242 // Send to the Debug Window, if open
243 if (m_log_callbacks.log_is_active()) {
244 NavmsgStatus ns;
245 ns.direction = NavmsgStatus::Direction::kOutput;
246 if (bout_filter) {
247 ns.accepted = NavmsgStatus::Accepted::kFilteredDropped;
248 } else {
249 if (!bxmit_ok) ns.status = NavmsgStatus::State::kTxError;
250 }
251 auto logaddr = std::make_shared<NavAddr0183>(driver->iface);
252 auto logmsg = std::make_shared<Nmea0183Msg>(
253 id, n0183_msg->payload, logaddr);
254 LogOutputMessage(logmsg, ns);
255 }
256 }
257 }
258 }
259 }
260 }
261}
262
263void Multiplexer::InitN2KCommListeners() {
264 // Create a series of N2K listeners
265 // to allow minimal N2K Debug window logging
266
267 // All N2K
268 //----------------------------------
269 Nmea2000Msg n2k_msg_All(static_cast<uint64_t>(1));
270 listener_N2K_All.Listen(n2k_msg_All, this, EVT_N2K_ALL);
271 Bind(EVT_N2K_ALL, [&](ObservedEvt ev) {
272 HandleN2K_Log(UnpackEvtPointer<Nmea2000Msg>(ev));
273 });
274}
275
276bool Multiplexer::HandleN2K_Log(std::shared_ptr<const Nmea2000Msg> n2k_msg) {
277 if (!m_log_callbacks.log_is_active()) return false;
278
279 auto payload = n2k_msg.get()->payload;
280 // extract PGN
281 unsigned int pgn = 0;
282 pgn += n2k_msg.get()->payload.at(3);
283 pgn += n2k_msg.get()->payload.at(4) << 8;
284 pgn += n2k_msg.get()->payload.at(5) << 16;
285
286#if 0
287 printf(" %d: payload\n", pgn);
288 for(size_t i=0; i< payload.size(); i++){
289 printf("%02X ", payload.at(i));
290 }
291 printf("\n");
292 std::string pretty = n2k_msg->to_string();
293 printf("%s\n\n", pretty.c_str());
294#endif
295
296 // Input, or output?
297 if (payload.at(0) == 0x94) { // output
298 NavmsgStatus ns;
299 ns.direction = NavmsgStatus::Direction::kOutput;
300 LogOutputMessage(n2k_msg, ns);
301 } else { // input
302 // extract data source
303 std::string source = n2k_msg.get()->source->to_string();
304
305 // extract source ID
306 unsigned char source_id = n2k_msg.get()->payload.at(7);
307 char ss[4];
308 sprintf(ss, "%d", source_id);
309 std::string ident = std::string(ss);
310
311 if (pgn == last_pgn_logged) {
312 n_N2K_repeat++;
313 return false;
314 } else {
315 if (n_N2K_repeat) {
316 wxString repeat_log_msg;
317 repeat_log_msg.Printf("...Repeated %d times\n", n_N2K_repeat);
318 // LogInputMessage(repeat_log_msg, "N2000", false, false); FIXME(leamas)
319 n_N2K_repeat = 0;
320 }
321 }
322
323 wxString log_msg;
324 log_msg.Printf("PGN: %d Source: %s ID: %s Desc: %s\n", pgn, source, ident,
325 N2K_LogMessage_Detail(pgn).c_str());
326
327 LogInputMessage(n2k_msg, false, false);
328
329 last_pgn_logged = pgn;
330 }
331 return true;
332}
333
334std::string Multiplexer::N2K_LogMessage_Detail(unsigned int pgn) {
335 std::string notused = "Not used by OCPN, maybe by Plugins";
336
337 switch (pgn) {
338 case 129029:
339 return "GNSS Position & DBoard: SAT System";
340 case 129025:
341 return "Position rapid";
342 case 129026:
343 return "COG/SOG rapid";
344 case 129038:
345 return "AIS Class A position report";
346 case 129039:
347 return "AIS Class B position report";
348 case 129041:
349 return "AIS Aids to Navigation (AtoN) Report";
350 case 129793:
351 return "AIS Base Station report";
352 case 129794:
353 return "AIS static data class A";
354 ;
355 case 129809:
356 return "AIS static data class B part A";
357 case 129810:
358 return "AIS static data class B part B";
359 case 127250:
360 return "Heading rapid";
361 case 129540:
362 return "GNSS Sats & DBoard: SAT Status";
363 //>> Dashboard
364 case 127245:
365 return "DBoard: Rudder data";
366 case 127257:
367 return "DBoard: Roll Pitch";
368 case 128259:
369 return "DBoard: Speed through water";
370 ;
371 case 128267:
372 return "DBoard: Depth Data";
373 case 128275:
374 return "DBoard: Distance log";
375 case 130306:
376 return "DBoard: Wind data";
377 case 130310:
378 return "DBoard: Envorinment data";
379 // Not used PGNs
380 case 126992:
381 return "System time. " + notused;
382 case 127233:
383 return "Man Overboard Notification. " + notused;
384 case 127237:
385 return "Heading/Track control. " + notused;
386 case 127251:
387 return "Rate of turn. " + notused;
388 case 127258:
389 return "Magnetic variation. " + notused;
390 case 127488:
391 return "Engine rapid param. " + notused;
392 case 127489:
393 return "Engine parameters dynamic. " + notused;
394 case 127493:
395 return "Transmission parameters dynamic. " + notused;
396 case 127497:
397 return "Trip Parameters, Engine. " + notused;
398 case 127501:
399 return "Binary status report. " + notused;
400 case 127505:
401 return "Fluid level. " + notused;
402 case 127506:
403 return "DC Detailed Status. " + notused;
404 case 127507:
405 return "Charger Status. " + notused;
406 case 127508:
407 return "Battery Status. " + notused;
408 case 127513:
409 return "Battery Configuration Status. " + notused;
410 case 128000:
411 return "Leeway. " + notused;
412 case 128776:
413 return "Windlass Control Status. " + notused;
414 case 128777:
415 return "Windlass Operating Status. " + notused;
416 case 128778:
417 return "Windlass Monitoring Status. " + notused;
418 case 129033:
419 return "Date,Time & Local offset. " + notused;
420 case 129539:
421 return "GNSS DOP data. " + notused;
422 case 129283:
423 return "Cross Track Error. " + notused;
424 case 129284:
425 return "Navigation info. " + notused;
426 case 129285:
427 return "Waypoint list. " + notused;
428 case 129802:
429 return "AIS Safety Related Broadcast Message. " + notused;
430 case 130074:
431 return "Waypoint list. " + notused;
432 case 130311:
433 return "Environmental parameters. " + notused;
434 case 130312:
435 return "Temperature. " + notused;
436 case 130313:
437 return "Humidity. " + notused;
438 case 130314:
439 return "Actual Pressure. " + notused;
440 case 130315:
441 return "Set Pressure. " + notused;
442 case 130316:
443 return "Temperature extended range. " + notused;
444 case 130323:
445 return "Meteorological Station Data. " + notused;
446 case 130576:
447 return "Trim Tab Position. " + notused;
448 case 130577:
449 return "Direction Data. " + notused;
450 default:
451 return "No description. Not used by OCPN, maybe passed to plugins";
452 }
453}
const std::vector< DriverPtr > & GetDrivers() const
void LogInputMessage(const std::shared_ptr< const NavMsg > &msg, bool is_filtered, bool is_error, const wxString error_msg="")
Logs an input message with context information.
Representation of message status as determined by the multiplexer.
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.
Custom event class for OpenCPN's notification system.
std::shared_ptr< const void > GetSharedPtr() const
Gets the event's payload data.
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.
Item in the log window.
Definition nmea_log.h:10