Goby3 3.6.1
2026.09.15
Loading...
Searching...
No Matches
goby_moos_app.h
Go to the documentation of this file.
1// Copyright 2011-2026:
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 <filesystem>
39#include <stdexcept> // for runtim...
40#include <string> // for string
41#include <unistd.h> // for symlink
42#include <utility> // for pair
43#include <vector> // for vector
44
45#include <MOOS/libMOOS/App/MOOSApp.h> // for CMOOSApp
46#include <MOOS/libMOOS/Comms/CommsTypes.h> // for MOOSMS...
47#include <MOOS/libMOOS/Comms/MOOSMsg.h> // for CMOOSMsg
48#include <MOOS/libMOOS/Utils/MOOSFileReader.h> // for CMOOSF...
49#include <MOOS/libMOOS/Utils/MOOSUtilityFunctions.h> // for MOOSTime
50#include <boost/algorithm/string/erase.hpp> // for erase_...
51#include <boost/algorithm/string/find.hpp> // for ifind_...
52#include <boost/algorithm/string/predicate.hpp> // for iequals
53#include <boost/algorithm/string/replace.hpp> // for replac...
54#include <boost/algorithm/string/trim.hpp> // for trim_copy
55#include <boost/bind/bind.hpp> // for bind, _1
56#include <boost/date_time/posix_time/posix_time_config.hpp> // for posix_...
57#include <boost/date_time/posix_time/posix_time_types.hpp> // for second...
58#include <boost/date_time/posix_time/time_formatters.hpp> // for to_iso...
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 std::filesystem::path launch_path(argv_[0]);
790 std::string binary_name = launch_path.filename().string();
791 application_name_ = binary_name;
792
793 //
794 // READ CONFIGURATION
795 //
796
797 boost::program_options::options_description od_all;
798 boost::program_options::variables_map var_map, po_env_var_map;
799 try
800 {
801 boost::program_options::options_description od_cli_only(
802 "Options given on command line only");
803 od_cli_only.add_options()("help,h", "writes this help message")(
804 "moos_file,c", boost::program_options::value<std::string>(&mission_file_),
805 "path to .moos file")("moos_name,a",
806 boost::program_options::value<std::string>(&application_name_),
807 "name to register with MOOS")(
808 "example_config,e", "writes an example .moos ProcessConfig block")(
809 "version,V", "writes the current version");
810
812 boost::program_options::options_description>
813 od_map;
814
815 std::string od_pb_always_desc =
816 "Options typically given in the .moos file, but may be specified on the command line";
817 std::string od_pb_never_desc = "Hidden options";
818 std::string od_pb_advanced_desc = "Advanced options";
819 std::string od_pb_developer_desc = "Developer options";
820 od_map.insert(
822 boost::program_options::options_description(od_pb_always_desc.c_str())));
823 od_map.insert(std::make_pair(
825 boost::program_options::options_description(od_pb_advanced_desc.c_str())));
826 od_map.insert(std::make_pair(
828 boost::program_options::options_description(od_pb_developer_desc.c_str())));
829 od_map.insert(
831 boost::program_options::options_description(od_pb_never_desc.c_str())));
832
833 std::map<std::string, std::string> environmental_var_map;
835 environmental_var_map);
836 std::vector<goby::middleware::ConfigReader::PositionalOption> positional_options;
838 positional_options);
839
840 for (const auto& od_p : od_map) od_all.add(od_p.second);
841 od_all.add(od_cli_only);
842
843 boost::program_options::positional_options_description p;
844 p.add("moos_file", 1);
845 p.add("moos_name", 1);
846 for (const auto& po : positional_options) { p.add(po.name.c_str(), po.position_max_count); }
847
848 boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_)
849 .options(od_all)
850 .positional(p)
851 .run(),
852 var_map);
853
854 if (!environmental_var_map.empty())
855 {
856 boost::program_options::store(
857 boost::program_options::parse_environment(
858 od_all,
859 [&environmental_var_map](const std::string& i_env_var) -> std::string {
860 return environmental_var_map.count(i_env_var)
861 ? environmental_var_map.at(i_env_var)
862 : "";
863 }),
864 po_env_var_map);
865 }
866
867 boost::program_options::notify(var_map);
868 boost::program_options::notify(po_env_var_map);
869
870 if (var_map.count("help"))
871 {
872 std::cerr << "Usage: " << binary_name << " [options] moos_file [moos_name]"
873 << std::endl;
874
875 std::cerr << od_map[goby::GobyFieldOptions::ConfigurationOptions::ALWAYS] << "\n";
876 std::cerr << od_cli_only << "\n";
877 exit(EXIT_SUCCESS);
878 }
879 else if (var_map.count("example_config"))
880 {
881 std::cout << "ProcessConfig = " << application_name_ << "\n{";
883 std::cout << "}" << std::endl;
884 exit(EXIT_SUCCESS);
885 }
886 else if (var_map.count("version"))
887 {
889 exit(EXIT_SUCCESS);
890 }
891
892 goby::glog.set_name(application_name_);
894
895 std::string protobuf_text;
896 std::ifstream fin;
897 fin.open(mission_file_.c_str());
898 if (fin.is_open())
899 {
900 std::string line;
901 bool in_process_config = false;
902 while (getline(fin, line))
903 {
904 std::string no_blanks_line = boost::algorithm::erase_all_copy(line, " ");
905 if (boost::algorithm::iequals(no_blanks_line, "PROCESSCONFIG=" + application_name_))
906 {
907 in_process_config = true;
908 }
909 else if (in_process_config &&
910 !boost::algorithm::ifind_first(line, "PROCESSCONFIG").empty())
911 {
912 break;
913 }
914
915 if (in_process_config)
916 protobuf_text += line + "\n";
917 }
918
919 if (!in_process_config)
920 {
922 goby::glog << "no ProcessConfig block for " << application_name_ << std::endl;
923 }
924
925 // trim off "ProcessConfig = __ {"
926 protobuf_text.erase(0, protobuf_text.find_first_of('{') + 1);
927
928 // trim off last "}" and anything that follows
929 protobuf_text.erase(protobuf_text.find_last_of('}'));
930
931 // convert "//" to "#" for comments
932 boost::algorithm::replace_all(protobuf_text, "//", "#");
933
934 google::protobuf::TextFormat::Parser parser;
935 goby::util::FlexOStreamErrorCollector error_collector(protobuf_text);
936 parser.RecordErrorsTo(&error_collector);
937 parser.AllowPartialMessage(true);
938 parser.ParseFromString(protobuf_text, cfg);
939
940 if (error_collector.has_errors() || error_collector.has_warnings())
941 {
943 goby::glog << "fatal configuration errors (see above)" << std::endl;
944 }
945 }
946 else
947 {
948 goby::glog.is(goby::util::logger::WARN) && goby::glog << "failed to open "
949 << mission_file_ << std::endl;
950 }
951
952 fin.close();
953
954 CMOOSFileReader moos_file_reader;
955 moos_file_reader.SetFile(mission_file_);
956 fetch_moos_globals(cfg, moos_file_reader);
957
958 // add any environmental variable options that don't exist in the cfg file
959 for (const auto& p : po_env_var_map)
960 {
961 // let protobuf deal with the defaults
962 if (!p.second.defaulted())
964 p.second, false);
965 }
966
967 // add / overwrite any options that are specified in the cfg file with those given on the command line
968 for (const auto& p : var_map)
969 {
970 if (!p.second.defaulted())
972 p.second, true);
973 }
974
975 // now the proto message must have all required fields
976 if (!cfg->IsInitialized())
977 {
978 std::vector<std::string> errors;
979 cfg->FindInitializationErrors(&errors);
980
981 std::stringstream err_msg;
982 err_msg << "Configuration is missing required parameters: \n";
983 for (const std::string& s : errors)
984 err_msg << goby::util::esc_red << s << "\n" << goby::util::esc_nocolor;
985
986 err_msg << "Make sure you specified a proper .moos file";
987 throw(goby::middleware::ConfigException(err_msg.str()));
988 }
989 }
991 {
992 // output all the available command line options
993 std::cerr << od_all << "\n";
994 std::cerr << "Problem parsing command-line configuration: \n" << e.what() << "\n";
995
996 throw;
997 }
998}
999
1000template <class MOOSAppType>
1002{
1003 //
1004 // PROCESS CONFIGURATION
1005 //
1006 goby::glog.add_stream(common_cfg_.verbosity(), &std::cout);
1007 if (common_cfg_.show_gui())
1008 {
1010 }
1011
1012 if (common_cfg_.log())
1013 {
1014 if (!common_cfg_.has_log_path())
1015 {
1017 goby::glog << "logging all terminal output to default directory ("
1018 << common_cfg_.log_path() << ")."
1019 << "set log_path for another path " << std::endl;
1020 }
1021
1022 if (!common_cfg_.log_path().empty())
1023 {
1024 using namespace boost::posix_time;
1025 std::string file_name_base = boost::replace_all_copy(application_name_, "/", "_") +
1026 "_" + common_cfg_.community();
1027
1028 std::string file_name =
1029 file_name_base +
1030 (common_cfg_.log_omit_file_timestamp()
1031 ? ""
1032 : std::string("_") + to_iso_string(second_clock::universal_time())) +
1033 ".txt";
1034
1036 goby::glog << "logging output to file: " << file_name << std::endl;
1037
1038 fout_.open(std::string(common_cfg_.log_path() + "/" + file_name).c_str());
1039
1040 if (!common_cfg_.log_omit_latest_symlink())
1041 {
1042 std::string file_symlink = file_name_base + "_latest.txt";
1043 // symlink to "latest.txt"
1044 remove(std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1045 symlink(file_name.c_str(),
1046 std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1047 }
1048
1049 // if fails, try logging to this directory
1050 if (!fout_.is_open())
1051 {
1052 fout_.open(std::string("./" + file_name).c_str());
1055 << "logging to current directory because given directory is unwritable!"
1056 << std::endl;
1057 }
1058 // if still no go, quit
1059 if (!fout_.is_open())
1060 {
1062 goby::glog << "cannot write to current directory, so cannot log." << std::endl;
1063 }
1064
1065 goby::glog.add_stream(common_cfg_.log_verbosity(), &fout_);
1066 }
1067 }
1068
1069 goby::moos::set_moos_technique(common_cfg_);
1070
1071 if (common_cfg_.time_warp_multiplier() != 1)
1072 {
1073 goby::time::SimulatorSettings::warp_factor = common_cfg_.time_warp_multiplier();
1076 std::chrono::system_clock::time_point(std::chrono::seconds(0));
1077 start_time_ *= common_cfg_.time_warp_multiplier();
1078 }
1079}
1080
1081// designed to run CMOOSApp derived applications
1082// using the MOOS "convention" of argv[1] == mission file, argv[2] == alternative name
1083template <typename App> int goby::moos::run(int argc, char* argv[])
1084{
1085 App::argc_ = argc;
1086 App::argv_ = argv;
1087
1088 try
1089 {
1090 App* app = App::get_instance();
1091 app->Run(App::application_name_.c_str(), App::mission_file_.c_str());
1092 }
1094 {
1095 // no further warning as the ApplicationBase Ctor handles this
1096 return 1;
1097 }
1098 catch (std::exception& e)
1099 {
1100 // some other exception
1101 goby::glog.is(goby::util::logger::DIE) && goby::glog << "uncaught exception: " << e.what()
1102 << std::endl;
1103 return 2;
1104 }
1105
1106 return 0;
1107}
1108
1109#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:369
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