Goby v2
goby_moos_app.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 GOBYMOOSAPP20100726H
24 #define GOBYMOOSAPP20100726H
25 
26 #include "goby/moos/moos_header.h"
27 #include "goby/moos/moos_translator.h"
28 #include "goby/util/as.h"
29 
30 #include <boost/algorithm/string.hpp>
31 #include <boost/bind.hpp>
32 #include <boost/date_time/posix_time/posix_time.hpp>
33 #include <boost/filesystem.hpp>
34 #include <boost/function.hpp>
35 #include <boost/signals2.hpp>
36 #include <map>
37 
38 #include "dynamic_moos_vars.h"
39 #include "goby/common/configuration_reader.h"
40 #include "goby/common/exception.h"
41 #include "goby/common/logger.h"
42 #include "goby/moos/protobuf/goby_moos_app.pb.h"
43 #include "goby/version.h"
44 #include "moos_protobuf_helpers.h"
45 
46 namespace goby
47 {
48 namespace moos
49 {
50 template <typename App> int run(int argc, char* argv[]);
51 
52 template <typename ProtobufMessage>
53 inline void protobuf_inbox(const CMOOSMsg& msg,
54  boost::function<void(const ProtobufMessage& msg)> handler)
55 {
56  ProtobufMessage pb_msg;
57  parse_for_moos(msg.GetString(), &pb_msg);
58  handler(pb_msg);
59 }
60 } // namespace moos
61 } // namespace goby
62 
63 // shell implementation so we can call superclass methods for when
64 // using AppCastingMOOSApp
65 class MOOSAppShell : public CMOOSApp
66 {
67  protected:
68  bool Iterate() { return true; }
69  bool OnStartUp() { return true; }
70  bool OnConnectToServer() { return true; }
71  bool OnNewMail(MOOSMSG_LIST& NewMail) { return true; }
72  void RegisterVariables() {}
73  void PostReport() {}
74 };
75 
76 template <class MOOSAppType = MOOSAppShell> class GobyMOOSAppSelector : public MOOSAppType
77 {
78  public:
79  static goby::uint64 microsec_moos_time()
80  {
81  return static_cast<goby::uint64>(MOOSTime() * 1.0e6);
82  }
83 
84  protected:
85  typedef boost::function<void(const CMOOSMsg& msg)> InboxFunc;
86 
87  template <typename ProtobufConfig>
88  explicit GobyMOOSAppSelector(ProtobufConfig* cfg)
89  : start_time_(MOOSTime()), configuration_read_(false), cout_cleared_(false),
90  connected_(false), started_up_(false), ignore_stale_(true),
91  dynamic_moos_vars_enabled_(true)
92  {
93  using goby::glog;
94 
95  read_configuration(cfg);
96 
97  // keep a copy for ourselves
98  common_cfg_ = cfg->common();
99  configuration_read_ = true;
100 
101  process_configuration();
102 
103  glog.is(goby::common::logger::DEBUG2) && glog << cfg->DebugString() << std::endl;
104  }
105 
106  virtual ~GobyMOOSAppSelector() {}
107 
108  template <typename ProtobufMessage>
109  void publish_pb(const std::string& key, const ProtobufMessage& msg)
110  {
111  std::string serialized;
112  bool is_binary = serialize_for_moos(&serialized, msg);
113  CMOOSMsg moos_msg = goby::moos::MOOSTranslator::make_moos_msg(
114  key, serialized, is_binary, goby::moos::moos_technique,
115  msg.GetDescriptor()->full_name());
116  publish(moos_msg);
117  }
118 
119  void publish(CMOOSMsg& msg)
120  {
121  if (connected_ && started_up_)
122  MOOSAppType::m_Comms.Post(msg);
123  else
124  msg_buffer_.push_back(msg);
125  }
126 
127  void publish(const std::string& key, const std::string& value)
128  {
129  CMOOSMsg msg(MOOS_NOTIFY, key, value);
130  publish(msg);
131  }
132 
133  void publish(const std::string& key, double value)
134  {
135  CMOOSMsg msg(MOOS_NOTIFY, key, value);
136  publish(msg);
137  }
138 
139  goby::moos::DynamicMOOSVars& dynamic_vars() { return dynamic_vars_; }
140  double start_time() const { return start_time_; }
141 
142  void subscribe(const std::string& var, InboxFunc handler = InboxFunc(), int blackout = 0);
143 
144  template <typename V, typename A1>
145  void subscribe(const std::string& var, void (V::*mem_func)(A1), V* obj, int blackout = 0)
146  {
147  subscribe(var, boost::bind(mem_func, obj, _1), blackout);
148  }
149 
150  // wildcard
151  void subscribe(const std::string& var_pattern, const std::string& app_pattern,
152  InboxFunc handler = InboxFunc(), int blackout = 0);
153 
154  template <typename V, typename A1>
155  void subscribe(const std::string& var_pattern, const std::string& app_pattern,
156  void (V::*mem_func)(A1), V* obj, int blackout = 0)
157  {
158  subscribe(var_pattern, app_pattern, boost::bind(mem_func, obj, _1), blackout);
159  }
160 
161  template <typename V, typename ProtobufMessage>
162  void subscribe_pb(const std::string& var, void (V::*mem_func)(const ProtobufMessage&), V* obj,
163  int blackout = 0)
164  {
165  subscribe_pb<ProtobufMessage>(var, boost::bind(mem_func, obj, _1), blackout);
166  }
167 
168  template <typename ProtobufMessage>
169  void subscribe_pb(const std::string& var,
170  boost::function<void(const ProtobufMessage& msg)> handler, int blackout = 0)
171  {
172  subscribe(var, boost::bind(&goby::moos::protobuf_inbox<ProtobufMessage>, _1, handler),
173  blackout);
174  }
175 
176  void register_timer(int period_seconds, boost::function<void()> handler)
177  {
178  int now = goby::common::goby_time<double>() / period_seconds;
179  now *= period_seconds;
180 
181  SynchronousLoop new_loop;
182  new_loop.unix_next = now + period_seconds;
183  new_loop.period_seconds = period_seconds;
184  new_loop.handler = handler;
185  synchronous_loops_.push_back(new_loop);
186  }
187 
188  template <typename V> void register_timer(int period_seconds, void (V::*mem_func)(), V* obj)
189  {
190  register_timer(period_seconds, boost::bind(mem_func, obj));
191  }
192 
193  template <typename App> friend int ::goby::moos::run(int argc, char* argv[]);
194 
195  virtual void loop() = 0;
196 
197  bool ignore_stale() { return ignore_stale_; }
198  void set_ignore_stale(bool b) { ignore_stale_ = b; }
199 
200  bool dynamic_moos_vars_enabled() { return dynamic_moos_vars_enabled_; }
201  void set_dynamic_moos_vars_enabled(bool b) { dynamic_moos_vars_enabled_ = b; }
202 
203  std::pair<std::string, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique>
204  parse_type_technique(const std::string& type_and_technique)
205  {
206  std::string protobuf_type;
207  goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique technique;
208  if (!type_and_technique.empty())
209  {
210  std::string::size_type colon_pos = type_and_technique.find(':');
211 
212  if (colon_pos != std::string::npos)
213  {
214  protobuf_type = type_and_technique.substr(0, colon_pos);
215  std::string str_technique = type_and_technique.substr(colon_pos + 1);
216 
217  if (!goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique_Parse(
218  str_technique, &technique))
219  throw(std::runtime_error("Invalid technique string"));
220  }
221  else
222  {
223  throw std::runtime_error("Missing colon (:)");
224  }
225  return std::make_pair(protobuf_type, technique);
226  }
227  else
228  {
229  throw std::runtime_error("Empty technique string");
230  }
231  }
232 
233  private:
234  // from CMOOSApp
235  bool Iterate();
236  bool OnStartUp();
237  bool OnConnectToServer();
238  bool OnDisconnectFromServer();
239  bool OnNewMail(MOOSMSG_LIST& NewMail);
240  void try_subscribing();
241  void do_subscriptions();
242 
243  int fetch_moos_globals(google::protobuf::Message* msg, CMOOSFileReader& moos_file_reader);
244 
245  void read_configuration(google::protobuf::Message* cfg);
246  void process_configuration();
247 
248  private:
249  // when we started (seconds since UNIX)
250  double start_time_;
251 
252  // have we read the configuration file fully?
253  bool configuration_read_;
254  bool cout_cleared_;
255 
256  std::ofstream fout_;
257 
258  // allows direct reading of newest publish to a given MOOS variable
259  goby::moos::DynamicMOOSVars dynamic_vars_;
260 
261  std::map<std::string, boost::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)> > >
262  mail_handlers_;
263 
264  std::map<std::pair<std::string, std::string>,
265  boost::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)> > >
266  wildcard_mail_handlers_;
267 
268  // CMOOSApp::OnConnectToServer()
269  bool connected_;
270  // CMOOSApp::OnStartUp()
271  bool started_up_;
272 
273  std::deque<CMOOSMsg> msg_buffer_;
274 
275  // MOOS Variable name, blackout time
276  std::deque<std::pair<std::string, int> > pending_subscriptions_;
277  std::deque<std::pair<std::string, int> > existing_subscriptions_;
278 
279  // MOOS Variable pattern, MOOS App pattern, blackout time
280  std::deque<std::pair<std::pair<std::string, std::string>, int> >
281  wildcard_pending_subscriptions_;
282  std::deque<std::pair<std::pair<std::string, std::string>, int> >
283  wildcard_existing_subscriptions_;
284 
285  struct SynchronousLoop
286  {
287  double unix_next;
288  int period_seconds;
289  boost::function<void()> handler;
290  };
291 
292  std::vector<SynchronousLoop> synchronous_loops_;
293 
294  GobyMOOSAppConfig common_cfg_;
295 
296  bool ignore_stale_;
297 
298  bool dynamic_moos_vars_enabled_;
299 
300  static int argc_;
301  static char** argv_;
302  static std::string mission_file_;
303  static std::string application_name_;
304 };
305 
307 {
308  public:
309  template <typename ProtobufConfig>
310  explicit GobyMOOSApp(ProtobufConfig* cfg) : GobyMOOSAppSelector<>(cfg)
311  {
312  }
313 };
314 
315 template <class MOOSAppType> std::string GobyMOOSAppSelector<MOOSAppType>::mission_file_;
316 
317 template <class MOOSAppType> std::string GobyMOOSAppSelector<MOOSAppType>::application_name_;
318 
319 template <class MOOSAppType> int GobyMOOSAppSelector<MOOSAppType>::argc_ = 0;
320 template <class MOOSAppType> char** GobyMOOSAppSelector<MOOSAppType>::argv_ = 0;
321 
322 template <class MOOSAppType> bool GobyMOOSAppSelector<MOOSAppType>::Iterate()
323 {
324  MOOSAppType::Iterate();
325 
326  if (!configuration_read_)
327  return true;
328 
329  // clear out MOOSApp cout for ncurses "scope" mode
330  // MOOS has stopped talking by first Iterate()
331  if (!cout_cleared_)
332  {
333  goby::glog.refresh();
334  cout_cleared_ = true;
335  }
336 
337  while (!msg_buffer_.empty() && (connected_ && started_up_))
338  {
339  goby::glog.is(goby::common::logger::DEBUG3) &&
340  goby::glog << "writing from buffer: " << msg_buffer_.front().GetKey() << ": "
341  << msg_buffer_.front().GetAsString() << std::endl;
342 
343  MOOSAppType::m_Comms.Post(msg_buffer_.front());
344  msg_buffer_.pop_front();
345  }
346 
347  loop();
348 
349  if (synchronous_loops_.size())
350  {
351  double now = goby::common::goby_time<double>();
352  for (typename std::vector<SynchronousLoop>::iterator it = synchronous_loops_.begin(),
353  end = synchronous_loops_.end();
354  it != end; ++it)
355  {
356  SynchronousLoop& loop = *it;
357  if (loop.unix_next <= now)
358  {
359  loop.handler();
360  loop.unix_next += loop.period_seconds;
361 
362  // fix jumps forward in time
363  if (loop.unix_next < now)
364  loop.unix_next = now + loop.period_seconds;
365  }
366 
367  // fix jumps backwards in time
368  if (loop.unix_next > (now + 2 * loop.period_seconds))
369  loop.unix_next = now + loop.period_seconds;
370  }
371  }
372 
373  return true;
374 }
375 
376 template <class MOOSAppType> bool GobyMOOSAppSelector<MOOSAppType>::OnNewMail(MOOSMSG_LIST& NewMail)
377 {
378  // for AppCasting (otherwise no-op)
379  MOOSAppType::OnNewMail(NewMail);
380 
381  for (MOOSMSG_LIST::const_iterator it = NewMail.begin(), end = NewMail.end(); it != end; ++it)
382  {
383  const CMOOSMsg& msg = *it;
384  goby::glog.is(goby::common::logger::DEBUG3) &&
385  goby::glog << "Received mail: " << msg.GetKey() << ", time: " << std::setprecision(15)
386  << msg.GetTime() << std::endl;
387 
388  // update dynamic moos variables - do this inside the loop so the newest is
389  // also the one referenced in the call to inbox()
390  if (dynamic_moos_vars_enabled_)
391  dynamic_vars().update_moos_vars(msg);
392 
393  if (msg.GetTime() < start_time_ && ignore_stale_)
394  {
395  goby::glog.is(goby::common::logger::WARN) &&
396  goby::glog << "ignoring normal mail from " << msg.GetKey()
397  << " from before we started (dynamics still updated)" << std::endl;
398  }
399  else if (mail_handlers_.count(msg.GetKey()))
400  (*mail_handlers_[msg.GetKey()])(msg);
401 
402  for (std::map<
403  std::pair<std::string, std::string>,
404  boost::shared_ptr<boost::signals2::signal<void(const CMOOSMsg&msg)> > >::iterator
405  it = wildcard_mail_handlers_.begin(),
406  end = wildcard_mail_handlers_.end();
407  it != end; ++it)
408  {
409  if (MOOSWildCmp(it->first.first, msg.GetKey()) &&
410  MOOSWildCmp(it->first.second, msg.GetSource()))
411  (*(it->second))(msg);
412  }
413  }
414 
415  return true;
416 }
417 
418 template <class MOOSAppType> bool GobyMOOSAppSelector<MOOSAppType>::OnDisconnectFromServer()
419 {
420  std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", disconnected from server."
421  << std::endl;
422  connected_ = false;
423  pending_subscriptions_.insert(pending_subscriptions_.end(), existing_subscriptions_.begin(),
424  existing_subscriptions_.end());
425  existing_subscriptions_.clear();
426  wildcard_pending_subscriptions_.insert(wildcard_pending_subscriptions_.end(),
427  wildcard_existing_subscriptions_.begin(),
428  wildcard_existing_subscriptions_.end());
429  wildcard_existing_subscriptions_.clear();
430  return true;
431 }
432 
433 template <class MOOSAppType> bool GobyMOOSAppSelector<MOOSAppType>::OnConnectToServer()
434 {
435  std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", connected to server." << std::endl;
436  connected_ = true;
437  try_subscribing();
438 
439  for (google::protobuf::RepeatedPtrField<GobyMOOSAppConfig::Initializer>::const_iterator
440  it = common_cfg_.initializer().begin(),
441  end = common_cfg_.initializer().end();
442  it != end; ++it)
443  {
444  const GobyMOOSAppConfig::Initializer& ini = *it;
445  if (ini.has_global_cfg_var())
446  {
447  std::string result;
448  if (MOOSAppType::m_MissionReader.GetValue(ini.global_cfg_var(), result))
449  {
450  if (ini.type() == GobyMOOSAppConfig::Initializer::INI_DOUBLE)
451  publish(ini.moos_var(), goby::util::as<double>(result));
452  else if (ini.type() == GobyMOOSAppConfig::Initializer::INI_STRING)
453  publish(ini.moos_var(), ini.trim() ? boost::trim_copy(result) : result);
454  }
455  }
456  else
457  {
458  if (ini.type() == GobyMOOSAppConfig::Initializer::INI_DOUBLE)
459  publish(ini.moos_var(), ini.dval());
460  else if (ini.type() == GobyMOOSAppConfig::Initializer::INI_STRING)
461  publish(ini.moos_var(), ini.trim() ? boost::trim_copy(ini.sval()) : ini.sval());
462  }
463  }
464 
465  return true;
466 }
467 
468 template <class MOOSAppType> bool GobyMOOSAppSelector<MOOSAppType>::OnStartUp()
469 {
470  MOOSAppType::OnStartUp();
471 
472  std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", starting ..." << std::endl;
473  CMOOSApp::SetCommsFreq(common_cfg_.comm_tick());
474  CMOOSApp::SetAppFreq(common_cfg_.app_tick());
475  started_up_ = true;
476  try_subscribing();
477  return true;
478 }
479 
480 template <class MOOSAppType>
481 void GobyMOOSAppSelector<MOOSAppType>::subscribe(const std::string& var, InboxFunc handler,
482  int blackout /* = 0 */)
483 {
484  goby::glog.is(goby::common::logger::VERBOSE) &&
485  goby::glog << "subscribing for MOOS variable: " << var << " @ " << blackout << std::endl;
486 
487  pending_subscriptions_.push_back(std::make_pair(var, blackout));
488  try_subscribing();
489 
490  if (!mail_handlers_[var])
491  mail_handlers_[var].reset(new boost::signals2::signal<void(const CMOOSMsg& msg)>);
492 
493  if (handler)
494  mail_handlers_[var]->connect(handler);
495 }
496 
497 template <class MOOSAppType>
498 void GobyMOOSAppSelector<MOOSAppType>::subscribe(const std::string& var_pattern,
499  const std::string& app_pattern, InboxFunc handler,
500  int blackout /* = 0 */)
501 {
502  goby::glog.is(goby::common::logger::VERBOSE) &&
503  goby::glog << "wildcard subscribing for MOOS variable pattern: " << var_pattern
504  << ", app pattern: " << app_pattern << " @ " << blackout << std::endl;
505 
506  std::pair<std::string, std::string> key = std::make_pair(var_pattern, app_pattern);
507  wildcard_pending_subscriptions_.push_back(std::make_pair(key, blackout));
508  try_subscribing();
509 
510  if (!wildcard_mail_handlers_.count(key))
511  wildcard_mail_handlers_.insert(std::make_pair(
512  key, boost::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)> >(
513  new boost::signals2::signal<void(const CMOOSMsg& msg)>)));
514 
515  if (handler)
516  wildcard_mail_handlers_[key]->connect(handler);
517 }
518 
519 template <class MOOSAppType> void GobyMOOSAppSelector<MOOSAppType>::try_subscribing()
520 {
521  if (connected_ && started_up_)
522  do_subscriptions();
523 }
524 
525 template <class MOOSAppType> void GobyMOOSAppSelector<MOOSAppType>::do_subscriptions()
526 {
527  MOOSAppType::RegisterVariables();
528 
529  while (!pending_subscriptions_.empty())
530  {
531  // variable name, blackout
532  if (MOOSAppType::m_Comms.Register(pending_subscriptions_.front().first,
533  pending_subscriptions_.front().second))
534  {
535  goby::glog.is(goby::common::logger::VERBOSE) &&
536  goby::glog << "subscribed for: " << pending_subscriptions_.front().first
537  << std::endl;
538  }
539  else
540  {
541  goby::glog.is(goby::common::logger::WARN) &&
542  goby::glog << "failed to subscribe for: " << pending_subscriptions_.front().first
543  << std::endl;
544  }
545  existing_subscriptions_.push_back(pending_subscriptions_.front());
546  pending_subscriptions_.pop_front();
547  }
548 
549  while (!wildcard_pending_subscriptions_.empty())
550  {
551  // variable name, blackout
552  if (MOOSAppType::m_Comms.Register(wildcard_pending_subscriptions_.front().first.first,
553  wildcard_pending_subscriptions_.front().first.second,
554  wildcard_pending_subscriptions_.front().second))
555  {
556  goby::glog.is(goby::common::logger::VERBOSE) &&
557  goby::glog << "subscribed for: "
558  << wildcard_pending_subscriptions_.front().first.first << ":"
559  << wildcard_pending_subscriptions_.front().first.second << std::endl;
560  }
561  else
562  {
563  goby::glog.is(goby::common::logger::WARN) &&
564  goby::glog << "failed to subscribe for: "
565  << wildcard_pending_subscriptions_.front().first.first << ":"
566  << wildcard_pending_subscriptions_.front().first.second << std::endl;
567  }
568 
569  wildcard_existing_subscriptions_.push_back(wildcard_pending_subscriptions_.front());
570  wildcard_pending_subscriptions_.pop_front();
571  }
572 }
573 
574 template <class MOOSAppType>
576  CMOOSFileReader& moos_file_reader)
577 {
578  int globals = 0;
579  const google::protobuf::Descriptor* desc = msg->GetDescriptor();
580  const google::protobuf::Reflection* refl = msg->GetReflection();
581 
582  for (int i = 0, n = desc->field_count(); i < n; ++i)
583  {
584  const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
585  if (field_desc->is_repeated())
586  continue;
587 
588  std::string moos_global = field_desc->options().GetExtension(goby::field).moos_global();
589 
590  switch (field_desc->cpp_type())
591  {
592  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
593  {
594  bool message_was_empty = !refl->HasField(*msg, field_desc);
595  int set_globals =
596  fetch_moos_globals(refl->MutableMessage(msg, field_desc), moos_file_reader);
597  if (set_globals == 0 && message_was_empty)
598  refl->ClearField(msg, field_desc);
599 
600  break;
601  }
602 
603  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
604  {
605  int result;
606  if (moos_file_reader.GetValue(moos_global, result))
607  {
608  refl->SetInt32(msg, field_desc, result);
609  ++globals;
610  }
611 
612  break;
613  }
614 
615  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
616  {
617  int result;
618  if (moos_file_reader.GetValue(moos_global, result))
619  {
620  refl->SetInt64(msg, field_desc, result);
621  ++globals;
622  }
623  break;
624  }
625 
626  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
627  {
628  unsigned result;
629  if (moos_file_reader.GetValue(moos_global, result))
630  {
631  refl->SetUInt32(msg, field_desc, result);
632  ++globals;
633  }
634 
635  break;
636  }
637 
638  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
639  {
640  unsigned result;
641  if (moos_file_reader.GetValue(moos_global, result))
642  {
643  refl->SetUInt64(msg, field_desc, result);
644  ++globals;
645  }
646  break;
647  }
648 
649  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
650  {
651  bool result;
652  if (moos_file_reader.GetValue(moos_global, result))
653  {
654  refl->SetBool(msg, field_desc, result);
655  ++globals;
656  }
657  break;
658  }
659 
660  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
661  {
662  std::string result;
663  if (moos_file_reader.GetValue(moos_global, result))
664  {
665  refl->SetString(msg, field_desc, result);
666  ++globals;
667  }
668 
669  break;
670  }
671 
672  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
673  {
674  float result;
675  if (moos_file_reader.GetValue(moos_global, result))
676  {
677  refl->SetFloat(msg, field_desc, result);
678  ++globals;
679  }
680 
681  break;
682  }
683 
684  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
685  {
686  double result;
687  if (moos_file_reader.GetValue(moos_global, result))
688  {
689  refl->SetDouble(msg, field_desc, result);
690  ++globals;
691  }
692  break;
693  }
694 
695  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
696  {
697  std::string result;
698  if (moos_file_reader.GetValue(moos_global, result))
699  {
700  const google::protobuf::EnumValueDescriptor* enum_desc =
701  refl->GetEnum(*msg, field_desc)->type()->FindValueByName(result);
702  if (!enum_desc)
703  throw(std::runtime_error(std::string("invalid enumeration " + result +
704  " for field " + field_desc->name())));
705 
706  refl->SetEnum(msg, field_desc, enum_desc);
707  ++globals;
708  }
709  break;
710  }
711  }
712  }
713  return globals;
714 }
715 
716 template <class MOOSAppType>
718 {
719  boost::filesystem::path launch_path(argv_[0]);
720 
721 #if BOOST_FILESYSTEM_VERSION == 3
722  application_name_ = launch_path.filename().string();
723 #else
724  application_name_ = launch_path.filename();
725 #endif
726 
727  //
728  // READ CONFIGURATION
729  //
730 
731  boost::program_options::options_description od_all;
732  boost::program_options::variables_map var_map;
733  try
734  {
735  boost::program_options::options_description od_cli_only("Given on command line only");
736  od_cli_only.add_options()("help,h", "writes this help message")(
737  "moos_file,c", boost::program_options::value<std::string>(&mission_file_),
738  "path to .moos file")("moos_name,a",
739  boost::program_options::value<std::string>(&application_name_),
740  "name to register with MOOS")(
741  "example_config,e", "writes an example .moos ProcessConfig block")(
742  "version,V", "writes the current version");
743 
744  boost::program_options::options_description od_both(
745  "Typically given in the .moos file, but may be specified on the command line");
746 
747  goby::common::ConfigReader::get_protobuf_program_options(od_both, cfg->GetDescriptor());
748  od_all.add(od_both);
749  od_all.add(od_cli_only);
750 
751  boost::program_options::positional_options_description p;
752  p.add("moos_file", 1);
753  p.add("moos_name", 2);
754 
755  boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_)
756  .options(od_all)
757  .positional(p)
758  .run(),
759  var_map);
760 
761  boost::program_options::notify(var_map);
762 
763  if (var_map.count("help"))
764  {
766  e.set_error(false);
767  throw(e);
768  }
769  else if (var_map.count("example_config"))
770  {
771  std::cout << "ProcessConfig = " << application_name_ << "\n{";
772  goby::common::ConfigReader::get_example_cfg_file(cfg, &std::cout, " ");
773  std::cout << "}" << std::endl;
774  exit(EXIT_SUCCESS);
775  }
776  else if (var_map.count("version"))
777  {
778  std::cout << goby::version_message() << std::endl;
779  exit(EXIT_SUCCESS);
780  }
781 
782  goby::glog.set_name(application_name_);
783  goby::glog.add_stream(goby::common::protobuf::GLogConfig::VERBOSE, &std::cout);
784 
785  std::string protobuf_text;
786  std::ifstream fin;
787  fin.open(mission_file_.c_str());
788  if (fin.is_open())
789  {
790  std::string line;
791  bool in_process_config = false;
792  while (getline(fin, line))
793  {
794  std::string no_blanks_line = boost::algorithm::erase_all_copy(line, " ");
795  if (boost::algorithm::iequals(no_blanks_line, "PROCESSCONFIG=" + application_name_))
796  {
797  in_process_config = true;
798  }
799  else if (in_process_config &&
800  !boost::algorithm::ifind_first(line, "PROCESSCONFIG").empty())
801  {
802  break;
803  }
804 
805  if (in_process_config)
806  protobuf_text += line + "\n";
807  }
808 
809  if (!in_process_config)
810  {
811  goby::glog.is(goby::common::logger::DIE) &&
812  goby::glog << "no ProcessConfig block for " << application_name_ << std::endl;
813  }
814 
815  // trim off "ProcessConfig = __ {"
816  protobuf_text.erase(0, protobuf_text.find_first_of('{') + 1);
817 
818  // trim off last "}" and anything that follows
819  protobuf_text.erase(protobuf_text.find_last_of('}'));
820 
821  // convert "//" to "#" for comments
822  boost::algorithm::replace_all(protobuf_text, "//", "#");
823 
824  google::protobuf::TextFormat::Parser parser;
825  FlexOStreamErrorCollector error_collector(protobuf_text);
826  parser.RecordErrorsTo(&error_collector);
827  parser.AllowPartialMessage(true);
828  parser.ParseFromString(protobuf_text, cfg);
829  if (error_collector.has_errors() || error_collector.has_warnings())
830  {
831  goby::glog.is(goby::common::logger::DIE) &&
832  goby::glog << "fatal configuration errors (see above)" << std::endl;
833  }
834  }
835  else
836  {
837  goby::glog.is(goby::common::logger::WARN) && goby::glog << "failed to open "
838  << mission_file_ << std::endl;
839  }
840 
841  fin.close();
842 
843  CMOOSFileReader moos_file_reader;
844  moos_file_reader.SetFile(mission_file_);
845  fetch_moos_globals(cfg, moos_file_reader);
846 
847  // add / overwrite any options that are specified in the cfg file with those given on the command line
848  typedef std::pair<std::string, boost::program_options::variable_value> P;
849  BOOST_FOREACH (const P& p, var_map)
850  {
851  // let protobuf deal with the defaults
852  if (!p.second.defaulted())
853  goby::common::ConfigReader::set_protobuf_program_option(var_map, *cfg, p.first,
854  p.second);
855  }
856 
857  // now the proto message must have all required fields
858  if (!cfg->IsInitialized())
859  {
860  std::vector<std::string> errors;
861  cfg->FindInitializationErrors(&errors);
862 
863  std::stringstream err_msg;
864  err_msg << "Configuration is missing required parameters: \n";
865  BOOST_FOREACH (const std::string& s, errors)
866  err_msg << goby::common::esc_red << s << "\n" << goby::common::esc_nocolor;
867 
868  err_msg << "Make sure you specified a proper .moos file";
869  throw(goby::common::ConfigException(err_msg.str()));
870  }
871  }
873  {
874  // output all the available command line options
875  std::cerr << od_all << "\n";
876  if (e.error())
877  std::cerr << "Problem parsing command-line configuration: \n" << e.what() << "\n";
878 
879  throw;
880  }
881 }
882 
883 template <class MOOSAppType> void GobyMOOSAppSelector<MOOSAppType>::process_configuration()
884 {
885  //
886  // PROCESS CONFIGURATION
887  //
888  goby::glog.add_stream(common_cfg_.verbosity(), &std::cout);
889  if (common_cfg_.show_gui())
890  {
891  goby::glog.enable_gui();
892  }
893 
894  if (common_cfg_.log())
895  {
896  if (!common_cfg_.has_log_path())
897  {
898  goby::glog.is(goby::common::logger::WARN) &&
899  goby::glog << "logging all terminal output to default directory ("
900  << common_cfg_.log_path() << ")."
901  << "set log_path for another path " << std::endl;
902  }
903 
904  if (!common_cfg_.log_path().empty())
905  {
906  using namespace boost::posix_time;
907  std::string file_name_base = boost::replace_all_copy(application_name_, "/", "_") +
908  "_" + common_cfg_.community();
909 
910  std::string file_name =
911  file_name_base + "_" + to_iso_string(second_clock::universal_time()) + ".txt";
912 
913  std::string file_symlink = file_name_base + "_latest.txt";
914 
915  goby::glog.is(goby::common::logger::VERBOSE) &&
916  goby::glog << "logging output to file: " << file_name << std::endl;
917 
918  fout_.open(std::string(common_cfg_.log_path() + "/" + file_name).c_str());
919 
920  // symlink to "latest.txt"
921  remove(std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
922  symlink(file_name.c_str(),
923  std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
924 
925  // if fails, try logging to this directory
926  if (!fout_.is_open())
927  {
928  fout_.open(std::string("./" + file_name).c_str());
929  goby::glog.is(goby::common::logger::WARN) &&
930  goby::glog
931  << "logging to current directory because given directory is unwritable!"
932  << std::endl;
933  }
934  // if still no go, quit
935  if (!fout_.is_open())
936  {
937  goby::glog.is(goby::common::logger::DIE) &&
938  goby::glog << "cannot write to current directory, so cannot log." << std::endl;
939  }
940 
941  goby::glog.add_stream(common_cfg_.log_verbosity(), &fout_);
942  }
943  }
944 
945  if (common_cfg_.has_moos_parser_technique())
946  goby::moos::moos_technique = common_cfg_.moos_parser_technique();
947  else if (common_cfg_.has_use_binary_protobuf())
948  goby::moos::moos_technique =
949  common_cfg_.use_binary_protobuf()
950  ? goby::moos::protobuf::TranslatorEntry::TECHNIQUE_PREFIXED_PROTOBUF_NATIVE_ENCODED
951  : goby::moos::protobuf::TranslatorEntry::TECHNIQUE_PREFIXED_PROTOBUF_TEXT_FORMAT;
952 
953  if (common_cfg_.time_warp_multiplier() != 1)
954  {
955  goby::common::goby_time_function = GobyMOOSAppSelector<MOOSAppType>::microsec_moos_time;
956  goby::common::goby_time_warp_factor = common_cfg_.time_warp_multiplier();
957  // MOOS hasn't processed the warp multiplier at the point start_time is set
958  start_time_ *= common_cfg_.time_warp_multiplier();
959  }
960 }
961 
962 // designed to run CMOOSApp derived applications
963 // using the MOOS "convention" of argv[1] == mission file, argv[2] == alternative name
964 template <typename App> int goby::moos::run(int argc, char* argv[])
965 {
966  App::argc_ = argc;
967  App::argv_ = argv;
968 
969  try
970  {
971  App* app = App::get_instance();
972  app->Run(App::application_name_.c_str(), App::mission_file_.c_str());
973  }
975  {
976  // no further warning as the ApplicationBase Ctor handles this
977  return 1;
978  }
979  catch (std::exception& e)
980  {
981  // some other exception
982  goby::glog.is(goby::common::logger::DIE) && goby::glog << "uncaught exception: " << e.what()
983  << std::endl;
984  return 2;
985  }
986 
987  return 0;
988 }
989 
990 #endif
void set_name(const std::string &s)
Set the name of the application that the logger is serving.
Definition: flex_ostream.h:67
void parse_for_moos(const std::string &in, google::protobuf::Message *msg)
Parses the string in to Google Protocol Buffers message msg. All errors are written to the goby::util...
indicates a problem with the runtime command line or .cfg file configuration (or –help was given) ...
Helpers for MOOS applications for serializing and parsed Google Protocol buffers messages.
int run(int argc, char *argv[], Config *cfg)
Run a Goby application derived from MinimalApplicationBase. blocks caller until MinimalApplicationBas...
double goby_time< double >()
Returns current UTC time as seconds and fractional seconds since 1970-01-01 00:00:00.
Definition: time.h:130
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.
google::protobuf::uint64 uint64
an unsigned 64 bit integer
void add_stream(logger::Verbosity verbosity=logger::VERBOSE, std::ostream *os=0)
Attach a stream object (e.g. std::cout, std::ofstream, ...) to the logger with desired verbosity...
Definition: flex_ostream.h:96