Goby v2
moos_bluefin_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 "moos_bluefin_driver.h"
24 #include "goby/acomms/modemdriver/driver_exception.h"
25 #include "goby/acomms/protobuf/mm_driver.pb.h"
26 #include "goby/common/logger.h"
27 #include "goby/common/time.h"
29 #include "goby/moos/moos_string.h"
30 #include "goby/moos/protobuf/bluefin_driver.pb.h"
31 #include "goby/util/binary.h"
32 #include "goby/util/linebasedcomms/nmea_sentence.h"
33 #include <boost/format.hpp>
34 
35 using goby::glog;
36 using goby::util::hex_decode;
37 using goby::util::hex_encode;
38 using namespace goby::common::logger;
39 using goby::acomms::operator<<;
41 using goby::common::nmea_time2ptime;
43 
44 goby::moos::BluefinCommsDriver::BluefinCommsDriver(goby::acomms::MACManager* mac)
45  : end_of_mac_window_(0), mac_(mac), last_request_id_(1 << 24)
46 {
47 }
48 
50 {
51  glog.is(DEBUG1) && glog << group(glog_out_group())
52  << "Goby MOOS Bluefin Comms driver starting up." << std::endl;
53 
54  driver_cfg_ = cfg;
55 
56  modem_to_rate_to_bytes_.clear();
57  for (int i = 0, n = driver_cfg_.ExtensionSize(protobuf::BluefinConfig::hardware_to_rate); i < n;
58  ++i)
59  {
60  const protobuf::HardwareRatePair& hardware_to_rate =
61  driver_cfg_.GetExtension(protobuf::BluefinConfig::hardware_to_rate, i);
62  modem_to_rate_to_bytes_[hardware_to_rate.hardware_name()][hardware_to_rate.rate()] =
63  hardware_to_rate.packet_bytes();
64  }
65 
66  goby_to_bluefin_id_.clear();
67  for (int i = 0, n = driver_cfg_.ExtensionSize(protobuf::BluefinConfig::modem_lookup); i < n;
68  ++i)
69  {
70  const protobuf::BluefinModemIdLookUp& modem_lookup =
71  driver_cfg_.GetExtension(protobuf::BluefinConfig::modem_lookup, i);
72  goby_to_bluefin_id_.left.insert(
73  std::make_pair(modem_lookup.goby_id(), modem_lookup.bluefin_id()));
74  }
75 
76  const std::string& moos_server = driver_cfg_.GetExtension(protobuf::BluefinConfig::moos_server);
77  int moos_port = driver_cfg_.GetExtension(protobuf::BluefinConfig::moos_port);
78  moos_client_.Run(moos_server.c_str(), moos_port, "goby.moos.BluefinCommsDriver");
79 
80  int i = 0;
81  while (!moos_client_.IsConnected())
82  {
83  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Trying to connect to MOOSDB at "
84  << moos_server << ":" << moos_port << ", try " << i++ << std::endl;
85  sleep(1);
86  }
87  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Connected to MOOSDB." << std::endl;
88 
89  moos_client_.Register(driver_cfg_.GetExtension(protobuf::BluefinConfig::nmea_in_moos_var), 0);
90 }
91 
92 void goby::moos::BluefinCommsDriver::shutdown() { moos_client_.Close(); }
93 
96 {
97  // copy so we can modify
99 
100  // allows zero to N third parties modify the transmission before sending.
101  signal_modify_transmission(&msg);
102 
103  switch (msg.type())
104  {
105  case goby::acomms::protobuf::ModemTransmission::DATA:
106  {
107  if (driver_cfg_.modem_id() == msg.src())
108  {
109  // this is our transmission
110  if (!modem_to_rate_to_bytes_.count(current_modem_))
111  {
112  glog.is(WARN) &&
113  glog
114  << group(glog_out_group()) << "Modem \"" << current_modem_
115  << "\" not configured for rate and bytes. Cannot initiate transmission."
116  << std::endl;
117  break;
118  }
119 
120  std::map<int, int>& rate_to_bytes = modem_to_rate_to_bytes_[current_modem_];
121 
122  if (!rate_to_bytes.count(msg.rate()))
123  {
124  glog.is(WARN) && glog << group(glog_out_group()) << "Modem \"" << current_modem_
125  << "\" not configured for rate: " << msg.rate()
126  << ". Cannot initiate transmission." << std::endl;
127  break;
128  }
129 
130  msg.set_max_frame_bytes(rate_to_bytes[msg.rate()]);
131 
132  // no data given to us, let's ask for some
133  if (msg.frame_size() == 0)
134  ModemDriverBase::signal_data_request(&msg);
135 
136  // don't send an empty message
137  if (msg.frame_size() && msg.frame(0).size())
138  {
139  NMEASentence nmea("$BPCPD", NMEASentence::IGNORE);
140  nmea.push_back(unix_time2nmea_time(goby_time<double>()));
141  nmea.push_back(++last_request_id_);
142 
143  int bf_dest = goby_to_bluefin_id_.left.count(msg.dest())
144  ? goby_to_bluefin_id_.left.at(msg.dest())
145  : -1;
146  nmea.push_back(bf_dest);
147  nmea.push_back(msg.rate());
148  nmea.push_back(static_cast<int>(msg.ack_requested()));
149  nmea.push_back(msg.frame_size());
150  for (int i = 0; i < 8; ++i)
151  nmea.push_back(i < msg.frame_size()
152  ? boost::to_upper_copy(hex_encode(msg.frame(i)))
153  : "");
154 
155  std::stringstream ss;
156  ss << "@PB[lamss.protobuf.FrontSeatRaw] raw: \"" << nmea.message() << "\"";
157 
159  out_raw.set_raw(ss.str());
160  ModemDriverBase::signal_raw_outgoing(out_raw);
161 
162  const std::string& out_moos_var =
163  driver_cfg_.GetExtension(protobuf::BluefinConfig::nmea_out_moos_var);
164 
165  glog.is(DEBUG1) && glog << group(glog_out_group()) << out_moos_var << ": "
166  << ss.str() << std::endl;
167 
168  moos_client_.Notify(out_moos_var, ss.str());
169  last_data_msg_ = msg;
170  }
171  }
172  }
173  break;
174 
175  case goby::acomms::protobuf::ModemTransmission::UNKNOWN:
176  case goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC:
177  case goby::acomms::protobuf::ModemTransmission::ACK:
178  glog.is(DEBUG1) && glog << group(glog_out_group())
179  << "Not initiating transmission: " << msg.ShortDebugString()
180  << "; invalid transmission type." << std::endl;
181  break;
182  }
183 }
184 
186 {
187  if (mac_ && mac_->running() && end_of_mac_window_ < goby_time<double>())
188  mac_->shutdown();
189 
190  MOOSMSG_LIST msgs;
191  if (moos_client_.Fetch(msgs))
192  {
193  for (MOOSMSG_LIST::iterator it = msgs.begin(), end = msgs.end(); it != end; ++it)
194  {
195  const std::string& in_moos_var =
196  driver_cfg_.GetExtension(protobuf::BluefinConfig::nmea_in_moos_var);
197  const std::string& s_val = it->GetString();
198 
199  const std::string raw = "raw: \"";
200  const std::string::size_type raw_pos = s_val.find(raw);
201  if (raw_pos == std::string::npos)
202  continue;
203 
204  const std::string::size_type end_pos = s_val.find("\"", raw_pos + raw.size());
205  if (end_pos == std::string::npos)
206  continue;
207 
208  std::string value =
209  s_val.substr(raw_pos + raw.size(), end_pos - (raw_pos + raw.size()));
210 
211  if (it->GetKey() == in_moos_var &&
212  (value.substr(0, 5) == "$BFCP" || value.substr(0, 6) == "$BFCMA"))
213  {
215  in_raw.set_raw(s_val);
216  ModemDriverBase::signal_raw_incoming(in_raw);
217  try
218  {
219  glog.is(DEBUG1) && glog << group(glog_in_group()) << in_moos_var << ": "
220  << s_val << std::endl;
221  NMEASentence nmea(value, NMEASentence::VALIDATE);
222  if (nmea.sentence_id() == "CMA") // Communications Medium Access
223  bfcma(nmea);
224  else if (nmea.sentence_id() == "CPS") // Communications Packet Sent
225  bfcps(nmea);
226  else if (nmea.sentence_id() == "CPR") // Communications Packet Received Data
227  bfcpr(nmea);
228  }
229  catch (std::exception& e)
230  {
231  glog.is(DEBUG1) && glog << warn << "Failed to handle message: " << e.what()
232  << std::endl;
233  }
234  }
235  }
236  }
237 }
238 
239 std::string goby::moos::BluefinCommsDriver::unix_time2nmea_time(double time)
240 {
241  boost::posix_time::ptime ptime = goby::common::unix_double2ptime(time);
242 
243  // HHMMSS.SSS
244  // it appears that exactly three digits of precision is important (sometimes)
245  boost::format f("%02d%02d%02d.%03d");
246  f % ptime.time_of_day().hours() % ptime.time_of_day().minutes() %
247  ptime.time_of_day().seconds() %
248  (ptime.time_of_day().fractional_seconds() * 1000 /
249  boost::posix_time::time_duration::ticks_per_second());
250 
251  return f.str();
252 }
253 
254 void goby::moos::BluefinCommsDriver::bfcma(const goby::util::NMEASentence& nmea)
255 {
256  enum
257  {
258  TIMESTAMP = 1,
259  END_OF_TIME_WINDOW = 2,
260  MODEM_ADDRESS = 3,
261  DEVICE_TYPE = 4
262  };
263  end_of_mac_window_ = goby::util::as<double>(nmea_time2ptime(nmea.at(END_OF_TIME_WINDOW)));
264  current_modem_ = nmea.at(DEVICE_TYPE);
265  if (mac_)
266  mac_->restart();
267 }
268 
269 void goby::moos::BluefinCommsDriver::bfcps(const goby::util::NMEASentence& nmea)
270 {
271  enum
272  {
273  TIMESTAMP = 1,
274  ACOUSTIC_MESSAGE_TIMESTAMP = 2,
275  REQUEST_ID = 3,
276  NUMBER_OF_FRAMES = 4,
277  FRAME_0_STATUS = 5,
278  FRAME_1_STATUS = 6,
279  FRAME_2_STATUS = 7,
280  FRAME_3_STATUS = 8,
281  FRAME_4_STATUS = 9,
282  FRAME_5_STATUS = 10,
283  FRAME_6_STATUS = 11,
284  FRAME_7_STATUS = 12
285  };
286 
287  if (nmea.as<int>(REQUEST_ID) == last_request_id_)
288  {
290  msg.set_time(goby_time<uint64>());
291  msg.set_src(last_data_msg_.dest()); // ack came from last data's destination, presumably
292  msg.set_dest(last_data_msg_.src());
293  msg.set_type(goby::acomms::protobuf::ModemTransmission::ACK);
294 
295  for (int i = 0, n = nmea.as<int>(NUMBER_OF_FRAMES); i < n; ++i)
296  {
297  if (nmea.as<int>(FRAME_0_STATUS + i) == 2)
298  msg.add_acked_frame(i);
299  }
300 
301  ModemDriverBase::signal_receive(msg);
302  }
303  else
304  {
305  glog.is(DEBUG1) &&
306  glog << group(glog_in_group()) << warn
307  << "Received CPS for message ID that was not the last request sent, ignoring..."
308  << std::endl;
309  }
310 }
311 
312 void goby::moos::BluefinCommsDriver::bfcpr(const goby::util::NMEASentence& nmea)
313 {
314  enum
315  {
316  TIMESTAMP = 1,
317  ARRIVAL_TIME = 2,
318  SOURCE = 3,
319  DESTINATION = 4,
320  TELEMETRY_MODE = 5,
321  NUMBER_OF_FRAMES = 6,
322  FRAME_0_STATUS = 7,
323  FRAME_0_HEX = 8,
324  FRAME_1_STATUS = 9,
325  FRAME_1_HEX = 10,
326  FRAME_2_STATUS = 11,
327  FRAME_2_HEX = 12,
328  FRAME_3_STATUS = 13,
329  FRAME_3_HEX = 14,
330  FRAME_4_STATUS = 15,
331  FRAME_4_HEX = 16,
332  FRAME_5_STATUS = 17,
333  FRAME_5_HEX = 18,
334  FRAME_6_STATUS = 19,
335  FRAME_6_HEX = 20,
336  FRAME_7_STATUS = 21,
337  FRAME_7_HEX = 22
338  };
339 
341  msg.set_time(goby::util::as<uint64>(nmea_time2ptime(nmea.at(ARRIVAL_TIME))));
342  msg.set_time_source(goby::acomms::protobuf::ModemTransmission::MODEM_TIME);
343 
344  int goby_src = goby_to_bluefin_id_.right.count(nmea.as<int>(SOURCE))
345  ? goby_to_bluefin_id_.right.at(nmea.as<int>(SOURCE))
346  : -1;
347  msg.set_src(goby_src);
348 
349  int goby_dest = goby_to_bluefin_id_.right.count(nmea.as<int>(DESTINATION))
350  ? goby_to_bluefin_id_.right.at(nmea.as<int>(DESTINATION))
351  : -1;
352 
353  msg.set_dest(goby_dest);
354  msg.set_type(goby::acomms::protobuf::ModemTransmission::DATA);
355  for (int i = 0, n = nmea.as<int>(NUMBER_OF_FRAMES); i < n; ++i)
356  {
357  if (nmea.as<int>(FRAME_0_STATUS + 2 * i) == 1)
358  msg.add_frame(hex_decode(nmea.at(FRAME_0_HEX + 2 * i)));
359  }
360 
361  glog.is(DEBUG1) && glog << group(glog_in_group())
362  << "Received BFCPR incoming data message: " << msg.DebugString()
363  << std::endl;
364 
365  ModemDriverBase::signal_receive(msg);
366 }
uint64 goby_time< uint64 >()
Returns current UTC time as integer microseconds since 1970-01-01 00:00:00.
Definition: time.h:113
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
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
void handle_initiate_transmission(const goby::acomms::protobuf::ModemTransmission &m)
Virtual initiate_transmission method. Typically connected to MACManager::signal_initiate_transmission...
provides an API to the goby-acomms MAC library. MACManager is essentially a std::list<protobuf::Modem...
Definition: mac_manager.h:51
void shutdown()
Shutdown the MAC.
common::FlexOstream glog
Access the Goby logger through this object.
void do_work()
Allows the modem driver to do its work.
void restart()
Restarts the MAC with original configuration.
Definition: mac_manager.cpp:97
void shutdown()
Shuts down the modem driver.
boost::posix_time::ptime unix_double2ptime(double given_time)
convert to boost date_time ptime from the number of seconds (including fractional) since 1/1/1970 0:0...
Definition: time.cpp:47
void startup(const goby::acomms::protobuf::DriverConfig &cfg)
Starts the modem driver. Must be called before poll().