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