Goby v2
iridium_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 <algorithm>
24 
25 #include "iridium_driver.h"
26 
27 #include "goby/acomms/acomms_helpers.h"
28 #include "goby/acomms/modemdriver/driver_exception.h"
29 #include "goby/acomms/modemdriver/rudics_packet.h"
30 #include "goby/common/logger.h"
31 #include "goby/util/binary.h"
32 
33 using goby::glog;
34 using namespace goby::common::logger;
36 using goby::acomms::operator<<;
37 
38 boost::shared_ptr<dccl::Codec> goby::acomms::iridium_header_dccl_;
39 
40 goby::acomms::IridiumDriver::IridiumDriver()
41  : fsm_(driver_cfg_), last_triple_plus_time_(0), serial_fd_(-1), next_frame_(0)
42 {
43  init_iridium_dccl();
44 
45  // assert(byte_string_to_uint32(uint32_to_byte_string(16540)) == 16540);
46 }
47 
48 goby::acomms::IridiumDriver::~IridiumDriver() {}
49 
51 {
52  driver_cfg_ = cfg;
53 
54  glog.is(DEBUG1) && glog << group(glog_out_group())
55  << "Goby Iridium RUDICS/SBD driver starting up." << std::endl;
56 
57  driver_cfg_.set_line_delimiter("\r");
58 
59  if (driver_cfg_.HasExtension(IridiumDriverConfig::debug_client_port))
60  {
61  debug_client_.reset(new goby::util::TCPClient(
62  "localhost", driver_cfg_.GetExtension(IridiumDriverConfig::debug_client_port), "\r"));
63  debug_client_->start();
64  }
65 
66  if (!driver_cfg_.HasExtension(IridiumDriverConfig::use_dtr) &&
67  driver_cfg_.connection_type() == protobuf::DriverConfig::CONNECTION_SERIAL)
68  {
69  driver_cfg_.SetExtension(IridiumDriverConfig::use_dtr, true);
70  }
71 
72  // dtr low hangs up
73  if (driver_cfg_.GetExtension(IridiumDriverConfig::use_dtr))
74  driver_cfg_.AddExtension(IridiumDriverConfig::config, "&D2");
75 
76  rudics_mac_msg_.set_src(driver_cfg_.modem_id());
77  rudics_mac_msg_.set_type(goby::acomms::protobuf::ModemTransmission::DATA);
78  rudics_mac_msg_.set_rate(RATE_RUDICS);
79 
80  modem_init();
81 }
82 
83 void goby::acomms::IridiumDriver::modem_init()
84 {
85  modem_start(driver_cfg_);
86 
87  fsm_.initiate();
88 
89  int i = 0;
90  bool dtr_set = false;
91  while (fsm_.state_cast<const fsm::Ready*>() == 0)
92  {
93  do_work();
94 
95  if (driver_cfg_.GetExtension(IridiumDriverConfig::use_dtr) && modem().active() && !dtr_set)
96  {
97  serial_fd_ = dynamic_cast<util::SerialClient&>(modem()).socket().native();
98  set_dtr(true);
99  glog.is(DEBUG1) && glog << group(glog_out_group()) << "DTR is: " << query_dtr()
100  << std::endl;
101  dtr_set = true;
102  }
103 
104  const int pause_ms = 10;
105  usleep(pause_ms * 1000);
106  ++i;
107 
108  const int start_timeout = driver_cfg_.GetExtension(IridiumDriverConfig::start_timeout);
109  if (i / (1000 / pause_ms) > start_timeout)
110  throw(ModemDriverException("Failed to startup.",
111  protobuf::ModemDriverStatus::STARTUP_FAILED));
112  }
113 }
114 
115 void goby::acomms::IridiumDriver::set_dtr(bool state)
116 {
117  int status;
118  if (ioctl(serial_fd_, TIOCMGET, &status) == -1)
119  {
120  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
121  << "IOCTL failed: " << strerror(errno) << std::endl;
122  }
123 
124  glog.is(DEBUG1) && glog << group(glog_out_group()) << "Setting DTR to "
125  << (state ? "high" : "low") << std::endl;
126  if (state)
127  status |= TIOCM_DTR;
128  else
129  status &= ~TIOCM_DTR;
130  if (ioctl(serial_fd_, TIOCMSET, &status) == -1)
131  {
132  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
133  << "IOCTL failed: " << strerror(errno) << std::endl;
134  }
135 }
136 
137 bool goby::acomms::IridiumDriver::query_dtr()
138 {
139  int status;
140  if (ioctl(serial_fd_, TIOCMGET, &status) == -1)
141  {
142  glog.is(DEBUG1) && glog << group(glog_out_group()) << warn
143  << "IOCTL failed: " << strerror(errno) << std::endl;
144  }
145  return (status & TIOCM_DTR);
146 }
147 
148 void goby::acomms::IridiumDriver::hangup()
149 {
150  if (driver_cfg_.GetExtension(IridiumDriverConfig::use_dtr))
151  {
152  set_dtr(false);
153  sleep(1);
154  set_dtr(true);
155  // phone doesn't give "NO CARRIER" message after DTR disconnect
156  fsm_.process_event(fsm::EvNoCarrier());
157  }
158  else
159  {
160  fsm_.process_event(fsm::EvHangup());
161  }
162 }
163 
165 {
166  hangup();
167 
168  while (fsm_.state_cast<const fsm::OnCall*>() != 0)
169  {
170  do_work();
171  usleep(10000);
172  }
173 
174  if (driver_cfg_.GetExtension(IridiumDriverConfig::use_dtr))
175  set_dtr(false);
176 
177  modem_close();
178 }
179 
181  const protobuf::ModemTransmission& orig_msg)
182 {
183  process_transmission(orig_msg, true);
184 }
185 
186 void goby::acomms::IridiumDriver::process_transmission(protobuf::ModemTransmission msg, bool dial)
187 {
189 
190  const static unsigned frame_max = IridiumHeader::descriptor()
191  ->FindFieldByName("frame_start")
192  ->options()
193  .GetExtension(dccl::field)
194  .max();
195 
196  if (!msg.has_frame_start())
197  msg.set_frame_start(next_frame_ % frame_max);
198 
199  // set the frame size, if not set or if it exceeds the max configured
200  if (!msg.has_max_frame_bytes() ||
201  msg.max_frame_bytes() > driver_cfg_.GetExtension(IridiumDriverConfig::max_frame_size))
202  msg.set_max_frame_bytes(driver_cfg_.GetExtension(IridiumDriverConfig::max_frame_size));
203 
204  signal_data_request(&msg);
205 
206  next_frame_ += msg.frame_size();
207 
208  if (!(msg.frame_size() == 0 || msg.frame(0).empty()))
209  {
210  if (dial && msg.rate() == RATE_RUDICS)
211  fsm_.process_event(fsm::EvDial());
212  send(msg);
213  }
214  else if (msg.rate() == RATE_SBD)
215  {
216  if (msg.GetExtension(IridiumDriverConfig::if_no_data_do_mailbox_check))
217  fsm_.process_event(fsm::EvSBDBeginData()); // mailbox check
218  }
219 }
220 
222 {
223  // if(glog.is(DEBUG1))
224  // display_state_cfg(&glog);
225  double now = goby_time<double>();
226 
227  const fsm::OnCall* on_call = fsm_.state_cast<const fsm::OnCall*>();
228 
229  if (on_call)
230  {
231  // if we're on a call, keep pushing data at the target rate
232  const double send_wait = on_call->last_bytes_sent() /
233  (driver_cfg_.GetExtension(IridiumDriverConfig::target_bit_rate) /
234  static_cast<double>(BITS_IN_BYTE));
235 
236  if (fsm_.data_out().empty() && (now > (on_call->last_tx_time() + send_wait)))
237  {
238  if (!on_call->bye_sent())
239  {
240  process_transmission(rudics_mac_msg_, false);
241  }
242  }
243 
244  if (!on_call->bye_sent() &&
245  now > (on_call->last_tx_time() +
246  driver_cfg_.GetExtension(IridiumDriverConfig::handshake_hangup_seconds)))
247  {
248  glog.is(DEBUG2) && glog << "Sending bye" << std::endl;
249  fsm_.process_event(fsm::EvSendBye());
250  }
251 
252  if ((on_call->bye_received() && on_call->bye_sent()) ||
253  (now > (on_call->last_rx_tx_time() +
254  driver_cfg_.GetExtension(IridiumDriverConfig::hangup_seconds_after_empty))))
255  {
256  hangup();
257  }
258  }
259 
260  try_serial_tx();
261 
262  std::string in;
263  while (modem().active() && modem_read(&in))
264  {
265  fsm::EvRxSerial data_event;
266  data_event.line = in;
267 
268  glog.is(DEBUG1) &&
269  glog << group(glog_in_group())
270  << (boost::algorithm::all(in, boost::is_print() || boost::is_any_of("\r\n"))
271  ? boost::trim_copy(in)
272  : goby::util::hex_encode(in))
273  << std::endl;
274 
275  if (debug_client_ && fsm_.state_cast<const fsm::OnCall*>() != 0)
276  {
277  debug_client_->write(in);
278  }
279 
280  fsm_.process_event(data_event);
281  }
282 
283  while (!fsm_.received().empty())
284  {
285  receive(fsm_.received().front());
286  fsm_.received().pop_front();
287  }
288 
289  if (debug_client_)
290  {
291  std::string line;
292  while (debug_client_->readline(&line))
293  {
294  fsm_.serial_tx_buffer().push_back(line);
295  fsm_.process_event(fsm::EvDial());
296  }
297  }
298 
299  // try sending again at the end to push newly generated messages before we wait
300  try_serial_tx();
301 }
302 
303 void goby::acomms::IridiumDriver::receive(const protobuf::ModemTransmission& msg)
304 {
305  glog.is(DEBUG2) && glog << group(glog_in_group()) << msg << std::endl;
306 
307  if (msg.type() == protobuf::ModemTransmission::DATA && msg.ack_requested() &&
308  msg.dest() == driver_cfg_.modem_id())
309  {
310  // make any acks
312  ack.set_type(goby::acomms::protobuf::ModemTransmission::ACK);
313  ack.set_src(msg.dest());
314  ack.set_dest(msg.src());
315  ack.set_rate(msg.rate());
316  for (int i = msg.frame_start(), n = msg.frame_size() + msg.frame_start(); i < n; ++i)
317  ack.add_acked_frame(i);
318  send(ack);
319  }
320 
321  signal_receive(msg);
322 }
323 
324 void goby::acomms::IridiumDriver::send(const protobuf::ModemTransmission& msg)
325 {
326  glog.is(DEBUG2) && glog << group(glog_out_group()) << msg << std::endl;
327 
328  if (msg.rate() == RATE_RUDICS)
329  fsm_.buffer_data_out(msg);
330  else if (msg.rate() == RATE_SBD)
331  {
332  bool on_call = (fsm_.state_cast<const fsm::OnCall*>() != 0);
333  if (on_call) // if we're on a call send it via the call
334  fsm_.buffer_data_out(msg);
335  else
336  {
337  std::string iridium_packet;
338  serialize_iridium_modem_message(&iridium_packet, msg);
339 
340  std::string rudics_packet;
341  serialize_rudics_packet(iridium_packet, &rudics_packet);
342  fsm_.process_event(fsm::EvSBDBeginData(rudics_packet));
343  }
344  }
345 }
346 
347 void goby::acomms::IridiumDriver::try_serial_tx()
348 {
349  fsm_.process_event(fsm::EvTxSerial());
350 
351  while (!fsm_.serial_tx_buffer().empty())
352  {
353  double now = goby_time<double>();
354  if (last_triple_plus_time_ + TRIPLE_PLUS_WAIT > now)
355  return;
356 
357  const std::string& line = fsm_.serial_tx_buffer().front();
358 
359  glog.is(DEBUG1) &&
360  glog << group(glog_out_group())
361  << (boost::algorithm::all(line, boost::is_print() || boost::is_any_of("\r\n"))
362  ? boost::trim_copy(line)
363  : goby::util::hex_encode(line))
364  << std::endl;
365 
366  modem_write(line);
367 
368  // this is safe as all other messages we use are \r terminated
369  if (line == "+++")
370  last_triple_plus_time_ = now;
371 
372  fsm_.serial_tx_buffer().pop_front();
373  }
374 }
375 
376 void goby::acomms::IridiumDriver::display_state_cfg(std::ostream* os)
377 {
378  char orthogonalRegion = 'a';
379 
380  for (fsm::IridiumDriverFSM::state_iterator pLeafState = fsm_.state_begin();
381  pLeafState != fsm_.state_end(); ++pLeafState)
382  {
383  *os << "Orthogonal region " << orthogonalRegion << ": ";
384 
385  const fsm::IridiumDriverFSM::state_base_type* pState = &*pLeafState;
386 
387  while (pState != 0)
388  {
389  if (pState != &*pLeafState)
390  {
391  *os << " -> ";
392  }
393 
394  std::string name = typeid(*pState).name();
395  std::string prefix = "N4goby6acomms3fsm";
396  std::string suffix = "E";
397 
398  if (name.find(prefix) != std::string::npos && name.find(suffix) != std::string::npos)
399  name =
400  name.substr(prefix.size() + 1, name.size() - prefix.size() - suffix.size() - 1);
401 
402  *os << name;
403 
404  pState = pState->outer_state_ptr();
405  }
406 
407  *os << "\n";
408  ++orthogonalRegion;
409  }
410 
411  *os << std::endl;
412 }
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
void modem_write(const std::string &out)
write a line to the serial port.
Definition: driver_base.cpp:48
virtual void do_work()=0
Allows the modem driver to do its work.
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
void shutdown()
Shuts down the modem driver.
provides a basic TCP client for line by line text based communications to a remote TCP server ...
Definition: tcp_client.h:34
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
void startup(const protobuf::DriverConfig &cfg)
Starts the modem driver. Must be called before poll().
void do_work()
Allows the modem driver to do its work.
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.
bool modem_read(std::string *in)
read a line from the serial port, including end-of-line character(s)
Definition: driver_base.cpp:57
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
void handle_initiate_transmission(const protobuf::ModemTransmission &m)
Virtual initiate_transmission method. Typically connected to MACManager::signal_initiate_transmission...
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