Goby v2
mm_driver.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 <sstream>
24 
25 #include <boost/algorithm/string.hpp>
26 #include <boost/assign.hpp>
27 #include <boost/foreach.hpp>
28 
29 #include <dccl/bitset.h>
30 
31 #include "goby/common/logger.h"
32 #include "goby/util/binary.h"
33 #include "goby/util/sci.h"
34 
35 #include "driver_exception.h"
36 #include "mm_driver.h"
37 
38 using goby::glog;
41 using goby::util::as;
42 using goby::util::hex_decode;
43 using goby::util::hex_encode;
45 using google::protobuf::uint32;
46 using namespace goby::common::tcolor;
47 using namespace goby::common::logger;
48 using namespace goby::common::logger_lock;
49 using goby::common::nmea_time2ptime;
50 
51 const boost::posix_time::time_duration goby::acomms::MMDriver::MODEM_WAIT =
52  boost::posix_time::seconds(5);
53 const boost::posix_time::time_duration goby::acomms::MMDriver::WAIT_AFTER_REBOOT =
54  boost::posix_time::seconds(2);
55 const boost::posix_time::time_duration
56  goby::acomms::MMDriver::HYDROID_GATEWAY_GPS_REQUEST_INTERVAL = boost::posix_time::seconds(30);
57 const std::string goby::acomms::MMDriver::SERIAL_DELIMITER = "\r";
58 const unsigned goby::acomms::MMDriver::PACKET_FRAME_COUNT[] = {1, 3, 3, 2, 2, 8};
59 const unsigned goby::acomms::MMDriver::PACKET_SIZE[] = {32, 64, 64, 256, 256, 256};
60 
61 //
62 // INITIALIZATION
63 //
64 
66  : last_write_time_(goby_time()), waiting_for_modem_(false), startup_done_(false),
67  global_fail_count_(0), present_fail_count_(0), clock_set_(false),
68  last_hydroid_gateway_gps_request_(goby_time()), is_hydroid_gateway_(false),
69  expected_remaining_caxst_(0), expected_remaining_cacst_(0), expected_ack_destination_(0),
70  local_cccyc_(false), last_keep_alive_time_(0), using_application_acks_(false),
71  application_ack_max_frames_(0), next_frame_(0), serial_fd_(-1)
72 {
73  initialize_talkers();
74 }
75 
77 {
78  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Goby Micro-Modem driver starting up."
79  << std::endl;
80 
81  if (startup_done_)
82  {
83  glog.is(DEBUG1) && glog << group(glog_out_group())
84  << " ... driver is already started, not restarting." << std::endl;
85  return;
86  }
87 
88  // store a copy for us later
89  driver_cfg_ = cfg;
90 
91  if (!cfg.has_line_delimiter())
92  driver_cfg_.set_line_delimiter(SERIAL_DELIMITER);
93 
94  if (!cfg.has_serial_baud())
95  driver_cfg_.set_serial_baud(DEFAULT_BAUD);
96 
97  // support the non-standard Hydroid gateway buoy
98  if (driver_cfg_.HasExtension(micromodem::protobuf::Config::hydroid_gateway_id))
99  set_hydroid_gateway_prefix(
100  driver_cfg_.GetExtension(micromodem::protobuf::Config::hydroid_gateway_id));
101 
102  using_application_acks_ =
103  driver_cfg_.GetExtension(micromodem::protobuf::Config::use_application_acks);
104  application_ack_max_frames_ = 32;
105  if (using_application_acks_)
107 
108  modem_start(driver_cfg_);
109 
110  if (driver_cfg_.connection_type() == protobuf::DriverConfig::CONNECTION_SERIAL)
111  {
112  // Grab our native file descrpitor for the serial port. Only works for linux.
113  // Used to change control lines (e.g. RTS) w/ linux through IOCTL calls.
114  // Would need #ifdef's for conditional compling if other platforms become desired.
115  serial_fd_ = dynamic_cast<util::SerialClient&>(modem()).socket().native();
116 
117  // The MM2 (at least, possibly MM1 as well) has an issue where serial comms are
118  // garbled when RTS is asserted and hardware flow control is disabled (HFC0).
119  // HFC is disabled by default on MM boot so we need to make sure
120  // RTS is low to talk to it. Lines besides TX/RX/GND are rarely wired into
121  // MM units, but the newer MM2 development boxes full 9-line serial passthrough.
122 
123  set_rts(false);
124  }
125 
126  write_cfg();
127 
128  // reboot, to ensure we get a $CAREV message
129  {
130  NMEASentence nmea("$CCMSC", NMEASentence::IGNORE);
131  nmea.push_back(driver_cfg_.modem_id());
132  nmea.push_back(driver_cfg_.modem_id());
133  nmea.push_back(0);
134  append_to_write_queue(nmea);
135  }
136 
137  while (!out_.empty())
138  {
139  do_work();
140  usleep(100000); // 10 Hz
141  }
142 
143  // some clock stuff -- set clk_mode_ zeros for starters
144  set_clock();
145  clk_mode_ = micromodem::protobuf::NO_SYNC_TO_PPS_AND_CCCLK_BAD;
146 
147  while (!clock_set_)
148  {
149  do_work();
150  usleep(100000); // 10 Hz
151  }
152 
153  // so that we know what the Micro-Modem has for all the NVRAM values, not just the ones we set
154  query_all_cfg();
155 
156  startup_done_ = true;
157 }
158 
159 void goby::acomms::MMDriver::set_rts(bool state)
160 {
161  int status;
162  if (ioctl(serial_fd_, TIOCMGET, &status) == -1)
163  {
164  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
165  << "IOCTL failed: " << strerror(errno) << std::endl;
166  }
167 
168  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Setting RTS to "
169  << (state ? "high" : "low") << std::endl;
170  if (state)
171  status |= TIOCM_RTS;
172  else
173  status &= ~TIOCM_RTS;
174  if (ioctl(serial_fd_, TIOCMSET, &status) == -1)
175  {
176  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
177  << "IOCTL failed: " << strerror(errno) << std::endl;
178  }
179 }
180 
181 bool goby::acomms::MMDriver::query_rts()
182 {
183  int status;
184  if (ioctl(serial_fd_, TIOCMGET, &status) == -1)
185  {
186  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
187  << "IOCTL failed: " << strerror(errno) << std::endl;
188  }
189  return (status & TIOCM_RTS);
190 }
191 
193 {
194  for (int i = 0, n = cfg.ExtensionSize(micromodem::protobuf::Config::nvram_cfg); i < n; ++i)
195  write_single_cfg(cfg.GetExtension(micromodem::protobuf::Config::nvram_cfg, i));
196 }
197 
198 void goby::acomms::MMDriver::initialize_talkers()
199 {
200  boost::assign::insert(sentence_id_map_)("ACK", ACK)("DRQ", DRQ)("RXA", RXA)("RXD", RXD)(
201  "RXP", RXP)("TXD", TXD)("TXA", TXA)("TXP", TXP)("TXF", TXF)("CYC", CYC)("MPC", MPC)(
202  "MPA", MPA)("MPR", MPR)("RSP", RSP)("MSC", MSC)("MSA", MSA)("MSR", MSR)("EXL", EXL)(
203  "MEC", MEC)("MEA", MEA)("MER", MER)("MUC", MUC)("MUA", MUA)("MUR", MUR)("PDT", PDT)(
204  "PNT", PNT)("TTA", TTA)("MFD", MFD)("CLK", CLK)("CFG", CFG)("AGC", AGC)("BBD", BBD)(
205  "CFR", CFR)("CST", CST)("MSG", MSG)("REV", REV)("DQF", DQF)("SHF", SHF)("MFD", MFD)(
206  "SNR", SNR)("DOP", DOP)("DBG", DBG)("FFL", FFL)("FST", FST)("ERR", ERR)("TOA", TOA)(
207  "XST", XST)("TDP", TDP)("RDP", RDP)("TMS", TMS)("TMQ", TMQ)("TMG", TMG)("SWP", SWP)("MSQ",
208  MSQ);
209 
210  boost::assign::insert(talker_id_map_)("CC", CC)("CA", CA)("SN", SN)("GP", GP);
211 
212  // from Micro-Modem Software Interface Guide v. 3.04
213  boost::assign::insert(description_map_)("$CAACK", "Acknowledgment of a transmitted packet")(
214  "$CADRQ", "Data request message, modem to host")("$CARXA",
215  "Received ASCII message, modem to host")(
216  "$CARXD", "Received binary message, modem to host")(
217  "$CARXP", "Incoming packet detected, modem to host")(
218  "$CCTXD", "Transmit binary data message, host to modem")(
219  "$CCTXA", "Transmit ASCII data message, host to modem")(
220  "$CATXD", "Echo back of transmit binary data message")(
221  "$CATXA", "Echo back of transmit ASCII data message")(
222  "$CATXP", "Start of packet transmission, modem to host")(
223  "$CATXF", "End of packet transmission, modem to host")(
224  "$CCCYC", "Network Cycle Initialization Command")(
225  "$CACYC", "Echo of Network Cycle Initialization command")(
226  "$CCMPC", "Mini-Packet Ping command, host to modem")(
227  "$CAMPC", "Echo of Ping command, modem to host")("$CAMPA",
228  "A Ping has been received, modem to host")(
229  "$CAMPR", "Reply to Ping has been received, modem to host")(
230  "$CCRSP", "Pinging with an FM sweep")("$CARSP", "Respose to FM sweep ping command")(
231  "$CCMSC", "Sleep command, host to modem")("$CAMSC", "Echo of Sleep command, modem to host")(
232  "$CAMSA", "A Sleep was received acoustically, modem to host")(
233  "$CAMSR", "A Sleep reply was received, modem to host")(
234  "$CCEXL", "External hardware control command, local modem only")(
235  "$CCMEC", "External hardware control command, host to modem")(
236  "$CAMEC", "Echo of hardware control command, modem to host")(
237  "$CAMEA", "Hardware control command received acoustically")(
238  "$CAMER", "Hardware control command reply received")(
239  "$CCMUC", "User Mini-Packet command, host to modem")(
240  "$CAMUC", "Echo of user Mini-Packet, modem to host")(
241  "$CAMUA", "Mini-Packet received acoustically, modem to host")(
242  "$CAMUR", "Reply to Mini-Packet received, modem to host")(
243  "$CCPDT", "Ping REMUS digital transponder, host to modem")(
244  "$CCPNT", "Ping narrowband transponder, host to modem")(
245  "$SNTTA", "Transponder travel times, modem to host")(
246  "$SNMFD", "Nav matched filter information, modem to host")(
247  "$CCCLK", "Set clock, host to modem")("$CCCFG",
248  "Set NVRAM configuration parameter, host to modem")(
249  "$CACFG", "Echo of NVRAM configuration parameter, modem to host")(
250  "$CCCFQ", "Query configuration parameter, host to modem")("$CCAGC",
251  "Set automatic gain control")(
252  "$CABBD", "Dump of baseband data to serial port, modem to host")(
253  "$CCCFR", "Measure noise level at receiver, host to modem")(
254  "$SNCFR", "Noise report, modem to host")("$CACST",
255  "Communication cycle receive statistics")(
256  "$CAXST", "Communication cycle transmit statistics")(
257  "$CAMSG", "Transaction message, modem to host")("$CAREV",
258  "Software revision message, modem to host")(
259  "$CADQF", "Data quality factor information, modem to host")(
260  "$CASHF", "Shift information, modem to host")(
261  "$CAMFD", "Comms matched filter information, modem to host")(
262  "$CACLK", "Time/Date message, modem to host")("$CASNR",
263  "SNR statistics on the incoming PSK packet")(
264  "$CADOP", "Doppler speed message, modem to host")(
265  "$CADBG", "Low level debug message, modem to host")("$CAERR",
266  "Error message, modem to host")(
267  "$CATOA", "Message from modem to host reporting time of arrival of the previous packet, "
268  "and the synchronous timing mode used to determine that time.")(
269  "$CCTDP", "Transmit (downlink) data packet with Flexible Data Protocol (Micro-Modem 2)")(
270  "$CCTDP", "Response to CCTDP for Flexible Data Protocol (Micro-Modem 2)")(
271  "$CARDP", "Reception of a FDP downlink data packet (Micro-Modem 2)")(
272  "$CCTMS", "Set the modem clock (Micro-Modem 2)")(
273  "$CATMS", "Response to set clock command (Micro-Modem 2)")(
274  "$CCTMQ", "Query modem time command (Micro-Modem 2)")(
275  "$CATMQ", "Response to time query (CCTMQ) command (Micro-Modem 2)")(
276  "$CATMG", "Informational message about timing source, printed when timing sources change "
277  "(Micro-Modem 2)")("$CCSWP", "Send an FM sweep")(
278  "$CCMSQ", "Send a maximal-length sequence");
279 
280  // from Micro-Modem Software Interface Guide v. 3.04
281  boost::assign::insert(cfg_map_)("AGC", "Turn on automatic gain control")(
282  "AGN", "Analog Gain (50 is 6 dB, 250 is 30 dB)")(
283  "ASD",
284  "Always Send Data. Tells the modem to send test data when the user does not provide any.")(
285  "BBD", "PSK Baseband data dump to serial port")(
286  "BND", "Frequency Bank (1, 2, 3 for band A, B, or C, 0 for user-defined PSK only band)")(
287  "BR1", "Baud rate for serial port 1 (3 = 19200)")(
288  "BR2", "Baud rate for serial port 2 (3 = 19200)")("BRN", "Run bootloader at next revert")(
289  "BSP", "Boot loader serial port")(
290  "BW0", "Bandwidth for Band 0 PSK CPR 0-1 Coprocessor power toggle switch 1")(
291  "CRL", "Cycle init reverb lockout (ms) 50")("CST", "Cycle statistics message 1")(
292  "CTO", "Cycle init timeout (sec) 10")("DBG", "Enable low-level debug messages 0")(
293  "DGM", "Diagnostic messaging 0")("DOP", "Whether or not to send the $CADOP message")(
294  "DQF", "Whether or not to send the $CADQF message")("DTH",
295  "Matched filter signal threshold, FSK")(
296  "DTO", "Data request timeout (sec)")("DTP", "Matched filter signal threshold, PSK")(
297  "ECD", "Int Delay at end of cycle (ms)")("EFF", "Feedforward taps for the LMS equalizer")(
298  "EFB", "Feedback taps for the LMS equalizer")("FMD", "PSK FM probe direction,0 up, 1 down")(
299  "FML", "PSK FM probe length, symbols")("FC0", "Carrier at Band 0 PSK only")(
300  "GPS", "GPS parser on aux. serial port")("HFC",
301  "Hardware flow control on main serial port")(
302  "MCM", "Enable current mode hydrophone power supply on Rev. C Multi-Channel Analog Board. "
303  "Must be set to 1 for Rev. B Multi-Channel Analog Board.")(
304  "MFD", "Whether or not to send the MFD messages")("IRE",
305  "Print impulse response of FM sweep")(
306  "MFC", "MFD calibration value (samples)")("MFD", "Whether or not to send the MFD messages")(
307  "MOD", "0 sends FSK minipacket, 1 sends PSK minipacket")(
308  "MPR", "Enable power toggling on Multi-Channel Analog Board")(
309  "MSE", "Print symbol mean squared error (dB) from the LMS equalizer")(
310  "MVM", "Enable voltage mode hydrophone power supply on Multi-Channel Analog Board")(
311  "NDT", "Detect threshold for nav detector")("NPT", "Power threshold for nav detector")(
312  "NRL", "Navigation reverb lockout (ms)")("NRV", "Number of CTOs before hard reboot")(
313  "PAD", "Power-amp delay (ms)")("PCM", "Passband channel mask")(
314  "POW", "Detection power threshold (dB) PRL Int Packet reverb lockout (ms)")(
315  "PTH", "Matched filter detector power threshold")("PTO", "Packet timeout (sec)")(
316  "REV", "Whether or not to send the $CAREV message")(
317  "SGP", "Show GPS messages on main serial port")(
318  "RXA", "Whether or not to send the $CARXA message")(
319  "RXD", "Whether or not to send the $CARXD message")(
320  "RXP", "Whether or not to send the $CARXP message")("SCG", "Set clock from GPS")(
321  "SHF", "Whether or not to send the $CASHF message")(
322  "SNR", "Turn on SNR stats for PSK comms")("SNV", "Synchronous transmission of packets")(
323  "SRC", "Default Source Address")("TAT", "Navigation turn-around-time (msec)")(
324  "TOA", "Display time of arrival of a packet (sec)")("TXD", "Delay before transmit (ms)")(
325  "TXP", "Turn on start of transmit message")("TXF", "Turn on end of transmit message")(
326  "XST", "Turn on transmit stats message, CAXST");
327 }
328 
329 void goby::acomms::MMDriver::set_hydroid_gateway_prefix(int id)
330 {
331  is_hydroid_gateway_ = true;
332  // If the buoy is in use, make the prefix #M<ID>
333  hydroid_gateway_gps_request_ = "#G" + as<std::string>(id) + "\r\n";
334  hydroid_gateway_modem_prefix_ = "#M" + as<std::string>(id);
335 
336  glog.is(DEBUG1) && glog << group(glog_out_group())
337  << "Setting the hydroid_gateway buoy prefix: out="
338  << hydroid_gateway_modem_prefix_ << std::endl;
339 }
340 
341 void goby::acomms::MMDriver::set_clock()
342 {
343  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Setting the Micro-Modem clock."
344  << std::endl;
345 
346  boost::posix_time::ptime p = goby_time();
347 
348  // for sync nav, let's make sure to send the ccclk at the beginning of the
349  // second: between 50ms-100ms after the top of the second
350  // see WHOI sync nav manual
351  // http://acomms.whoi.edu/documents/Synchronous%20Navigation%20With%20MicroModem%20RevD.pdf
352  double frac_sec =
353  double(p.time_of_day().fractional_seconds()) / p.time_of_day().ticks_per_second();
354  while (frac_sec > 100e-3 || frac_sec < 50e-3)
355  {
356  // wait 1 ms
357  usleep(1000);
358  p = goby_time();
359  frac_sec =
360  double(p.time_of_day().fractional_seconds()) / p.time_of_day().ticks_per_second();
361  }
362 
363  if (revision_.mm_major >= 2)
364  {
365  NMEASentence nmea("$CCTMS", NMEASentence::IGNORE);
366  std::stringstream iso_time;
367  boost::posix_time::time_facet* facet =
368  new boost::posix_time::time_facet("%Y-%m-%dT%H:%M:%SZ");
369  iso_time.imbue(std::locale(iso_time.getloc(), facet));
370  iso_time << (p + boost::posix_time::seconds(1));
371  nmea.push_back(iso_time.str());
372  nmea.push_back(0);
373 
374  append_to_write_queue(nmea);
375  }
376  else
377  {
378  NMEASentence nmea("$CCCLK", NMEASentence::IGNORE);
379  nmea.push_back(int(p.date().year()));
380  nmea.push_back(int(p.date().month()));
381  nmea.push_back(int(p.date().day()));
382  nmea.push_back(int(p.time_of_day().hours()));
383  nmea.push_back(int(p.time_of_day().minutes()));
384  nmea.push_back(int(p.time_of_day().seconds() + 1));
385 
386  append_to_write_queue(nmea);
387 
388  // take a breath to let the clock be set
389  sleep(1);
390  }
391 }
392 
393 void goby::acomms::MMDriver::write_cfg()
394 {
395  // reset nvram if requested and not a Hydroid buoy
396  // as this resets the baud to 19200 and the buoy
397  // requires 4800
398  if (!is_hydroid_gateway_ && driver_cfg_.GetExtension(micromodem::protobuf::Config::reset_nvram))
399  write_single_cfg("ALL,0");
400 
401  // try to enforce CST to be enabled
402  write_single_cfg("CST,1");
403 
404  for (int i = 0, n = driver_cfg_.ExtensionSize(micromodem::protobuf::Config::nvram_cfg); i < n;
405  ++i)
406  write_single_cfg(driver_cfg_.GetExtension(micromodem::protobuf::Config::nvram_cfg, i));
407 
408  // enforce SRC to be set the same as provided modem id. we need this for sanity...
409  write_single_cfg("SRC," + as<std::string>(driver_cfg_.modem_id()));
410 
411  // enforce REV to be enabled, we use it for current time and detecting modem reboots
412  write_single_cfg("REV,1");
413 
414  // enforce RXP to be enabled, we use it to detect start of received message
415  write_single_cfg("RXP,1");
416 }
417 
418 void goby::acomms::MMDriver::write_single_cfg(const std::string& s)
419 {
420  NMEASentence nmea("$CCCFG", NMEASentence::IGNORE);
421 
422  // old three letter cfg (always upper case)
423  const std::string::size_type MM1_CFG_LENGTH = 3;
424  if (s.find(',') == MM1_CFG_LENGTH)
425  nmea.push_back(boost::to_upper_copy(s));
426  else // new config
427  nmea.push_back(s);
428 
429  append_to_write_queue(nmea);
430 }
431 
432 void goby::acomms::MMDriver::query_all_cfg()
433 {
434  if (revision_.mm_major >= 2)
435  {
436  NMEASentence nmea("$CCCFQ,TOP", NMEASentence::IGNORE);
437  append_to_write_queue(nmea);
438  }
439  else
440  {
441  NMEASentence nmea("$CCCFQ,ALL", NMEASentence::IGNORE);
442  append_to_write_queue(nmea);
443  }
444 }
445 
446 //
447 // SHUTDOWN
448 //
449 
451 {
452  out_.clear();
453  startup_done_ = false;
454  modem_close();
455 }
456 
458 
459 //
460 // LOOP
461 //
462 
464 {
465  // don't try to set the clock if we already have outgoing
466  // messages queued since the time will be wrong by the time
467  // we can send
468  if (!clock_set_ && out_.empty())
469  set_clock();
470 
471  // send a message periodically (query the source ID) to the local modem to ascertain that it is still alive
472  double now = goby::common::goby_time<double>();
473  if (last_keep_alive_time_ +
474  driver_cfg_.GetExtension(micromodem::protobuf::Config::keep_alive_seconds) <=
475  now)
476  {
477  NMEASentence nmea("$CCCFQ", NMEASentence::IGNORE);
478  nmea.push_back("SRC");
479  append_to_write_queue(nmea);
480  last_keep_alive_time_ = now;
481  }
482 
483  // keep trying to send stuff to the modem
484  try_send();
485 
486  // read any incoming messages from the modem
487  std::string in;
488  while (modem_read(&in))
489  {
490  boost::trim(in);
491  // Check for whether the hydroid_gateway buoy is being used and if so, remove the prefix
492  if (is_hydroid_gateway_)
493  in.erase(0, HYDROID_GATEWAY_PREFIX_LENGTH);
494 
495  // try to handle the received message, posting appropriate signals
496  try
497  {
498  NMEASentence nmea(in, NMEASentence::VALIDATE);
499  process_receive(nmea);
500  }
501  catch (std::exception& e)
502  {
503  glog.is(DEBUG1) && glog << group(glog_in_group()) << warn
504  << "Failed to handle message: " << e.what() << std::endl;
505  }
506  }
507 
508  // if we're using a hydroid buoy query it for its GPS position
509  if (is_hydroid_gateway_ &&
510  last_hydroid_gateway_gps_request_ + HYDROID_GATEWAY_GPS_REQUEST_INTERVAL < goby_time())
511  {
512  modem_write(hydroid_gateway_gps_request_);
513  last_hydroid_gateway_gps_request_ = goby_time();
514  }
515 }
516 
517 //
518 // HANDLE MAC SIGNALS
519 //
520 
522 {
523  transmit_msg_.CopyFrom(msg);
524 
525  try
526  {
527  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Beginning to initiate transmission."
528  << std::endl;
529 
530  // allows zero to N third parties modify the transmission before sending.
531  signal_modify_transmission(&transmit_msg_);
532 
533  switch (transmit_msg_.type())
534  {
535  case protobuf::ModemTransmission::DATA: cccyc(&transmit_msg_); break;
536  case protobuf::ModemTransmission::DRIVER_SPECIFIC:
537  {
538  switch (transmit_msg_.GetExtension(micromodem::protobuf::type))
539  {
540  case micromodem::protobuf::MICROMODEM_MINI_DATA: ccmuc(&transmit_msg_); break;
541  case micromodem::protobuf::MICROMODEM_FLEXIBLE_DATA:
542  cctdp(&transmit_msg_);
543  break;
544  case micromodem::protobuf::MICROMODEM_TWO_WAY_PING: ccmpc(transmit_msg_); break;
545  case micromodem::protobuf::MICROMODEM_REMUS_LBL_RANGING:
546  ccpdt(transmit_msg_);
547  break;
548  case micromodem::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING:
549  ccpnt(transmit_msg_);
550  break;
551  case micromodem::protobuf::MICROMODEM_HARDWARE_CONTROL:
552  ccmec(transmit_msg_);
553  break;
554  case micromodem::protobuf::MICROMODEM_GENERIC_LBL_RANGING:
555  ccpgt(transmit_msg_);
556  break;
557  case micromodem::protobuf::MICROMODEM_FM_SWEEP: ccswp(transmit_msg_); break;
558  case micromodem::protobuf::MICROMODEM_M_SEQUENCE: ccmsq(transmit_msg_); break;
559  default:
560  glog.is(DEBUG1) &&
561  glog << group(glog_out_group()) << warn
562  << "Not initiating transmission because we were given an invalid "
563  "DRIVER_SPECIFIC transmission type for the Micro-Modem:"
564  << transmit_msg_ << std::endl;
565  break;
566  }
567  }
568  break;
569 
570  default:
571  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
572  << "Not initiating transmission because we were given an "
573  "invalid transmission type for the base Driver:"
574  << transmit_msg_ << std::endl;
575  break;
576  }
577  }
578 
579  catch (ModemDriverException& e)
580  {
581  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
582  << "Failed to initiate transmission: " << e.what() << std::endl;
583  }
584 }
585 
586 void goby::acomms::MMDriver::cccyc(protobuf::ModemTransmission* msg)
587 {
588  glog.is(DEBUG1) && glog << group(glog_out_group()) << "\tthis is a DATA transmission"
589  << std::endl;
590 
591  // we initiated this cycle so don't grab data *again* on the CACYC (in cacyc())
592  local_cccyc_ = true;
593 
594  msg->set_max_num_frames(PACKET_FRAME_COUNT[msg->rate()]);
595  msg->set_max_frame_bytes(PACKET_SIZE[msg->rate()]);
596 
597  cache_outgoing_data(msg);
598 
599  // don't start a local cycle if we have no data
600  const bool is_local_cycle = msg->src() == driver_cfg_.modem_id();
601  if (!(is_local_cycle && (msg->frame_size() == 0 || msg->frame(0) == "")))
602  {
603  //$CCCYC,CMD,ADR1,ADR2,Packet Type,ACK,Npkt*CS
604  NMEASentence nmea("$CCCYC", NMEASentence::IGNORE);
605  nmea.push_back(0); // CMD: deprecated field
606  nmea.push_back(msg->src()); // ADR1
607  nmea.push_back(msg->dest()); // ADR2
608  nmea.push_back(msg->rate()); // Packet Type (transmission rate)
609  nmea.push_back(
610  is_local_cycle
611  ? static_cast<int>(msg->ack_requested())
612  : 1); // ACK: deprecated field, but still dictates the value provided by CADRQ
613  nmea.push_back(is_local_cycle ? msg->frame_size()
614  : msg->max_num_frames()); // number of frames we want
615 
616  append_to_write_queue(nmea);
617 
618  // if rate 0 we have an extra transmission for the CCCYC
619  expected_remaining_caxst_ = (msg->rate() == 0 && is_local_cycle) ? 1 : 0;
620  }
621  else
622  {
623  glog.is(DEBUG1) && glog << group(glog_out_group())
624  << "Not initiating transmission because we have no data to send"
625  << std::endl;
626  }
627 }
628 
629 void goby::acomms::MMDriver::ccmuc(protobuf::ModemTransmission* msg)
630 {
631  glog.is(DEBUG1) && glog << group(glog_out_group())
632  << "\tthis is a MICROMODEM_MINI_DATA transmission" << std::endl;
633 
634  const int MINI_NUM_FRAMES = 1;
635  const int MINI_PACKET_SIZE = 2;
636  msg->set_max_num_frames(MINI_NUM_FRAMES);
637  msg->set_max_frame_bytes(MINI_PACKET_SIZE);
638 
639  cache_outgoing_data(msg);
640 
641  if (msg->frame_size() > 0 && msg->frame(0).size())
642  {
643  glog.is(DEBUG1) && glog << "Mini-data message: " << *msg << std::endl;
644  msg->mutable_frame(0)->resize(MINI_PACKET_SIZE);
645  glog.is(DEBUG1) && glog << "Mini-data message after resize: " << *msg << std::endl;
646 
647  if ((msg->frame(0)[0] & 0x1F) != msg->frame(0)[0])
648  {
649  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
650  << "MINI transmission can only be 13 bits; top three bits "
651  "passed were *not* zeros, so discarding. You should AND "
652  "your two bytes with 0x1FFF to get 13 bits"
653  << std::endl;
654  msg->mutable_frame(0)->at(0) &= 0x1F;
655  }
656 
657  //$CCMUC,SRC,DEST,HHHH*CS
658  NMEASentence nmea("$CCMUC", NMEASentence::IGNORE);
659  nmea.push_back(msg->src()); // ADR1
660  nmea.push_back(msg->dest()); // ADR2
661  nmea.push_back(goby::util::hex_encode(msg->frame(0))); //HHHH
662  append_to_write_queue(nmea);
663  }
664  else
665  {
666  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
667  << "MINI transmission failed: no data provided" << std::endl;
668  }
669 }
670 
671 void goby::acomms::MMDriver::cctdp(protobuf::ModemTransmission* msg)
672 {
673  glog.is(DEBUG1) && glog << group(glog_out_group())
674  << "\tthis is a MICROMODEM_FLEXIBLE_DATA transmission" << std::endl;
675 
676  const int FDP_NUM_FRAMES = 1;
677  const int FDP_MAX_PACKET_SIZE = 100;
678 
679  msg->set_max_num_frames(FDP_NUM_FRAMES);
680 
681  // if the requestor of the transmission already set a maximum, don't overwrite it.
682  if (!msg->has_max_frame_bytes())
683  msg->set_max_frame_bytes(FDP_MAX_PACKET_SIZE);
684 
685  cache_outgoing_data(msg);
686 
687  if (msg->frame_size() > 0 && msg->frame(0).size())
688  {
689  glog.is(DEBUG1) && glog << "FDP data message: " << *msg << std::endl;
690 
691  //$CCTDP,dest,rate,ack,reserved,hexdata*CS
692  NMEASentence nmea("$CCTDP", NMEASentence::IGNORE);
693  nmea.push_back(msg->dest());
694  nmea.push_back(msg->rate());
695  if (msg->ack_requested())
696  {
697  if (!using_application_acks_)
698  {
699  glog.is(WARN) &&
700  glog << "Firmware ACK not yet supported for FDP. Enable application acks "
701  "(use_application_acks: true) for ACK capability."
702  << std::endl;
703  }
704  else
705  {
706  expected_ack_destination_ = msg->dest();
707  frames_waiting_for_ack_.insert(next_frame_++);
708  }
709  }
710 
711  nmea.push_back(0); // ack
712  nmea.push_back(0); // reserved
713 
714  nmea.push_back(goby::util::hex_encode(msg->frame(0))); //HHHH
715  append_to_write_queue(nmea);
716  }
717  else
718  {
719  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
720  << "FDP transmission failed: no data provided" << std::endl;
721  }
722 }
723 
724 void goby::acomms::MMDriver::ccmpc(const protobuf::ModemTransmission& msg)
725 {
726  glog.is(DEBUG1) && glog << group(glog_out_group())
727  << "\tthis is a MICROMODEM_TWO_WAY_PING transmission" << std::endl;
728 
729  //$CCMPC,SRC,DEST*CS
730  NMEASentence nmea("$CCMPC", NMEASentence::IGNORE);
731  nmea.push_back(msg.src()); // ADR1
732  nmea.push_back(msg.dest()); // ADR2
733 
734  append_to_write_queue(nmea);
735 }
736 
737 void goby::acomms::MMDriver::ccpdt(const protobuf::ModemTransmission& msg)
738 {
739  glog.is(DEBUG1) && glog << group(glog_out_group())
740  << "\tthis is a MICROMODEM_REMUS_LBL_RANGING transmission" << std::endl;
741 
742  last_lbl_type_ = micromodem::protobuf::MICROMODEM_REMUS_LBL_RANGING;
743 
744  // start with configuration parameters
746  driver_cfg_.GetExtension(micromodem::protobuf::Config::remus_lbl);
747  // merge (overwriting any duplicates) the parameters given in the request
748  params.MergeFrom(msg.GetExtension(micromodem::protobuf::remus_lbl));
749 
750  uint32 tat = params.turnaround_ms();
751  if (static_cast<unsigned>(nvram_cfg_["TAT"]) != tat)
752  write_single_cfg("TAT," + as<std::string>(tat));
753 
754  // $CCPDT,GRP,CHANNEL,SF,STO,Timeout,AF,BF,CF,DF*CS
755  NMEASentence nmea("$CCPDT", NMEASentence::IGNORE);
756  nmea.push_back(1); // GRP 1 is the only group right now
757  nmea.push_back(msg.src() % 4 + 1); // can only use 1-4
758  nmea.push_back(0); // synchronize may not work?
759  nmea.push_back(0); // synchronize may not work?
760  // REMUS LBL is 50 ms turn-around time, assume 1500 m/s speed of sound
761  nmea.push_back(int((params.lbl_max_range() * 2.0 / ROUGH_SPEED_OF_SOUND) * 1000 + tat));
762  nmea.push_back(params.enable_beacons() >> 0 & 1);
763  nmea.push_back(params.enable_beacons() >> 1 & 1);
764  nmea.push_back(params.enable_beacons() >> 2 & 1);
765  nmea.push_back(params.enable_beacons() >> 3 & 1);
766  append_to_write_queue(nmea);
767 }
768 
769 void goby::acomms::MMDriver::ccpnt(const protobuf::ModemTransmission& msg)
770 {
771  glog.is(DEBUG1) && glog << group(glog_out_group())
772  << "\tthis is a MICROMODEM_NARROWBAND_LBL_RANGING transmission"
773  << std::endl;
774 
775  last_lbl_type_ = micromodem::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING;
776 
777  // start with configuration parameters
779  driver_cfg_.GetExtension(micromodem::protobuf::Config::narrowband_lbl);
780  // merge (overwriting any duplicates) the parameters given in the request
781  params.MergeFrom(msg.GetExtension(micromodem::protobuf::narrowband_lbl));
782 
783  uint32 tat = params.turnaround_ms();
784  if (static_cast<unsigned>(nvram_cfg_["TAT"]) != tat)
785  write_single_cfg("TAT," + as<std::string>(tat));
786 
787  // $CCPNT, Ftx, Ttx, Trx, Timeout, FA, FB, FC, FD,Tflag*CS
788  NMEASentence nmea("$CCPNT", NMEASentence::IGNORE);
789  nmea.push_back(params.transmit_freq());
790  nmea.push_back(params.transmit_ping_ms());
791  nmea.push_back(params.receive_ping_ms());
792  nmea.push_back(
793  static_cast<int>((params.lbl_max_range() * 2.0 / ROUGH_SPEED_OF_SOUND) * 1000 + tat));
794 
795  // no more than four allowed
796  const int MAX_NUMBER_RX_BEACONS = 4;
797 
798  int number_rx_freq_provided = std::min(MAX_NUMBER_RX_BEACONS, params.receive_freq_size());
799 
800  for (int i = 0, n = std::max(MAX_NUMBER_RX_BEACONS, number_rx_freq_provided); i < n; ++i)
801  {
802  if (i < number_rx_freq_provided)
803  nmea.push_back(params.receive_freq(i));
804  else
805  nmea.push_back(0);
806  }
807 
808  nmea.push_back(static_cast<int>(params.transmit_flag()));
809  append_to_write_queue(nmea);
810 }
811 
812 void goby::acomms::MMDriver::ccmec(const protobuf::ModemTransmission& msg)
813 {
814  glog.is(DEBUG1) && glog << group(glog_out_group())
815  << "\tthis is a MICROMODEM_HARDWARE_CONTROL transmission" << std::endl;
816 
817  micromodem::protobuf::HardwareControl params = msg.GetExtension(micromodem::protobuf::hw_ctl);
818 
819  // $CCMEC,source,dest,line,mode,arg*CS
820  NMEASentence nmea("$CCMEC", NMEASentence::IGNORE);
821  nmea.push_back(msg.src());
822  nmea.push_back(msg.dest());
823  nmea.push_back(static_cast<int>(params.line()));
824  nmea.push_back(static_cast<int>(params.mode()));
825  nmea.push_back(static_cast<int>(params.arg()));
826 
827  append_to_write_queue(nmea);
828 }
829 
830 void goby::acomms::MMDriver::ccpgt(const protobuf::ModemTransmission& msg)
831 {
832  glog.is(DEBUG1) && glog << group(glog_out_group())
833  << "\tthis is a MICROMODEM_GENERIC_LBL_RANGING transmission"
834  << std::endl;
835 
836  last_lbl_type_ = micromodem::protobuf::MICROMODEM_GENERIC_LBL_RANGING;
837 
838  // start with configuration parameters
840  driver_cfg_.GetExtension(micromodem::protobuf::Config::generic_lbl);
841  // merge (overwriting any duplicates) the parameters given in the request
842  params.MergeFrom(msg.GetExtension(micromodem::protobuf::generic_lbl));
843 
844  uint32 tat = params.turnaround_ms();
845  if (static_cast<unsigned>(nvram_cfg_["TAT"]) != tat)
846  write_single_cfg("TAT," + as<std::string>(tat));
847 
848  // $CCPDT,GRP,CHANNEL,SF,STO,Timeout,AF,BF,CF,DF*CS
849  // $CCPGT,Ftx,nbits,tx_seq_code,timeout,Frx,rx_code1,rx_code2,rx_code3,rx_code4,bandwidth,reserved*CS
850  NMEASentence nmea("$CCPGT", NMEASentence::IGNORE);
851  nmea.push_back(params.transmit_freq());
852  nmea.push_back(params.n_bits());
853  nmea.push_back(params.transmit_seq_code());
854  // REMUS LBL is 50 ms turn-around time, assume 1500 m/s speed of sound
855  nmea.push_back(int((params.lbl_max_range() * 2.0 / ROUGH_SPEED_OF_SOUND) * 1000 + tat));
856  nmea.push_back(params.receive_freq());
857 
858  // up to four receive_seq_codes
859  const int MAX_NUMBER_RX_BEACONS = 4;
860  int number_rx_seq_codes_provided = params.receive_seq_code_size();
861  for (int i = 0; i < MAX_NUMBER_RX_BEACONS; ++i)
862  {
863  if (i < number_rx_seq_codes_provided)
864  nmea.push_back(params.receive_seq_code(i));
865  else
866  nmea.push_back(0);
867  }
868 
869  nmea.push_back(params.bandwidth());
870  nmea.push_back(0); // reserved
871  append_to_write_queue(nmea);
872 }
873 
874 void goby::acomms::MMDriver::ccswp(const protobuf::ModemTransmission& msg)
875 {
876  glog.is(DEBUG1) && glog << group(glog_out_group())
877  << "\tthis is a MICROMODEM_FM_SWEEP transmission" << std::endl;
878 
879  // start with configuration parameters
881  driver_cfg_.GetExtension(micromodem::protobuf::Config::fm_sweep);
882  params.MergeFrom(msg.GetExtension(micromodem::protobuf::fm_sweep));
883 
884  // $CCSWP,start_freqx10, stop_freqx10, bw_Hz, duration_msx10, nreps, reptime_msx10*CS
885  NMEASentence nmea("$CCSWP", NMEASentence::IGNORE);
886  nmea.push_back(static_cast<int>(params.start_freq() * 10));
887  nmea.push_back(static_cast<int>(params.stop_freq() * 10));
888  nmea.push_back(static_cast<int>(std::abs(params.start_freq() - params.stop_freq())));
889  nmea.push_back(static_cast<int>(params.duration_ms() * 10));
890  nmea.push_back(params.number_repetitions());
891  nmea.push_back(static_cast<int>(params.repetition_period_ms() * 10));
892 
893  append_to_write_queue(nmea);
894 }
895 
896 void goby::acomms::MMDriver::ccmsq(const protobuf::ModemTransmission& msg)
897 {
898  glog.is(DEBUG1) && glog << group(glog_out_group())
899  << "\tthis is a MICROMODEM_M_SEQUENCE transmission" << std::endl;
900 
901  // start with configuration parameters
903  driver_cfg_.GetExtension(micromodem::protobuf::Config::m_sequence);
904  params.MergeFrom(msg.GetExtension(micromodem::protobuf::m_sequence));
905 
906  // $CCMSQ,seqlen_bits,nreps,cycles_per_chip,carrier_Hz*CS
907  NMEASentence nmea("$CCMSQ", NMEASentence::IGNORE);
908  nmea.push_back(params.seqlen_bits());
909  nmea.push_back(params.number_repetitions());
910  nmea.push_back(params.carrier_cycles_per_chip());
911  nmea.push_back(params.carrier_freq());
912 
913  append_to_write_queue(nmea);
914 }
915 
916 //
917 // OUTGOING NMEA
918 //
919 
920 void goby::acomms::MMDriver::append_to_write_queue(const NMEASentence& nmea)
921 {
922  out_.push_back(nmea);
923  try_send(); // try to push it now without waiting for the next call to do_work();
924 }
925 
926 void goby::acomms::MMDriver::try_send()
927 {
928  if (out_.empty())
929  return;
930 
931  const util::NMEASentence& nmea = out_.front();
932 
933  bool resend = waiting_for_modem_ && (last_write_time_ <= (goby_time() - MODEM_WAIT));
934  if (!waiting_for_modem_)
935  {
936  mm_write(nmea);
937  }
938  else if (resend)
939  {
940  glog.is(DEBUG1) &&
941  glog << group(glog_out_group()) << "resending last command; no serial ack in "
942  << (goby_time() - last_write_time_).total_seconds() << " second(s). " << std::endl;
943  ++global_fail_count_;
944 
945  if (global_fail_count_ == MAX_FAILS_BEFORE_DEAD)
946  {
947  shutdown();
948  global_fail_count_ = 0;
949  throw(ModemDriverException("Micro-Modem appears to not be responding!",
950  protobuf::ModemDriverStatus::MODEM_NOT_RESPONDING));
951  }
952 
953  try
954  {
955  // try to increment the present (current NMEA sentence) fail counter
956  // will throw if fail counter exceeds RETRIES
957  increment_present_fail();
958  // assuming we're still ok, write the line again
959  mm_write(nmea);
960  }
961  catch (ModemDriverException& e)
962  {
963  present_fail_exceeds_retries();
964  }
965  }
966 }
967 
968 void goby::acomms::MMDriver::mm_write(const util::NMEASentence& nmea)
969 {
970  protobuf::ModemRaw raw_msg;
971  raw_msg.set_raw(nmea.message());
972  raw_msg.set_description(description_map_[nmea.front()]);
973 
974  glog.is(DEBUG1) && glog << group(glog_out_group()) << hydroid_gateway_modem_prefix_
975  << raw_msg.raw() << "\n"
976  << "^ " << magenta << raw_msg.description() << nocolor << std::endl;
977 
978  signal_raw_outgoing(raw_msg);
979 
980  modem_write(hydroid_gateway_modem_prefix_ + raw_msg.raw() + "\r\n");
981 
982  waiting_for_modem_ = true;
983  last_write_time_ = goby_time();
984 }
985 
986 void goby::acomms::MMDriver::increment_present_fail()
987 {
988  ++present_fail_count_;
989  if (present_fail_count_ >= RETRIES)
990  throw(ModemDriverException("Fail count exceeds RETRIES",
991  protobuf::ModemDriverStatus::MODEM_NOT_RESPONDING));
992 }
993 
994 void goby::acomms::MMDriver::present_fail_exceeds_retries()
995 {
996  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
997  << "Micro-Modem did not respond to our command even after " << RETRIES
998  << " retries. continuing onwards anyway..." << std::endl;
999  pop_out();
1000 }
1001 
1002 void goby::acomms::MMDriver::pop_out()
1003 {
1004  waiting_for_modem_ = false;
1005 
1006  if (!out_.empty())
1007  out_.pop_front();
1008  else
1009  {
1010  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
1011  << "Expected to pop outgoing NMEA message but out_ deque is empty"
1012  << std::endl;
1013  }
1014 
1015  present_fail_count_ = 0;
1016 }
1017 
1018 //
1019 // INCOMING NMEA
1020 //
1021 
1022 void goby::acomms::MMDriver::process_receive(const NMEASentence& nmea)
1023 {
1024  // need to print this first so raw log messages appear causal (as they are)
1025  protobuf::ModemRaw raw_msg;
1026  raw_msg.set_raw(nmea.message());
1027  raw_msg.set_description(description_map_[nmea.front()]);
1028 
1029  if (sentence_id_map_[nmea.sentence_id()] == CFG)
1030  *raw_msg.mutable_description() += ": " + cfg_map_[nmea.at(1)];
1031 
1032  glog.is(DEBUG1) && glog << group(glog_in_group()) << hydroid_gateway_modem_prefix_
1033  << raw_msg.raw() << "\n"
1034  << "^ " << magenta << raw_msg.description() << nocolor << std::endl;
1035 
1036  signal_raw_incoming(raw_msg);
1037 
1038  global_fail_count_ = 0;
1039 
1040  // look at the sentence id (last three characters of the NMEA 0183 talker)
1041  switch (sentence_id_map_[nmea.sentence_id()])
1042  {
1043  //
1044  // local modem
1045  //
1046  case REV: carev(nmea); break; // software revision
1047  case ERR: caerr(nmea); break; // error message
1048  case DRQ: cadrq(nmea, transmit_msg_); break; // data request
1049  case CFG: cacfg(nmea); break; // configuration
1050  case CLK: receive_time(nmea, CLK); break; // clock
1051  case TMS: receive_time(nmea, TMS); break; // clock (MM2)
1052  case TMQ:
1053  receive_time(nmea, TMQ);
1054  break; // clock (MM2)
1055 
1056  //
1057  // data cycle
1058  //
1059  case CYC: cacyc(nmea, &transmit_msg_); break; // cycle init
1060  case XST: caxst(nmea, &transmit_msg_); break; // transmit stats for clock mode
1061  case RXD: carxd(nmea, &receive_msg_); break; // data receive
1062  case MSG: camsg(nmea, &receive_msg_); break; // for picking up BAD_CRC
1063  case CST: cacst(nmea, &receive_msg_); break; // transmit stats for clock mode
1064  case MUA: camua(nmea, &receive_msg_); break; // mini-packet receive
1065  case RDP: cardp(nmea, &receive_msg_); break; // FDP receive
1066  case ACK:
1067  caack(nmea, &receive_msg_);
1068  break; // acknowledge
1069 
1070  //
1071  // ranging
1072  //
1073  case MPR: campr(nmea, &receive_msg_); break; // two way ping
1074  case MPA: campa(nmea, &receive_msg_); break; // hear request for two way ping
1075  case TTA:
1076  sntta(nmea, &receive_msg_);
1077  break; // remus / narrowband lbl times
1078 
1079  // hardware control
1080  case MER: camer(nmea, &receive_msg_); break; // reply to hardware control
1081 
1082  default: break;
1083  }
1084 
1085  // clear the last send given modem acknowledgement
1086  if (!out_.empty())
1087  {
1088  if (out_.front().sentence_id() == "CFG")
1089  {
1090  if (nmea.size() == 3 && nmea.at(1) == out_.front().at(1) &&
1091  nmea.at(2) == out_.front().at(2)) // special case, needs to match all three parts
1092  pop_out();
1093  }
1094  else if (out_.front().sentence_id() == "CFQ" &&
1095  nmea.sentence_id() == "CFG") // special case CFG acks CFQ
1096  {
1097  pop_out();
1098  }
1099  else if (out_.front().sentence_id() == "MSC" &&
1100  (nmea.sentence_id() == "REV" || nmea.sentence_id() == "MSC"))
1101  {
1102  pop_out();
1103  }
1104  else if (out_.front().sentence_id() == nmea.sentence_id()) // general matching sentence id
1105  {
1106  pop_out();
1107  }
1108  }
1109 }
1110 
1111 void goby::acomms::MMDriver::caack(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1112 {
1113  // ACK has nothing to do with us!
1114  if (as<int32>(nmea[2]) != driver_cfg_.modem_id())
1115  return;
1116  if (as<unsigned>(nmea[1]) != expected_ack_destination_)
1117  return;
1118 
1119  // WHOI counts starting at 1, Goby counts starting at 0
1120  uint32 frame = as<uint32>(nmea[3]) - 1;
1121 
1122  handle_ack(as<uint32>(nmea[1]), as<uint32>(nmea[2]), frame, m);
1123 
1124  // if enabled cacst will signal_receive
1125  if (!nvram_cfg_["CST"])
1126  signal_receive_and_clear(m);
1127 }
1128 
1129 void goby::acomms::MMDriver::handle_ack(uint32 src, uint32 dest, uint32 frame,
1131 {
1132  if (frames_waiting_for_ack_.count(frame))
1133  {
1134  m->set_time(goby_time<uint64>());
1135  m->set_src(src);
1136  m->set_dest(dest);
1137  m->set_type(protobuf::ModemTransmission::ACK);
1138  m->add_acked_frame(frame);
1139 
1140  frames_waiting_for_ack_.erase(frame);
1141 
1142  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Received ACK from " << m->src()
1143  << " for frame " << frame << std::endl;
1144  }
1145  else
1146  {
1147  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Received ACK for Micro-Modem frame "
1148  << frame + 1 << " (Goby frame " << frame
1149  << ") that we were not expecting." << std::endl;
1150  }
1151 }
1152 
1153 void goby::acomms::MMDriver::camer(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1154 {
1155  int src_field = 1;
1156  int dest_field = 2;
1157 
1158  // this are swapped in older MM2 revisions
1159  if (revision_.mm_major == 2 && revision_.mm_minor == 0 && revision_.mm_patch < 18307)
1160  {
1161  src_field = 2;
1162  dest_field = 1;
1163  }
1164 
1165  if (as<int32>(nmea[dest_field]) != driver_cfg_.modem_id())
1166  return;
1167 
1168  m->set_time(goby_time<uint64>());
1169  // I think these are reversed from what the manual states
1170  m->set_src(as<uint32>(nmea[src_field]));
1171  m->set_dest(as<uint32>(nmea[dest_field]));
1172  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1173  m->SetExtension(micromodem::protobuf::type,
1174  micromodem::protobuf::MICROMODEM_HARDWARE_CONTROL_REPLY);
1175 
1177  m->MutableExtension(micromodem::protobuf::hw_ctl);
1178 
1179  hw_ctl->set_line(nmea.as<micromodem::protobuf::HardwareLine>(3));
1180  hw_ctl->set_mode(nmea.as<micromodem::protobuf::HardwareControlMode>(4));
1181  hw_ctl->set_arg(nmea.as<micromodem::protobuf::HardwareControlArgument>(5));
1182 
1183  // if enabled cacst will signal_receive
1184  if (!nvram_cfg_["CST"])
1185  signal_receive_and_clear(m);
1186 }
1187 
1188 void goby::acomms::MMDriver::cadrq(const NMEASentence& nmea_in,
1189  const protobuf::ModemTransmission& m)
1190 {
1191  //$CADRQ,HHMMSS,SRC,DEST,ACK,N,F#*CS
1192 
1193  NMEASentence nmea_out("$CCTXD", NMEASentence::IGNORE);
1194 
1195  // WHOI counts frames from 1, we count from 0
1196  int frame = as<int>(nmea_in[6]) - 1;
1197 
1198  if (frame < m.frame_size() && !m.frame(frame).empty())
1199  {
1200  // use the cached data
1201  nmea_out.push_back(m.src());
1202  nmea_out.push_back(m.dest());
1203  nmea_out.push_back(int(m.ack_requested()));
1204 
1205  int max_bytes = nmea_in.as<int>(5);
1206  nmea_out.push_back(
1207  hex_encode(m.frame(frame) + std::string(max_bytes - m.frame(frame).size(), '\0')));
1208  // nmea_out.push_back(hex_encode(m.frame(frame)));
1209 
1210  if (m.ack_requested())
1211  {
1212  expected_ack_destination_ = m.dest();
1213  frames_waiting_for_ack_.insert(next_frame_++);
1214  }
1215  }
1216  else
1217  {
1218  // send a blank message to supress further DRQ
1219  nmea_out.push_back(nmea_in[2]); // SRC
1220  nmea_out.push_back(nmea_in[3]); // DEST
1221  nmea_out.push_back(nmea_in[4]); // ACK
1222  nmea_out.push_back(""); // no data
1223  }
1224  append_to_write_queue(nmea_out);
1225 }
1226 
1227 void goby::acomms::MMDriver::camsg(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1228 {
1229  // CAMSG,BAD_CRC,4
1230  if ((nmea.as<std::string>(1) == "BAD_CRC" || nmea.as<std::string>(1) == "Bad CRC") &&
1231  !frames_waiting_to_receive_.empty()) // it's not a bad CRC on the CCCYC
1232  {
1233  m->AddExtension(micromodem::protobuf::frame_with_bad_crc, m->frame_size());
1234  // add a blank (placeholder) frame
1235  m->add_frame();
1236  // assume it's for the next frame
1237  frames_waiting_to_receive_.erase(frames_waiting_to_receive_.begin());
1238 
1239  // if enabled cacst will signal_receive, otherwise signal if this is the last frame
1240  if (frames_waiting_to_receive_.empty() && !nvram_cfg_["CST"])
1241  signal_receive_and_clear(m);
1242 
1243  glog.is(DEBUG1) && glog << group(glog_in_group()) << warn << "Received message with bad CRC"
1244  << std::endl;
1245  }
1246 }
1247 
1248 void goby::acomms::MMDriver::carxd(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1249 {
1250  // WHOI counts from 1, we count from 0
1251  unsigned frame = as<uint32>(nmea[4]) - 1;
1252  if (frame == 0)
1253  {
1254  m->set_time(goby_time<uint64>());
1255  m->set_src(as<uint32>(nmea[1]));
1256  m->set_dest(as<uint32>(nmea[2]));
1257  m->set_type(protobuf::ModemTransmission::DATA);
1258  m->set_ack_requested(as<bool>(nmea[3]));
1259  }
1260 
1261  if (!nmea[5].empty()) // don't add blank messages
1262  {
1263  if (static_cast<unsigned>(m->frame_size()) != frame)
1264  {
1265  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
1266  << "frame count mismatch: (Micro-Modem reports): " << frame
1267  << ", (goby expects): " << m->frame_size() << std::endl;
1268  }
1269 
1270  m->add_frame(hex_decode(nmea[5]));
1271  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Received "
1272  << m->frame(m->frame_size() - 1).size() << " byte DATA frame "
1273  << frame << " from " << m->src() << std::endl;
1274  }
1275 
1276  frames_waiting_to_receive_.erase(frame);
1277 
1278  // if enabled cacst will signal_receive, otherwise signal if this is the last frame
1279  if (frames_waiting_to_receive_.empty() && !nvram_cfg_["CST"])
1280  signal_receive_and_clear(m);
1281 }
1282 
1283 void goby::acomms::MMDriver::camua(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1284 {
1285  // m->Clear();
1286 
1287  m->set_time(goby_time<uint64>());
1288  m->set_src(as<uint32>(nmea[1]));
1289  m->set_dest(as<uint32>(nmea[2]));
1290  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1291  m->SetExtension(micromodem::protobuf::type, micromodem::protobuf::MICROMODEM_MINI_DATA);
1292 
1293  m->add_frame(goby::util::hex_decode(nmea[3]));
1294 
1295  glog.is(DEBUG1) && glog << group(glog_in_group())
1296  << "Received MICROMODEM_MINI_DATA packet from " << m->src()
1297  << std::endl;
1298 
1299  // if enabled cacst will signal_receive
1300  if (!nvram_cfg_["CST"])
1301  signal_receive_and_clear(m);
1302 }
1303 
1304 void goby::acomms::MMDriver::cardp(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1305 {
1306  enum
1307  {
1308  SRC = 1,
1309  DEST = 2,
1310  RATE = 3,
1311  ACK = 4,
1312  RESERVED = 5,
1313  DATA_MINI = 6,
1314  DATA = 7
1315  };
1316 
1317  m->set_time(goby_time<uint64>());
1318  m->set_src(as<uint32>(nmea[SRC]));
1319  m->set_dest(as<uint32>(nmea[DEST]));
1320  m->set_rate(as<uint32>(nmea[RATE]));
1321  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1322  m->SetExtension(micromodem::protobuf::type, micromodem::protobuf::MICROMODEM_FLEXIBLE_DATA);
1323 
1324  std::vector<std::string> frames, frames_data;
1325 
1326  if (!nmea[DATA_MINI].empty())
1327  boost::split(frames, nmea[DATA_MINI], boost::is_any_of(";"));
1328  if (!nmea[DATA].empty())
1329  boost::split(frames_data, nmea[DATA], boost::is_any_of(";"));
1330  frames.insert(frames.end(), frames_data.begin(), frames_data.end());
1331 
1332  bool bad_frame = false;
1333  std::string frame_hex;
1334  const int num_fields = 3;
1335  for (int f = 0, n = frames.size() / num_fields; f < n; ++f)
1336  {
1337  // offset from f
1338  enum
1339  {
1340  CRCCHECK = 0,
1341  NBYTES = 1,
1342  DATA = 2
1343  };
1344 
1345  if (!goby::util::as<bool>(frames[f * num_fields + CRCCHECK]))
1346  {
1347  bad_frame = true;
1348  break;
1349  }
1350  else
1351  {
1352  frame_hex += frames[f * num_fields + DATA];
1353  }
1354  }
1355 
1356  if (bad_frame)
1357  {
1358  m->AddExtension(micromodem::protobuf::frame_with_bad_crc, 0);
1359  m->add_frame();
1360  }
1361  else
1362  {
1363  m->add_frame(goby::util::hex_decode(frame_hex));
1364  if (using_application_acks_)
1365  process_incoming_app_ack(m);
1366  }
1367 
1368  glog.is(DEBUG1) && glog << group(glog_in_group())
1369  << "Received MICROMODEM_FLEXIBLE_DATA packet from " << m->src()
1370  << std::endl;
1371 
1372  // if enabled cacst will signal_receive
1373  if (!nvram_cfg_["CST"])
1374  signal_receive_and_clear(m);
1375 }
1376 
1377 void goby::acomms::MMDriver::process_incoming_app_ack(protobuf::ModemTransmission* m)
1378 {
1379  if (dccl_.id(m->frame(0)) == dccl_.id<micromodem::protobuf::MMApplicationAck>())
1380  {
1382  dccl_.decode(m->mutable_frame(0), &acks);
1383  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Received ACKS " << acks.DebugString()
1384  << std::endl;
1385 
1386  // this data message requires a future ACK from us
1387  if (m->dest() == driver_cfg_.modem_id() && acks.ack_requested())
1388  {
1389  frames_to_ack_[m->src()].insert(acks.frame_start());
1390  }
1391 
1392  // process any acks that were included in this message
1393  for (int i = 0, n = acks.part_size(); i < n; ++i)
1394  {
1395  if (acks.part(i).ack_dest() == driver_cfg_.modem_id())
1396  {
1397  for (int j = 0, o = application_ack_max_frames_; j < o; ++j)
1398  {
1399  if (acks.part(i).acked_frames() & (1ul << j))
1400  {
1402  handle_ack(m->src(), acks.part(i).ack_dest(), j, &msg);
1403  signal_receive(msg);
1404  }
1405  }
1406  }
1407  }
1408  }
1409 }
1410 
1411 void goby::acomms::MMDriver::cacfg(const NMEASentence& nmea)
1412 {
1413  nvram_cfg_[nmea[1]] = nmea.as<int>(2);
1414 }
1415 
1416 void goby::acomms::MMDriver::receive_time(const NMEASentence& nmea, SentenceIDs sentence_id)
1417 {
1418  if (out_.empty() || (sentence_id == CLK && out_.front().sentence_id() != "CLK") ||
1419  (sentence_id == TMS && out_.front().sentence_id() != "TMS") ||
1420  (sentence_id == TMQ && out_.front().sentence_id() != "TMQ"))
1421  return;
1422 
1423  using namespace boost::posix_time;
1424  using namespace boost::gregorian;
1425  ptime expected = goby_time();
1426  ptime reported;
1427 
1428  if (sentence_id == CLK)
1429  {
1430  reported = ptime(date(nmea.as<int>(1), nmea.as<int>(2), nmea.as<int>(3)),
1431  time_duration(nmea.as<int>(4), nmea.as<int>(5), nmea.as<int>(6), 0));
1432  }
1433  else if (sentence_id == TMS || sentence_id == TMQ)
1434  {
1435  int time_field = 0;
1436  if (sentence_id == TMS)
1437  time_field = 2;
1438  else if (sentence_id == TMQ)
1439  time_field = 1;
1440 
1441  std::string t = nmea.at(time_field).substr(0, nmea.at(time_field).size() - 1);
1442  boost::posix_time::time_input_facet* tif = new boost::posix_time::time_input_facet;
1443  tif->set_iso_extended_format();
1444  std::istringstream iso_time(t);
1445  iso_time.imbue(std::locale(std::locale::classic(), tif));
1446  iso_time >> reported;
1447  }
1448  // glog.is(DEBUG1) && glog << group(glog_in_group()) << "Micro-Modem reported time: " << reported << std::endl;
1449 
1450  // make sure the modem reports its time as set at the right time
1451  // we may end up oversetting the clock, but better safe than sorry...
1452  boost::posix_time::time_duration t_diff = (reported - expected);
1453 
1454  // glog.is(DEBUG1) && glog << group(glog_in_group()) << "Difference: " << t_diff << std::endl;
1455 
1456  if (abs(int(t_diff.total_milliseconds())) <
1457  driver_cfg_.GetExtension(micromodem::protobuf::Config::allowed_skew_ms))
1458  {
1459  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Micro-Modem clock acceptably set."
1460  << std::endl;
1461  clock_set_ = true;
1462  }
1463  else
1464  {
1465  glog.is(DEBUG1) &&
1466  glog << group(glog_out_group())
1467  << "Time is not within allowed skew, setting Micro-Modem clock again."
1468  << std::endl;
1469  clock_set_ = false;
1470  }
1471 }
1472 
1473 void goby::acomms::MMDriver::caxst(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1474 {
1476  m->AddExtension(micromodem::protobuf::transmit_stat);
1477 
1478  // old XST has date as first field, and we'll assume all dates are
1479  // greater than UNIX epoch
1480  xst->set_version(nmea.as<int>(1) > 19700000 ? 0 : nmea.as<int>(1));
1481 
1482  try
1483  {
1484  int version_offset = 0; // offset in NMEA field number
1485  if (xst->version() == 0)
1486  {
1487  version_offset = 0;
1488  }
1489  else if (xst->version() == 6)
1490  {
1491  version_offset = 1;
1492  }
1493 
1494  xst->set_date(nmea.as<std::string>(1 + version_offset));
1495  xst->set_time(nmea.as<std::string>(2 + version_offset));
1496 
1497  micromodem::protobuf::ClockMode clock_mode =
1498  micromodem::protobuf::ClockMode_IsValid(nmea.as<int>(3 + version_offset))
1499  ? nmea.as<micromodem::protobuf::ClockMode>(3 + version_offset)
1500  : micromodem::protobuf::INVALID_CLOCK_MODE;
1501 
1502  xst->set_clock_mode(clock_mode);
1503 
1504  micromodem::protobuf::TransmitMode transmit_mode =
1505  micromodem::protobuf::TransmitMode_IsValid(nmea.as<int>(4 + version_offset))
1506  ? nmea.as<micromodem::protobuf::TransmitMode>(4 + version_offset)
1507  : micromodem::protobuf::INVALID_TRANSMIT_MODE;
1508 
1509  xst->set_mode(transmit_mode);
1510 
1511  xst->set_probe_length(nmea.as<int32>(5 + version_offset));
1512  xst->set_bandwidth(nmea.as<int32>(6 + version_offset));
1513  xst->set_carrier_freq(nmea.as<int32>(7 + version_offset));
1514  xst->set_rate(nmea.as<int32>(8 + version_offset));
1515  xst->set_source(nmea.as<int32>(9 + version_offset));
1516  xst->set_dest(nmea.as<int32>(10 + version_offset));
1517  xst->set_ack_requested(nmea.as<bool>(11 + version_offset));
1518  xst->set_number_frames_expected(nmea.as<int32>(12 + version_offset));
1519  xst->set_number_frames_sent(nmea.as<int32>(13 + version_offset));
1520 
1521  micromodem::protobuf::PacketType packet_type =
1522  micromodem::protobuf::PacketType_IsValid(nmea.as<int>(14 + version_offset))
1523  ? nmea.as<micromodem::protobuf::PacketType>(14 + version_offset)
1524  : micromodem::protobuf::PACKET_TYPE_UNKNOWN;
1525 
1526  xst->set_packet_type(packet_type);
1527  xst->set_number_bytes(nmea.as<int32>(15 + version_offset));
1528 
1529  clk_mode_ = xst->clock_mode();
1530  }
1531  catch (std::out_of_range& e) // thrown by std::vector::at() called by NMEASentence::as()
1532  {
1533  glog.is(DEBUG1) && glog << group(glog_in_group()) << warn
1534  << "$CAXST message shorter than expected" << std::endl;
1535  }
1536 
1537  if (expected_remaining_caxst_ == 0)
1538  {
1540  m->Clear();
1541  }
1542  else
1543  {
1544  --expected_remaining_caxst_;
1545  }
1546 }
1547 
1548 void goby::acomms::MMDriver::campr(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1549 {
1550  m->set_time(goby_time<uint64>());
1551 
1552  // $CAMPR,SRC,DEST,TRAVELTIME*CS
1553  // reverse src and dest so they match the original request
1554  m->set_src(as<uint32>(nmea[1]));
1555  m->set_dest(as<uint32>(nmea[2]));
1556 
1557  micromodem::protobuf::RangingReply* ranging_reply =
1558  m->MutableExtension(micromodem::protobuf::ranging_reply);
1559 
1560  if (nmea.size() > 3)
1561  ranging_reply->add_one_way_travel_time(as<double>(nmea[3]));
1562 
1563  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1564  m->SetExtension(micromodem::protobuf::type, micromodem::protobuf::MICROMODEM_TWO_WAY_PING);
1565 
1566  glog.is(DEBUG1) &&
1567  glog << group(glog_in_group()) << "Received MICROMODEM_TWO_WAY_PING response from "
1568  << m->src() << ", 1-way travel time: "
1569  << ranging_reply->one_way_travel_time(ranging_reply->one_way_travel_time_size() - 1)
1570  << "s" << std::endl;
1571 
1572  // if enabled cacst will signal_receive
1573  if (!nvram_cfg_["CST"])
1574  signal_receive_and_clear(m);
1575 }
1576 
1577 void goby::acomms::MMDriver::campa(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1578 {
1579  m->set_time(goby_time<uint64>());
1580 
1581  // $CAMPR,SRC,DEST*CS
1582  m->set_src(as<uint32>(nmea[1]));
1583  m->set_dest(as<uint32>(nmea[2]));
1584 
1585  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1586  m->SetExtension(micromodem::protobuf::type, micromodem::protobuf::MICROMODEM_TWO_WAY_PING);
1587 
1588  // if enabled cacst will signal_receive
1589  if (!nvram_cfg_["CST"])
1590  signal_receive_and_clear(m);
1591 }
1592 
1593 void goby::acomms::MMDriver::sntta(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1594 {
1595  // m->Clear();
1596 
1597  micromodem::protobuf::RangingReply* ranging_reply =
1598  m->MutableExtension(micromodem::protobuf::ranging_reply);
1599 
1600  ranging_reply->add_one_way_travel_time(as<double>(nmea[1]));
1601  ranging_reply->add_one_way_travel_time(as<double>(nmea[2]));
1602  ranging_reply->add_one_way_travel_time(as<double>(nmea[3]));
1603  ranging_reply->add_one_way_travel_time(as<double>(nmea[4]));
1604 
1605  m->set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
1606  m->SetExtension(micromodem::protobuf::type, last_lbl_type_);
1607 
1608  m->set_src(driver_cfg_.modem_id());
1609  m->set_time(as<uint64>(nmea_time2ptime(nmea[5])));
1610  m->set_time_source(protobuf::ModemTransmission::MODEM_TIME);
1611 
1612  if (last_lbl_type_ == micromodem::protobuf::MICROMODEM_REMUS_LBL_RANGING)
1613  glog.is(DEBUG1) && glog << group(glog_in_group())
1614  << "Received MICROMODEM_REMUS_LBL_RANGING response " << std::endl;
1615  else if (last_lbl_type_ == micromodem::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING)
1616  glog.is(DEBUG1) && glog << group(glog_in_group())
1617  << "Received MICROMODEM_NARROWBAND_LBL_RANGING response "
1618  << std::endl;
1619 
1620  // no cacst on sntta, so signal receive here
1621  signal_receive_and_clear(m);
1622 }
1623 
1624 void goby::acomms::MMDriver::carev(const NMEASentence& nmea)
1625 {
1626  if (nmea[2] == "INIT")
1627  {
1628  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Micro-Modem rebooted." << std::endl;
1629  // reboot
1630  sleep(WAIT_AFTER_REBOOT.total_seconds());
1631  clock_set_ = false;
1632  }
1633  else if (nmea[2] == "AUV")
1634  {
1635  std::vector<std::string> rev_parts;
1636  boost::split(rev_parts, nmea[3], boost::is_any_of("."));
1637  if (rev_parts.size() == 3 || rev_parts.size() == 4)
1638  {
1639  revision_.mm_major = as<int>(rev_parts[0]);
1640  revision_.mm_minor = as<int>(rev_parts[1]);
1641  revision_.mm_patch = as<int>(rev_parts[2]);
1642  if (rev_parts.size() == 4)
1643  {
1644  revision_.mm_patch *= 100;
1645  revision_.mm_patch += as<int>(rev_parts[3]);
1646  }
1647 
1648  glog.is(DEBUG1) && glog << group(glog_in_group()) << "Revision: " << revision_.mm_major
1649  << "." << revision_.mm_minor << "." << revision_.mm_patch
1650  << std::endl;
1651  }
1652  else
1653  {
1654  glog.is(WARN) && glog << group(glog_in_group()) << "Bad revision string: " << nmea[3]
1655  << std::endl;
1656  }
1657  }
1658 }
1659 
1660 void goby::acomms::MMDriver::caerr(const NMEASentence& nmea)
1661 {
1662  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
1663  << "Micro-Modem reports error: " << nmea.message() << std::endl;
1664 
1665  // recover quicker if old firmware does not understand one of our commands
1666  if (nmea.at(2) == "NMEA")
1667  {
1668  waiting_for_modem_ = false;
1669 
1670  try
1671  {
1672  increment_present_fail();
1673  }
1674  catch (ModemDriverException& e)
1675  {
1676  present_fail_exceeds_retries();
1677  }
1678  }
1679 }
1680 
1681 void goby::acomms::MMDriver::cacyc(const NMEASentence& nmea, protobuf::ModemTransmission* msg)
1682 {
1683  // we're sending
1684  if (as<int32>(nmea[2]) == driver_cfg_.modem_id())
1685  {
1686  // handle a third-party CYC
1687  if (!local_cccyc_)
1688  {
1689  // we have to clear the message if XST isn't set since
1690  // otherwise the transmit_msg was never cleared (or copied from initiate_transmission)
1691  if (!nvram_cfg_["XST"])
1692  msg->Clear();
1693 
1694  msg->set_time(goby_time<uint64>());
1695 
1696  msg->set_src(as<uint32>(nmea[2])); // ADR1
1697  msg->set_dest(as<uint32>(nmea[3])); // ADR2
1698  msg->set_rate(as<uint32>(nmea[4])); // Rate
1699  msg->set_max_num_frames(as<uint32>(nmea[6])); // Npkts, number of packets
1700  msg->set_max_frame_bytes(PACKET_SIZE[msg->rate()]);
1701 
1702  cache_outgoing_data(msg);
1703  }
1704  else // clear flag for next cycle
1705  {
1706  local_cccyc_ = false;
1707  }
1708  }
1709  // we're receiving
1710  else
1711  {
1712  unsigned rate = as<uint32>(nmea[4]);
1713  if (local_cccyc_ && rate != 0) // clear flag for next cycle
1714  {
1715  // if we poll for rates > 0, we get *two* CACYC - the one from the poll and the one from the message
1716  // thus, ignore the first of these
1717  local_cccyc_ = false;
1718  return;
1719  }
1720 
1721  unsigned num_frames = as<uint32>(nmea[6]);
1722  if (!frames_waiting_to_receive_.empty())
1723  {
1724  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn << "flushing "
1725  << frames_waiting_to_receive_.size()
1726  << " expected frames that were never received." << std::endl;
1727  frames_waiting_to_receive_.clear();
1728  }
1729 
1730  for (unsigned i = 0; i < num_frames; ++i) frames_waiting_to_receive_.insert(i);
1731 
1732  // if rate 0 and we didn't send the CCCYC, we have two cacsts (one for the cacyc and one for the carxd)
1733  // otherwise, we have one
1734  expected_remaining_cacst_ = (as<int32>(nmea[4]) == 0 && !local_cccyc_) ? 1 : 0;
1735 
1736  local_cccyc_ = false;
1737  }
1738 }
1739 
1740 void goby::acomms::MMDriver::cache_outgoing_data(protobuf::ModemTransmission* msg)
1741 {
1742  if (msg->src() == driver_cfg_.modem_id())
1743  {
1744  if ((!using_application_acks_ ||
1745  (using_application_acks_ &&
1746  (next_frame_ + (int)msg->max_num_frames() > application_ack_max_frames_))))
1747  {
1748  if (!frames_waiting_for_ack_.empty())
1749  {
1750  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn << "flushing "
1751  << frames_waiting_for_ack_.size()
1752  << " expected acknowledgments that were never received."
1753  << std::endl;
1754  frames_waiting_for_ack_.clear();
1755  }
1756 
1757  expected_ack_destination_ = 0;
1758  next_frame_ = 0;
1759  }
1760 
1761  if (using_application_acks_)
1762  process_outgoing_app_ack(msg);
1763  else
1764  signal_data_request(msg);
1765  }
1766 }
1767 
1768 void goby::acomms::MMDriver::process_outgoing_app_ack(protobuf::ModemTransmission* msg)
1769 {
1770  if (msg->frame_size())
1771  {
1772  glog.is(WARN) &&
1773  glog << group(glog_out_group())
1774  << "Must use data request callback when using application acknowledgments"
1775  << std::endl;
1776  }
1777  else
1778  {
1779  // build up message to ack
1781 
1782  for (std::map<unsigned, std::set<unsigned> >::const_iterator it = frames_to_ack_.begin(),
1783  end = frames_to_ack_.end();
1784  it != end; ++it)
1785  {
1786  micromodem::protobuf::MMApplicationAck::AckPart& acks_part = *acks.add_part();
1787  acks_part.set_ack_dest(it->first);
1788 
1789  goby::uint32 acked_frames = 0;
1790  for (std::set<unsigned>::const_iterator jt = it->second.begin(),
1791  jend = it->second.end();
1792  jt != jend; ++jt)
1793  acked_frames |= (1ul << *jt);
1794 
1795  acks_part.set_acked_frames(acked_frames);
1796  }
1797  frames_to_ack_.clear();
1798 
1799  acks.set_frame_start(next_frame_);
1800  msg->set_frame_start(next_frame_);
1801 
1802  // calculate size of ack message
1803  unsigned ack_size = dccl_.size(acks);
1804 
1805  // insert placeholder
1806  msg->add_frame()->resize(ack_size, 0);
1807 
1808  // get actual data
1809  signal_data_request(msg);
1810 
1811  // now that we know if an ack is requested, set that
1812  acks.set_ack_requested(msg->ack_requested());
1813  std::string ack_bytes;
1814  dccl_.encode(&ack_bytes, acks);
1815  // insert real ack message
1816  msg->mutable_frame(0)->replace(0, ack_size, ack_bytes);
1817 
1818  // if we're not sending any data and we don't have acks, don't send anything
1819  if (acks.part_size() == 0 && msg->frame_size() == 1 && msg->frame(0).size() == ack_size)
1820  msg->clear_frame();
1821  else if (msg->dest() ==
1822  goby::acomms::QUERY_DESTINATION_ID) // make sure we have a real destination
1823  msg->set_dest(goby::acomms::BROADCAST_ID);
1824  }
1825 }
1826 
1827 void goby::acomms::MMDriver::validate_transmission_start(const protobuf::ModemTransmission& message)
1828 {
1829  if (message.src() < 0)
1830  throw("ModemTransmission::src is invalid: " + as<std::string>(message.src()));
1831  else if (message.dest() < 0)
1832  throw("ModemTransmission::dest is invalid: " + as<std::string>(message.dest()));
1833  else if (message.rate() < 0 || message.rate() > 5)
1834  throw("ModemTransmission::rate is invalid: " + as<std::string>(message.rate()));
1835 }
1836 
1837 void goby::acomms::MMDriver::cacst(const NMEASentence& nmea, protobuf::ModemTransmission* m)
1838 {
1840  m->AddExtension(micromodem::protobuf::receive_stat);
1841 
1842  cst->set_version(nmea.as<int>(1) < 6 ? 0 : nmea.as<int>(1));
1843 
1844  try
1845  {
1846  int version_offset = 0; // offset in NMEA field number
1847  if (cst->version() == 0)
1848  {
1849  version_offset = 0;
1850  }
1851  else if (cst->version() == 6)
1852  {
1853  version_offset = 1;
1854  }
1855 
1856  micromodem::protobuf::ReceiveMode mode =
1857  micromodem::protobuf::ReceiveMode_IsValid(nmea.as<int>(1 + version_offset))
1858  ? nmea.as<micromodem::protobuf::ReceiveMode>(1 + version_offset)
1859  : micromodem::protobuf::INVALID_RECEIVE_MODE;
1860 
1861  cst->set_mode(mode);
1862  cst->set_time(as<uint64>(nmea_time2ptime(nmea.as<std::string>(2 + version_offset))));
1863 
1864  micromodem::protobuf::ClockMode clock_mode =
1865  micromodem::protobuf::ClockMode_IsValid(nmea.as<int>(3 + version_offset))
1866  ? nmea.as<micromodem::protobuf::ClockMode>(3 + version_offset)
1867  : micromodem::protobuf::INVALID_CLOCK_MODE;
1868 
1869  cst->set_clock_mode(clock_mode);
1870  cst->set_mfd_peak(nmea.as<double>(4 + version_offset));
1871  cst->set_mfd_power(nmea.as<double>(5 + version_offset));
1872  cst->set_mfd_ratio(nmea.as<double>(6 + version_offset));
1873  cst->set_spl(nmea.as<double>(7 + version_offset));
1874  cst->set_shf_agn(nmea.as<double>(8 + version_offset));
1875  cst->set_shf_ainpshift(nmea.as<double>(9 + version_offset));
1876  cst->set_shf_ainshift(nmea.as<double>(10 + version_offset));
1877  cst->set_shf_mfdshift(nmea.as<double>(11 + version_offset));
1878  cst->set_shf_p2bshift(nmea.as<double>(12 + version_offset));
1879  cst->set_rate(nmea.as<int32>(13 + version_offset));
1880  cst->set_source(nmea.as<int32>(14 + version_offset));
1881  cst->set_dest(nmea.as<int32>(15 + version_offset));
1882 
1883  micromodem::protobuf::PSKErrorCode psk_error_code =
1884  micromodem::protobuf::PSKErrorCode_IsValid(nmea.as<int>(16 + version_offset))
1885  ? nmea.as<micromodem::protobuf::PSKErrorCode>(16 + version_offset)
1886  : micromodem::protobuf::INVALID_PSK_ERROR_CODE;
1887 
1888  cst->set_psk_error_code(psk_error_code);
1889 
1890  micromodem::protobuf::PacketType packet_type =
1891  micromodem::protobuf::PacketType_IsValid(nmea.as<int>(17 + version_offset))
1892  ? nmea.as<micromodem::protobuf::PacketType>(17 + version_offset)
1893  : micromodem::protobuf::PACKET_TYPE_UNKNOWN;
1894 
1895  cst->set_packet_type(packet_type);
1896  cst->set_number_frames(nmea.as<int32>(18 + version_offset));
1897  cst->set_number_bad_frames(nmea.as<int32>(19 + version_offset));
1898  cst->set_snr_rss(nmea.as<double>(20 + version_offset));
1899  cst->set_snr_in(nmea.as<double>(21 + version_offset));
1900  cst->set_snr_out(nmea.as<double>(22 + version_offset));
1901  cst->set_snr_symbols(nmea.as<double>(23 + version_offset));
1902  cst->set_mse_equalizer(nmea.as<double>(24 + version_offset));
1903  cst->set_data_quality_factor(nmea.as<int32>(25 + version_offset));
1904  cst->set_doppler(nmea.as<double>(26 + version_offset));
1905  cst->set_stddev_noise(nmea.as<double>(27 + version_offset));
1906  cst->set_carrier_freq(nmea.as<double>(28 + version_offset));
1907  cst->set_bandwidth(nmea.as<double>(29 + version_offset));
1908  }
1909  catch (std::out_of_range& e) // thrown by std::vector::at() called by NMEASentence::as()
1910  {
1911  glog.is(DEBUG1) && glog << group(glog_in_group()) << warn
1912  << "$CACST message shorter than expected" << std::endl;
1913  }
1914 
1915  //
1916  // set toa parameters
1917  //
1918 
1919  // timing relative to synched pps is good, if ccclk is bad, and range is
1920  // known to be less than ~1500m, range is still usable
1921  clk_mode_ = cst->clock_mode();
1922  if (clk_mode_ == micromodem::protobuf::SYNC_TO_PPS_AND_CCCLK_GOOD ||
1923  clk_mode_ == micromodem::protobuf::SYNC_TO_PPS_AND_CCCLK_BAD)
1924  {
1925  micromodem::protobuf::RangingReply* ranging_reply =
1926  m->MutableExtension(micromodem::protobuf::ranging_reply);
1927  boost::posix_time::ptime toa = as<boost::posix_time::ptime>(cst->time());
1928  double frac_sec =
1929  double(toa.time_of_day().fractional_seconds()) / toa.time_of_day().ticks_per_second();
1930 
1931  ranging_reply->add_one_way_travel_time(frac_sec);
1932 
1933  ranging_reply->set_ambiguity(micromodem::protobuf::RangingReply::OWTT_SECOND_AMBIGUOUS);
1934 
1935  ranging_reply->set_receiver_clk_mode(clk_mode_);
1936  ranging_reply->set_is_one_way_synchronous(true);
1937  }
1938 
1939  if (cst->has_time())
1940  {
1941  m->set_time(cst->time());
1942  m->set_time_source(protobuf::ModemTransmission::MODEM_TIME);
1943  }
1944 
1945  // aggregate cacst until they form a coherent "single" transmission
1946  if (expected_remaining_cacst_ == 0)
1947  signal_receive_and_clear(m); // CACST is last, so flush the received message
1948  else
1949  --expected_remaining_cacst_;
1950 }
1951 
1952 //
1953 // UTILITY
1954 //
1955 
1956 void goby::acomms::MMDriver::signal_receive_and_clear(protobuf::ModemTransmission* message)
1957 {
1958  try
1959  {
1960  signal_receive(*message);
1961  message->Clear();
1962  }
1963  catch (std::exception& e)
1964  {
1965  message->Clear();
1966  throw;
1967  }
1968 }
1969 
1970 void goby::acomms::MMDriver::set_silent(bool silent)
1971 {
1972  if (silent)
1973  write_single_cfg("SRC,0"); // set to Goby broadcast ID to prevent ACKs
1974  else
1975  write_single_cfg("SRC," + as<std::string>(driver_cfg_.modem_id()));
1976 }
provides a basic client for line by line text based communications over a 8N1 tty (such as an RS-232 ...
Definition: serial_client.h:35
Contains functions for adding color to Terminal window streams.
Definition: term_color.h:54
void modem_write(const std::string &out)
write a line to the serial port.
Definition: driver_base.cpp:48
void shutdown()
Stops the driver.
Definition: mm_driver.cpp:450
MMDriver()
Default constructor.
Definition: mm_driver.cpp:65
void modem_start(const protobuf::DriverConfig &cfg)
start the physical connection to the modem (serial port, TCP, etc.). must be called before ModemDrive...
Definition: driver_base.cpp:68
google::protobuf::uint32 uint32
an unsigned 32 bit integer
void handle_initiate_transmission(const protobuf::ModemTransmission &m)
See ModemDriverBase::handle_initiate_transmission()
Definition: mm_driver.cpp:521
boost::signals2::signal< void(const protobuf::ModemRaw &msg)> signal_raw_incoming
Called after any message is received from the modem by the driver. Used by the MACManager for auto-di...
Definition: driver_base.h:107
boost::signals2::signal< void(const protobuf::ModemTransmission &message)> signal_transmit_result
Called when a transmission is completed.
Definition: driver_base.h:91
std::ostream & magenta(std::ostream &os)
All text following this manipulator is magenta (e.g. std::cout << magenta << "text";) ...
Definition: term_color.h:86
double goby_time< double >()
Returns current UTC time as seconds and fractional seconds since 1970-01-01 00:00:00.
Definition: time.h:130
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
boost::signals2::signal< void(const protobuf::ModemTransmission &message)> signal_receive
Called when a binary data transmission is received from the modem.
Definition: driver_base.h:85
const int BROADCAST_ID
special modem id for the broadcast destination - no one is assigned this address. Analogous to 192...
void do_work()
See ModemDriverBase::do_work()
Definition: mm_driver.cpp:463
util::LineBasedInterface & modem()
use for direct access to the modem
Definition: driver_base.h:157
common::FlexOstream glog
Access the Goby logger through this object.
~MMDriver()
Destructor.
Definition: mm_driver.cpp:457
boost::signals2::signal< void(const protobuf::ModemRaw &msg)> signal_raw_outgoing
Called after any message is sent from the driver to the modem. Useful for higher level analysis and d...
Definition: driver_base.h:112
bool modem_read(std::string *in)
read a line from the serial port, including end-of-line character(s)
Definition: driver_base.cpp:57
void startup(const protobuf::DriverConfig &cfg)
Starts the driver.
Definition: mm_driver.cpp:76
boost::signals2::signal< void(protobuf::ModemTransmission *msg)> signal_data_request
Called when the modem or modem driver needs data to send. The returned data should be stored in Modem...
Definition: driver_base.h:96
double ptime2unix_double(boost::posix_time::ptime given_time)
convert from boost date_time ptime to the number of seconds (including fractional) since 1/1/1970 0:0...
Definition: time.cpp:28
void modem_close()
closes the serial port. Use modem_start to reopen the port.
Definition: driver_base.cpp:66
boost::signals2::signal< void(protobuf::ModemTransmission *msg_request)> signal_modify_transmission
Called before the modem driver begins processing a transmission. This allows a third party to modify ...
Definition: driver_base.h:102
std::ostream & nocolor(std::ostream &os)
All text following this manipulator is uncolored (e.g. std::cout << green << "green" << nocolor << "u...
Definition: term_color.h:104
google::protobuf::int32 int32
a signed 32 bit integer
void update_cfg(const protobuf::DriverConfig &cfg)
Update configuration while running (not required to be implemented)
Definition: mm_driver.cpp:192
const int QUERY_DESTINATION_ID
special modem id used internally to goby-acomms for indicating that the MAC layer (amac) is agnostic ...