Goby v2
iridium_driver_fsm.h
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 #ifndef IridiumModemDriverFSM20130826H
24 #define IridiumModemDriverFSM20130826H
25 
26 #include <boost/circular_buffer.hpp>
27 
28 #include <boost/mpl/list.hpp>
29 #include <boost/statechart/custom_reaction.hpp>
30 #include <boost/statechart/deep_history.hpp>
31 #include <boost/statechart/event.hpp>
32 #include <boost/statechart/in_state_reaction.hpp>
33 #include <boost/statechart/simple_state.hpp>
34 #include <boost/statechart/state.hpp>
35 #include <boost/statechart/state_machine.hpp>
36 #include <boost/statechart/transition.hpp>
37 #include <iostream>
38 
39 #include "goby/acomms/modemdriver/iridium_driver_common.h"
40 #include "goby/acomms/protobuf/iridium_driver.pb.h"
41 #include "goby/acomms/protobuf/modem_message.pb.h"
42 
43 namespace goby
44 {
45 namespace acomms
46 {
47 inline unsigned sbd_csum(const std::string& data)
48 {
49  unsigned int csum = 0;
50  for (std::string::const_iterator it = data.begin(), end = data.end(); it != end; ++it)
51  csum += *it & 0xFF;
52  return csum;
53 }
54 
55 namespace fsm
56 {
58 {
59  StateNotify(const std::string& name) : name_(name)
60  {
61  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver") << name_
62  << std::endl;
63  }
64  ~StateNotify()
65  {
66  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver") << "~" << name_
67  << std::endl;
68  }
69 
70  private:
71  std::string name_;
72 };
73 
74 // events
76 {
77  std::string line;
78 };
80 {
81 };
82 
83 struct EvRxOnCallSerial : boost::statechart::event<EvRxOnCallSerial>
84 {
85  std::string line;
86 };
87 struct EvTxOnCallSerial : boost::statechart::event<EvTxOnCallSerial>
88 {
89 };
90 
92 {
93  EvAck(const std::string& response) : response_(response) {}
94 
95  std::string response_;
96 };
97 
99 {
100 };
102 {
103 };
104 
106 {
107 };
109 {
110 };
112 {
113 };
114 
116 {
117 };
118 
120 {
121 };
123 {
124 };
126 {
127 };
128 
130 {
131 };
132 
134 {
135 };
136 struct EvSBDBeginData : boost::statechart::event<EvSBDBeginData>
137 {
138  EvSBDBeginData(const std::string& data = "", bool in_response_to_ring_alert = false)
139  : data_(data), in_response_to_ring_alert_(in_response_to_ring_alert)
140  {
141  }
142  std::string data_;
143  bool in_response_to_ring_alert_;
144 };
145 
146 struct EvSBDSendBufferCleared : boost::statechart::event<EvSBDSendBufferCleared>
147 {
148 };
149 
150 struct EvSBDWriteReady : boost::statechart::event<EvSBDWriteReady>
151 {
152 };
153 struct EvSBDWriteComplete : boost::statechart::event<EvSBDWriteComplete>
154 {
155 };
156 struct EvSBDTransmitComplete : boost::statechart::event<EvSBDTransmitComplete>
157 {
158  EvSBDTransmitComplete(const std::string& sbdi) : sbdi_(sbdi) {}
159  std::string sbdi_;
160 };
161 struct EvSBDReceiveComplete : boost::statechart::event<EvSBDReceiveComplete>
162 {
163 };
164 
165 // pre-declare
166 struct Active;
167 struct Command;
168 struct Configure;
169 struct Ready;
170 struct Answer;
171 struct Dial;
172 struct HangingUp;
173 struct PostDisconnected;
174 
175 struct Online;
176 struct OnCall;
177 struct NotOnCall;
178 
179 struct SBD;
180 struct SBDConfigure;
181 struct SBDReady;
182 struct SBDClearBuffers;
183 struct SBDWrite;
184 struct SBDTransmit;
185 struct SBDReceive;
186 
187 // state machine
188 struct IridiumDriverFSM : boost::statechart::state_machine<IridiumDriverFSM, Active>
189 {
190  public:
191  IridiumDriverFSM(const protobuf::DriverConfig& driver_cfg)
192  : serial_tx_buffer_(SERIAL_BUFFER_CAPACITY), received_(RECEIVED_BUFFER_CAPACITY),
193  driver_cfg_(driver_cfg), data_out_(DATA_BUFFER_CAPACITY)
194  {
195  ++count_;
196  glog_ir_group_ = "iridiumdriver::" + goby::util::as<std::string>(count_);
197  }
198 
199  void buffer_data_out(const goby::acomms::protobuf::ModemTransmission& msg);
200 
201  // messages for the serial line at next opportunity
202  boost::circular_buffer<std::string>& serial_tx_buffer() { return serial_tx_buffer_; }
203 
204  // received messages to be passed up out of the ModemDriver
205  boost::circular_buffer<protobuf::ModemTransmission>& received() { return received_; }
206 
207  // data that should (eventually) be sent out across the Iridium connection
208  boost::circular_buffer<protobuf::ModemTransmission>& data_out() { return data_out_; }
209 
210  const protobuf::DriverConfig& driver_cfg() const { return driver_cfg_; }
211 
212  const std::string& glog_ir_group() const { return glog_ir_group_; }
213 
214  private:
215  enum
216  {
217  SERIAL_BUFFER_CAPACITY = 10
218  };
219  enum
220  {
221  RECEIVED_BUFFER_CAPACITY = 10
222  };
223 
224  boost::circular_buffer<std::string> serial_tx_buffer_;
225  boost::circular_buffer<protobuf::ModemTransmission> received_;
226  const protobuf::DriverConfig& driver_cfg_;
227 
228  enum
229  {
230  DATA_BUFFER_CAPACITY = 5
231  };
232  boost::circular_buffer<protobuf::ModemTransmission> data_out_;
233 
234  std::string glog_ir_group_;
235 
236  static int count_;
237 };
238 
239 struct Active : boost::statechart::simple_state<Active, IridiumDriverFSM,
240  boost::mpl::list<Command, NotOnCall> >
241 {
242  typedef boost::mpl::list<boost::statechart::transition<EvReset, Active> > reactions;
243 };
244 
245 // Command
246 struct Command : boost::statechart::simple_state<Command, Active::orthogonal<0>,
247  boost::mpl::list<Configure, SBD> >,
249 {
250  public:
251  void in_state_react(const EvRxSerial&);
252  void in_state_react(const EvTxSerial&);
253  void in_state_react(const EvAck&);
254 
255  Command() : StateNotify("Command"), at_out_(AT_BUFFER_CAPACITY) {}
256  ~Command() {}
257 
258  typedef boost::mpl::list<
259  boost::statechart::in_state_reaction<EvRxSerial, Command, &Command::in_state_react>,
260  boost::statechart::in_state_reaction<EvTxSerial, Command, &Command::in_state_react>,
261  boost::statechart::transition<EvOnline, Online>,
262  boost::statechart::in_state_reaction<EvAck, Command, &Command::in_state_react> >
263  reactions;
264 
266  {
267  ATSentenceMeta() : last_send_time_(0), tries_(0) {}
268  double last_send_time_;
269  int tries_;
270  };
271 
272  void push_at_command(const std::string& cmd)
273  {
274  at_out_.push_back(std::make_pair(ATSentenceMeta(), cmd));
275  }
276 
277  boost::circular_buffer<std::pair<ATSentenceMeta, std::string> >& at_out() { return at_out_; }
278 
279  void clear_sbd_rx_buffer() { sbd_rx_buffer_.clear(); }
280 
281  void handle_sbd_rx(const std::string& in);
282 
283  private:
284  enum
285  {
286  AT_BUFFER_CAPACITY = 100
287  };
288  boost::circular_buffer<std::pair<ATSentenceMeta, std::string> > at_out_;
289  enum
290  {
291  COMMAND_TIMEOUT_SECONDS = 2,
292  DIAL_TIMEOUT_SECONDS = 60,
293  SBDIX_TIMEOUT_SECONDS = DIAL_TIMEOUT_SECONDS,
294  TRIPLE_PLUS_TIMEOUT_SECONDS = 6,
295  HANGUP_TIMEOUT_SECONDS = 10,
296  ANSWER_TIMEOUT_SECONDS = 30
297  };
298 
299  enum
300  {
301  RETRIES_BEFORE_RESET = 3
302  };
303  std::string sbd_rx_buffer_;
304 };
305 
306 struct Configure : boost::statechart::state<Configure, Command::orthogonal<0> >, StateNotify
307 {
308  typedef boost::mpl::list<boost::statechart::transition<EvAtEmpty, Ready> > reactions;
309 
310  Configure(my_context ctx) : my_base(ctx), StateNotify("Configure")
311  {
312  context<Command>().push_at_command("");
313  for (int i = 0, n = context<IridiumDriverFSM>().driver_cfg().ExtensionSize(
314  IridiumDriverConfig::config);
315  i < n; ++i)
316  {
317  context<Command>().push_at_command(
318  context<IridiumDriverFSM>().driver_cfg().GetExtension(IridiumDriverConfig::config,
319  i));
320  }
321  }
322 
323  ~Configure() { post_event(EvConfigured()); }
324 };
325 
326 struct Ready : boost::statechart::simple_state<Ready, Command::orthogonal<0> >, StateNotify
327 {
328  public:
329  Ready() : StateNotify("Ready") {}
330  ~Ready() {}
331 
332  boost::statechart::result react(const EvDial&)
333  {
334  if (state_downcast<const NotOnCall*>() != 0)
335  {
336  return transit<Dial>();
337  }
338  else
339  {
340  glog.is(goby::common::logger::DEBUG1) &&
341  glog << group("iridiumdriver") << "Not dialing since we are already on a call."
342  << std::endl;
343  return discard_event();
344  }
345  }
346 
347  typedef boost::mpl::list<boost::statechart::transition<EvRing, Answer>,
348  boost::statechart::custom_reaction<EvDial> >
349  reactions;
350 
351  private:
352 };
353 
354 struct HangingUp : boost::statechart::state<HangingUp, Command::orthogonal<0> >, StateNotify
355 {
356  public:
357  HangingUp(my_context ctx) : my_base(ctx), StateNotify("HangingUp")
358  {
359  context<Command>().push_at_command("+++");
360  context<Command>().push_at_command("H");
361  }
362  ~HangingUp() {}
363 
364  typedef boost::mpl::list<boost::statechart::transition<EvAtEmpty, Ready> > reactions;
365 
366  private:
367 };
368 
369 struct PostDisconnected : boost::statechart::state<PostDisconnected, Command::orthogonal<0> >,
371 {
372  public:
373  PostDisconnected(my_context ctx) : my_base(ctx), StateNotify("PostDisconnected")
374  {
375  glog.is(goby::common::logger::DEBUG1) &&
376  glog << group("iridiumdriver") << "Disconnected; checking error details: " << std::endl;
377  context<Command>().push_at_command("+CEER");
378  }
379  ~PostDisconnected() {}
380 
381  typedef boost::mpl::list<boost::statechart::transition<EvAtEmpty, Ready> > reactions;
382 
383  private:
384 };
385 
386 struct Dial : boost::statechart::state<Dial, Command::orthogonal<0> >, StateNotify
387 {
388  typedef boost::mpl::list<boost::statechart::custom_reaction<EvNoCarrier> > reactions;
389 
390  Dial(my_context ctx) : my_base(ctx), StateNotify("Dial"), dial_attempts_(0) { dial(); }
391  ~Dial() {}
392 
393  boost::statechart::result react(const EvNoCarrier&);
394  void dial();
395 
396  private:
397  int dial_attempts_;
398 };
399 
400 struct Answer : boost::statechart::state<Answer, Command::orthogonal<0> >, StateNotify
401 {
402  typedef boost::mpl::list<boost::statechart::transition<EvNoCarrier, Ready> > reactions;
403 
404  Answer(my_context ctx) : my_base(ctx), StateNotify("Answer")
405  {
406  context<Command>().push_at_command("A");
407  }
408  ~Answer() {}
409 };
410 
411 // Online
412 struct Online : boost::statechart::simple_state<Online, Active::orthogonal<0> >, StateNotify
413 {
414  Online() : StateNotify("Online") {}
415  ~Online() {}
416 
417  void in_state_react(const EvRxSerial&);
418  void in_state_react(const EvTxSerial&);
419 
420  typedef boost::mpl::list<
421  boost::statechart::transition<EvHangup, HangingUp>,
422  boost::statechart::transition<EvDisconnect, PostDisconnected>,
423  boost::statechart::in_state_reaction<EvRxSerial, Online, &Online::in_state_react>,
424  boost::statechart::in_state_reaction<EvTxSerial, Online, &Online::in_state_react> >
425  reactions;
426 };
427 
428 // Orthogonal on-call / not-on-call
429 struct NotOnCall : boost::statechart::simple_state<NotOnCall, Active::orthogonal<1> >, StateNotify
430 {
431  typedef boost::mpl::list<boost::statechart::transition<EvConnect, OnCall> > reactions;
432 
433  NotOnCall() : StateNotify("NotOnCall") {}
434  ~NotOnCall() {}
435 };
436 
437 struct OnCall : boost::statechart::state<OnCall, Active::orthogonal<1> >, StateNotify, OnCallBase
438 {
439  public:
440  OnCall(my_context ctx) : my_base(ctx), StateNotify("OnCall")
441  {
442  // add a brief identifier that is *different* than the "~" which is what PPP uses
443  // add a carriage return to clear out any garbage
444  // at the *beginning* of transmission
445  context<IridiumDriverFSM>().serial_tx_buffer().push_front("goby\r");
446 
447  // connecting necessarily puts the DTE online
448  post_event(EvOnline());
449  }
450  ~OnCall()
451  {
452  // signal the disconnect event for the command state to handle
453  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver") << "Sent "
454  << total_bytes_sent()
455  << " bytes on this call." << std::endl;
456  post_event(EvDisconnect());
457  }
458 
459  void in_state_react(const EvRxOnCallSerial&);
460  void in_state_react(const EvTxOnCallSerial&);
461  void in_state_react(const EvSendBye&);
462 
463  typedef boost::mpl::list<
464  boost::statechart::transition<EvNoCarrier, NotOnCall>,
465  boost::statechart::in_state_reaction<EvRxOnCallSerial, OnCall, &OnCall::in_state_react>,
466  boost::statechart::in_state_reaction<EvTxOnCallSerial, OnCall, &OnCall::in_state_react>,
467  boost::statechart::in_state_reaction<EvSendBye, OnCall, &OnCall::in_state_react>
468 
469  >
470  reactions;
471 
472  private:
473 };
474 
475 struct SBD : boost::statechart::simple_state<SBD, Command::orthogonal<1>, SBDReady>, StateNotify
476 {
477  SBD() : StateNotify("SBD") {}
478  ~SBD() {}
479 
480  void set_data(const EvSBDBeginData& e)
481  {
482  set_data(e.data_);
483  in_response_to_ring_alert_ = e.in_response_to_ring_alert_;
484  }
485  void clear_data() { data_.clear(); }
486  const std::string& data() const { return data_; }
487  bool in_response_to_ring_alert() const { return in_response_to_ring_alert_; }
488 
489  private:
490  void set_data(const std::string& data)
491  {
492  if (data.empty())
493  data_ = data;
494  else
495  {
496  unsigned int csum = sbd_csum(data);
497 
498  const int bits_in_byte = 8;
499  data_ = data + std::string(1, (csum & 0xFF00) >> bits_in_byte) +
500  std::string(1, (csum & 0xFF));
501  }
502  }
503 
504  private:
505  std::string data_;
506  bool in_response_to_ring_alert_;
507 };
508 
509 /* struct SBDConfigure : boost::statechart::simple_state<SBDConfigure, SBD >, StateNotify */
510 /* { */
511 /* typedef boost::mpl::list< */
512 /* boost::statechart::transition< EvConfigured, SBDReady > */
513 /* > reactions; */
514 
515 /* SBDConfigure() : StateNotify("SBDConfigure") */
516 /* { */
517 /* } */
518 /* ~SBDConfigure() { */
519 /* } */
520 
521 /* }; */
522 
524 {
525  typedef boost::mpl::list<
526  boost::statechart::transition<EvSBDBeginData, SBDClearBuffers, SBD, &SBD::set_data> >
527  reactions;
528 
529  SBDReady() : StateNotify("SBDReady") {}
530 
531  ~SBDReady() {}
532 };
533 
534 struct SBDClearBuffers : boost::statechart::state<SBDClearBuffers, SBD>, StateNotify
535 {
536  typedef boost::mpl::list<boost::statechart::transition<EvSBDSendBufferCleared, SBDWrite> >
537  reactions;
538 
539  SBDClearBuffers(my_context ctx) : my_base(ctx), StateNotify("SBDClearBuffers")
540  {
541  context<Command>().clear_sbd_rx_buffer();
542  context<Command>().push_at_command("+SBDD2");
543  }
544 
545  ~SBDClearBuffers() {}
546 };
547 
549 {
550  SBDWrite(my_context ctx) : my_base(ctx), StateNotify("SBDWrite")
551  {
552  if (context<SBD>().data().empty())
553  {
554  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver")
555  << "Mailbox Check." << std::endl;
556  post_event(EvSBDWriteComplete()); // Mailbox Check
557  }
558  else
559  {
560  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver")
561  << "Writing data." << std::endl;
562 
563  const int csum_bytes = 2;
564  context<Command>().push_at_command(
565  "+SBDWB=" + goby::util::as<std::string>(context<SBD>().data().size() - csum_bytes));
566  }
567  }
568 
569  void in_state_react(const EvSBDWriteReady&)
570  {
571  context<IridiumDriverFSM>().serial_tx_buffer().push_back(context<SBD>().data());
572  }
573 
574  ~SBDWrite() {}
575 
576  typedef boost::mpl::list<
577  boost::statechart::in_state_reaction<EvSBDWriteReady, SBDWrite, &SBDWrite::in_state_react>,
578  boost::statechart::transition<EvSBDWriteComplete, SBDTransmit> >
579  reactions;
580 };
581 
583 {
584  typedef boost::mpl::list<boost::statechart::custom_reaction<EvSBDTransmitComplete> > reactions;
585  SBDTransmit(my_context ctx) : my_base(ctx), StateNotify("SBDTransmit")
586  {
587  if (context<SBD>().in_response_to_ring_alert())
588  context<Command>().push_at_command("+SBDIXA");
589  else
590  context<Command>().push_at_command("+SBDIX");
591  }
592  ~SBDTransmit() { context<SBD>().clear_data(); }
593 
594  std::string mo_status_as_string(int code)
595  {
596  switch (code)
597  {
598  // success
599  case 0: return "MO message, if any, transferred successfully";
600  case 1:
601  return "MO message, if any, transferred successfully, but the MT message in the "
602  "queue was too big to be transferred";
603  case 2:
604  return "MO message, if any, transferred successfully, but the requested Location "
605  "Update was not accepted";
606  case 3:
607  case 4:
608  return "Reserved, but indicate MO session success if used";
609 
610  // failure
611  case 5:
612  case 6:
613  case 7:
614  case 8:
615  case 9:
616  case 20:
617  case 21:
618  case 22:
619  case 23:
620  case 24:
621  case 25:
622  case 26:
623  case 27:
624  case 28:
625  case 29:
626  case 30:
627  case 31:
628  case 33:
629  case 34:
630  case 36:
631  default: return "Reserved, but indicate MO session failure if used";
632  case 10: return "GSS reported that the call did not complete in the allowed time";
633  case 11: return "MO message queue at the GSS is full";
634  case 12: return "MO message has too many segments";
635  case 13: return "GSS reported that the session did not complete";
636  case 14: return "Invalid segment size";
637  case 15: return "Access is denied";
638  case 16: return "Modem has been locked and may not make SBD calls";
639  case 17: return "Gateway not responding (local session timeout)";
640  case 18: return "Connection lost (RF drop)";
641  case 19: return "Link failure (A protocol error caused termination of the call)";
642  case 32: return "No network service, unable to initiate call";
643  case 35: return "Iridium 9523 is busy, unable to initiate call";
644  }
645  }
646 
647  boost::statechart::result react(const EvSBDTransmitComplete& e)
648  {
649  // +SBDIX:<MO status>,<MOMSN>,<MT status>,<MTMSN>,<MT length>,<MT queued>
650  std::vector<std::string> sbdi_fields;
651  boost::algorithm::split(sbdi_fields, e.sbdi_, boost::is_any_of(":,"));
652 
653  std::for_each(sbdi_fields.begin(), sbdi_fields.end(),
654  boost::bind(&boost::trim<std::string>, _1, std::locale()));
655 
656  if (sbdi_fields.size() != 7)
657  {
658  glog.is(goby::common::logger::DEBUG1) && glog << group("iridiumdriver")
659  << "Invalid +SBDI response: " << e.sbdi_
660  << std::endl;
661  return transit<SBDReady>();
662  }
663  else
664  {
665  enum
666  {
667  MO_STATUS = 1,
668  MOMSN = 2,
669  MT_STATUS = 3,
670  MTMSN = 4,
671  MT_LENGTH = 5,
672  MT_QUEUED = 6
673  };
674  enum
675  {
676  MT_STATUS_NO_MESSAGE = 0,
677  MT_STATUS_RECEIVED_MESSAGE = 1,
678  MT_STATUS_ERROR = 2
679  };
680 
681  // 0-4 OK, 5-36 FAILURE
682  enum
683  {
684  MO_STATUS_SUCCESS_MAX = 4,
685  MO_STATUS_FAILURE_MIN = 5
686  };
687 
688  int mo_status = goby::util::as<int>(sbdi_fields[MO_STATUS]);
689  if (mo_status <= MO_STATUS_SUCCESS_MAX)
690  {
691  glog.is(goby::common::logger::DEBUG1) &&
692  glog << group("iridiumdriver")
693  << "Success sending SBDIX: " << mo_status_as_string(mo_status)
694  << std::endl;
695  }
696  else
697  {
698  glog.is(goby::common::logger::WARN) &&
699  glog << group("iridiumdriver")
700  << "Error sending SBD packet: " << mo_status_as_string(mo_status)
701  << std::endl;
702  return transit<SBDReady>();
703  }
704 
705  int mt_status = goby::util::as<int>(sbdi_fields[MT_STATUS]);
706  if (mt_status == MT_STATUS_RECEIVED_MESSAGE)
707  return transit<SBDReceive>();
708  else
709  return transit<SBDReady>();
710  }
711  }
712 };
713 
715 {
716  typedef boost::mpl::list<boost::statechart::transition<EvSBDReceiveComplete, SBDReady> >
717  reactions;
718  SBDReceive(my_context ctx) : my_base(ctx), StateNotify("SBDReceive")
719  {
720  context<Command>().push_at_command("+SBDRB");
721  }
722  ~SBDReceive() {}
723 };
724 
725 } // namespace fsm
726 } // namespace acomms
727 } // namespace goby
728 
729 #endif
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.