Goby v2
liaison_acomms.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they 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 Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include <numeric>
24 #include <sstream>
25 
26 #include <Wt/Chart/WCartesianChart>
27 #include <Wt/Chart/WDataSeries>
28 #include <Wt/WBreak>
29 #include <Wt/WCheckBox>
30 #include <Wt/WColor>
31 #include <Wt/WComboBox>
32 #include <Wt/WContainerWidget>
33 #include <Wt/WDialog>
34 #include <Wt/WDoubleSpinBox>
35 #include <Wt/WFont>
36 #include <Wt/WGroupBox>
37 #include <Wt/WLabel>
38 #include <Wt/WLineEdit>
39 #include <Wt/WPanel>
40 #include <Wt/WPushButton>
41 #include <Wt/WStandardItem>
42 #include <Wt/WStandardItemModel>
43 #include <Wt/WTable>
44 
45 #include "goby/util/as.h"
46 #include "goby/util/dynamic_protobuf_manager.h"
47 
48 #include "goby/acomms/protobuf/mm_driver.pb.h"
50 
51 #include "liaison_acomms.h"
52 
53 const std::string STRIPE_ODD_CLASS = "odd";
54 const std::string STRIPE_EVEN_CLASS = "even";
55 
56 using namespace goby::common::logger;
57 using namespace Wt;
58 
59 goby::common::LiaisonAcomms::LiaisonAcomms(ZeroMQService* zeromq_service,
60  const protobuf::LiaisonConfig& cfg,
61  Wt::WContainerWidget* parent)
62  : LiaisonContainer(parent), MOOSNode(zeromq_service), zeromq_service_(zeromq_service),
63  cfg_(cfg.GetExtension(protobuf::acomms_config)), have_acomms_config_(false), driver_rx_(RX),
64  driver_tx_(TX), mm_rx_stats_box_(0), mm_rx_stats_range_(600)
65 {
66  for (int i = 0, n = cfg.load_shared_library_size(); i < n; ++i)
67  dccl_.load_library(cfg.load_shared_library(i));
68 
69  protobuf::ZeroMQServiceConfig ipc_sockets;
70  protobuf::ZeroMQServiceConfig::Socket* internal_subscribe_socket = ipc_sockets.add_socket();
71  internal_subscribe_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::SUBSCRIBE);
72  internal_subscribe_socket->set_socket_id(LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
73  internal_subscribe_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
74  internal_subscribe_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
75  internal_subscribe_socket->set_socket_name(liaison_internal_publish_socket_name());
76 
77  protobuf::ZeroMQServiceConfig::Socket* internal_publish_socket = ipc_sockets.add_socket();
78  internal_publish_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::PUBLISH);
79  internal_publish_socket->set_socket_id(LIAISON_INTERNAL_PUBLISH_SOCKET);
80  internal_publish_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
81  internal_publish_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
82  internal_publish_socket->set_socket_name(liaison_internal_subscribe_socket_name());
83 
84  zeromq_service_->merge_cfg(ipc_sockets);
85 
86  subscribe("ACOMMS_CONFIG", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
87  subscribe("ACOMMS_QSIZE", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
88  subscribe("ACOMMS_QUEUE_RECEIVE", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
89  subscribe("ACOMMS_MAC_SLOT_START", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
90  subscribe("ACOMMS_MODEM_TRANSMIT", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
91  subscribe("ACOMMS_MODEM_RECEIVE", LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
92 
93  MOOSNode::send(CMOOSMsg(MOOS_NOTIFY, "ACOMMS_CONFIG_REQUEST", "Request"),
94  LIAISON_INTERNAL_PUBLISH_SOCKET);
95 
96  timer_.setInterval(1 / cfg.update_freq() * 1.0e3);
97  timer_.timeout().connect(this, &LiaisonAcomms::loop);
98 
99  WPanel* dccl_panel = new Wt::WPanel(this);
100  dccl_panel->setTitle("DCCL");
101  dccl_panel->setCollapsible(true);
102  dccl_panel->setCollapsed(cfg_.minimize_dccl());
103 
104  WContainerWidget* dccl_box = new Wt::WContainerWidget();
105  dccl_panel->setCentralWidget(dccl_box);
106  new WLabel("Message: ", dccl_box);
107  dccl_combo_ = new WComboBox(dccl_box);
108 
109  dccl_combo_->addItem("(Choose a message)");
110 
111  dccl_combo_->sactivated().connect(this, &LiaisonAcomms::dccl_select);
112 
113  dccl_message_ = new WPushButton("Show .proto", dccl_box);
114  dccl_message_->clicked().connect(this, &LiaisonAcomms::dccl_message);
115 
116  dccl_analyze_ = new WPushButton("Analyze", dccl_box);
117  dccl_analyze_->clicked().connect(this, &LiaisonAcomms::dccl_analyze);
118 
119  new WBreak(dccl_box);
120 
121  dccl_message_text_ = new WText("", Wt::PlainText, dccl_box);
122  WFont mono(Wt::WFont::Monospace);
123  dccl_message_text_->decorationStyle().setFont(mono);
124  dccl_message_text_->hide();
125 
126  new WBreak(dccl_box);
127 
128  dccl_analyze_text_ = new WText("", Wt::PlainText, dccl_box);
129  dccl_analyze_text_->decorationStyle().setFont(mono);
130  dccl_analyze_text_->hide();
131 
132  WPanel* queue_panel = new Wt::WPanel(this);
133  queue_panel->setTitle("Queue");
134  queue_panel->setCollapsible(true);
135  WContainerWidget* queue_box = new Wt::WContainerWidget();
136  queue_panel->setCentralWidget(queue_box);
137  queue_panel->setCollapsed(cfg_.minimize_queue());
138 
139  queue_table_ = new Wt::WTable(queue_box);
140  queue_table_->setStyleClass("basic_small");
141  queue_table_->setHeaderCount(1);
142  queue_table_->elementAt(0, 0)->addWidget(new WText("DCCL Message"));
143  queue_table_->elementAt(0, 1)->addWidget(new WText("Queue Size"));
144  queue_table_->elementAt(0, 2)->addWidget(new WText("Time since Receive"));
145 
146  WPanel* amac_panel = new Wt::WPanel(this);
147  amac_panel->setTitle("AMAC");
148  amac_panel->setCollapsible(true);
149  amac_box_ = new Wt::WContainerWidget();
150  amac_panel->setCentralWidget(amac_box_);
151  amac_panel->setCollapsed(cfg_.minimize_amac());
152 
153  last_slot_.set_slot_index(-1);
154 
155  driver_panel_ = new Wt::WPanel(this);
156  driver_panel_->setTitle("Driver");
157  driver_panel_->setCollapsible(true);
158  driver_box_ = new Wt::WContainerWidget();
159  driver_panel_->setCentralWidget(driver_box_);
160  driver_panel_->setCollapsed(cfg_.minimize_driver());
161 
162  driver_tx_.box = new Wt::WGroupBox("Transmit", driver_box_);
163  driver_tx_.last_time_text = new Wt::WText(driver_tx_.box);
164  driver_tx_.box->clicked().connect(
165  boost::bind(&LiaisonAcomms::driver_info, this, _1, &driver_tx_));
166 
167  driver_rx_.box = new Wt::WGroupBox("Receive", driver_box_);
168  driver_rx_.last_time_text = new Wt::WText(driver_rx_.box);
169  driver_rx_.box->clicked().connect(
170  boost::bind(&LiaisonAcomms::driver_info, this, _1, &driver_rx_));
171 
172  set_name("MOOSAcomms");
173 }
174 
175 void goby::common::LiaisonAcomms::loop()
176 {
177  while (zeromq_service_->poll(0)) {}
178 
180  for (std::map<dccl::int32, QueueStats>::const_iterator it = queue_stats_.begin(),
181  end = queue_stats_.end();
182  it != end; ++it)
183  {
184  if (it->second.last_rx_time > 0)
185  it->second.last_rx_time_text->setText(
186  "Last: " + format_seconds(now - it->second.last_rx_time) + " ago");
187  else
188  it->second.last_rx_time_text->setText("Never");
189  }
190 
191  if (mac_bars_.count(last_slot_.slot_index()))
192  {
193  int seconds_into_slot = now - static_cast<int>(last_slot_.time() / 1e6);
194  mac_bars_[last_slot_.slot_index()]->setValue(seconds_into_slot);
195 
196  int seconds_into_cycle = seconds_into_slot;
197  for (int i = 0, n = last_slot_.slot_index(); i < n; ++i)
198  seconds_into_cycle += acomms_config_.mac_cfg().slot(i).slot_seconds();
199  mac_cycle_bar_->setValue(seconds_into_cycle);
200  }
201 
202  update_driver_stats(now, &driver_rx_);
203  update_driver_stats(now, &driver_tx_);
204 
205  if (mm_rx_stats_box_)
206  {
207  for (int i = mm_rx_stats_model_->rowCount() - 1, n = 0; i >= n; --i)
208  {
209  int time = Wt::asNumber(mm_rx_stats_model_->data(i, TIME_COLUMN, DisplayRole));
210  int elapsed = time - goby_time<double>();
211  if (elapsed < -mm_rx_stats_range_ - 10)
212  break;
213  else
214  mm_rx_stats_model_->setData(i, ELAPSED_COLUMN, elapsed, DisplayRole);
215  }
216  }
217 }
218 
219 void goby::common::LiaisonAcomms::update_driver_stats(int now,
220  LiaisonAcomms::DriverStats* driver_stats)
221 {
222  if (driver_stats->last_time > 0)
223  {
224  driver_stats->last_time_text->setText(
225  "Last: " + format_seconds(now - driver_stats->last_time) + " ago");
226 
227  if (now - driver_stats->last_time > 10)
228  driver_stats->last_time_text->decorationStyle().setBackgroundColor(Wt::WColor());
229  }
230  else
231  {
232  driver_stats->last_time_text->setText("Never");
233  }
234 }
235 
236 void goby::common::LiaisonAcomms::dccl_analyze(const WMouseEvent& event)
237 {
238  if (dccl_analyze_text_->isHidden())
239  {
240  dccl_analyze_->setText("Hide Analysis");
241  dccl_analyze_text_->show();
242  }
243  else
244  {
245  dccl_analyze_->setText("Analyze");
246  dccl_analyze_text_->hide();
247  }
248 }
249 
250 void goby::common::LiaisonAcomms::dccl_message(const WMouseEvent& event)
251 {
252  if (dccl_message_text_->isHidden())
253  {
254  dccl_message_->setText("Hide .proto");
255  dccl_message_text_->show();
256  }
257  else
258  {
259  dccl_message_->setText("Show .proto");
260  dccl_message_text_->hide();
261  }
262 }
263 
264 void goby::common::LiaisonAcomms::dccl_select(WString msg)
265 {
266  std::string m = msg.narrow();
267  m = m.substr(m.find(" ") + 1);
268 
269  const google::protobuf::Descriptor* desc =
270  goby::util::DynamicProtobufManager::find_descriptor(m);
271  if (desc)
272  {
273  std::stringstream ss;
274  boost::mutex::scoped_lock l(dccl_mutex_);
275  dccl_.info(desc, &ss);
276  dccl_analyze_text_->setText(ss.str());
277 
278  std::string proto = desc->DebugString();
279  boost::replace_all(proto, "\n", " ");
280  boost::replace_all(proto, ";", ";\n");
281  boost::replace_all(proto, "} ", "}\n");
282  dccl_message_text_->setText(proto);
283  }
284  else
285  {
286  dccl_analyze_text_->setText("");
287  dccl_message_text_->setText("");
288  }
289 }
290 
291 void goby::common::LiaisonAcomms::moos_inbox(CMOOSMsg& msg)
292 {
293  using goby::moos::operator<<;
294 
295  if (msg.GetKey() == "ACOMMS_CONFIG" && !have_acomms_config_)
296  {
297  acomms_config_.ParseFromString(dccl::b64_decode(msg.GetString()));
298  process_acomms_config();
299  have_acomms_config_ = true;
300  }
301  else if (msg.GetKey() == "ACOMMS_QSIZE")
302  {
304  parse_for_moos(msg.GetString(), &size);
305 
306  if (queue_bars_.count(size.dccl_id()))
307  {
308  queue_bars_[size.dccl_id()]->setValue(size.size());
309  }
310  }
311  else if (msg.GetKey() == "ACOMMS_QUEUE_RECEIVE")
312  {
313  boost::shared_ptr<google::protobuf::Message> dccl_msg =
314  dynamic_parse_for_moos(msg.GetString());
315  if (dccl_msg && queue_stats_.count(dccl_.id(dccl_msg->GetDescriptor())))
316  {
317  queue_stats_[dccl_.id(dccl_msg->GetDescriptor())].last_rx_time =
319  }
320  }
321  else if (msg.GetKey() == "ACOMMS_MAC_SLOT_START")
322  {
323  if (mac_slots_.count(last_slot_.slot_index()))
324  {
325  mac_slots_[last_slot_.slot_index()]->decorationStyle().setBackgroundColor(Wt::WColor());
326  mac_slots_[last_slot_.slot_index()]->decorationStyle().setForegroundColor(Wt::WColor());
327  // mac_slots_[last_slot_.slot_index()]->disable();
328  mac_bars_[last_slot_.slot_index()]->setHidden(true);
329  }
330 
331  parse_for_moos(msg.GetString(), &last_slot_);
332 
333  if (mac_slots_.count(last_slot_.slot_index()))
334  {
335  // mac_slots_[last_slot_.slot_index()]->enable();
336  mac_bars_[last_slot_.slot_index()]->setHidden(false);
337  mac_slots_[last_slot_.slot_index()]->decorationStyle().setBackgroundColor(goby_orange);
338  mac_slots_[last_slot_.slot_index()]->decorationStyle().setForegroundColor(Wt::white);
339  }
340  }
341  else if (msg.GetKey() == "ACOMMS_MODEM_TRANSMIT")
342  {
344  parse_for_moos(msg.GetString(), &tx_msg);
345 
346  bool tx_good = true;
347 
348  for (int i = 0, n = tx_msg.ExtensionSize(micromodem::protobuf::transmit_stat); i < n; ++i)
349  tx_good =
350  tx_good && (tx_msg.GetExtension(micromodem::protobuf::transmit_stat, i).mode() ==
351  micromodem::protobuf::TRANSMIT_SUCCESSFUL);
352 
353  handle_modem_message(&driver_tx_, tx_good, tx_msg);
354  }
355  else if (msg.GetKey() == "ACOMMS_MODEM_RECEIVE")
356  {
358  parse_for_moos(msg.GetString(), &rx_msg);
359 
360  bool rx_good = true;
361  for (int i = 0, n = rx_msg.ExtensionSize(micromodem::protobuf::receive_stat); i < n; ++i)
362  rx_good =
363  rx_good && (rx_msg.GetExtension(micromodem::protobuf::receive_stat, i).mode() ==
364  micromodem::protobuf::RECEIVE_GOOD);
365 
366  handle_modem_message(&driver_rx_, rx_good, rx_msg);
367 
368  for (int i = 0, n = rx_msg.ExtensionSize(micromodem::protobuf::receive_stat); i < n; ++i)
369  {
371  rx_msg.GetExtension(micromodem::protobuf::receive_stat, i);
372 
373  if (rx_stats.packet_type() != micromodem::protobuf::PSK)
374  continue;
375 
376  std::vector<WStandardItem*> row;
377  WStandardItem* time = new WStandardItem;
378  time->setData(rx_stats.time() / 1e6, DisplayRole);
379  row.push_back(time);
380 
381  WStandardItem* elapsed = new WStandardItem;
382  elapsed->setData(0, DisplayRole);
383  row.push_back(elapsed);
384 
385  WStandardItem* mse = new WStandardItem;
386  mse->setData(rx_stats.mse_equalizer(), DisplayRole);
387  row.push_back(mse);
388 
389  WStandardItem* snr_in = new WStandardItem;
390  snr_in->setData(rx_stats.snr_in(), DisplayRole);
391  row.push_back(snr_in);
392 
393  WStandardItem* snr_out = new WStandardItem;
394  snr_out->setData(rx_stats.snr_out(), DisplayRole);
395  row.push_back(snr_out);
396 
397  WStandardItem* doppler = new WStandardItem;
398  doppler->setData(rx_stats.doppler(), DisplayRole);
399  row.push_back(doppler);
400 
401  WStandardItem* bad_frames = new WStandardItem;
402  bad_frames->setData(double(rx_stats.number_bad_frames()) / rx_stats.number_frames() *
403  100.0,
404  DisplayRole);
405  row.push_back(bad_frames);
406 
407  mm_rx_stats_model_->appendRow(row);
408  }
409  }
410 }
411 
412 void goby::common::LiaisonAcomms::handle_modem_message(
413  LiaisonAcomms::DriverStats* driver_stats, bool good,
415 {
416  driver_stats->last_time = msg.time() / 1e6;
417 
418  if (good)
419  driver_stats->last_time_text->decorationStyle().setBackgroundColor(goby_blue);
420  else
421  driver_stats->last_time_text->decorationStyle().setBackgroundColor(goby_orange);
422 
423  driver_stats->last_msg_ = msg;
424 }
425 
426 void goby::common::LiaisonAcomms::process_acomms_config()
427 {
428  boost::mutex::scoped_lock l(dccl_mutex_);
429 
430  // load messages in queue config
431  for (int i = 0, n = acomms_config_.queue_cfg().message_entry_size(); i < n; ++i)
432  {
434  acomms_config_.queue_cfg().message_entry(i);
435 
436  const google::protobuf::Descriptor* desc =
437  goby::util::DynamicProtobufManager::find_descriptor(q.protobuf_name());
438 
439  if (desc)
440  {
441  dccl_.load(desc);
442  int id = dccl_.id(desc);
443  queue_cfg_[id] = i;
444 
445  glog.is(DEBUG1) && glog << "Loaded: " << desc << ": " << q.protobuf_name() << std::endl;
446 
447  int row = queue_table_->rowCount();
448  queue_table_->elementAt(row, 0)->addWidget(new WText(q.protobuf_name()));
449  QueueBar* new_bar = new QueueBar;
450  queue_table_->elementAt(row, 1)->addWidget(new_bar);
451 
452  new_bar->setMinimum(0);
453  new_bar->setMaximum(q.max_queue());
454 
455  queue_bars_[id] = new_bar;
456 
457  QueueStats new_stats;
458  new_stats.last_rx_time_text =
459  new WText(goby::util::as<std::string>(new_stats.last_rx_time));
460  queue_table_->elementAt(row, 2)->addWidget(new_stats.last_rx_time_text);
461  queue_stats_[id] = new_stats;
462 
463  WPushButton* info = new WPushButton("Info");
464  info->clicked().connect(boost::bind(&LiaisonAcomms::queue_info, this, _1, id));
465  queue_table_->elementAt(row, 3)->addWidget(info);
466 
467  WPushButton* flush = new WPushButton("Clear");
468  flush->clicked().connect(boost::bind(&LiaisonAcomms::queue_flush, this, _1, id));
469  queue_table_->elementAt(row, 4)->addWidget(flush);
470 
471  if (row & 1)
472  queue_table_->rowAt(row)->setStyleClass(STRIPE_ODD_CLASS);
473  else
474  queue_table_->rowAt(row)->setStyleClass(STRIPE_EVEN_CLASS);
475  }
476  else
477  {
478  glog.is(WARN) && glog << "Cannot find message: " << q.protobuf_name() << std::endl;
479  }
480  }
481 
482  // load DCCL messages into drop down box
483  for (std::map<dccl::int32, const google::protobuf::Descriptor*>::const_iterator
484  it = dccl_.loaded().begin(),
485  it_end = dccl_.loaded().end();
486  it != it_end; ++it)
487  {
488  dccl_combo_->addItem(
489  std::string(goby::util::as<std::string>(it->first) + ": " + it->second->full_name()));
490  }
491 
492  Wt::WContainerWidget* mac_top_box = new Wt::WContainerWidget(amac_box_);
493 
494  new Wt::WText("Number of slots in the cycle: " +
495  goby::util::as<std::string>(acomms_config_.mac_cfg().slot_size()) + ".",
496  mac_top_box);
497  mac_cycle_bar_ = new MACBar(mac_top_box);
498  mac_cycle_bar_->setMinimum(0);
499  int cycle_length = 0;
500  for (int i = 0, n = acomms_config_.mac_cfg().slot_size(); i < n; ++i)
501  cycle_length += acomms_config_.mac_cfg().slot(i).slot_seconds();
502 
503  mac_cycle_bar_->setMaximum(cycle_length);
504  mac_cycle_bar_->setFloatSide(Wt::Right);
505 
506  new Wt::WText("<hr/>", mac_top_box);
507 
508  mac_slot_style_.setBorder(Wt::WBorder(Wt::WBorder::Solid, Wt::WBorder::Thin));
509  for (int i = 0, n = acomms_config_.mac_cfg().slot_size(); i < n; ++i)
510  {
511  const goby::acomms::protobuf::ModemTransmission& slot = acomms_config_.mac_cfg().slot(i);
512 
513  Wt::WContainerWidget* box = new Wt::WContainerWidget(amac_box_);
514  box->setDecorationStyle(mac_slot_style_);
515  box->clicked().connect(boost::bind(&LiaisonAcomms::mac_info, this, _1, i));
516 
517  double height = std::log10(slot.slot_seconds());
518  box->setPadding(Wt::WLength(height / 2, Wt::WLength::FontEm), Wt::Top | Wt::Bottom);
519  box->setPadding(Wt::WLength(3), Wt::Right | Wt::Left);
520  box->setMargin(Wt::WLength(3));
521  box->resize(Wt::WLength(), Wt::WLength(1 + height, Wt::WLength::FontEm));
522 
523  std::string slot_text;
524  if (slot.type() == goby::acomms::protobuf::ModemTransmission::UNKNOWN)
525  {
526  slot_text = "Non-Acomms Slot";
527  if (slot.has_unique_id())
528  slot_text += " (" + goby::util::as<std::string>(slot.unique_id()) + ")";
529  }
530  else if (slot.type() == goby::acomms::protobuf::ModemTransmission::DATA)
531  {
532  std::stringstream ss;
533  ss << "Data Slot | Source: " << slot.src();
534  if (slot.dest() != -1)
535  ss << " | Dest: " << slot.dest();
536  ss << " | Rate: " << slot.rate();
537  slot_text = ss.str();
538  }
539  else
540  {
541  slot_text = slot.DebugString();
542  }
543 
544  new Wt::WText(slot_text, box);
545 
546  // new Wt::WBreak(box);
547 
548  MACBar* new_bar = new MACBar(box);
549  new_bar->setMinimum(0);
550  new_bar->setMaximum(slot.slot_seconds());
551  new_bar->setFloatSide(Wt::Right);
552  new_bar->setHidden(true);
553 
554  mac_bars_[i] = new_bar;
555 
556  // box->disable();
557 
558  mac_slots_[i] = box;
559  }
560 
561  driver_panel_->setTitle("Driver: " +
562  goby::acomms::protobuf::DriverType_Name(acomms_config_.driver_type()));
563 
564  if (acomms_config_.driver_type() == goby::acomms::protobuf::DRIVER_WHOI_MICROMODEM)
565  {
566  mm_rx_stats_box_ = new WGroupBox("WHOI Micro-Modem Receive Statistics", driver_box_);
567  mm_rx_stats_model_ = new WStandardItemModel(0, MAX_COLUMN + 1, mm_rx_stats_box_);
568 
569  mm_rx_stats_graph_ = new Chart::WCartesianChart(Chart::ScatterPlot, mm_rx_stats_box_);
570  mm_rx_stats_graph_->setModel(mm_rx_stats_model_);
571  mm_rx_stats_graph_->setXSeriesColumn(ELAPSED_COLUMN);
572 
573  for (int i = MSE_COLUMN, n = MAX_COLUMN; i <= n; ++i)
574  {
575  Chart::WDataSeries s(i, Chart::LineSeries);
576  Chart::MarkerType type;
577  switch (i)
578  {
579  default:
580  case MSE_COLUMN: type = Chart::CircleMarker; break;
581  case SNR_IN_COLUMN: type = Chart::SquareMarker; break;
582  case SNR_OUT_COLUMN: type = Chart::CrossMarker; break;
583  case DOPPLER_COLUMN: type = Chart::TriangleMarker; break;
584  case PERCENT_BAD_FRAMES_COLUMN: type = Chart::XCrossMarker; break;
585  }
586  s.setMarker(type);
587  s.setHidden(true);
588  mm_rx_stats_graph_->addSeries(s);
589  }
590 
591  mm_rx_stats_graph_->axis(Chart::XAxis).setTitle("Seconds ago");
592 
593  mm_rx_stats_model_->setHeaderData(MSE_COLUMN, std::string("MSE"));
594  mm_rx_stats_model_->setHeaderData(SNR_IN_COLUMN, std::string("SNR In"));
595  mm_rx_stats_model_->setHeaderData(SNR_OUT_COLUMN, std::string("SNR Out"));
596  mm_rx_stats_model_->setHeaderData(DOPPLER_COLUMN, std::string("Doppler"));
597  mm_rx_stats_model_->setHeaderData(PERCENT_BAD_FRAMES_COLUMN, std::string("% bad frames"));
598 
599  mm_rx_stats_graph_->setPlotAreaPadding(120, Right);
600  mm_rx_stats_graph_->setPlotAreaPadding(50, Left);
601  mm_rx_stats_graph_->setPlotAreaPadding(40, Top | Bottom);
602  mm_rx_stats_graph_->setMargin(10, Top | Bottom); // add margin vertically
603  mm_rx_stats_graph_->setMargin(WLength::Auto, Left | Right); // center horizontally
604  mm_rx_stats_graph_->resize(Wt::WLength(40, Wt::WLength::FontEm), 300);
605 
606  mm_rx_stats_graph_->setLegendEnabled(true);
607 
608  mm_range(mm_rx_stats_range_);
609 
610  mm_rx_stats_graph_->axis(Chart::Y1Axis).setVisible(false);
611  mm_rx_stats_graph_->axis(Chart::Y1Axis).setVisible(false);
612 
613  WGroupBox* y1axis_group = new Wt::WGroupBox("Y1 Axis", mm_rx_stats_box_);
614  y1axis_group->resize(Wt::WLength(45, Wt::WLength::Percentage), Wt::WLength::Auto);
615  y1axis_group->setInline(true);
616  WGroupBox* y2axis_group = new Wt::WGroupBox("Y2 Axis", mm_rx_stats_box_);
617  y2axis_group->resize(Wt::WLength(45, Wt::WLength::Percentage), Wt::WLength::Auto);
618  y2axis_group->setInline(true);
619  for (int i = MSE_COLUMN, n = MAX_COLUMN; i <= n; ++i)
620  {
621  for (int j = 0, m = 2; j < m; ++j)
622  {
623  mm_rx_chks_[i][j] = new WCheckBox(asString(mm_rx_stats_model_->headerData(i)),
624  j == 0 ? y1axis_group : y2axis_group);
625  mm_rx_chks_[i][j]->resize(Wt::WLength(40, Wt::WLength::Percentage),
626  Wt::WLength::Auto);
627 
628  mm_rx_chks_[i][j]->checked().connect(
629  boost::bind(&LiaisonAcomms::mm_check, this, j, i, true));
630  mm_rx_chks_[i][j]->unChecked().connect(
631  boost::bind(&LiaisonAcomms::mm_check, this, j, i, false));
632  }
633  }
634 
635  mm_rx_chks_[MSE_COLUMN][0]->setChecked(true);
636  mm_check(0, MSE_COLUMN, true);
637 
638  new Wt::WBreak(mm_rx_stats_box_);
639 
640  new Wt::WText("X Axis Range (seconds):", mm_rx_stats_box_);
641 
642  WDoubleSpinBox* range_box = new WDoubleSpinBox(mm_rx_stats_box_);
643  range_box->setMinimum(10);
644  range_box->setMaximum(1e100);
645  range_box->setSingleStep(600);
646  range_box->setDecimals(0);
647  range_box->setValue(mm_rx_stats_range_);
648  range_box->valueChanged().connect(this, &LiaisonAcomms::mm_range);
649  }
650 }
651 
652 void goby::common::LiaisonAcomms::mm_check(int axis, int column, bool checked)
653 {
654  std::cout << "Axis: " << axis << ", column: " << column << ", check: " << checked << std::endl;
655 
656  Chart::WDataSeries& s = mm_rx_stats_graph_->series(column);
657  s.setHidden(!checked);
658  s.bindToAxis(axis == 0 ? Chart::Y1Axis : Chart::Y2Axis);
659 
660  // uncheck the corresponding other axis
661  if (checked)
662  mm_rx_chks_[column][axis == 0 ? 1 : 0]->setChecked(false);
663 
664  bool axis_enabled[2] = {false, false};
665  for (std::map<int, std::map<int, Wt::WCheckBox*> >::iterator it = mm_rx_chks_.begin(),
666  end = mm_rx_chks_.end();
667  it != end; ++it)
668  {
669  for (int i = 0, n = 2; i < n; ++i)
670  {
671  if (it->second[i]->isChecked())
672  axis_enabled[i] = true;
673  }
674  }
675 
676  for (int i = 0, n = 2; i < n; ++i)
677  mm_rx_stats_graph_->axis(i == 0 ? Chart::Y1Axis : Chart::Y2Axis)
678  .setVisible(axis_enabled[i]);
679 }
680 
681 void goby::common::LiaisonAcomms::mm_range(double range)
682 {
683  mm_rx_stats_range_ = range;
684  mm_rx_stats_graph_->axis(Chart::XAxis).setRange(-mm_rx_stats_range_, 0);
685  mm_rx_stats_graph_->axis(Chart::XAxis).setLabelInterval(mm_rx_stats_range_ / 5);
686 }
687 
688 Wt::WString goby::common::QueueBar::text() const
689 {
690  return std::string(goby::util::as<std::string>(value()) + "/" +
691  goby::util::as<std::string>(maximum()));
692 }
693 
694 Wt::WString goby::common::MACBar::text() const
695 {
696  return std::string(goby::util::as<std::string>(value()) + "/" +
697  goby::util::as<std::string>(maximum()) + " s");
698 }
699 
700 void goby::common::LiaisonAcomms::queue_info(const Wt::WMouseEvent& event, int id)
701 {
702  const google::protobuf::Descriptor* desc = 0;
703  {
704  boost::mutex::scoped_lock l(dccl_mutex_);
705  if (dccl_.loaded().count(id))
706  desc = dccl_.loaded().find(id)->second;
707  }
708 
709  if (!desc)
710  return;
711 
712  glog.is(DEBUG1) && glog << "Queue info for " << desc->full_name() << std::endl;
713 
714  WDialog dialog("Queue info for " + desc->full_name());
715  WContainerWidget* message_div = new WContainerWidget(dialog.contents());
716  new WText(std::string("<pre>" +
717  acomms_config_.queue_cfg().message_entry(queue_cfg_[id]).DebugString() +
718  "</pre>"),
719  message_div);
720 
721  message_div->setOverflow(WContainerWidget::OverflowAuto);
722 
723  WPushButton ok("OK", dialog.contents());
724 
725  dialog.rejectWhenEscapePressed();
726  ok.clicked().connect(&dialog, &WDialog::accept);
727 
728  if (dialog.exec() == WDialog::Accepted) {}
729 }
730 
731 void goby::common::LiaisonAcomms::mac_info(const Wt::WMouseEvent& event, int id)
732 {
733  WDialog dialog("AMAC Slot Info for Slot #" + goby::util::as<std::string>(id));
734  WContainerWidget* message_div = new WContainerWidget(dialog.contents());
735  new WText(std::string("<pre>" + acomms_config_.mac_cfg().slot(id).DebugString() + "</pre>"),
736  message_div);
737 
738  message_div->setOverflow(WContainerWidget::OverflowAuto);
739 
740  WPushButton ok("OK", dialog.contents());
741 
742  dialog.rejectWhenEscapePressed();
743  ok.clicked().connect(&dialog, &WDialog::accept);
744 
745  if (dialog.exec() == WDialog::Accepted) {}
746 }
747 
748 void goby::common::LiaisonAcomms::driver_info(const Wt::WMouseEvent& event,
749  LiaisonAcomms::DriverStats* driver_stats)
750 {
751  WDialog dialog("Last Message " +
752  std::string(driver_stats->direction == RX ? "Received" : "Transmitted"));
753  WContainerWidget* message_div = new WContainerWidget(dialog.contents());
754  WText* txt = new WText(std::string(driver_stats->last_msg_.DebugString()), message_div);
755  txt->setTextFormat(PlainText);
756 
757  message_div->setMaximumSize(800, 600);
758  message_div->setOverflow(WContainerWidget::OverflowAuto);
759 
760  WPushButton ok("OK", dialog.contents());
761 
762  dialog.rejectWhenEscapePressed();
763  ok.clicked().connect(&dialog, &WDialog::accept);
764 
765  if (dialog.exec() == WDialog::Accepted) {}
766 }
767 
768 void goby::common::LiaisonAcomms::queue_flush(const Wt::WMouseEvent& event, int id)
769 {
771  flush.set_dccl_id(id);
772  std::string serialized;
773  serialize_for_moos(&serialized, flush);
774 
775  MOOSNode::send(CMOOSMsg(MOOS_NOTIFY, "ACOMMS_FLUSH_QUEUE", serialized),
776  LIAISON_INTERNAL_PUBLISH_SOCKET);
777 }
778 
779 std::string goby::common::LiaisonAcomms::format_seconds(int sec)
780 {
781  if (sec < 60)
782  return goby::util::as<std::string>(sec) + " s";
783  else
784  return goby::util::as<std::string>(sec / 60) + ":" + ((sec % 60 < 10) ? "0" : "") +
785  goby::util::as<std::string>(sec % 60);
786 }
void parse_for_moos(const std::string &in, google::protobuf::Message *msg)
Parses the string in to Google Protocol Buffers message msg. All errors are written to the goby::util...
Helpers for MOOS applications for serializing and parsed Google Protocol buffers messages.
double goby_time< double >()
Returns current UTC time as seconds and fractional seconds since 1970-01-01 00:00:00.
Definition: time.h:130
common::FlexOstream glog
Access the Goby logger through this object.