NMEA: GSV satellite info field SNR is optional
[marnav.git] / examples / qtnmeadiag / MainWindow.cpp
blob8f94aa934f11358ea7b527b2c8654e98a4000d22
1 #include "MainWindow.hpp"
2 #include <vector>
3 #include <QComboBox>
4 #include <QCoreApplication>
5 #include <QFileDialog>
6 #include <QFontDatabase>
7 #include <QGridLayout>
8 #include <QLabel>
9 #include <QLineEdit>
10 #include <QListWidget>
11 #include <QMenuBar>
12 #include <QMessageBox>
13 #include <QPushButton>
14 #include <QSerialPort>
15 #include <QTextStream>
16 #include <QToolBar>
17 #include <marnav/nmea/checksum.hpp>
18 #include <marnav/nmea/nmea.hpp>
19 #include <marnav/nmea/bod.hpp>
20 #include <marnav/nmea/gga.hpp>
21 #include <marnav/nmea/gll.hpp>
22 #include <marnav/nmea/gsa.hpp>
23 #include <marnav/nmea/gsv.hpp>
24 #include <marnav/nmea/hdg.hpp>
25 #include <marnav/nmea/mwv.hpp>
26 #include <marnav/nmea/rmb.hpp>
27 #include <marnav/nmea/rmc.hpp>
28 #include <marnav/nmea/rte.hpp>
29 #include <marnav/nmea/vtg.hpp>
30 #include <marnav/nmea/pgrme.hpp>
31 #include <marnav/nmea/pgrmm.hpp>
32 #include <marnav/nmea/pgrmz.hpp>
33 #include <marnav/nmea/string.hpp>
35 namespace marnav_example
37 /// @cond DEV
38 namespace detail
40 static QString render(const marnav::nmea::status & t)
42 return marnav::nmea::to_string(t).c_str();
45 static QString render(const marnav::nmea::direction & t)
47 return marnav::nmea::to_string(t).c_str();
50 static QString render(const marnav::nmea::reference & t)
52 return marnav::nmea::to_string(t).c_str();
55 static QString render(const marnav::nmea::date & t)
57 return marnav::nmea::to_string(t).c_str();
60 static QString render(const marnav::nmea::quality & t)
62 return marnav::nmea::to_string(t).c_str();
65 static QString render(const marnav::nmea::pgrmz::fix_type & t)
67 return marnav::nmea::to_string(t).c_str();
70 static QString render(const marnav::nmea::unit::distance & t)
72 return marnav::nmea::to_string(t).c_str();
75 static QString render(const marnav::nmea::unit::velocity & t)
77 return marnav::nmea::to_string(t).c_str();
80 static QString render(uint32_t t)
82 return QString{"%1"}.arg(t);
85 static QString render(const std::string & t)
87 return QString{t.c_str()};
90 static QString render(const marnav::nmea::time & t)
92 return QString{"%1:%2:%3"}
93 .arg(t.hour(), 2, 10, QLatin1Char('0'))
94 .arg(t.minutes(), 2, 10, QLatin1Char('0'))
95 .arg(t.seconds(), 2, 10, QLatin1Char('0'));
98 static QString render(const marnav::geo::latitude & t)
100 return QString{" %1°%2'%3%4"}
101 .arg(t.degrees(), 2, 10, QLatin1Char('0'))
102 .arg(t.minutes(), 2, 10, QLatin1Char('0'))
103 .arg(t.seconds(), 2, 'f', 1, QLatin1Char('0'))
104 .arg(to_string(t.hem()).c_str());
107 static QString render(const marnav::geo::longitude & t)
109 return QString{"%1°%2'%3%4"}
110 .arg(t.degrees(), 3, 10, QLatin1Char('0'))
111 .arg(t.minutes(), 2, 10, QLatin1Char('0'))
112 .arg(t.seconds(), 2, 'f', 1, QLatin1Char('0'))
113 .arg(to_string(t.hem()).c_str());
116 static QString render(const marnav::nmea::selection_mode & t)
118 switch (t) {
119 case marnav::nmea::selection_mode::manual:
120 return QString{"manual"};
121 case marnav::nmea::selection_mode::automatic:
122 return QString{"automatic"};
123 default:
124 break;
126 return "-";
129 static QString render(const marnav::nmea::route & t)
131 switch (t) {
132 case marnav::nmea::route::complete:
133 return QString{"complete"};
134 case marnav::nmea::route::working:
135 return QString{"working"};
136 default:
137 break;
139 return "-";
142 static QString render(const marnav::nmea::waypoint & t)
144 return t.c_str();
147 static QString render(const marnav::nmea::mode_indicator & t)
149 switch (t) {
150 case marnav::nmea::mode_indicator::invalid:
151 return QString{"invalid"};
152 case marnav::nmea::mode_indicator::autonomous:
153 return QString{"autonomous"};
154 case marnav::nmea::mode_indicator::differential:
155 return QString{"differential"};
156 case marnav::nmea::mode_indicator::estimated:
157 return QString{"estimated"};
158 case marnav::nmea::mode_indicator::manual_input:
159 return QString{"manual input"};
160 case marnav::nmea::mode_indicator::simulated:
161 return QString{"simulated"};
162 case marnav::nmea::mode_indicator::data_not_valid:
163 return QString{"not valid"};
164 case marnav::nmea::mode_indicator::precise:
165 return QString{"precise"};
166 default:
167 break;
169 return "-";
172 static QString render(const marnav::units::length & t)
174 return QString{"%1 NM"}.arg(t.get<marnav::units::nautical_miles>().value());
177 static QString render(const marnav::units::velocity & t)
179 return QString{"%1 knots"}.arg(t.get<marnav::units::knots>().value());
182 template <typename T> static QString render(const marnav::utils::optional<T> & t)
184 if (!t)
185 return "-";
186 return render(*t);
189 static QString details_rmb(const marnav::nmea::sentence * s)
191 const auto t = marnav::nmea::sentence_cast<marnav::nmea::rmb>(s);
192 QString result;
193 result += "\nActive : " + render(t->get_active());
194 result += "\nCross Track Error: " + render(t->get_cross_track_error());
195 result += "\nWaypoint From : " + render(t->get_waypoint_from());
196 result += "\nWaypoint To : " + render(t->get_waypoint_to());
197 result += "\nLatitude : " + render(t->get_lat());
198 result += "\nLongitude : " + render(t->get_lon());
199 result += "\nRange : " + render(t->get_range());
200 result += "\nBearing : " + render(t->get_bearing());
201 result += "\nDest. Velocity : " + render(t->get_dst_velocity());
202 result += "\nArrival Status : " + render(t->get_arrival_status());
203 result += "\nMode Indicator : " + render(t->get_mode_ind());
204 return result;
207 static QString details_rmc(const marnav::nmea::sentence * s)
209 const auto t = marnav::nmea::sentence_cast<marnav::nmea::rmc>(s);
210 QString result;
211 result += "\nTime UTC : " + render(t->get_time_utc());
212 result += "\nStatus : " + render(t->get_status());
213 result += "\nLatitude : " + render(t->get_lat());
214 result += "\nLongitude: " + render(t->get_lon());
215 result += "\nSOG : " + render(t->get_sog());
216 result += "\nHeading : " + render(t->get_heading());
217 result += "\nDate : " + render(t->get_date());
218 result += "\nMagn Dev : " + render(t->get_mag());
219 result += "\nMagn Hem : " + render(t->get_mag_hem());
220 result += "\nMode Ind : " + render(t->get_mode_ind());
221 return result;
224 static QString details_mwv(const marnav::nmea::sentence * s)
226 const auto t = marnav::nmea::sentence_cast<marnav::nmea::mwv>(s);
227 QString result;
228 result += "\nAngle : " + render(t->get_angle());
229 result += "\nAngle Ref : " + render(t->get_angle_ref());
230 result += "\nSpeed : " + render(t->get_speed());
231 result += "\nSpeed Unit: " + render(t->get_speed_unit());
232 result += "\nData Valid: " + render(t->get_data_valid());
233 return result;
236 static QString details_gga(const marnav::nmea::sentence * s)
238 const auto t = marnav::nmea::sentence_cast<marnav::nmea::gga>(s);
239 QString result;
240 result += "\nTime : " + render(t->get_time());
241 result += "\nLatitude : " + render(t->get_lat());
242 result += "\nLongitude : " + render(t->get_lon());
243 result += "\nQuality Ind : " + render(t->get_quality_indicator());
244 result += "\nNum Satellites : " + render(t->get_n_satellites());
245 result += "\nHoriz Dilution : " + render(t->get_hor_dilution());
246 result += "\nAltitude : " + render(t->get_altitude());
247 result += "\nGeodial Sep : " + render(t->get_geodial_separation());
248 result += "\nDGPS Age : " + render(t->get_dgps_age());
249 result += "\nDGPS Ref : " + render(t->get_dgps_ref());
250 return result;
253 static QString details_gsv(const marnav::nmea::sentence * s)
255 const auto t = marnav::nmea::sentence_cast<marnav::nmea::gsv>(s);
256 QString result;
257 result += "\nNum Messages : " + render(t->get_n_messages());
258 result += "\nMessages Number: " + render(t->get_message_number());
259 result += "\nNum Sat in View: " + render(t->get_n_satellites_in_view());
260 for (int i = 0; i < 4; ++i) {
261 const auto sat = t->get_sat(i);
262 if (sat) {
263 result += QString{"\nSat: PRN:%1 ELEV:%2 AZIMUTH:%3 SNR:%4"}
264 .arg(render(sat->prn), 2)
265 .arg(render(sat->elevation), 2)
266 .arg(render(sat->azimuth), 3)
267 .arg(render(sat->snr), 2);
270 return result;
273 static QString details_gsa(const marnav::nmea::sentence * s)
275 const auto t = marnav::nmea::sentence_cast<marnav::nmea::gsa>(s);
276 QString result;
277 result += "\nSelection Mode: " + render(t->get_sel_mode());
278 result += "\nMode : " + render(t->get_mode());
279 for (auto i = 0; i < marnav::nmea::gsa::max_satellite_ids; ++i) {
280 result += QString{"\nSatellite %1 : %2"}.arg(i, 2).arg(render(t->get_satellite_id(i)));
282 result += "\nPDOP : " + render(t->get_pdop());
283 result += "\nHDOP : " + render(t->get_hdop());
284 result += "\nVDOP : " + render(t->get_vdop());
285 return result;
288 static QString details_gll(const marnav::nmea::sentence * s)
290 const auto t = marnav::nmea::sentence_cast<marnav::nmea::gll>(s);
291 QString result;
292 result += "\nLatitude : " + render(t->get_lat());
293 result += "\nLongitude : " + render(t->get_lon());
294 result += "\nTime UTC : " + render(t->get_time_utc());
295 result += "\nStatus : " + render(t->get_data_valid());
296 result += "\nMode Indicator: " + render(t->get_mode_ind());
297 return result;
300 static QString details_bod(const marnav::nmea::sentence * s)
302 const auto t = marnav::nmea::sentence_cast<marnav::nmea::bod>(s);
303 QString result;
304 result += "\nBearing True : " + render(t->get_bearing_true());
305 result += "\nBearing Magn : " + render(t->get_bearing_magn());
306 result += "\nWaypoint To : " + render(t->get_waypoint_to());
307 result += "\nWaypoint From: " + render(t->get_waypoint_from());
308 return result;
311 static QString details_vtg(const marnav::nmea::sentence * s)
313 const auto t = marnav::nmea::sentence_cast<marnav::nmea::vtg>(s);
314 QString result;
315 result += "\nTrack True : " + render(t->get_track_true());
316 result += "\nTrack Magn : " + render(t->get_track_magn());
317 result += "\nSpeed Knots : " + render(t->get_speed_kn());
318 result += "\nSpeed kmh : " + render(t->get_speed_kmh());
319 result += "\nMode Indicator: " + render(t->get_mode_ind());
320 return result;
323 static QString details_pgrme(const marnav::nmea::sentence * s)
325 const auto t = marnav::nmea::sentence_cast<marnav::nmea::pgrme>(s);
326 QString result;
327 result += "\nHPE : " + render(t->get_horizontal_position_error());
328 result += "\nVPE : " + render(t->get_vertical_position_error());
329 result += "\nOverall sph equiv pos err: "
330 + render(t->get_overall_spherical_equiv_position_error());
331 return result;
334 static QString details_pgrmm(const marnav::nmea::sentence * s)
336 const auto t = marnav::nmea::sentence_cast<marnav::nmea::pgrmm>(s);
337 QString result;
338 result += "\nMap Datum: " + render(t->get_map_datum());
339 return result;
342 static QString details_pgrmz(const marnav::nmea::sentence * s)
344 const auto t = marnav::nmea::sentence_cast<marnav::nmea::pgrmz>(s);
345 QString result;
346 result += "\nAltitude: " + render(t->get_altitude()) + " feet";
347 result += "\nFix Type: " + render(t->get_fix());
348 return result;
351 static QString details_hdg(const marnav::nmea::sentence * s)
353 const auto t = marnav::nmea::sentence_cast<marnav::nmea::hdg>(s);
354 QString result;
355 result += "\nHeading : " + render(t->get_heading());
356 result += "\nMagn Deviation: " + render(t->get_magn_dev()) + " "
357 + render(t->get_magn_dev_hem());
358 result += "\nMagn Variation: " + render(t->get_magn_var()) + " "
359 + render(t->get_magn_var_hem());
360 return result;
363 static QString details_rte(const marnav::nmea::sentence * s)
365 const auto t = marnav::nmea::sentence_cast<marnav::nmea::rte>(s);
366 QString result;
367 result += "\nNum Messages : " + render(t->get_n_messages());
368 result += "\nMessage Number: " + render(t->get_message_number());
369 result += "\nMessage Mode : " + render(t->get_message_mode());
370 for (int i = 0; i < marnav::nmea::rte::max_waypoints; ++i) {
371 const auto wp = t->get_waypoint_id(i);
372 if (!wp)
373 break;
374 result += QString{"\nWaypoint ID %1: %2"}.arg(i, 2).arg(render(wp));
376 return result;
379 /// @endcond
381 static QString get_details(const marnav::nmea::sentence * s)
383 #define ADD_SENTENCE(s) \
385 marnav::nmea::s::ID, detail::details_##s \
388 struct entry {
389 marnav::nmea::sentence_id id;
390 std::function<QString(const marnav::nmea::sentence *)> func;
392 using container = std::vector<entry>;
393 static const container details = {
394 // common
395 ADD_SENTENCE(bod), ADD_SENTENCE(gga), ADD_SENTENCE(gsa), ADD_SENTENCE(gll),
396 ADD_SENTENCE(gsv), ADD_SENTENCE(hdg), ADD_SENTENCE(mwv), ADD_SENTENCE(rmb),
397 ADD_SENTENCE(rmc), ADD_SENTENCE(rte), ADD_SENTENCE(vtg),
399 // vendor specific
400 ADD_SENTENCE(pgrme), ADD_SENTENCE(pgrmm), ADD_SENTENCE(pgrmz),
402 #undef ADD_SENTENCE
404 auto i = std::find_if(begin(details), end(details),
405 [s](const container::value_type & entry) { return entry.id == s->id(); });
407 return (i == end(details)) ? QString{"unknown"} : i->func(s);
410 MainWindow::MainWindow()
412 setWindowTitle(QCoreApplication::instance()->applicationName());
414 create_actions();
415 create_menus();
416 setup_ui();
418 port = new QSerialPort(this);
421 MainWindow::~MainWindow()
423 on_close();
426 void MainWindow::create_menus()
428 auto menu_file = menuBar()->addMenu(tr("&File"));
429 menu_file->addAction(action_open_file);
430 menu_file->addSeparator();
431 menu_file->addAction(action_open_port);
432 menu_file->addAction(action_close_port);
433 menu_file->addSeparator();
434 menu_file->addAction(action_exit);
436 auto menu_help = menuBar()->addMenu(tr("&Help"));
437 menu_help->addAction(action_about);
438 menu_help->addAction(action_about_qt);
441 void MainWindow::create_actions()
443 action_exit = new QAction(tr("E&xit"), this);
444 action_exit->setStatusTip(tr("Quits the application"));
445 action_exit->setShortcut(tr("Ctrl+Q"));
446 connect(action_exit, &QAction::triggered, this, &MainWindow::close);
448 action_about = new QAction(tr("About ..."), this);
449 action_about->setStatusTip(tr("Shows the About dialog"));
450 connect(action_about, &QAction::triggered, this, &MainWindow::on_about);
452 action_about_qt = new QAction(tr("About Qt ..."), this);
453 action_about_qt->setStatusTip(tr("Shows information about Qt"));
454 connect(action_about_qt, &QAction::triggered, this, &MainWindow::on_about_qt);
456 action_open_port = new QAction(tr("Open Port"), this);
457 action_open_port->setStatusTip(tr("Opens Communication Port to read data"));
458 connect(action_open_port, &QAction::triggered, this, &MainWindow::on_open);
460 action_close_port = new QAction(tr("Close Port"), this);
461 action_close_port->setStatusTip(tr("Closes Communication Port"));
462 connect(action_close_port, &QAction::triggered, this, &MainWindow::on_close);
463 action_close_port->setEnabled(false);
465 action_open_file = new QAction(tr("Open File"), this);
466 action_open_file->setStatusTip(tr("Opens file containing NMEA sentences"));
467 connect(action_open_file, &QAction::triggered, this, &MainWindow::on_open_file);
470 void MainWindow::setup_ui()
472 QWidget * center = new QWidget(this);
473 center->setMinimumWidth(1024);
474 center->setMinimumHeight(600);
476 sentence_list = new QListWidget(this);
477 sentence_list->setSelectionMode(QAbstractItemView::SingleSelection);
478 connect(sentence_list, &QListWidget::itemSelectionChanged, this,
479 &MainWindow::on_sentence_selection);
481 sentence_desc = new QLabel("", this);
482 sentence_desc->setTextFormat(Qt::PlainText);
483 sentence_desc->setAlignment(Qt::AlignLeft | Qt::AlignTop);
485 QFont font{"Monospace"};
486 font.setStyleHint(QFont::TypeWriter);
488 sentence_list->setFont(font);
489 sentence_desc->setFont(font);
491 port_name = new QLineEdit(center);
492 port_name->setText("/dev/ttyUSB0");
493 cb_baudrate = new QComboBox(center);
494 cb_baudrate->setEditable(false);
495 cb_baudrate->addItems({"4800", "38400"});
497 auto l1 = new QHBoxLayout;
498 l1->addWidget(sentence_list, 2);
499 l1->addWidget(sentence_desc, 1);
501 auto l0 = new QGridLayout;
502 l0->addWidget(new QLabel(tr("Port:"), this), 0, 0);
503 l0->addWidget(port_name, 0, 1);
504 l0->addWidget(cb_baudrate, 0, 2);
505 l0->addLayout(l1, 1, 0, 1, 3);
507 center->setLayout(l0);
508 setCentralWidget(center);
511 void MainWindow::read_sentences_from_file(QString filename)
513 QFile file{filename};
514 if (!file.open(QFile::ReadOnly)) {
515 QMessageBox::critical(this, "Error", "Unable to open file: " + filename);
516 return;
519 QTextStream ifs(&file);
520 do {
521 QString s = ifs.readLine();
522 if (s.isNull())
523 break;
524 if (s.isEmpty())
525 continue;
526 if (s.startsWith("#"))
527 continue;
528 add_item(s);
529 } while (true);
532 void MainWindow::open_file(QString filename)
534 QFileInfo file(filename);
535 if (!file.exists())
536 return;
537 read_sentences_from_file(filename);
540 void MainWindow::on_open_file()
542 auto filename
543 = QFileDialog::getOpenFileName(this, tr("Open File"), ".", tr("Text Files (*.txt)"));
545 if (filename.size() == 0)
546 return;
548 sentence_list->clear();
549 sentence_desc->setText("");
551 read_sentences_from_file(filename);
554 void MainWindow::add_item(QString raw_sentence)
556 // if the list grows too large, remove a certain amount from
557 // the oldest entries before adding the new one.
559 if (sentence_list->count() > 1000) {
560 for (int i = 0; i < 10; ++i)
561 delete sentence_list->takeItem(0);
563 sentence_list->addItem(raw_sentence);
566 void MainWindow::on_about()
568 QCoreApplication * app = QCoreApplication::instance();
569 QMessageBox::about(this, app->applicationName(), app->applicationName()
570 + ": example of Qt using marnav\n\nVersion: " + app->applicationVersion()
571 + "\n\nSee file: LICENSE");
574 void MainWindow::on_about_qt()
576 QCoreApplication * app = QCoreApplication::instance();
577 QMessageBox::aboutQt(this, app->applicationName());
580 void MainWindow::on_sentence_selection()
582 auto items = sentence_list->selectedItems();
583 if (items.empty())
584 return;
586 auto item = items.front();
587 try {
588 auto s = marnav::nmea::make_sentence(item->text().toStdString());
589 sentence_desc->setText(QString{"Tag: %1\nTalker: %2\n\n%3\n"}
590 .arg(s->tag().c_str())
591 .arg(to_string(s->get_talker()).c_str())
592 .arg(get_details(s.get())));
593 } catch (marnav::nmea::unknown_sentence &) {
594 sentence_desc->setText("Unknown Sentence");
595 item->setForeground(Qt::red);
596 } catch (marnav::nmea::checksum_error &) {
597 sentence_desc->setText("Checksum Error");
598 item->setForeground(Qt::red);
599 } catch (std::invalid_argument & error) {
600 sentence_desc->setText("Error: " + QString::fromStdString(error.what()));
601 item->setForeground(Qt::red);
605 void MainWindow::on_open()
607 action_open_port->setEnabled(false);
608 action_close_port->setEnabled(true);
609 action_open_file->setEnabled(false);
610 cb_baudrate->setEnabled(false);
611 port_name->setEnabled(false);
613 port->setPortName(port_name->text());
614 port->setBaudRate(cb_baudrate->currentText().toInt());
615 port->setParity(QSerialPort::NoParity);
616 port->setDataBits(QSerialPort::Data8);
617 port->setStopBits(QSerialPort::OneStop);
619 connect(port, &QSerialPort::readyRead, this, &MainWindow::on_data_ready);
621 if (!port->open(QIODevice::ReadOnly)) {
622 on_close();
623 QMessageBox::critical(this, "Error", "Unable to open port: " + port->portName());
626 sentence_list->clear();
627 sentence_desc->setText("");
630 void MainWindow::on_close()
632 action_open_port->setEnabled(true);
633 action_close_port->setEnabled(true);
634 action_open_file->setEnabled(true);
635 cb_baudrate->setEnabled(true);
636 port_name->setEnabled(true);
638 disconnect(port, &QSerialPort::readyRead, this, &MainWindow::on_data_ready);
640 port->close();
643 void MainWindow::on_data_ready()
645 while (true) {
646 char raw;
647 auto rc = port->read(&raw, sizeof(raw));
648 if (rc == 0) {
649 // no more data for now
650 return;
652 if (rc < 0) {
653 // an error has ocurred
654 on_close();
655 return;
658 switch (raw) {
659 case '\r':
660 break;
661 case '\n':
662 add_item(received_data.c_str());
663 received_data.clear();
664 break;
665 default:
666 if (received_data.size() > marnav::nmea::sentence::max_length) {
667 // error ocurred, discard data
668 received_data.clear();
669 } else {
670 received_data += raw;
672 break;