Goby v2
waveglider_sv2_frontseat_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 "goby/common/logger.h"
24 #include "goby/util/as.h"
25 
26 #include "waveglider_sv2_frontseat_driver.h"
27 #include <stdint.h>
28 
29 namespace gpb = goby::moos::protobuf;
30 using goby::glog;
32 using namespace goby::common::logger;
33 using namespace goby::common::tcolor;
34 
35 const int allowed_skew = 30;
36 
37 // allows iFrontSeat to load our library
38 extern "C"
39 {
40  FrontSeatInterfaceBase* frontseat_driver_load(iFrontSeatConfig* cfg)
41  {
42  return new WavegliderSV2FrontSeat(*cfg);
43  }
44 }
45 
46 uint16_t crc_compute_incrementally(uint16_t crc, char a);
47 uint16_t crc_compute(const std::string& buffer, unsigned offset, unsigned count, uint16_t seed);
48 
49 WavegliderSV2FrontSeat::WavegliderSV2FrontSeat(const iFrontSeatConfig& cfg)
50  : FrontSeatInterfaceBase(cfg), waveglider_sv2_config_(cfg.GetExtension(waveglider_sv2_config)),
51  frontseat_providing_data_(false), last_frontseat_data_time_(0),
52  frontseat_state_(gpb::FRONTSEAT_NOT_CONNECTED),
53  serial_(goby::moos::SV2SerialConnection::create(io_, waveglider_sv2_config_.pm_serial_port(),
54  waveglider_sv2_config_.pm_serial_baud())),
55  queued_messages_(1), dccl_("SV2.id", getenv("IFRONTSEAT_DRIVER_LIBRARY"))
56 {
57  serial_->message_signal.connect(
58  boost::bind(&WavegliderSV2FrontSeat::handle_sv2_message, this, _1));
59  serial_->start();
60 
61  glog.is(VERBOSE) && glog << "Connected to WavegliderSV2 serial port." << std::endl;
62  frontseat_state_ = gpb::FRONTSEAT_ACCEPTING_COMMANDS;
63 }
64 
65 void WavegliderSV2FrontSeat::loop()
66 {
67  try
68  {
69  io_.poll();
70  }
71  catch (std::exception& e)
72  {
73  glog.is(WARN) && glog << "Failed to poll serial or process received data: " << e.what()
74  << std::endl;
75  }
76 
77  // if we haven't gotten data for a while, set this boolean so that the
78  // FrontSeatInterfaceBase class knows
79  if (goby_time<double>() > last_frontseat_data_time_ + allowed_skew)
80  frontseat_providing_data_ = false;
81 }
82 
83 void WavegliderSV2FrontSeat::send_command_to_frontseat(const gpb::CommandRequest& command)
84 {
85  if (command.has_desired_course())
86  {
87  dccl::uint32 board_addr = waveglider_sv2_config_.board_id() << dccl::BITS_IN_BYTE |
88  waveglider_sv2_config_.task_id();
89 
90  boost::shared_ptr<goby::moos::protobuf::SV2CommandFollowFixedHeading> hdg_cmd(
92  hdg_cmd->mutable_header()->set_start_of_frame(0x7e);
93  hdg_cmd->mutable_header()->set_dest(goby::moos::protobuf::SV2Header::BOARD_ID_CC
94  << dccl::BITS_IN_BYTE |
95  goby::moos::protobuf::SV2Header::CCTASK_ID_COMMAND);
96  hdg_cmd->mutable_header()->set_src(board_addr);
97  hdg_cmd->mutable_header()->set_transaction_id(command.request_id());
98  hdg_cmd->mutable_header()->set_message_type(goby::moos::protobuf::MESSAGE_TYPE_ACK);
99 
100  hdg_cmd->set_original_msg_type(goby::moos::protobuf::MESSAGE_TYPE_REQUEST_QUEUED_MESSAGE);
101  hdg_cmd->set_command_format(0x0001);
102 
104  hdg_cmd->mutable_body();
105 
106  body->set_level2id(0x0A);
107  body->set_wgmsid(0xFFFFFFFF);
108  body->set_data_size(18);
109  body->set_structure_id(0x10);
110  body->set_command_value(0x0008);
111  body->set_reserved(0);
112  body->set_heading_degrees(command.desired_course().heading());
113  body->set_latitude(0);
114  body->set_longitude(0);
115  body->set_crc16(0); // will compute later
116 
117  std::string bytes;
118  dccl_.encode(&bytes, *body);
119  // remove the prefix created by DCCL and calculate CRC16
120  bytes = bytes.substr(2);
121  enum
122  {
123  CRC_SIZE = 2
124  };
125  uint16_t calculated = crc_compute(bytes, 0, bytes.size() - CRC_SIZE, 0);
126  body->set_crc16(calculated);
127 
128  hdg_cmd->mutable_footer()->set_crc16(0); // will compute later
129  hdg_cmd->mutable_header()->set_message_size(
130  dccl_.size(*hdg_cmd) - 3); // doesn't include start of frame byte or extra 2-byte prefix
131 
132  glog.is(DEBUG1) && glog << "Queuing fixed heading cmd for heading of: "
133  << command.desired_course().heading() << std::endl;
134  glog.is(DEBUG2) && glog << hdg_cmd->DebugString() << std::endl;
135  queued_messages_.push_back(hdg_cmd);
136  }
137  else
138  {
139  glog.is(VERBOSE) && glog << "Unhandled command: " << command.ShortDebugString()
140  << std::endl;
141  }
142 }
143 
144 void WavegliderSV2FrontSeat::send_data_to_frontseat(const gpb::FrontSeatInterfaceData& data) {}
145 
146 void WavegliderSV2FrontSeat::send_raw_to_frontseat(const gpb::FrontSeatRaw& data) {}
147 
148 bool WavegliderSV2FrontSeat::frontseat_providing_data() const { return frontseat_providing_data_; }
149 
150 goby::moos::protobuf::FrontSeatState WavegliderSV2FrontSeat::frontseat_state() const
151 {
152  return frontseat_state_;
153 }
154 
155 void WavegliderSV2FrontSeat::handle_sv2_message(const std::string& message)
156 {
157  enum
158  {
159  MESSAGE_TYPE_START = 9,
160  MESSAGE_TYPE_SIZE = 2
161  };
162 
163  // add prefix for DCCL id
164  std::string bytes = message.substr(MESSAGE_TYPE_START, MESSAGE_TYPE_SIZE) + message;
165  bool ack_requested = !(bytes[1] & 0x80);
166  glog.is(DEBUG2) && glog << (ack_requested ? "ACK Requested" : "No ACK Requested") << std::endl;
167  bytes[1] &= 0x7F; // remove the ack requested bit;
168 
169  unsigned dccl_id = dccl_.id(bytes);
170  if (dccl_id == dccl_.id<goby::moos::protobuf::SV2RequestEnumerate>())
171  {
173  dccl_.decode(bytes, &enum_msg);
174  glog.is(DEBUG1) && glog << "Received enumeration request." << std::endl;
175  glog.is(DEBUG2) && glog << enum_msg.DebugString() << std::endl;
176  check_crc(message, enum_msg.footer().crc16());
177  handle_enumeration_request(enum_msg);
178  }
179  else if (dccl_id == dccl_.id<goby::moos::protobuf::SV2RequestStatus>())
180  {
182  dccl_.decode(bytes, &request);
183  glog.is(DEBUG1) && glog << "Received status request." << std::endl;
184  glog.is(DEBUG2) && glog << request.DebugString() << std::endl;
185  frontseat_providing_data_ = true;
186  last_frontseat_data_time_ = goby::common::goby_time<double>();
187  handle_request_status(request);
188  }
189  else if (dccl_id == dccl_.id<goby::moos::protobuf::SV2RequestQueuedMessage>())
190  {
192  dccl_.decode(bytes, &request);
193  glog.is(DEBUG1) && glog << "Received queue message request. " << std::endl;
194  glog.is(DEBUG2) && glog << request.DebugString() << std::endl;
195  handle_request_queued_message(request);
196  }
197  else if (dccl_id == dccl_.id<goby::moos::protobuf::SV2ACKNAKQueuedMessage>())
198  {
200  dccl_.decode(bytes, &ack);
201  glog.is(DEBUG1) && glog << "Received queue message ack/nak." << std::endl;
202  glog.is(DEBUG2) && glog << ack.DebugString() << std::endl;
203  // HANDLE ACK QUEUED MESSAGE
204  }
205  else if (dccl_id == dccl_.id<goby::moos::protobuf::SV2GenericNAK>())
206  {
208  dccl_.decode(bytes, &nak);
209  glog.is(DEBUG1) && glog << "Received generic nak." << std::endl;
210  glog.is(DEBUG2) && glog << nak.DebugString() << std::endl;
211  // HANDLE NAK
212  }
213  else if (dccl_id == dccl_.id<goby::moos::protobuf::SV2GenericACK>())
214  {
216  dccl_.decode(bytes, &ack);
217  glog.is(DEBUG1) && glog << "Received generic ack." << std::endl;
218  glog.is(DEBUG2) && glog << ack.DebugString() << std::endl;
219  // HANDLE ACK
220  }
221  else
222  {
223  glog.is(DEBUG1) && glog << "Received unhandled message type: " << std::hex << dccl_id
224  << std::dec << std::endl;
225  }
226 }
227 
228 void WavegliderSV2FrontSeat::check_crc(const std::string& message, uint16_t expected)
229 {
230  enum
231  {
232  MAGIC_SIZE = 1,
233  CRC_SIZE = 2
234  };
235 
236  uint16_t calculated =
237  crc_compute(message, MAGIC_SIZE, message.size() - MAGIC_SIZE - CRC_SIZE, 0);
238  glog.is(DEBUG2) && glog << "Given CRC: " << std::hex << expected << ", computed: " << calculated
239  << std::dec << std::endl;
240 
241  if (calculated != expected)
242  glog.is(WARN) && glog << "Invalid CRC16" << std::endl;
243 }
244 
245 void WavegliderSV2FrontSeat::add_crc(std::string* message)
246 {
247  enum
248  {
249  MAGIC_SIZE = 1,
250  CRC_SIZE = 2
251  };
252 
253  uint16_t calculated =
254  crc_compute(*message, MAGIC_SIZE, message->size() - MAGIC_SIZE - CRC_SIZE, 0);
255  message->at(message->size() - 1) = (calculated >> dccl::BITS_IN_BYTE) & 0xff;
256  message->at(message->size() - 2) = (calculated)&0xff;
257  glog.is(DEBUG2) && glog << "Computed CRC: " << std::hex << calculated << std::dec << std::endl;
258 }
259 
260 void WavegliderSV2FrontSeat::handle_enumeration_request(
262 {
264 
265  dccl::uint32 board_addr =
266  waveglider_sv2_config_.board_id() << dccl::BITS_IN_BYTE | waveglider_sv2_config_.task_id();
267 
268  reply.mutable_header()->set_start_of_frame(0x7e);
269  reply.mutable_header()->set_dest(goby::moos::protobuf::SV2Header::BOARD_ID_CC
270  << dccl::BITS_IN_BYTE |
271  goby::moos::protobuf::SV2Header::CCTASK_ID_MAIN);
272  reply.mutable_header()->set_src(board_addr);
273  reply.mutable_header()->set_transaction_id(request.header().transaction_id());
274  reply.mutable_header()->set_message_type(goby::moos::protobuf::MESSAGE_TYPE_ACK);
275 
276  reply.set_original_msg_type(request.header().message_type());
277  reply.set_number_of_devices_responding(1);
278  reply.set_number_of_devices_in_message(1);
279  reply.set_version(1);
280  reply.set_device_type(0x1001);
281  reply.set_board_addr(board_addr);
282  reply.set_serial_number("000001");
283  reply.set_location(0);
284  reply.set_polling_frequency(1);
285 
286  enum
287  {
288  TELEMETRY = 0x01,
289  POWER = 0x02,
290  EVENT = 0x04,
291  COMMAND_ACK_NAK = 0x08
292  };
293  reply.set_extra_info(COMMAND_ACK_NAK);
294 
295  reply.set_firmware_major(0);
296  reply.set_firmware_minor(0);
297  reply.set_firmware_revision(1);
298 
299  std::string description("iFrontSeat Driver");
300  reply.set_description(description + std::string(20 - description.size(), '\0'));
301  reply.mutable_footer()->set_crc16(0); // will compute later
302 
303  reply.mutable_header()->set_message_size(
304  dccl_.size(reply) - 3); // doesn't include start of frame byte or extra 2-byte prefix
305 
306  glog.is(DEBUG1) && glog << "Sent enumeration reply." << std::endl;
307  glog.is(DEBUG2) && glog << reply.DebugString() << std::endl;
308 
309  encode_and_write(reply);
310 }
311 
312 void WavegliderSV2FrontSeat::handle_request_status(
314 {
316 
317  dccl::uint32 board_addr =
318  waveglider_sv2_config_.board_id() << dccl::BITS_IN_BYTE | waveglider_sv2_config_.task_id();
319 
320  reply.mutable_header()->set_start_of_frame(0x7e);
321  reply.mutable_header()->set_dest(goby::moos::protobuf::SV2Header::BOARD_ID_CC
322  << dccl::BITS_IN_BYTE |
323  goby::moos::protobuf::SV2Header::CCTASK_ID_MAIN);
324  reply.mutable_header()->set_src(board_addr);
325  reply.mutable_header()->set_transaction_id(request.header().transaction_id());
326  reply.mutable_header()->set_message_type(goby::moos::protobuf::MESSAGE_TYPE_ACK);
327 
328  reply.set_original_msg_type(request.header().message_type());
329  reply.set_number_of_devices_responding(1);
330  reply.set_number_of_devices_in_message(1);
331 
332  bool queued_message_waiting = (queued_messages_.size() > 0);
333  if (queued_message_waiting)
334  reply.set_version(
335  0x8001); // if queued messages available set bit 15 (manual says message type field?).
336  else
337  reply.set_version(0x0001);
338 
339  reply.set_board_addr(board_addr);
340 
341  reply.set_alarms(0);
342  reply.set_leak_sensor_1(0);
343  reply.set_leak_sensor_2(0);
344  reply.set_humid_temp(0);
345  reply.set_relative_humidity(0);
346  reply.set_pressure_temp(0);
347  reply.set_pressure(0);
348 
349  reply.mutable_footer()->set_crc16(0); // will compute later
350  reply.mutable_header()->set_message_size(dccl_.size(reply) - 3);
351  glog.is(DEBUG1) && glog << "Sent status reply." << std::endl;
352  glog.is(DEBUG2) && glog << reply.DebugString() << std::endl;
353 
354  encode_and_write(reply);
355 }
356 
357 void WavegliderSV2FrontSeat::handle_request_queued_message(
359 {
360  if (queued_messages_.size())
361  {
362  boost::shared_ptr<goby::moos::protobuf::SV2CommandFollowFixedHeading> reply =
363  queued_messages_.front();
364  reply->mutable_header()->set_transaction_id(request.header().transaction_id());
365  glog.is(DEBUG1) && glog << "Sent queued Message reply." << std::endl;
366  glog.is(DEBUG2) && glog << reply->DebugString() << std::endl;
367  encode_and_write(*reply);
368  queued_messages_.pop_front();
369  }
370  else
371  {
372  glog.is(WARN) && glog << "No queued message to provide!" << std::endl;
373  }
374 }
375 
376 void WavegliderSV2FrontSeat::encode_and_write(const google::protobuf::Message& message)
377 {
378  try
379  {
380  std::string bytes;
381  dccl_.encode(&bytes, message);
382  // remove the prefix created by DCCL and calculate CRC16
383  bytes = bytes.substr(2);
384  add_crc(&bytes);
385  glog.is(DEBUG2) && glog << "Sending encoded bytes (w/out escapes): "
386  << dccl::hex_encode(bytes) << std::endl;
387  serial_->write_start(bytes);
388  }
389  catch (std::exception& e)
390  {
391  glog.is(WARN) && glog << "Failed to encode and write message: " << e.what() << std::endl;
392  }
393 }
394 
395 uint16_t crc_compute(const std::string& buffer, unsigned offset, unsigned count, uint16_t seed)
396 {
397  uint16_t crc = seed;
398  for (unsigned idx = offset; count-- > 0; ++idx)
399  crc = crc_compute_incrementally(crc, buffer[idx]);
400  return crc;
401 }
402 
403 uint16_t crc_compute_incrementally(uint16_t crc, char a)
404 {
405  int i;
406  crc ^= (a & 0xff);
407  for (i = 0; i < 8; i++)
408  {
409  if ((crc & 1) == 1)
410  {
411  crc = (uint16_t)((crc >> 1) ^ 0xA001);
412  }
413  else
414  {
415  crc = (uint16_t)(crc >> 1);
416  }
417  }
418  return crc;
419 }
Contains functions for adding color to Terminal window streams.
Definition: term_color.h:54
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
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.