Goby v2
benthos_atm900_driver_fsm.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 <boost/algorithm/string.hpp>
24 #include <boost/algorithm/string/classification.hpp>
25 #include <boost/units/base_units/metric/knot.hpp>
26 #include <boost/units/systems/si/prefixes.hpp>
27 
28 #include "goby/common/logger.h"
29 #include "goby/common/time.h"
30 #include "goby/util/binary.h"
31 
32 #include "benthos_atm900_driver.h"
33 #include "benthos_atm900_driver_fsm.h"
34 #include "rudics_packet.h"
35 
36 using goby::glog;
37 using namespace goby::common::logger;
39 
40 int goby::acomms::benthos_fsm::BenthosATM900FSM::count_ = 0;
41 
42 void goby::acomms::benthos_fsm::BenthosATM900FSM::buffer_data_out(
44 {
45  data_out_.push_back(msg);
46 }
47 
48 void goby::acomms::benthos_fsm::Active::in_state_react(const EvRxSerial& e)
49 {
50  std::string in = e.line;
51  boost::trim(in);
52 
53  static const std::string data = "DATA";
54  static const std::string user = "user:";
55 
56  if (in.compare(0, data.size(), data) == 0)
57  {
58  // data
59  post_event(EvReceive(in));
60  }
61  else if (in.compare(0, user.size(), user) == 0) // shell prompt
62  {
63  post_event(EvShellPrompt());
64 
65  if (in.find("Lowpower") != std::string::npos)
66  post_event(EvAck(in));
67  }
68  else // presumably a response to something else
69  {
70  post_event(EvAck(in));
71 
72  static const std::string connect = "CONNECT";
73  if (in.compare(0, connect.size(), connect) == 0)
74  post_event(EvConnect());
75  }
76 }
77 
78 goby::acomms::benthos_fsm::ReceiveData::ReceiveData(my_context ctx)
79  : my_base(ctx), StateNotify("ReceiveData"), reported_size_(0)
80 {
81  try
82  {
83  if (const EvReceive* ev_rx = dynamic_cast<const EvReceive*>(triggering_event()))
84  {
85  std::string first = ev_rx->first_;
86  // remove extra spaces in the string
87  boost::erase_all(first, " ");
88  // e.g. DATA(0037):b2b645b7097cb585d181b0c34ff1a13b
89  enum
90  {
91  SIZE_START = 5,
92  SIZE_END = 9,
93  BYTES_START = 11
94  };
95  if (first.size() < BYTES_START)
96  throw(std::runtime_error("String too short"));
97 
98  std::string size_str = first.substr(SIZE_START, SIZE_END - SIZE_START);
99  boost::trim_left_if(size_str, boost::is_any_of("0"));
100  reported_size_ = boost::lexical_cast<unsigned>(size_str);
101  encoded_bytes_ += goby::util::hex_decode(first.substr(BYTES_START));
102  }
103  else
104  {
105  throw(std::runtime_error("Invalid triggering_event, expected EvReceive"));
106  }
107  }
108  catch (std::exception& e)
109  {
110  goby::glog.is(goby::common::logger::WARN) &&
111  goby::glog << "Invalid data received, ignoring: " << e.what() << std::endl;
112  post_event(EvReceiveComplete());
113  }
114 }
115 
116 void goby::acomms::benthos_fsm::ReceiveData::in_state_react(const EvRxSerial& e)
117 {
118  try
119  {
120  std::string in = e.line;
121  boost::trim(in);
122  const std::string source = "Source";
123  const std::string crc = "CRC";
124 
125  if (in == "<EOP>")
126  {
127  const benthos::protobuf::ReceiveStatistics& rx_stats =
128  rx_msg_.GetExtension(benthos::protobuf::receive_stat);
129 
130  if (rx_stats.crc() == benthos::protobuf::ReceiveStatistics::CRC_PASS)
131  parse_benthos_modem_message(encoded_bytes_, &rx_msg_);
132 
133  context<BenthosATM900FSM>().received().push_back(rx_msg_);
134 
135  post_event(EvReceiveComplete());
136  }
137  else if (encoded_bytes_.size() < reported_size_)
138  {
139  // assume more bytes
140  boost::erase_all(in, " ");
141  encoded_bytes_ += goby::util::hex_decode(in);
142  }
143  else if (in.compare(0, source.size(), source) == 0)
144  {
145  // Source:001 Destination:002
146  std::vector<std::string> src_dest;
147  boost::split(src_dest, in, boost::is_any_of(" "), boost::token_compress_on);
148  if (src_dest.size() != 2)
149  {
150  throw(std::runtime_error(
151  "Invalid source/dest string, expected \"Source:NNN Destination: MMM\""));
152  }
153 
154  enum
155  {
156  SOURCE_ID_START = 7,
157  DEST_ID_START = 12
158  };
159  for (int i = 0; i < 2; ++i)
160  {
161  std::string id_str = src_dest[i].substr(i == 0 ? SOURCE_ID_START : DEST_ID_START);
162  boost::trim_left_if(id_str, boost::is_any_of("0"));
163 
164  // handle special case of "000"
165  if (id_str.empty())
166  id_str = "0";
167 
168  int id = boost::lexical_cast<unsigned>(id_str);
169  if (i == 0)
170  rx_msg_.set_src(id);
171  else if (i == 1)
172  rx_msg_.set_dest(id);
173  }
174  }
175  else if (in.compare(0, crc.size(), crc) == 0)
176  {
178  *rx_msg_.MutableExtension(benthos::protobuf::receive_stat);
179 
180  // CRC:Pass MPD:03.2 SNR:31.3 AGC:91 SPD:+00.0 CCERR:013
181  std::vector<std::string> stats;
182  boost::split(stats, in, boost::is_any_of(" "), boost::token_compress_on);
183 
184  enum StatField
185  {
186  STAT_CRC = 0,
187  STAT_MPD = 1,
188  STAT_AGC = 2,
189  STAT_SNR = 3,
190  STAT_SPD = 4,
191  STAT_CCERR = 5
192  };
193 
194  // use int instead of enum to satisfy compiler (enum is local type)
195  std::map<std::string, int> statmap;
196  statmap.insert(std::make_pair("CRC", (int)STAT_CRC));
197  statmap.insert(std::make_pair("MPD", (int)STAT_MPD));
198  statmap.insert(std::make_pair("SNR", (int)STAT_SNR));
199  statmap.insert(std::make_pair("AGC", (int)STAT_AGC));
200  statmap.insert(std::make_pair("SPD", (int)STAT_SPD));
201  statmap.insert(std::make_pair("CCERR", (int)STAT_CCERR));
202 
203  for (unsigned i = 0; i < stats.size(); ++i)
204  {
205  std::string::size_type col_pos = stats[i].find(":");
206  if (col_pos == std::string::npos)
207  continue;
208 
209  std::string key_str = stats[i].substr(0, col_pos);
210 
211  std::map<std::string, int>::const_iterator it = statmap.find(key_str);
212  if (it == statmap.end())
213  continue;
214 
215  StatField field = static_cast<StatField>(it->second);
216  std::string val_str = stats[i].substr(col_pos + 1);
217  boost::trim(val_str);
218  boost::trim_left_if(val_str, boost::is_any_of("+0"));
219  if (val_str.empty())
220  val_str = "0";
221  float val = 0;
222  if (field != STAT_CRC)
223  val = boost::lexical_cast<float>(val_str);
224  switch (field)
225  {
226  case STAT_CRC:
227  if (val_str == "Pass")
228  {
229  rx_stats.set_crc(benthos::protobuf::ReceiveStatistics::CRC_PASS);
230  }
231  else
232  {
233  rx_stats.set_crc(benthos::protobuf::ReceiveStatistics::CRC_FAIL);
234  }
235  break;
236  case STAT_MPD:
237  rx_stats.set_multipath_delay_with_units(val * boost::units::si::milli *
238  boost::units::si::seconds);
239  break;
240  case STAT_SNR: rx_stats.set_snr(val); break;
241  case STAT_AGC: rx_stats.set_auto_gain_control(val); break;
242  case STAT_SPD:
243  {
244  boost::units::metric::knot_base_unit::unit_type knots;
245  rx_stats.set_relative_speed_with_units(val * knots);
246  break;
247  }
248  case STAT_CCERR: rx_stats.set_corrected_channel_error(val); break;
249  }
250  }
251  }
252  }
253  catch (std::exception& e)
254  {
255  goby::glog.is(goby::common::logger::WARN) &&
256  goby::glog << "Invalid data received, ignoring. Reason: " << e.what() << std::endl;
257  post_event(EvReceiveComplete());
258  }
259 }
260 
261 void goby::acomms::benthos_fsm::Range::in_state_react(const EvRxSerial& e)
262 {
263  try
264  {
265  std::string in = e.line;
266  boost::trim(in);
267 
268  static const std::string tx_time = "TX time";
269  static const std::string range = "Range";
270 
271  if (!context<Command>().at_out().empty())
272  {
273  const std::string& last_cmd = context<Command>().at_out().front().second;
274  if (last_cmd.substr(0, 3) == "ATR" &&
275  boost::iequals(in.substr(0, tx_time.size()), tx_time))
276  {
277  context<Command>().at_out().pop_front();
278  }
279  }
280 
281  if (in.compare(0, range.size(), range) == 0)
282  {
283  protobuf::ModemTransmission range_msg;
284 
285  // Range 1 to 2 : 1499.6 m (Round-trip 1999.5 ms) speed 0.0 m/s
286  // ^-range_pos ^- col_pos ^- rt_start_pos ^- rt_end_pos
287 
288  const std::string roundtrip = "(Round-trip";
289  const std::string ms = "ms)";
290 
291  std::string::size_type range_pos = in.find(range);
292  std::string::size_type col_pos = in.find(":");
293  std::string::size_type rt_start_pos = in.find(roundtrip);
294  std::string::size_type rt_end_pos = in.find(ms);
295 
296  if (range_pos == std::string::npos || col_pos == std::string::npos ||
297  rt_start_pos == std::string::npos || rt_end_pos == std::string::npos)
298  throw(std::runtime_error("Invalid format for range string"));
299 
300  // "1 to 2"
301  std::string src_to_dest =
302  in.substr(range_pos + range.size(), col_pos - (range_pos + range.size()));
303  boost::trim(src_to_dest);
304  std::vector<std::string> src_dest;
305  boost::split(src_dest, src_to_dest, boost::is_any_of(" to"), boost::token_compress_on);
306  if (src_dest.size() != 2)
307  {
308  throw(std::runtime_error("Invalid source/dest string, expected \"Range 1 to 2\""));
309  }
310 
311  range_msg.set_src(boost::lexical_cast<unsigned>(src_dest[0]));
312  range_msg.set_dest(boost::lexical_cast<unsigned>(src_dest[1]));
313 
314  range_msg.set_type(protobuf::ModemTransmission::DRIVER_SPECIFIC);
315  range_msg.SetExtension(benthos::protobuf::type,
316  benthos::protobuf::BENTHOS_TWO_WAY_PING);
317 
318  std::string rt_ms = in.substr(rt_start_pos + roundtrip.size(),
319  rt_end_pos - (rt_start_pos + roundtrip.size()));
320  boost::trim(rt_ms);
321 
322  range_msg.MutableExtension(benthos::protobuf::ranging_reply)
323  ->set_one_way_travel_time_with_units(boost::lexical_cast<double>(rt_ms) / 2 *
324  boost::units::si::milli *
325  boost::units::si::seconds);
326 
327  context<BenthosATM900FSM>().received().push_back(range_msg);
328  post_event(EvRangingComplete());
329  }
330  }
331  catch (std::exception& e)
332  {
333  goby::glog.is(goby::common::logger::WARN) &&
334  goby::glog << "Invalid ranging data received, ignoring. Reason: " << e.what()
335  << std::endl;
336  post_event(EvRangingComplete());
337  }
338 }
339 
340 void goby::acomms::benthos_fsm::Command::in_state_react(const EvTxSerial&)
341 {
342  double now = goby_time<double>();
343 
344  static const std::string atd = "ATD";
345 
346  if (!at_out_.empty())
347  {
348  double timeout = COMMAND_TIMEOUT_SECONDS;
349 
350  if ((at_out_.front().first.last_send_time_ + timeout) < now)
351  {
352  std::string at_command;
353  if (at_out_.front().second == "+++")
354  {
355  // Benthos implementation of Hayes requires 1 second pause *before* +++
356  usleep(1300000);
357  // insert \r after +++ so that we accidentally send +++ during command mode, this is recognized as an (invalid) command
358  at_command = at_out_.front().second + "\r";
359  }
360  else
361  {
362  at_command = at_out_.front().second + "\r";
363  }
364 
365  if (++at_out_.front().first.tries_ > RETRIES_BEFORE_RESET)
366  {
367  glog.is(DEBUG1) && glog << group("benthosatm900") << warn
368  << "No valid response after " << RETRIES_BEFORE_RESET
369  << " tries. Resetting state machine" << std::endl;
370  post_event(EvReset());
371  }
372  else
373  {
374  context<BenthosATM900FSM>().serial_tx_buffer().push_back(at_command);
375  at_out_.front().first.last_send_time_ = now;
376  }
377  }
378  }
379 }
380 
381 void goby::acomms::benthos_fsm::TransmitData::in_state_react(const EvTxSerial&)
382 {
383  boost::circular_buffer<protobuf::ModemTransmission>& data_out =
384  context<BenthosATM900FSM>().data_out();
385  if (!data_out.empty())
386  {
387  // frame message
388  std::string packet;
389  serialize_benthos_modem_message(&packet, data_out.front());
390  context<BenthosATM900FSM>().serial_tx_buffer().push_back(packet);
391 
392  data_out.pop_front();
393  }
394 }
395 
396 void goby::acomms::benthos_fsm::TransmitData::in_state_react(const EvAck& e)
397 {
398  static const std::string forwarding_delay_up = "Forwarding Delay Up";
399  if (e.response_.compare(0, forwarding_delay_up.size(), forwarding_delay_up) == 0)
400  {
401  post_event(EvTransmitBegun());
402  }
403 }
404 
405 void goby::acomms::benthos_fsm::Command::in_state_react(const EvAck& e)
406 {
407  bool valid = false;
408  if (!at_out().empty())
409  {
410  const std::string& last_cmd = at_out().front().second;
411  if (last_cmd.size() > 0 && (e.response_ == "OK"))
412  {
413  if (last_cmd == "ATH")
414  post_event(EvNoCarrier());
415  }
416 
417  static const std::string connect = "CONNECT";
418  static const std::string user = "user";
419 
420  if (e.response_ == "OK")
421  {
422  valid = true;
423  }
424  else if (e.response_.compare(0, connect.size(), connect) == 0 &&
425  last_cmd.substr(0, 3) != "+++")
426  {
427  valid = true;
428  }
429  else if (e.response_ == "Command '+++' not found" &&
430  last_cmd.substr(0, 3) ==
431  "+++") // response to '+++' during command mode, so this is OK
432  {
433  valid = true;
434  }
435  else if (last_cmd.substr(0, 3) == "+++" &&
436  (e.response_.compare(0, user.size(), user) == 0))
437  {
438  // no response in command mode other than giving us a new user:N> prompt
439  valid = true;
440  }
441  else if (last_cmd.substr(0, 3) == "ATL" &&
442  e.response_.find("Lowpower") != std::string::npos)
443  {
444  post_event(EvLowPower());
445  valid = true;
446  }
447  else if (last_cmd.size() > 0) // deal with varied CLAM responses
448  {
449  std::string::size_type eq_pos = last_cmd.find('=');
450  static const std::string cfg_store = "cfg store";
451  static const std::string date_cmd = "date -t";
452 
453  if (last_cmd[0] == '@' && eq_pos != std::string::npos &&
454  boost::iequals(last_cmd.substr(1, eq_pos - 1), e.response_.substr(0, eq_pos - 1)))
455  {
456  // configuration sentences
457  // e.g. last_cmd: "@P1EchoChar=Dis"
458  // in: "P1EchoChar | Dis"
459  valid = true;
460  }
461  else if (e.response_ == "Configuration stored." &&
462  boost::iequals(last_cmd.substr(0, cfg_store.size()), cfg_store))
463  {
464  valid = true;
465  }
466  else if (boost::iequals(last_cmd.substr(0, date_cmd.size()), date_cmd) &&
467  e.response_.find(last_cmd.substr(date_cmd.size(), 8)) !=
468  std::string::npos) // match time in response e.g. 19:04:26
469  {
470  valid = true;
471  }
472  }
473 
474  if (valid)
475  {
476  glog.is(DEBUG2) && glog << "Popping: " << at_out().front().second << std::endl;
477  at_out().pop_front();
478  if (at_out().empty())
479  post_event(EvAtEmpty());
480  }
481  }
482 
483  if (!valid)
484  {
485  glog.is(DEBUG1) && glog << group("benthosatm900") << "Ignoring: '" << e.response_ << "'"
486  << std::endl;
487  }
488 }
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
void connect(Signal *signal, Slot slot)
connect a signal to a slot (e.g. function pointer)
Definition: connect.h:36
common::FlexOstream glog
Access the Goby logger through this object.