Goby v2
waveglider_sv2_serial_client.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 #include <boost/asio.hpp>
24 #include <dccl/binary.h>
25 
26 namespace goby
27 {
28 namespace moos
29 {
30 class SV2SerialConnection : public boost::enable_shared_from_this<SV2SerialConnection>
31 {
32  public:
33  static boost::shared_ptr<SV2SerialConnection> create(boost::asio::io_service& io_service,
34  std::string name, int baud = 115200)
35  {
36  return boost::shared_ptr<SV2SerialConnection>(
37  new SV2SerialConnection(io_service, name, baud));
38  }
39 
40  boost::asio::serial_port& socket() { return socket_; }
41 
42  void start() { read_start(); }
43 
44  void close() { socket_.close(); }
45 
46  void read_start()
47  {
48  std::fill(message_.begin(), message_.end(), 0);
49  last_escape_ = 0;
50 
51  boost::asio::async_read(
52  socket_, buffer_, boost::asio::transfer_exactly(1),
53  boost::bind(&SV2SerialConnection::handle_read, this, _1, _2, PART_MAGIC));
54  }
55 
56  void write_start(std::string data)
57  {
58  add_escapes(&data);
59  boost::asio::async_write(socket_, boost::asio::buffer(data),
60  boost::bind(&SV2SerialConnection::handle_write, this, _1, _2));
61  }
62 
64 
65  boost::signals2::signal<void(const std::string& message)> message_signal;
66 
67  private:
68  enum MessagePart
69  {
70  PART_MAGIC,
71  PART_HEADER,
72  PART_COMPLETE
73  };
74 
75  SV2SerialConnection(boost::asio::io_service& io_service, std::string name, int baud)
76  : socket_(io_service),
77  message_(SV2_MAX_SIZE * 2) // leave room for escape chars (in theory, every byte)
78  {
79  using goby::glog;
80  using goby::common::logger::DIE;
81  try
82  {
83  socket_.open(name);
84  }
85  catch (std::exception& e)
86  {
87  glog.is(DIE) && glog << "Failed to open serial port: " << name << std::endl;
88  }
89 
90  socket_.set_option(boost::asio::serial_port_base::baud_rate(baud));
91 
92  // no flow control
93  socket_.set_option(boost::asio::serial_port_base::flow_control(
94  boost::asio::serial_port_base::flow_control::none));
95 
96  // 8N1
97  socket_.set_option(boost::asio::serial_port_base::character_size(8));
98  socket_.set_option(
99  boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
100  socket_.set_option(boost::asio::serial_port_base::stop_bits(
101  boost::asio::serial_port_base::stop_bits::one));
102  }
103 
104  void handle_write(const boost::system::error_code& error, size_t bytes_transferred)
105  {
106  if (error)
107  {
108  using goby::glog;
109  using goby::common::logger::WARN;
110  glog.is(WARN) && glog << "Error writing to serial connection: " << error << std::endl;
111  }
112  }
113 
114  void handle_read(const boost::system::error_code& error, size_t bytes_transferred,
115  MessagePart part)
116  {
117  using goby::glog;
118  using goby::common::logger::DEBUG1;
119  using goby::common::logger::DEBUG2;
120  using goby::common::logger::WARN;
121 
122  if (!error)
123  {
124  std::istream istrm(&buffer_);
125 
126  switch (part)
127  {
128  case PART_MAGIC:
129  {
130  if ((istrm.peek() & 0xFF) != SV2_MAGIC_BYTE)
131  {
132  glog.is(DEBUG2) && glog << std::hex << istrm.peek() << std::dec
133  << ": Not magic byte, continuing to read."
134  << std::endl;
135  read_start();
136  buffer_.consume(bytes_transferred);
137  }
138  else
139  {
140  glog.is(DEBUG2) && glog << "Received magic byte. Starting read of "
141  << SV2_HEADER_SIZE << " bytes" << std::endl;
142  message_insert_ = &message_[0];
143  istrm.read(message_insert_, bytes_transferred);
144  message_insert_ += bytes_transferred;
145 
146  // read the header
147  boost::asio::async_read(socket_, buffer_,
148  boost::asio::transfer_exactly(SV2_HEADER_SIZE),
149  boost::bind(&SV2SerialConnection::handle_read, this,
150  _1, _2, PART_HEADER));
151  }
152  break;
153  }
154 
155  case PART_HEADER:
156  {
157  istrm.read(message_insert_, bytes_transferred);
158  message_insert_ += bytes_transferred;
159 
160  if (!check_escapes(part))
161  return;
162  remove_escapes();
163 
164  message_size_ = message_[1] & 0xFF;
165  message_size_ |= (message_[2] & 0xFF) << 8;
166 
167  glog.is(DEBUG2) &&
168  glog << "Received header: "
169  << dccl::hex_encode(std::string(
170  message_.begin(), message_.begin() + SV2_HEADER_SIZE + 1))
171  << ", size of " << message_size_ << std::endl;
172 
173  // read the rest of the message
174  boost::asio::async_read(
175  socket_, buffer_,
176  boost::asio::transfer_exactly(message_size_ - SV2_HEADER_SIZE),
177  boost::bind(&SV2SerialConnection::handle_read, this, _1, _2,
178  PART_COMPLETE));
179  break;
180  }
181 
182  case PART_COMPLETE:
183  {
184  istrm.read(message_insert_, bytes_transferred);
185  message_insert_ += bytes_transferred;
186 
187  if (!check_escapes(part))
188  return;
189  remove_escapes();
190 
191  glog.is(DEBUG1) &&
192  glog << "Read message: "
193  << dccl::hex_encode(std::string(message_.begin(),
194  message_.begin() + message_size_ + 1))
195  << std::endl;
196  message_signal(
197  std::string(message_.begin(), message_.begin() + message_size_ + 1));
198  read_start();
199 
200  break;
201  }
202  }
203  }
204  else
205  {
206  if (error == boost::asio::error::eof)
207  {
208  glog.is(DEBUG1) && glog << "Connection reached EOF" << std::endl;
209  }
210  else if (error == boost::asio::error::operation_aborted)
211  {
212  glog.is(DEBUG1) && glog << "Read operation aborted (socket closed)" << std::endl;
213  }
214  else
215  {
216  glog.is(WARN) && glog << "Error reading from serial connection: " << error
217  << std::endl;
218  }
219  }
220  }
221 
222  bool check_escapes(MessagePart part)
223  {
224  using goby::glog;
225  using goby::common::logger::DEBUG1;
226  using goby::common::logger::WARN;
227  int escape = std::count(message_.begin(), message_.end(), SV2_ESCAPE);
228  if (escape != last_escape_)
229  {
230  boost::asio::async_read(
231  socket_, buffer_, boost::asio::transfer_exactly(escape - last_escape_),
232  boost::bind(&SV2SerialConnection::handle_read, this, _1, _2, part));
233  glog.is(DEBUG1) && glog << "Reading " << escape - last_escape_
234  << " more bytes because of escape characters." << std::endl;
235 
236  last_escape_ = escape;
237  return false;
238  }
239  else
240  {
241  return true;
242  }
243  }
244 
245  void remove_escapes()
246  {
247  if (last_escape_ == 0)
248  return;
249 
250  // remove escape characters - order matters (or else 0x7d5d5e would become 0x7e)
251  int before_size = message_.size();
252  boost::replace_all(message_, std::string(1, SV2_ESCAPE) + std::string(1, 0x5E),
253  std::string(1, SV2_MAGIC_BYTE));
254  boost::replace_all(message_, std::string(1, SV2_ESCAPE) + std::string(1, 0x5D),
255  std::string(1, SV2_ESCAPE));
256  int after_size = message_.size();
257 
258  message_insert_ -= (before_size - after_size);
259  message_.resize(before_size);
260  last_escape_ = std::count(message_.begin(), message_.end(), SV2_ESCAPE);
261  }
262 
263  void add_escapes(std::string* message)
264  {
265  // add escape characters - order matters (or else 0x7e becomes 0x7d5d5e)
266  boost::replace_all(*message, std::string(1, SV2_ESCAPE),
267  std::string(1, SV2_ESCAPE) + std::string(1, 0x5D));
268  boost::replace_all(*message, std::string(1, SV2_MAGIC_BYTE),
269  std::string(1, SV2_ESCAPE) + std::string(1, 0x5E));
270  // restore the actual start of frame
271  boost::replace_first(*message, std::string(1, SV2_ESCAPE) + std::string(1, 0x5E),
272  std::string(1, SV2_MAGIC_BYTE));
273  }
274 
275  private:
276  boost::asio::serial_port socket_;
277  boost::asio::streambuf buffer_;
278  std::vector<char> message_;
279  char* message_insert_;
280  int message_size_;
281  int last_escape_;
282 
283  enum
284  {
285  SV2_MAGIC_BYTE = 0x7e,
286  SV2_ESCAPE = 0x7d
287  };
288  enum
289  {
290  SV2_HEADER_SIZE = 10
291  }; // not including magic byte
292  enum
293  {
294  SV2_MAX_SIZE = 557
295  };
296 };
297 } // namespace moos
298 } // namespace goby
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.