Goby v2
iridium_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 
26 #include "goby/acomms/acomms_constants.h"
27 #include "goby/common/logger.h"
28 #include "goby/common/time.h"
29 #include "goby/util/binary.h"
30 
31 #include "iridium_driver_fsm.h"
32 #include "rudics_packet.h"
33 
34 using goby::glog;
35 using namespace goby::common::logger;
37 
38 int goby::acomms::fsm::IridiumDriverFSM::count_ = 0;
39 
40 void goby::acomms::fsm::IridiumDriverFSM::buffer_data_out(
42 {
43  data_out_.push_back(msg);
44 }
45 
46 void goby::acomms::fsm::Command::in_state_react(const EvRxSerial& e)
47 {
48  std::string in = e.line;
49 
50  // deal with SBD received data special case
51  if (!at_out().empty() && at_out().front().second == "+SBDRB")
52  {
53  handle_sbd_rx(in);
54  return;
55  }
56 
57  boost::trim(in);
58 
59  // deal with echo getting turned back on unintentionally
60  if (!at_out().empty() && at_out().front().second != "E" &&
61  (in == std::string("AT" + at_out().front().second)))
62  {
63  glog.is(WARN) && glog << group("iridiumdriver") << "Echo turned on. Disabling" << std::endl;
64  // push to front so we send this before anything else
65  at_out_.insert(at_out_.begin() + 1, std::make_pair(ATSentenceMeta(), "E"));
66  return;
67  }
68 
69  static const std::string connect = "CONNECT";
70  static const std::string sbdi = "+SBDI";
71 
72  if (in == "OK")
73  {
74  post_event(EvAck(in));
75  }
76  else if (in == "RING")
77  {
78  post_event(EvRing());
79  }
80  else if (in == "NO CARRIER")
81  {
82  post_event(EvAck(in));
83  post_event(EvNoCarrier());
84  }
85  else if (in.compare(0, connect.size(), connect) == 0)
86  {
87  post_event(EvAck(in));
88  post_event(EvConnect());
89  }
90  else if (in == "NO DIALTONE")
91  {
92  post_event(EvAck(in));
93  post_event(EvNoCarrier());
94  }
95  else if (in == "BUSY")
96  {
97  post_event(EvAck(in));
98  post_event(EvNoCarrier());
99  }
100  else if (in == "ERROR")
101  {
102  post_event(EvReset());
103  }
104  else if (in == "0" || in == "1" || in == "2" || in == "3")
105  {
106  post_event(EvAck(in));
107  }
108  else if (in == "READY")
109  {
110  post_event(EvAck(in));
111  }
112  else if (in.compare(0, sbdi.size(), sbdi) == 0)
113  {
114  post_event(EvSBDTransmitComplete(in));
115  }
116  else if (in == "SBDRING")
117  {
118  post_event(EvSBDBeginData("", true));
119  }
120 }
121 
122 void goby::acomms::fsm::Command::handle_sbd_rx(const std::string& in)
123 {
124  enum
125  {
126  SBD_FIELD_SIZE_BYTES = 2,
127  SBD_BITS_IN_BYTE = 8,
128  SBD_CHECKSUM_BYTES = 2
129  };
130 
131  if (sbd_rx_buffer_.empty() && in.at(0) == '\n')
132  sbd_rx_buffer_ = in.substr(1); // discard left over '\n' from last command
133  else
134  sbd_rx_buffer_ += in;
135 
136  // need to build up message in pieces since we use \r delimiter
137  if (sbd_rx_buffer_.size() < SBD_FIELD_SIZE_BYTES)
138  return;
139  else
140  {
141  unsigned sbd_rx_size =
142  ((sbd_rx_buffer_[0] & 0xff) << SBD_BITS_IN_BYTE) | (sbd_rx_buffer_[1] & 0xff);
143  glog.is(DEBUG1) && glog << group("iridiumdriver") << "SBD RX Size: " << sbd_rx_size
144  << std::endl;
145 
146  if (sbd_rx_buffer_.size() < (SBD_FIELD_SIZE_BYTES + sbd_rx_size))
147  {
148  return; // keep building up message
149  }
150  else
151  {
152  std::string sbd_rx_data = sbd_rx_buffer_.substr(SBD_FIELD_SIZE_BYTES, sbd_rx_size);
153  std::string bytes;
154  parse_rudics_packet(&bytes, sbd_rx_data);
155  protobuf::ModemTransmission msg;
156  parse_iridium_modem_message(bytes, &msg);
157  context<IridiumDriverFSM>().received().push_back(msg);
158  at_out().pop_front();
159 
160  post_event(EvSBDReceiveComplete());
161 
162  // clear out the checksum
163  push_at_command("");
164  }
165  }
166 }
167 
168 void goby::acomms::fsm::Command::in_state_react(const EvTxSerial&)
169 {
170  double now = goby_time<double>();
171 
172  if (!at_out_.empty())
173  {
174  double timeout = COMMAND_TIMEOUT_SECONDS;
175  switch (at_out_.front().second[0])
176  {
177  default: break;
178  case 'D': timeout = DIAL_TIMEOUT_SECONDS; break;
179  case 'A': timeout = ANSWER_TIMEOUT_SECONDS; break;
180  case 'H': timeout = HANGUP_TIMEOUT_SECONDS; break;
181  case '+':
182  if (at_out_.front().second == "+++")
183  timeout = TRIPLE_PLUS_TIMEOUT_SECONDS;
184  break;
185  }
186 
187  static const std::string sbdi = "+SBDI";
188  if (at_out_.front().second.compare(0, sbdi.size(), sbdi) == 0)
189  timeout = SBDIX_TIMEOUT_SECONDS;
190 
191  if (at_out_.front().second == "+SBDRB")
192  clear_sbd_rx_buffer();
193 
194  if ((at_out_.front().first.last_send_time_ + timeout) < now)
195  {
196  std::string at_command;
197  if (at_out_.front().second != "+++")
198  at_command = "AT" + at_out_.front().second + "\r";
199  else
200  at_command = at_out_.front().second;
201 
202  if (++at_out_.front().first.tries_ > RETRIES_BEFORE_RESET)
203  {
204  glog.is(DEBUG1) && glog << group("iridiumdriver") << warn
205  << "No valid response after " << RETRIES_BEFORE_RESET
206  << " tries. Resetting state machine" << std::endl;
207  post_event(EvReset());
208  }
209  else
210  {
211  context<IridiumDriverFSM>().serial_tx_buffer().push_back(at_command);
212  at_out_.front().first.last_send_time_ = now;
213  }
214  }
215  }
216 }
217 
218 void goby::acomms::fsm::Online::in_state_react(const EvRxSerial& e)
219 {
220  EvRxOnCallSerial eo;
221  eo.line = e.line;
222  post_event(eo);
223 }
224 
225 void goby::acomms::fsm::Online::in_state_react(const EvTxSerial&)
226 {
227  post_event(EvTxOnCallSerial());
228 }
229 
230 void goby::acomms::fsm::Command::in_state_react(const EvAck& e)
231 {
232  // deal with the numeric codes
233  if (e.response_.size() > 0)
234  {
235  switch (e.response_[0])
236  {
237  case '0':
238  if (!at_out().empty() && at_out().front().second == "+SBDD2")
239  {
240  post_event(EvSBDSendBufferCleared());
241  }
242  else if (at_out().empty()) // no AT command before this - we write the data directly
243  {
244  post_event(EvSBDWriteComplete());
245  push_at_command("AT"); // this is so the "OK" will have something to clear
246  }
247  return; // all followed by "OK" which will clear the sentence
248  case '1': return;
249  case '2': return;
250  case '3': return;
251  default: break;
252  }
253  }
254 
255  if (!at_out().empty())
256  {
257  const std::string& last_at = at_out().front().second;
258  if (last_at.size() > 0 && (e.response_ == "OK"))
259  {
260  switch (last_at[0])
261  {
262  case 'H':
263  post_event(EvNoCarrier());
264  break;
265 
266  // 2015-08-18 - Iridium 9523 gave "OK" in response to a dial (as failure?)
267  // if this happens, assume it's a failure.
268  case 'D': post_event(EvNoCarrier()); break;
269 
270  default: break;
271  }
272  }
273 
274  if (e.response_ == "READY") // used for SBD
275  post_event(EvSBDWriteReady());
276 
277  at_out().pop_front();
278  if (at_out().empty())
279  post_event(EvAtEmpty());
280  }
281  else
282  {
283  glog.is(DEBUG1) && glog << group("iridiumdriver") << warn << "Unexpected '" << e.response_
284  << "'" << std::endl;
285  }
286 }
287 
288 boost::statechart::result goby::acomms::fsm::Dial::react(const EvNoCarrier& x)
289 {
290  const int redial_wait_seconds = 2;
291  glog.is(DEBUG1) && glog << group("iridiumdriver") << "Redialing in " << redial_wait_seconds
292  << " seconds ..." << std::endl;
293 
294  sleep(redial_wait_seconds);
295 
296  const int max_attempts =
297  context<IridiumDriverFSM>().driver_cfg().GetExtension(IridiumDriverConfig::dial_attempts);
298  if (dial_attempts_ < max_attempts)
299  {
300  dial();
301  return discard_event();
302  }
303  else
304  {
305  glog.is(DEBUG1) && glog << warn << group("iridiumdriver") << "Failed to connect after "
306  << max_attempts << " tries." << std::endl;
307 
308  return transit<Ready>();
309  }
310 }
311 
312 void goby::acomms::fsm::Dial::dial()
313 {
314  ++dial_attempts_;
315  context<Command>().push_at_command("D" + context<IridiumDriverFSM>()
316  .driver_cfg()
317  .GetExtension(IridiumDriverConfig::remote)
318  .iridium_number());
319 }
320 
321 void goby::acomms::fsm::OnCall::in_state_react(const EvRxOnCallSerial& e)
322 {
323  std::string in = e.line;
324 
325  // check that it's not "NO CARRIER"
326  static const std::string no_carrier = "NO CARRIER";
327  if (in.find(no_carrier) != std::string::npos)
328  {
329  post_event(EvNoCarrier());
330  }
331  else if (boost::trim_copy(in) == "goby")
332  {
333  glog.is(DEBUG1) && glog << group("iridiumdriver") << "Detected start of Goby RUDICS call"
334  << std::endl;
335  }
336  else if (boost::trim_copy(in) == "bye")
337  {
338  glog.is(DEBUG1) && glog << group("iridiumdriver")
339  << "Detected remote completion of Goby RUDICS call" << std::endl;
340  set_bye_received(true);
341  }
342  else
343  {
344  std::string bytes;
345  try
346  {
347  parse_rudics_packet(&bytes, in);
348 
349  protobuf::ModemTransmission msg;
350  parse_iridium_modem_message(bytes, &msg);
351  context<IridiumDriverFSM>().received().push_back(msg);
352  set_last_rx_time(goby_time<double>());
353  }
354  catch (RudicsPacketException& e)
355  {
356  glog.is(DEBUG1) && glog << warn << group("iridiumdriver")
357  << "Could not decode packet: " << e.what() << std::endl;
358  }
359  }
360 }
361 
362 void goby::acomms::fsm::OnCall::in_state_react(const EvTxOnCallSerial&)
363 {
364  const static double target_byte_rate = (context<IridiumDriverFSM>().driver_cfg().GetExtension(
365  IridiumDriverConfig::target_bit_rate) /
366  static_cast<double>(goby::acomms::BITS_IN_BYTE));
367 
368  const double send_wait = last_bytes_sent() / target_byte_rate;
369 
370  double now = goby_time<double>();
371  boost::circular_buffer<protobuf::ModemTransmission>& data_out =
372  context<IridiumDriverFSM>().data_out();
373  if (!data_out.empty() && (now > last_tx_time() + send_wait))
374  {
375  // serialize the (protobuf) message
376  std::string bytes;
377  serialize_iridium_modem_message(&bytes, data_out.front());
378 
379  // frame message
380  std::string rudics_packet;
381  serialize_rudics_packet(bytes, &rudics_packet);
382 
383  context<IridiumDriverFSM>().serial_tx_buffer().push_back(rudics_packet);
384  data_out.pop_front();
385 
386  set_last_bytes_sent(rudics_packet.size());
387  set_last_tx_time(now);
388  }
389 }
390 
391 void goby::acomms::fsm::OnCall::in_state_react(const EvSendBye&)
392 {
393  context<IridiumDriverFSM>().serial_tx_buffer().push_front("bye\r");
394  set_bye_sent(true);
395 }
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 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.