Goby3 3.4.0
2026.04.13
Loading...
Searching...
No Matches
goby_moos_app.h
Go to the documentation of this file.
1// Copyright 2011-2024:
2// GobySoft, LLC (2013-)
3// Massachusetts Institute of Technology (2007-2014)
4// Community contributors (see AUTHORS file)
5// File authors:
6// Toby Schneider <toby@gobysoft.org>
7// James D. Turner <james.turner@nrl.navy.mil>
8// Shawn Dooley <shawn@shawndooley.net>
9//
10//
11// This file is part of the Goby Underwater Autonomy Project Libraries
12// ("The Goby Libraries").
13//
14// The Goby Libraries are free software: you can redistribute them and/or modify
15// them under the terms of the GNU Lesser General Public License as published by
16// the Free Software Foundation, either version 2.1 of the License, or
17// (at your option) any later version.
18//
19// The Goby Libraries are distributed in the hope that they will be useful,
20// but WITHOUT ANY WARRANTY; without even the implied warranty of
21// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22// GNU Lesser General Public License for more details.
23//
24// You should have received a copy of the GNU Lesser General Public License
25// along with Goby. If not, see <http://www.gnu.org/licenses/>.
26
27#ifndef GOBY_MOOS_GOBY_MOOS_APP_H
28#define GOBY_MOOS_GOBY_MOOS_APP_H
29
30#include <chrono> // for system...
31#include <cstdio> // for remove
32#include <cstdlib> // for exit
33#include <deque> // for deque
34#include <iomanip> // for operat...
35#include <iostream> // for operat...
36#include <map> // for map
37#include <memory> // for allocator
38#include <stdexcept> // for runtim...
39#include <string> // for string
40#include <unistd.h> // for symlink
41#include <utility> // for pair
42#include <vector> // for vector
43
44#include <MOOS/libMOOS/App/MOOSApp.h> // for CMOOSApp
45#include <MOOS/libMOOS/Comms/CommsTypes.h> // for MOOSMS...
46#include <MOOS/libMOOS/Comms/MOOSMsg.h> // for CMOOSMsg
47#include <MOOS/libMOOS/Utils/MOOSFileReader.h> // for CMOOSF...
48#include <MOOS/libMOOS/Utils/MOOSUtilityFunctions.h> // for MOOSTime
49#include <boost/algorithm/string/erase.hpp> // for erase_...
50#include <boost/algorithm/string/find.hpp> // for ifind_...
51#include <boost/algorithm/string/predicate.hpp> // for iequals
52#include <boost/algorithm/string/replace.hpp> // for replac...
53#include <boost/algorithm/string/trim.hpp> // for trim_copy
54#include <boost/bind/bind.hpp> // for bind, _1
55#include <boost/date_time/posix_time/posix_time_config.hpp> // for posix_...
56#include <boost/date_time/posix_time/posix_time_types.hpp> // for second...
57#include <boost/date_time/posix_time/time_formatters.hpp> // for to_iso...
58#include <boost/filesystem.hpp> // for path
59#include <boost/function.hpp> // for function
60#include <boost/lexical_cast/bad_lexical_cast.hpp> // for bad_le...
61#include <boost/program_options/options_description.hpp> // for operat...
62#include <boost/program_options/parsers.hpp> // for basic_...
63#include <boost/program_options/positional_options.hpp> // for positi...
64#include <boost/program_options/value_semantic.hpp> // for value
65#include <boost/program_options/variables_map.hpp> // for variab...
66#include <boost/range/iterator_range_core.hpp> // for iterat...
67#include <boost/signals2/signal.hpp> // for signal
68#include <boost/units/quantity.hpp> // for operator/
69#include <boost/units/systems/si/time.hpp> // for seconds
70#include <google/protobuf/descriptor.h> // for FieldD...
71#include <google/protobuf/descriptor.pb.h> // for FieldO...
72#include <google/protobuf/message.h> // for Reflec...
73#include <google/protobuf/text_format.h> // for TextFo...
74
76#include "goby/moos/moos_protobuf_helpers.h" // for set_mo...
77#include "goby/moos/moos_translator.h" // for MOOSTr...
78#include "goby/moos/protobuf/goby_moos_app.pb.h" // for GobyMO...
79#include "goby/moos/protobuf/translator.pb.h" // for Transl...
80#include "goby/protobuf/option_extensions.pb.h" // for GobyFi...
81#include "goby/time/convert.h" // for System...
82#include "goby/time/simulation.h" // for Simula...
83#include "goby/time/system_clock.h" // for System...
84#include "goby/time/types.h" // for SITime
85#include "goby/util/as.h" // for as
86#include "goby/util/debug_logger/flex_ostream.h" // for operat...
88#include "goby/util/debug_logger/term_color.h" // for esc_no...
89#include "goby/util/protobuf/debug_logger.pb.h" // for GLogCo...
90
91#include "dynamic_moos_vars.h" // for Dynami...
92
93namespace goby
94{
95namespace moos
96{
98
99template <typename App> int run(int argc, char* argv[]);
100
101template <typename ProtobufMessage>
102inline void protobuf_inbox(const CMOOSMsg& msg,
103 boost::function<void(const ProtobufMessage& msg)> handler)
104{
105 ProtobufMessage pb_msg;
106 parse_for_moos(msg.GetString(), &pb_msg);
107 handler(pb_msg);
108}
109
110// shell implementation so we can call superclass methods for when
111// using AppCastingMOOSApp
112class MOOSAppShell : public CMOOSApp
113{
114 protected:
115 bool Iterate() override { return true; }
116 bool OnStartUp() override { return true; }
117 bool OnConnectToServer() override { return true; }
118 bool OnNewMail(MOOSMSG_LIST& /*NewMail*/) override { return true; }
120 void PostReport() {}
121};
122
123template <class MOOSAppType = MOOSAppShell> class GobyMOOSAppSelector : public MOOSAppType
124{
125 protected:
126 typedef boost::function<void(const CMOOSMsg& msg)> InboxFunc;
127
128 template <typename ProtobufConfig>
129 explicit GobyMOOSAppSelector(ProtobufConfig* cfg)
130 : start_time_(MOOSTime()),
131 configuration_read_(false),
132 cout_cleared_(false),
133 connected_(false),
134 started_up_(false),
135 ignore_stale_(true),
136 dynamic_moos_vars_enabled_(true)
137 {
138 using goby::glog;
139
140 read_configuration(cfg);
141
142 // keep a copy for ourselves
143 common_cfg_ = cfg->common();
144 configuration_read_ = true;
145
146 process_configuration();
147
148 glog.is(goby::util::logger::DEBUG2) && glog << cfg->DebugString() << std::endl;
149 }
150
151 ~GobyMOOSAppSelector() override = default;
152
153 template <typename ProtobufMessage>
154 void publish_pb(const std::string& key, const ProtobufMessage& msg)
155 {
156 std::string serialized;
157 bool is_binary = serialize_for_moos(&serialized, msg);
159 key, serialized, is_binary, goby::moos::moos_technique,
160 msg.GetDescriptor()->full_name());
161 publish(moos_msg);
162 }
163
164 void publish(CMOOSMsg& msg)
165 {
166 if (connected_ && started_up_)
167 MOOSAppType::m_Comms.Post(msg);
168 else
169 msg_buffer_.push_back(msg);
170 }
171
172 void publish(const std::string& key, const std::string& value)
173 {
174 CMOOSMsg msg(MOOS_NOTIFY, key, value);
175 publish(msg);
176 }
177
178 void publish(const std::string& key, double value)
179 {
180 CMOOSMsg msg(MOOS_NOTIFY, key, value);
181 publish(msg);
182 }
183
184 goby::moos::DynamicMOOSVars& dynamic_vars() { return dynamic_vars_; }
185 double start_time() const { return start_time_; }
186
187 void subscribe(const std::string& var, const InboxFunc& handler = InboxFunc(),
188 double blackout = 0);
189
190 template <typename V, typename A1>
191 void subscribe(const std::string& var, void (V::*mem_func)(A1), V* obj, double blackout = 0)
192 {
193 subscribe(var, boost::bind(mem_func, obj, boost::placeholders::_1), blackout);
194 }
195
196 // wildcard
197 void subscribe(const std::string& var_pattern, const std::string& app_pattern,
198 const InboxFunc& handler = InboxFunc(), double blackout = 0);
199
200 template <typename V, typename A1>
201 void subscribe(const std::string& var_pattern, const std::string& app_pattern,
202 void (V::*mem_func)(A1), V* obj, double blackout = 0)
203 {
204 subscribe(var_pattern, app_pattern, boost::bind(mem_func, obj, boost::placeholders::_1),
205 blackout);
206 }
207
208 template <typename V, typename ProtobufMessage>
209 void subscribe_pb(const std::string& var, void (V::*mem_func)(const ProtobufMessage&), V* obj,
210 double blackout = 0)
211 {
212 subscribe_pb<ProtobufMessage>(var, boost::bind(mem_func, obj, boost::placeholders::_1),
213 blackout);
214 }
215
216 template <typename ProtobufMessage>
217 void subscribe_pb(const std::string& var,
218 boost::function<void(const ProtobufMessage& msg)> handler,
219 double blackout = 0)
220 {
221 subscribe(var,
222 boost::bind(&goby::moos::protobuf_inbox<ProtobufMessage>, boost::placeholders::_1,
223 handler),
224 blackout);
225 }
226
227 void register_timer(int period_seconds, const boost::function<void()>& handler)
228 {
229 int now = (goby::time::SystemClock::now<goby::time::SITime>() / boost::units::si::seconds) /
230 period_seconds;
231 now *= period_seconds;
232
233 SynchronousLoop new_loop;
234 new_loop.unix_next = now + period_seconds;
235 new_loop.period_seconds = period_seconds;
236 new_loop.handler = handler;
237 synchronous_loops_.push_back(new_loop);
238 }
239
240 template <typename V> void register_timer(int period_seconds, void (V::*mem_func)(), V* obj)
241 {
242 register_timer(period_seconds, boost::bind(mem_func, obj));
243 }
244
245 template <typename App> friend int ::goby::moos::run(int argc, char* argv[]);
246
247 virtual void loop() = 0;
248
249 bool ignore_stale() { return ignore_stale_; }
250 void set_ignore_stale(bool b) { ignore_stale_ = b; }
251
252 bool dynamic_moos_vars_enabled() { return dynamic_moos_vars_enabled_; }
253 void set_dynamic_moos_vars_enabled(bool b) { dynamic_moos_vars_enabled_ = b; }
254
255 std::pair<std::string, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique>
256 parse_type_technique(const std::string& type_and_technique)
257 {
258 std::string protobuf_type;
260 if (!type_and_technique.empty())
261 {
262 std::string::size_type colon_pos = type_and_technique.find(':');
263
264 if (colon_pos != std::string::npos)
265 {
266 protobuf_type = type_and_technique.substr(0, colon_pos);
267 std::string str_technique = type_and_technique.substr(colon_pos + 1);
268
270 str_technique, &technique))
271 throw(std::runtime_error("Invalid technique string"));
272 }
273 else
274 {
275 throw std::runtime_error("Missing colon (:)");
276 }
277 return std::make_pair(protobuf_type, technique);
278 }
279 else
280 {
281 throw std::runtime_error("Empty technique string");
282 }
283 }
284
285 private:
286 // from CMOOSApp
287 bool Iterate() override;
288 bool OnStartUp() override;
289 bool OnConnectToServer() override;
290 bool OnDisconnectFromServer() override;
291 bool OnNewMail(MOOSMSG_LIST& NewMail) override;
292 void try_subscribing();
293 void do_subscriptions();
294
295 int fetch_moos_globals(google::protobuf::Message* msg, CMOOSFileReader& moos_file_reader);
296
297 void read_configuration(google::protobuf::Message* cfg);
298 void process_configuration();
299
300 private:
301 // when we started (seconds since UNIX)
302 double start_time_;
303
304 // have we read the configuration file fully?
305 bool configuration_read_;
306 bool cout_cleared_;
307
308 std::ofstream fout_;
309
310 // allows direct reading of newest publish to a given MOOS variable
311 goby::moos::DynamicMOOSVars dynamic_vars_;
312
313 std::map<std::string, std::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)>>>
314 mail_handlers_;
315
316 std::map<std::pair<std::string, std::string>,
317 std::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)>>>
318 wildcard_mail_handlers_;
319
320 // CMOOSApp::OnConnectToServer()
321 bool connected_;
322 // CMOOSApp::OnStartUp()
323 bool started_up_;
324
325 std::deque<CMOOSMsg> msg_buffer_;
326
327 // MOOS Variable name, blackout time
328 std::deque<std::pair<std::string, double>> pending_subscriptions_;
329 std::deque<std::pair<std::string, double>> existing_subscriptions_;
330
331 // MOOS Variable pattern, MOOS App pattern, blackout time
332 std::deque<std::pair<std::pair<std::string, std::string>, double>>
333 wildcard_pending_subscriptions_;
334 std::deque<std::pair<std::pair<std::string, std::string>, double>>
335 wildcard_existing_subscriptions_;
336
337 struct SynchronousLoop
338 {
339 double unix_next;
340 int period_seconds;
341 boost::function<void()> handler;
342 };
343
344 std::vector<SynchronousLoop> synchronous_loops_;
345
346 protobuf::GobyMOOSAppConfig common_cfg_;
347
348 bool ignore_stale_;
349
350 bool dynamic_moos_vars_enabled_;
351
352 static int argc_;
353 static char** argv_;
354 static std::string mission_file_;
355 static std::string application_name_;
356};
357
359{
360 public:
361 template <typename ProtobufConfig>
362 explicit GobyMOOSApp(ProtobufConfig* cfg) : GobyMOOSAppSelector<>(cfg)
363 {
364 }
365};
366} // namespace moos
367} // namespace goby
368
369template <class MOOSAppType>
371
372template <class MOOSAppType>
374
375template <class MOOSAppType> int goby::moos::GobyMOOSAppSelector<MOOSAppType>::argc_ = 0;
376template <class MOOSAppType> char** goby::moos::GobyMOOSAppSelector<MOOSAppType>::argv_ = nullptr;
377
378template <class MOOSAppType> bool goby::moos::GobyMOOSAppSelector<MOOSAppType>::Iterate()
379{
380 MOOSAppType::Iterate();
381
382 if (!configuration_read_)
383 return true;
384
385 // clear out MOOSApp cout for ncurses "scope" mode
386 // MOOS has stopped talking by first Iterate()
387 if (!cout_cleared_)
388 {
390 cout_cleared_ = true;
391 }
392
393 while (!msg_buffer_.empty() && (connected_ && started_up_))
394 {
396 goby::glog << "writing from buffer: " << msg_buffer_.front().GetKey() << ": "
397 << msg_buffer_.front().GetAsString() << std::endl;
398
399 MOOSAppType::m_Comms.Post(msg_buffer_.front());
400 msg_buffer_.pop_front();
401 }
402
403 loop();
404
405 if (synchronous_loops_.size())
406 {
407 double now = goby::time::SystemClock::now<goby::time::SITime>() / boost::units::si::seconds;
408 for (typename std::vector<SynchronousLoop>::iterator it = synchronous_loops_.begin(),
409 end = synchronous_loops_.end();
410 it != end; ++it)
411 {
412 SynchronousLoop& loop = *it;
413 if (loop.unix_next <= now)
414 {
415 loop.handler();
416 loop.unix_next += loop.period_seconds;
417
418 // fix jumps forward in time
419 if (loop.unix_next < now)
420 loop.unix_next = now + loop.period_seconds;
421 }
422
423 // fix jumps backwards in time
424 if (loop.unix_next > (now + 2 * loop.period_seconds))
425 loop.unix_next = now + loop.period_seconds;
426 }
427 }
428
429 return true;
430}
431
432template <class MOOSAppType>
434{
435 // for AppCasting (otherwise no-op)
436 MOOSAppType::OnNewMail(NewMail);
437
438 for (const auto& msg : NewMail)
439 {
441 goby::glog << "Received mail: " << msg.GetKey() << ", time: " << std::setprecision(15)
442 << msg.GetTime() << std::endl;
443
444 // update dynamic moos variables - do this inside the loop so the newest is
445 // also the one referenced in the call to inbox()
446 if (dynamic_moos_vars_enabled_)
447 dynamic_vars().update_moos_vars(msg);
448
449 if (msg.GetTime() < start_time_ && ignore_stale_)
450 {
452 goby::glog << "ignoring normal mail from " << msg.GetKey()
453 << " from before we started (dynamics still updated)" << std::endl;
454 }
455 else if (mail_handlers_.count(msg.GetKey()))
456 (*mail_handlers_[msg.GetKey()])(msg);
457
458 for (auto& wildcard_mail_handler : wildcard_mail_handlers_)
459 {
460 if (MOOSWildCmp(wildcard_mail_handler.first.first, msg.GetKey()) &&
461 MOOSWildCmp(wildcard_mail_handler.first.second, msg.GetSource()))
462 (*(wildcard_mail_handler.second))(msg);
463 }
464 }
465
466 return true;
467}
468
469template <class MOOSAppType>
471{
472 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", disconnected from server."
473 << std::endl;
474 connected_ = false;
475 pending_subscriptions_.insert(pending_subscriptions_.end(), existing_subscriptions_.begin(),
476 existing_subscriptions_.end());
477 existing_subscriptions_.clear();
478 wildcard_pending_subscriptions_.insert(wildcard_pending_subscriptions_.end(),
479 wildcard_existing_subscriptions_.begin(),
480 wildcard_existing_subscriptions_.end());
481 wildcard_existing_subscriptions_.clear();
482 return true;
483}
484
486{
487 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", connected to server." << std::endl;
488 connected_ = true;
489 try_subscribing();
490
491 for (const auto& ini : common_cfg_.initializer())
492 {
493 if (ini.has_global_cfg_var())
494 {
495 std::string result;
496 if (MOOSAppType::m_MissionReader.GetValue(ini.global_cfg_var(), result))
497 {
498 if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_DOUBLE)
499 publish(ini.moos_var(), goby::util::as<double>(result));
500 else if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_STRING)
501 publish(ini.moos_var(), ini.trim() ? boost::trim_copy(result) : result);
502 }
503 }
504 else
505 {
506 if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_DOUBLE)
507 publish(ini.moos_var(), ini.dval());
508 else if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_STRING)
509 publish(ini.moos_var(), ini.trim() ? boost::trim_copy(ini.sval()) : ini.sval());
510 }
511 }
512
513 return true;
514}
515
516template <class MOOSAppType> bool goby::moos::GobyMOOSAppSelector<MOOSAppType>::OnStartUp()
517{
518 MOOSAppType::OnStartUp();
519
520 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", starting ..." << std::endl;
521 CMOOSApp::SetCommsFreq(common_cfg_.comm_tick());
522 CMOOSApp::SetAppFreq(common_cfg_.app_tick());
523 started_up_ = true;
524 try_subscribing();
525 return true;
526}
527
528template <class MOOSAppType>
530 const InboxFunc& handler,
531 double blackout /* = 0 */)
532{
534 goby::glog << "subscribing for MOOS variable: " << var << " @ " << blackout << std::endl;
535
536 pending_subscriptions_.emplace_back(var, blackout);
537 try_subscribing();
538
539 if (!mail_handlers_[var])
540 mail_handlers_[var].reset(new boost::signals2::signal<void(const CMOOSMsg& msg)>);
541
542 if (handler)
543 mail_handlers_[var]->connect(handler);
544}
545
546template <class MOOSAppType>
548 const std::string& app_pattern,
549 const InboxFunc& handler,
550 double blackout /* = 0 */)
551{
553 goby::glog << "wildcard subscribing for MOOS variable pattern: " << var_pattern
554 << ", app pattern: " << app_pattern << " @ " << blackout << std::endl;
555
556 std::pair<std::string, std::string> key = std::make_pair(var_pattern, app_pattern);
557 wildcard_pending_subscriptions_.emplace_back(key, blackout);
558 try_subscribing();
559
560 if (!wildcard_mail_handlers_.count(key))
561 wildcard_mail_handlers_.insert(std::make_pair(
562 key, std::make_shared<boost::signals2::signal<void(const CMOOSMsg& msg)>>()));
563
564 if (handler)
565 wildcard_mail_handlers_[key]->connect(handler);
566}
567
569{
570 if (connected_ && started_up_)
571 do_subscriptions();
572}
573
575{
576 MOOSAppType::RegisterVariables();
577
578 while (!pending_subscriptions_.empty())
579 {
580 // variable name, blackout
581 if (MOOSAppType::m_Comms.Register(pending_subscriptions_.front().first,
582 pending_subscriptions_.front().second))
583 {
585 goby::glog << "subscribed for: " << pending_subscriptions_.front().first
586 << std::endl;
587 }
588 else
589 {
591 goby::glog << "failed to subscribe for: " << pending_subscriptions_.front().first
592 << std::endl;
593 }
594 existing_subscriptions_.push_back(pending_subscriptions_.front());
595 pending_subscriptions_.pop_front();
596 }
597
598 while (!wildcard_pending_subscriptions_.empty())
599 {
600 // variable name, blackout
601 if (MOOSAppType::m_Comms.Register(wildcard_pending_subscriptions_.front().first.first,
602 wildcard_pending_subscriptions_.front().first.second,
603 wildcard_pending_subscriptions_.front().second))
604 {
606 goby::glog << "subscribed for: "
607 << wildcard_pending_subscriptions_.front().first.first << ":"
608 << wildcard_pending_subscriptions_.front().first.second << std::endl;
609 }
610 else
611 {
613 goby::glog << "failed to subscribe for: "
614 << wildcard_pending_subscriptions_.front().first.first << ":"
615 << wildcard_pending_subscriptions_.front().first.second << std::endl;
616 }
617
618 wildcard_existing_subscriptions_.push_back(wildcard_pending_subscriptions_.front());
619 wildcard_pending_subscriptions_.pop_front();
620 }
621}
622
623template <class MOOSAppType>
625 google::protobuf::Message* msg, CMOOSFileReader& moos_file_reader)
626{
627 int globals = 0;
628 const google::protobuf::Descriptor* desc = msg->GetDescriptor();
629 const google::protobuf::Reflection* refl = msg->GetReflection();
630
631 for (int i = 0, n = desc->field_count(); i < n; ++i)
632 {
633 const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
634
635 // we don't support repeated fields or oneof fields containing MOOS globals
636 if (field_desc->is_repeated() || field_desc->containing_oneof())
637 continue;
638
639 std::string moos_global = field_desc->options().GetExtension(goby::field).moos_global();
640
641 switch (field_desc->cpp_type())
642 {
643 case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
644 {
645 bool message_was_empty = !refl->HasField(*msg, field_desc);
646 int set_globals =
647 fetch_moos_globals(refl->MutableMessage(msg, field_desc), moos_file_reader);
648 if (set_globals == 0 && message_was_empty)
649 refl->ClearField(msg, field_desc);
650
651 break;
652 }
653
654 case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
655 {
656 int result;
657 if (moos_file_reader.GetValue(moos_global, result))
658 {
659 refl->SetInt32(msg, field_desc, result);
660 ++globals;
661 }
662
663 break;
664 }
665
666 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
667 {
668 int result;
669 if (moos_file_reader.GetValue(moos_global, result))
670 {
671 refl->SetInt64(msg, field_desc, result);
672 ++globals;
673 }
674 break;
675 }
676
677 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
678 {
679 unsigned result;
680 if (moos_file_reader.GetValue(moos_global, result))
681 {
682 refl->SetUInt32(msg, field_desc, result);
683 ++globals;
684 }
685
686 break;
687 }
688
689 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
690 {
691 unsigned result;
692 if (moos_file_reader.GetValue(moos_global, result))
693 {
694 refl->SetUInt64(msg, field_desc, result);
695 ++globals;
696 }
697 break;
698 }
699
700 case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
701 {
702 enum Result
703 {
704 RESULT_TRUE = 1,
705 RESULT_FALSE = 0,
706 RESULT_UNSPECIFIED = -1
707 };
708
709 Result result = RESULT_UNSPECIFIED;
710 // avoid parsing pLogger "LOG = some string" as "log = false"
711 std::string svalue;
712 if (moos_file_reader.GetValue(moos_global, svalue))
713 {
714 if (MOOSStrCmp(svalue, "TRUE"))
715 result = RESULT_TRUE;
716 else if (MOOSStrCmp(svalue, "FALSE"))
717 result = RESULT_FALSE;
718 else if (MOOSIsNumeric(svalue))
719 result = atof(svalue.c_str()) > 0 ? RESULT_TRUE : RESULT_FALSE;
720 }
721 if (result != RESULT_UNSPECIFIED)
722 {
723 refl->SetBool(msg, field_desc, result);
724 ++globals;
725 }
726 break;
727 }
728
729 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
730 {
731 std::string result;
732 if (moos_file_reader.GetValue(moos_global, result))
733 {
734 refl->SetString(msg, field_desc, result);
735 ++globals;
736 }
737
738 break;
739 }
740
741 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
742 {
743 float result;
744 if (moos_file_reader.GetValue(moos_global, result))
745 {
746 refl->SetFloat(msg, field_desc, result);
747 ++globals;
748 }
749
750 break;
751 }
752
753 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
754 {
755 double result;
756 if (moos_file_reader.GetValue(moos_global, result))
757 {
758 refl->SetDouble(msg, field_desc, result);
759 ++globals;
760 }
761 break;
762 }
763
764 case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
765 {
766 std::string result;
767 if (moos_file_reader.GetValue(moos_global, result))
768 {
769 const google::protobuf::EnumValueDescriptor* enum_desc =
770 refl->GetEnum(*msg, field_desc)->type()->FindValueByName(result);
771 if (!enum_desc)
772 throw(std::runtime_error(std::string("invalid enumeration " + result +
773 " for field " + field_desc->name())));
774
775 refl->SetEnum(msg, field_desc, enum_desc);
776 ++globals;
777 }
778 break;
779 }
780 }
781 }
782 return globals;
783}
784
785template <class MOOSAppType>
788{
789 boost::filesystem::path launch_path(argv_[0]);
790
791#if BOOST_FILESYSTEM_VERSION == 3
792 std::string binary_name = launch_path.filename().string();
793#else
794 std::string binary_name = launch_path.filename();
795#endif
796 application_name_ = binary_name;
797
798 //
799 // READ CONFIGURATION
800 //
801
802 boost::program_options::options_description od_all;
803 boost::program_options::variables_map var_map, po_env_var_map;
804 try
805 {
806 boost::program_options::options_description od_cli_only(
807 "Options given on command line only");
808 od_cli_only.add_options()("help,h", "writes this help message")(
809 "moos_file,c", boost::program_options::value<std::string>(&mission_file_),
810 "path to .moos file")("moos_name,a",
811 boost::program_options::value<std::string>(&application_name_),
812 "name to register with MOOS")(
813 "example_config,e", "writes an example .moos ProcessConfig block")(
814 "version,V", "writes the current version");
815
817 boost::program_options::options_description>
818 od_map;
819
820 std::string od_pb_always_desc =
821 "Options typically given in the .moos file, but may be specified on the command line";
822 std::string od_pb_never_desc = "Hidden options";
823 std::string od_pb_advanced_desc = "Advanced options";
824 std::string od_pb_developer_desc = "Developer options";
825 od_map.insert(
827 boost::program_options::options_description(od_pb_always_desc.c_str())));
828 od_map.insert(std::make_pair(
830 boost::program_options::options_description(od_pb_advanced_desc.c_str())));
831 od_map.insert(std::make_pair(
833 boost::program_options::options_description(od_pb_developer_desc.c_str())));
834 od_map.insert(
836 boost::program_options::options_description(od_pb_never_desc.c_str())));
837
838 std::map<std::string, std::string> environmental_var_map;
840 environmental_var_map);
841 std::vector<goby::middleware::ConfigReader::PositionalOption> positional_options;
843 positional_options);
844
845 for (const auto& od_p : od_map) od_all.add(od_p.second);
846 od_all.add(od_cli_only);
847
848 boost::program_options::positional_options_description p;
849 p.add("moos_file", 1);
850 p.add("moos_name", 1);
851 for (const auto& po : positional_options) { p.add(po.name.c_str(), po.position_max_count); }
852
853 boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_)
854 .options(od_all)
855 .positional(p)
856 .run(),
857 var_map);
858
859 if (!environmental_var_map.empty())
860 {
861 boost::program_options::store(
862 boost::program_options::parse_environment(
863 od_all,
864 [&environmental_var_map](const std::string& i_env_var) -> std::string {
865 return environmental_var_map.count(i_env_var)
866 ? environmental_var_map.at(i_env_var)
867 : "";
868 }),
869 po_env_var_map);
870 }
871
872 boost::program_options::notify(var_map);
873 boost::program_options::notify(po_env_var_map);
874
875 if (var_map.count("help"))
876 {
877 std::cerr << "Usage: " << binary_name << " [options] moos_file [moos_name]"
878 << std::endl;
879
880 std::cerr << od_map[goby::GobyFieldOptions::ConfigurationOptions::ALWAYS] << "\n";
881 std::cerr << od_cli_only << "\n";
882 exit(EXIT_SUCCESS);
883 }
884 else if (var_map.count("example_config"))
885 {
886 std::cout << "ProcessConfig = " << application_name_ << "\n{";
888 std::cout << "}" << std::endl;
889 exit(EXIT_SUCCESS);
890 }
891 else if (var_map.count("version"))
892 {
894 exit(EXIT_SUCCESS);
895 }
896
897 goby::glog.set_name(application_name_);
899
900 std::string protobuf_text;
901 std::ifstream fin;
902 fin.open(mission_file_.c_str());
903 if (fin.is_open())
904 {
905 std::string line;
906 bool in_process_config = false;
907 while (getline(fin, line))
908 {
909 std::string no_blanks_line = boost::algorithm::erase_all_copy(line, " ");
910 if (boost::algorithm::iequals(no_blanks_line, "PROCESSCONFIG=" + application_name_))
911 {
912 in_process_config = true;
913 }
914 else if (in_process_config &&
915 !boost::algorithm::ifind_first(line, "PROCESSCONFIG").empty())
916 {
917 break;
918 }
919
920 if (in_process_config)
921 protobuf_text += line + "\n";
922 }
923
924 if (!in_process_config)
925 {
927 goby::glog << "no ProcessConfig block for " << application_name_ << std::endl;
928 }
929
930 // trim off "ProcessConfig = __ {"
931 protobuf_text.erase(0, protobuf_text.find_first_of('{') + 1);
932
933 // trim off last "}" and anything that follows
934 protobuf_text.erase(protobuf_text.find_last_of('}'));
935
936 // convert "//" to "#" for comments
937 boost::algorithm::replace_all(protobuf_text, "//", "#");
938
939 google::protobuf::TextFormat::Parser parser;
940 goby::util::FlexOStreamErrorCollector error_collector(protobuf_text);
941 parser.RecordErrorsTo(&error_collector);
942 parser.AllowPartialMessage(true);
943 parser.ParseFromString(protobuf_text, cfg);
944
945 if (error_collector.has_errors() || error_collector.has_warnings())
946 {
948 goby::glog << "fatal configuration errors (see above)" << std::endl;
949 }
950 }
951 else
952 {
953 goby::glog.is(goby::util::logger::WARN) && goby::glog << "failed to open "
954 << mission_file_ << std::endl;
955 }
956
957 fin.close();
958
959 CMOOSFileReader moos_file_reader;
960 moos_file_reader.SetFile(mission_file_);
961 fetch_moos_globals(cfg, moos_file_reader);
962
963 // add any environmental variable options that don't exist in the cfg file
964 for (const auto& p : po_env_var_map)
965 {
966 // let protobuf deal with the defaults
967 if (!p.second.defaulted())
969 p.second, false);
970 }
971
972 // add / overwrite any options that are specified in the cfg file with those given on the command line
973 for (const auto& p : var_map)
974 {
975 if (!p.second.defaulted())
977 p.second, true);
978 }
979
980 // now the proto message must have all required fields
981 if (!cfg->IsInitialized())
982 {
983 std::vector<std::string> errors;
984 cfg->FindInitializationErrors(&errors);
985
986 std::stringstream err_msg;
987 err_msg << "Configuration is missing required parameters: \n";
988 for (const std::string& s : errors)
989 err_msg << goby::util::esc_red << s << "\n" << goby::util::esc_nocolor;
990
991 err_msg << "Make sure you specified a proper .moos file";
992 throw(goby::middleware::ConfigException(err_msg.str()));
993 }
994 }
996 {
997 // output all the available command line options
998 std::cerr << od_all << "\n";
999 std::cerr << "Problem parsing command-line configuration: \n" << e.what() << "\n";
1000
1001 throw;
1002 }
1003}
1004
1005template <class MOOSAppType>
1007{
1008 //
1009 // PROCESS CONFIGURATION
1010 //
1011 goby::glog.add_stream(common_cfg_.verbosity(), &std::cout);
1012 if (common_cfg_.show_gui())
1013 {
1015 }
1016
1017 if (common_cfg_.log())
1018 {
1019 if (!common_cfg_.has_log_path())
1020 {
1022 goby::glog << "logging all terminal output to default directory ("
1023 << common_cfg_.log_path() << ")."
1024 << "set log_path for another path " << std::endl;
1025 }
1026
1027 if (!common_cfg_.log_path().empty())
1028 {
1029 using namespace boost::posix_time;
1030 std::string file_name_base = boost::replace_all_copy(application_name_, "/", "_") +
1031 "_" + common_cfg_.community();
1032
1033 std::string file_name =
1034 file_name_base +
1035 (common_cfg_.log_omit_file_timestamp()
1036 ? ""
1037 : std::string("_") + to_iso_string(second_clock::universal_time())) +
1038 ".txt";
1039
1041 goby::glog << "logging output to file: " << file_name << std::endl;
1042
1043 fout_.open(std::string(common_cfg_.log_path() + "/" + file_name).c_str());
1044
1045 if (!common_cfg_.log_omit_latest_symlink())
1046 {
1047 std::string file_symlink = file_name_base + "_latest.txt";
1048 // symlink to "latest.txt"
1049 remove(std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1050 symlink(file_name.c_str(),
1051 std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1052 }
1053
1054 // if fails, try logging to this directory
1055 if (!fout_.is_open())
1056 {
1057 fout_.open(std::string("./" + file_name).c_str());
1060 << "logging to current directory because given directory is unwritable!"
1061 << std::endl;
1062 }
1063 // if still no go, quit
1064 if (!fout_.is_open())
1065 {
1067 goby::glog << "cannot write to current directory, so cannot log." << std::endl;
1068 }
1069
1070 goby::glog.add_stream(common_cfg_.log_verbosity(), &fout_);
1071 }
1072 }
1073
1074 goby::moos::set_moos_technique(common_cfg_);
1075
1076 if (common_cfg_.time_warp_multiplier() != 1)
1077 {
1078 goby::time::SimulatorSettings::warp_factor = common_cfg_.time_warp_multiplier();
1081 std::chrono::system_clock::time_point(std::chrono::seconds(0));
1082 start_time_ *= common_cfg_.time_warp_multiplier();
1083 }
1084}
1085
1086// designed to run CMOOSApp derived applications
1087// using the MOOS "convention" of argv[1] == mission file, argv[2] == alternative name
1088template <typename App> int goby::moos::run(int argc, char* argv[])
1089{
1090 App::argc_ = argc;
1091 App::argv_ = argv;
1092
1093 try
1094 {
1095 App* app = App::get_instance();
1096 app->Run(App::application_name_.c_str(), App::mission_file_.c_str());
1097 }
1099 {
1100 // no further warning as the ApplicationBase Ctor handles this
1101 return 1;
1102 }
1103 catch (std::exception& e)
1104 {
1105 // some other exception
1106 goby::glog.is(goby::util::logger::DIE) && goby::glog << "uncaught exception: " << e.what()
1107 << std::endl;
1108 return 2;
1109 }
1110
1111 return 0;
1112}
1113
1114#endif
GobyFieldOptions_ConfigurationOptions_ConfigAction ConfigAction
indicates a problem with the runtime command line or .cfg file configuration (or –help was given)
static void get_protobuf_program_options(std::map< goby::GobyFieldOptions::ConfigurationOptions::ConfigAction, boost::program_options::options_description > &od_map, const google::protobuf::Descriptor *desc, std::map< std::string, std::string > &environmental_var_map)
static void get_example_cfg_file(google::protobuf::Message *message, std::ostream *human_desc_ss, const std::string &indent="", goby::GobyFieldOptions::ConfigurationOptions::ConfigAction action=goby::GobyFieldOptions::ConfigurationOptions::ALWAYS)
static void get_positional_options(const google::protobuf::Descriptor *desc, std::vector< PositionalOption > &positional_options)
static void set_protobuf_program_option(const boost::program_options::variables_map &vm, google::protobuf::Message &message, const std::string &full_name, const boost::program_options::variable_value &value, bool overwrite_if_exists)
void publish(const std::string &key, double value)
void subscribe(const std::string &var, void(V::*mem_func)(A1), V *obj, double blackout=0)
boost::function< void(const CMOOSMsg &msg)> InboxFunc
void publish(const std::string &key, const std::string &value)
void subscribe_pb(const std::string &var, boost::function< void(const ProtobufMessage &msg)> handler, double blackout=0)
void register_timer(int period_seconds, void(V::*mem_func)(), V *obj)
GobyMOOSAppSelector(ProtobufConfig *cfg)
void subscribe(const std::string &var_pattern, const std::string &app_pattern, void(V::*mem_func)(A1), V *obj, double blackout=0)
void subscribe_pb(const std::string &var, void(V::*mem_func)(const ProtobufMessage &), V *obj, double blackout=0)
void subscribe(const std::string &var, const InboxFunc &handler=InboxFunc(), double blackout=0)
goby::moos::DynamicMOOSVars & dynamic_vars()
std::pair< std::string, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique > parse_type_technique(const std::string &type_and_technique)
~GobyMOOSAppSelector() override=default
void publish_pb(const std::string &key, const ProtobufMessage &msg)
void register_timer(int period_seconds, const boost::function< void()> &handler)
GobyMOOSApp(ProtobufConfig *cfg)
bool OnStartUp() override
bool OnNewMail(MOOSMSG_LIST &) override
bool OnConnectToServer() override
static CMOOSMsg make_moos_msg(const std::string &var, const std::string &str, bool is_binary, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique technique, const std::string &pb_name)
static bool ParserSerializerTechnique_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ParserSerializerTechnique *value)
bool is(goby::util::logger::Verbosity verbosity)
void set_name(const std::string &s)
Set the name of the application that the logger is serving.
void add_stream(logger::Verbosity verbosity=logger::VERBOSE, std::ostream *os=nullptr)
Attach a stream object (e.g. std::cout, std::ofstream, ...) to the logger with desired verbosity.
static constexpr Verbosity VERBOSE
const Descriptor * GetDescriptor() const
Definition message.h:357
void FindInitializationErrors(std::vector< std::string > *errors) const
bool IsInitialized() const override
void SetUInt32(Message *message, const FieldDescriptor *field, uint32_t value) const
void ClearField(Message *message, const FieldDescriptor *field) const
Message * MutableMessage(Message *message, const FieldDescriptor *field, MessageFactory *factory=nullptr) const
void SetInt64(Message *message, const FieldDescriptor *field, int64_t value) const
bool HasField(const Message &message, const FieldDescriptor *field) const
void SetInt32(Message *message, const FieldDescriptor *field, int32_t value) const
void SetDouble(Message *message, const FieldDescriptor *field, double value) const
void SetFloat(Message *message, const FieldDescriptor *field, float value) const
const EnumValueDescriptor * GetEnum(const Message &message, const FieldDescriptor *field) const
void SetString(Message *message, const FieldDescriptor *field, std::string value) const
void SetUInt64(Message *message, const FieldDescriptor *field, uint64_t value) const
void SetEnum(Message *message, const FieldDescriptor *field, const EnumValueDescriptor *value) const
void SetBool(Message *message, const FieldDescriptor *field, bool value) const
Helpers for MOOS applications for serializing and parsed Google Protocol buffers messages.
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...
bool serialize_for_moos(std::string *out, const google::protobuf::Message &msg)
Converts the Google Protocol Buffers message msg into a suitable (human readable) string out for send...
void write_version_message()
void protobuf_inbox(const CMOOSMsg &msg, boost::function< void(const ProtobufMessage &msg)> handler)
void set_moos_technique(const goby::moos::protobuf::GobyMOOSAppConfig &cfg)
goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique moos_technique
int run(int argc, char *argv[])
const std::string esc_red
Definition term_color.h:37
constexpr T e
Definition constants.h:35
const std::string esc_nocolor
Definition term_color.h:51
The global namespace for the Goby project.
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::FieldOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyFieldOptions >, 11, false > field
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::MessageOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyMessageOptions >, 11, false > msg
util::FlexOstream glog
Access the Goby logger through this object.
int run(const goby::middleware::ConfiguratorInterface< typename App::ConfigType > &cfgtor)
Run a Goby application using the provided Configurator.
Definition interface.h:356
static bool using_sim_time
Enables simulation time if true (if false, none of the remaining parameters are used)
Definition simulation.h:38
static std::chrono::system_clock::time_point reference_time
Reference time when calculating SystemClock::now(). If this is unset, the default is 1 January of the...
Definition simulation.h:42
static int warp_factor
Warp factor to speed up (or slow time) the time values returned by SteadyClock::now() and SystemClock...
Definition simulation.h:40