Goby3 3.2.3
2025.05.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/smart_ptr/shared_ptr.hpp> // for shared...
69#include <boost/units/quantity.hpp> // for operator/
70#include <boost/units/systems/si/time.hpp> // for seconds
71#include <google/protobuf/descriptor.h> // for FieldD...
72#include <google/protobuf/descriptor.pb.h> // for FieldO...
73#include <google/protobuf/message.h> // for Reflec...
74#include <google/protobuf/text_format.h> // for TextFo...
75
77#include "goby/moos/moos_protobuf_helpers.h" // for set_mo...
78#include "goby/moos/moos_translator.h" // for MOOSTr...
79#include "goby/moos/protobuf/goby_moos_app.pb.h" // for GobyMO...
80#include "goby/moos/protobuf/translator.pb.h" // for Transl...
81#include "goby/protobuf/option_extensions.pb.h" // for GobyFi...
82#include "goby/time/convert.h" // for System...
83#include "goby/time/simulation.h" // for Simula...
84#include "goby/time/system_clock.h" // for System...
85#include "goby/time/types.h" // for SITime
86#include "goby/util/as.h" // for as
87#include "goby/util/debug_logger/flex_ostream.h" // for operat...
89#include "goby/util/debug_logger/term_color.h" // for esc_no...
90#include "goby/util/protobuf/debug_logger.pb.h" // for GLogCo...
91
92#include "dynamic_moos_vars.h" // for Dynami...
93
94namespace goby
95{
96namespace moos
97{
99
100template <typename App> int run(int argc, char* argv[]);
101
102template <typename ProtobufMessage>
103inline void protobuf_inbox(const CMOOSMsg& msg,
104 boost::function<void(const ProtobufMessage& msg)> handler)
105{
106 ProtobufMessage pb_msg;
107 parse_for_moos(msg.GetString(), &pb_msg);
108 handler(pb_msg);
109}
110
111// shell implementation so we can call superclass methods for when
112// using AppCastingMOOSApp
113class MOOSAppShell : public CMOOSApp
114{
115 protected:
116 bool Iterate() override { return true; }
117 bool OnStartUp() override { return true; }
118 bool OnConnectToServer() override { return true; }
119 bool OnNewMail(MOOSMSG_LIST& /*NewMail*/) override { return true; }
121 void PostReport() {}
122};
123
124template <class MOOSAppType = MOOSAppShell> class GobyMOOSAppSelector : public MOOSAppType
125{
126 protected:
127 typedef boost::function<void(const CMOOSMsg& msg)> InboxFunc;
128
129 template <typename ProtobufConfig>
130 explicit GobyMOOSAppSelector(ProtobufConfig* cfg)
131 : start_time_(MOOSTime()),
132 configuration_read_(false),
133 cout_cleared_(false),
134 connected_(false),
135 started_up_(false),
136 ignore_stale_(true),
137 dynamic_moos_vars_enabled_(true)
138 {
139 using goby::glog;
140
141 read_configuration(cfg);
142
143 // keep a copy for ourselves
144 common_cfg_ = cfg->common();
145 configuration_read_ = true;
146
147 process_configuration();
148
149 glog.is(goby::util::logger::DEBUG2) && glog << cfg->DebugString() << std::endl;
150 }
151
152 ~GobyMOOSAppSelector() override = default;
153
154 template <typename ProtobufMessage>
155 void publish_pb(const std::string& key, const ProtobufMessage& msg)
156 {
157 std::string serialized;
158 bool is_binary = serialize_for_moos(&serialized, msg);
160 key, serialized, is_binary, goby::moos::moos_technique,
161 msg.GetDescriptor()->full_name());
162 publish(moos_msg);
163 }
164
165 void publish(CMOOSMsg& msg)
166 {
167 if (connected_ && started_up_)
168 MOOSAppType::m_Comms.Post(msg);
169 else
170 msg_buffer_.push_back(msg);
171 }
172
173 void publish(const std::string& key, const std::string& value)
174 {
175 CMOOSMsg msg(MOOS_NOTIFY, key, value);
176 publish(msg);
177 }
178
179 void publish(const std::string& key, double value)
180 {
181 CMOOSMsg msg(MOOS_NOTIFY, key, value);
182 publish(msg);
183 }
184
185 goby::moos::DynamicMOOSVars& dynamic_vars() { return dynamic_vars_; }
186 double start_time() const { return start_time_; }
187
188 void subscribe(const std::string& var, const InboxFunc& handler = InboxFunc(),
189 double blackout = 0);
190
191 template <typename V, typename A1>
192 void subscribe(const std::string& var, void (V::*mem_func)(A1), V* obj, double blackout = 0)
193 {
194 subscribe(var, boost::bind(mem_func, obj, boost::placeholders::_1), blackout);
195 }
196
197 // wildcard
198 void subscribe(const std::string& var_pattern, const std::string& app_pattern,
199 const InboxFunc& handler = InboxFunc(), double blackout = 0);
200
201 template <typename V, typename A1>
202 void subscribe(const std::string& var_pattern, const std::string& app_pattern,
203 void (V::*mem_func)(A1), V* obj, double blackout = 0)
204 {
205 subscribe(var_pattern, app_pattern, boost::bind(mem_func, obj, boost::placeholders::_1),
206 blackout);
207 }
208
209 template <typename V, typename ProtobufMessage>
210 void subscribe_pb(const std::string& var, void (V::*mem_func)(const ProtobufMessage&), V* obj,
211 double blackout = 0)
212 {
213 subscribe_pb<ProtobufMessage>(var, boost::bind(mem_func, obj, boost::placeholders::_1),
214 blackout);
215 }
216
217 template <typename ProtobufMessage>
218 void subscribe_pb(const std::string& var,
219 boost::function<void(const ProtobufMessage& msg)> handler,
220 double blackout = 0)
221 {
222 subscribe(var,
223 boost::bind(&goby::moos::protobuf_inbox<ProtobufMessage>, boost::placeholders::_1,
224 handler),
225 blackout);
226 }
227
228 void register_timer(int period_seconds, const boost::function<void()>& handler)
229 {
230 int now = (goby::time::SystemClock::now<goby::time::SITime>() / boost::units::si::seconds) /
231 period_seconds;
232 now *= period_seconds;
233
234 SynchronousLoop new_loop;
235 new_loop.unix_next = now + period_seconds;
236 new_loop.period_seconds = period_seconds;
237 new_loop.handler = handler;
238 synchronous_loops_.push_back(new_loop);
239 }
240
241 template <typename V> void register_timer(int period_seconds, void (V::*mem_func)(), V* obj)
242 {
243 register_timer(period_seconds, boost::bind(mem_func, obj));
244 }
245
246 template <typename App> friend int ::goby::moos::run(int argc, char* argv[]);
247
248 virtual void loop() = 0;
249
250 bool ignore_stale() { return ignore_stale_; }
251 void set_ignore_stale(bool b) { ignore_stale_ = b; }
252
253 bool dynamic_moos_vars_enabled() { return dynamic_moos_vars_enabled_; }
254 void set_dynamic_moos_vars_enabled(bool b) { dynamic_moos_vars_enabled_ = b; }
255
256 std::pair<std::string, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique>
257 parse_type_technique(const std::string& type_and_technique)
258 {
259 std::string protobuf_type;
261 if (!type_and_technique.empty())
262 {
263 std::string::size_type colon_pos = type_and_technique.find(':');
264
265 if (colon_pos != std::string::npos)
266 {
267 protobuf_type = type_and_technique.substr(0, colon_pos);
268 std::string str_technique = type_and_technique.substr(colon_pos + 1);
269
271 str_technique, &technique))
272 throw(std::runtime_error("Invalid technique string"));
273 }
274 else
275 {
276 throw std::runtime_error("Missing colon (:)");
277 }
278 return std::make_pair(protobuf_type, technique);
279 }
280 else
281 {
282 throw std::runtime_error("Empty technique string");
283 }
284 }
285
286 private:
287 // from CMOOSApp
288 bool Iterate() override;
289 bool OnStartUp() override;
290 bool OnConnectToServer() override;
291 bool OnDisconnectFromServer() override;
292 bool OnNewMail(MOOSMSG_LIST& NewMail) override;
293 void try_subscribing();
294 void do_subscriptions();
295
296 int fetch_moos_globals(google::protobuf::Message* msg, CMOOSFileReader& moos_file_reader);
297
298 void read_configuration(google::protobuf::Message* cfg);
299 void process_configuration();
300
301 private:
302 // when we started (seconds since UNIX)
303 double start_time_;
304
305 // have we read the configuration file fully?
306 bool configuration_read_;
307 bool cout_cleared_;
308
309 std::ofstream fout_;
310
311 // allows direct reading of newest publish to a given MOOS variable
312 goby::moos::DynamicMOOSVars dynamic_vars_;
313
314 std::map<std::string, std::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)>>>
315 mail_handlers_;
316
317 std::map<std::pair<std::string, std::string>,
318 std::shared_ptr<boost::signals2::signal<void(const CMOOSMsg& msg)>>>
319 wildcard_mail_handlers_;
320
321 // CMOOSApp::OnConnectToServer()
322 bool connected_;
323 // CMOOSApp::OnStartUp()
324 bool started_up_;
325
326 std::deque<CMOOSMsg> msg_buffer_;
327
328 // MOOS Variable name, blackout time
329 std::deque<std::pair<std::string, double>> pending_subscriptions_;
330 std::deque<std::pair<std::string, double>> existing_subscriptions_;
331
332 // MOOS Variable pattern, MOOS App pattern, blackout time
333 std::deque<std::pair<std::pair<std::string, std::string>, double>>
334 wildcard_pending_subscriptions_;
335 std::deque<std::pair<std::pair<std::string, std::string>, double>>
336 wildcard_existing_subscriptions_;
337
338 struct SynchronousLoop
339 {
340 double unix_next;
341 int period_seconds;
342 boost::function<void()> handler;
343 };
344
345 std::vector<SynchronousLoop> synchronous_loops_;
346
347 protobuf::GobyMOOSAppConfig common_cfg_;
348
349 bool ignore_stale_;
350
351 bool dynamic_moos_vars_enabled_;
352
353 static int argc_;
354 static char** argv_;
355 static std::string mission_file_;
356 static std::string application_name_;
357};
358
360{
361 public:
362 template <typename ProtobufConfig>
363 explicit GobyMOOSApp(ProtobufConfig* cfg) : GobyMOOSAppSelector<>(cfg)
364 {
365 }
366};
367} // namespace moos
368} // namespace goby
369
370template <class MOOSAppType>
372
373template <class MOOSAppType>
375
376template <class MOOSAppType> int goby::moos::GobyMOOSAppSelector<MOOSAppType>::argc_ = 0;
377template <class MOOSAppType> char** goby::moos::GobyMOOSAppSelector<MOOSAppType>::argv_ = nullptr;
378
379template <class MOOSAppType> bool goby::moos::GobyMOOSAppSelector<MOOSAppType>::Iterate()
380{
381 MOOSAppType::Iterate();
382
383 if (!configuration_read_)
384 return true;
385
386 // clear out MOOSApp cout for ncurses "scope" mode
387 // MOOS has stopped talking by first Iterate()
388 if (!cout_cleared_)
389 {
391 cout_cleared_ = true;
392 }
393
394 while (!msg_buffer_.empty() && (connected_ && started_up_))
395 {
397 goby::glog << "writing from buffer: " << msg_buffer_.front().GetKey() << ": "
398 << msg_buffer_.front().GetAsString() << std::endl;
399
400 MOOSAppType::m_Comms.Post(msg_buffer_.front());
401 msg_buffer_.pop_front();
402 }
403
404 loop();
405
406 if (synchronous_loops_.size())
407 {
408 double now = goby::time::SystemClock::now<goby::time::SITime>() / boost::units::si::seconds;
409 for (typename std::vector<SynchronousLoop>::iterator it = synchronous_loops_.begin(),
410 end = synchronous_loops_.end();
411 it != end; ++it)
412 {
413 SynchronousLoop& loop = *it;
414 if (loop.unix_next <= now)
415 {
416 loop.handler();
417 loop.unix_next += loop.period_seconds;
418
419 // fix jumps forward in time
420 if (loop.unix_next < now)
421 loop.unix_next = now + loop.period_seconds;
422 }
423
424 // fix jumps backwards in time
425 if (loop.unix_next > (now + 2 * loop.period_seconds))
426 loop.unix_next = now + loop.period_seconds;
427 }
428 }
429
430 return true;
431}
432
433template <class MOOSAppType>
435{
436 // for AppCasting (otherwise no-op)
437 MOOSAppType::OnNewMail(NewMail);
438
439 for (const auto& msg : NewMail)
440 {
442 goby::glog << "Received mail: " << msg.GetKey() << ", time: " << std::setprecision(15)
443 << msg.GetTime() << std::endl;
444
445 // update dynamic moos variables - do this inside the loop so the newest is
446 // also the one referenced in the call to inbox()
447 if (dynamic_moos_vars_enabled_)
448 dynamic_vars().update_moos_vars(msg);
449
450 if (msg.GetTime() < start_time_ && ignore_stale_)
451 {
453 goby::glog << "ignoring normal mail from " << msg.GetKey()
454 << " from before we started (dynamics still updated)" << std::endl;
455 }
456 else if (mail_handlers_.count(msg.GetKey()))
457 (*mail_handlers_[msg.GetKey()])(msg);
458
459 for (auto& wildcard_mail_handler : wildcard_mail_handlers_)
460 {
461 if (MOOSWildCmp(wildcard_mail_handler.first.first, msg.GetKey()) &&
462 MOOSWildCmp(wildcard_mail_handler.first.second, msg.GetSource()))
463 (*(wildcard_mail_handler.second))(msg);
464 }
465 }
466
467 return true;
468}
469
470template <class MOOSAppType>
472{
473 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", disconnected from server."
474 << std::endl;
475 connected_ = false;
476 pending_subscriptions_.insert(pending_subscriptions_.end(), existing_subscriptions_.begin(),
477 existing_subscriptions_.end());
478 existing_subscriptions_.clear();
479 wildcard_pending_subscriptions_.insert(wildcard_pending_subscriptions_.end(),
480 wildcard_existing_subscriptions_.begin(),
481 wildcard_existing_subscriptions_.end());
482 wildcard_existing_subscriptions_.clear();
483 return true;
484}
485
487{
488 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", connected to server." << std::endl;
489 connected_ = true;
490 try_subscribing();
491
492 for (const auto& ini : common_cfg_.initializer())
493 {
494 if (ini.has_global_cfg_var())
495 {
496 std::string result;
497 if (MOOSAppType::m_MissionReader.GetValue(ini.global_cfg_var(), result))
498 {
499 if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_DOUBLE)
500 publish(ini.moos_var(), goby::util::as<double>(result));
501 else if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_STRING)
502 publish(ini.moos_var(), ini.trim() ? boost::trim_copy(result) : result);
503 }
504 }
505 else
506 {
507 if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_DOUBLE)
508 publish(ini.moos_var(), ini.dval());
509 else if (ini.type() == protobuf::GobyMOOSAppConfig::Initializer::INI_STRING)
510 publish(ini.moos_var(), ini.trim() ? boost::trim_copy(ini.sval()) : ini.sval());
511 }
512 }
513
514 return true;
515}
516
517template <class MOOSAppType> bool goby::moos::GobyMOOSAppSelector<MOOSAppType>::OnStartUp()
518{
519 MOOSAppType::OnStartUp();
520
521 std::cout << MOOSAppType::m_MissionReader.GetAppName() << ", starting ..." << std::endl;
522 CMOOSApp::SetCommsFreq(common_cfg_.comm_tick());
523 CMOOSApp::SetAppFreq(common_cfg_.app_tick());
524 started_up_ = true;
525 try_subscribing();
526 return true;
527}
528
529template <class MOOSAppType>
531 const InboxFunc& handler,
532 double blackout /* = 0 */)
533{
535 goby::glog << "subscribing for MOOS variable: " << var << " @ " << blackout << std::endl;
536
537 pending_subscriptions_.emplace_back(var, blackout);
538 try_subscribing();
539
540 if (!mail_handlers_[var])
541 mail_handlers_[var].reset(new boost::signals2::signal<void(const CMOOSMsg& msg)>);
542
543 if (handler)
544 mail_handlers_[var]->connect(handler);
545}
546
547template <class MOOSAppType>
549 const std::string& app_pattern,
550 const InboxFunc& handler,
551 double blackout /* = 0 */)
552{
554 goby::glog << "wildcard subscribing for MOOS variable pattern: " << var_pattern
555 << ", app pattern: " << app_pattern << " @ " << blackout << std::endl;
556
557 std::pair<std::string, std::string> key = std::make_pair(var_pattern, app_pattern);
558 wildcard_pending_subscriptions_.emplace_back(key, blackout);
559 try_subscribing();
560
561 if (!wildcard_mail_handlers_.count(key))
562 wildcard_mail_handlers_.insert(std::make_pair(
563 key, std::make_shared<boost::signals2::signal<void(const CMOOSMsg& msg)>>()));
564
565 if (handler)
566 wildcard_mail_handlers_[key]->connect(handler);
567}
568
570{
571 if (connected_ && started_up_)
572 do_subscriptions();
573}
574
576{
577 MOOSAppType::RegisterVariables();
578
579 while (!pending_subscriptions_.empty())
580 {
581 // variable name, blackout
582 if (MOOSAppType::m_Comms.Register(pending_subscriptions_.front().first,
583 pending_subscriptions_.front().second))
584 {
586 goby::glog << "subscribed for: " << pending_subscriptions_.front().first
587 << std::endl;
588 }
589 else
590 {
592 goby::glog << "failed to subscribe for: " << pending_subscriptions_.front().first
593 << std::endl;
594 }
595 existing_subscriptions_.push_back(pending_subscriptions_.front());
596 pending_subscriptions_.pop_front();
597 }
598
599 while (!wildcard_pending_subscriptions_.empty())
600 {
601 // variable name, blackout
602 if (MOOSAppType::m_Comms.Register(wildcard_pending_subscriptions_.front().first.first,
603 wildcard_pending_subscriptions_.front().first.second,
604 wildcard_pending_subscriptions_.front().second))
605 {
607 goby::glog << "subscribed for: "
608 << wildcard_pending_subscriptions_.front().first.first << ":"
609 << wildcard_pending_subscriptions_.front().first.second << std::endl;
610 }
611 else
612 {
614 goby::glog << "failed to subscribe for: "
615 << wildcard_pending_subscriptions_.front().first.first << ":"
616 << wildcard_pending_subscriptions_.front().first.second << std::endl;
617 }
618
619 wildcard_existing_subscriptions_.push_back(wildcard_pending_subscriptions_.front());
620 wildcard_pending_subscriptions_.pop_front();
621 }
622}
623
624template <class MOOSAppType>
626 google::protobuf::Message* msg, CMOOSFileReader& moos_file_reader)
627{
628 int globals = 0;
629 const google::protobuf::Descriptor* desc = msg->GetDescriptor();
630 const google::protobuf::Reflection* refl = msg->GetReflection();
631
632 for (int i = 0, n = desc->field_count(); i < n; ++i)
633 {
634 const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
635
636 // we don't support repeated fields or oneof fields containing MOOS globals
637 if (field_desc->is_repeated() || field_desc->containing_oneof())
638 continue;
639
640 std::string moos_global = field_desc->options().GetExtension(goby::field).moos_global();
641
642 switch (field_desc->cpp_type())
643 {
644 case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
645 {
646 bool message_was_empty = !refl->HasField(*msg, field_desc);
647 int set_globals =
648 fetch_moos_globals(refl->MutableMessage(msg, field_desc), moos_file_reader);
649 if (set_globals == 0 && message_was_empty)
650 refl->ClearField(msg, field_desc);
651
652 break;
653 }
654
655 case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
656 {
657 int result;
658 if (moos_file_reader.GetValue(moos_global, result))
659 {
660 refl->SetInt32(msg, field_desc, result);
661 ++globals;
662 }
663
664 break;
665 }
666
667 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
668 {
669 int result;
670 if (moos_file_reader.GetValue(moos_global, result))
671 {
672 refl->SetInt64(msg, field_desc, result);
673 ++globals;
674 }
675 break;
676 }
677
678 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
679 {
680 unsigned result;
681 if (moos_file_reader.GetValue(moos_global, result))
682 {
683 refl->SetUInt32(msg, field_desc, result);
684 ++globals;
685 }
686
687 break;
688 }
689
690 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
691 {
692 unsigned result;
693 if (moos_file_reader.GetValue(moos_global, result))
694 {
695 refl->SetUInt64(msg, field_desc, result);
696 ++globals;
697 }
698 break;
699 }
700
701 case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
702 {
703 enum Result
704 {
705 RESULT_TRUE = 1,
706 RESULT_FALSE = 0,
707 RESULT_UNSPECIFIED = -1
708 };
709
710 Result result = RESULT_UNSPECIFIED;
711 // avoid parsing pLogger "LOG = some string" as "log = false"
712 std::string svalue;
713 if (moos_file_reader.GetValue(moos_global, svalue))
714 {
715 if (MOOSStrCmp(svalue, "TRUE"))
716 result = RESULT_TRUE;
717 else if (MOOSStrCmp(svalue, "FALSE"))
718 result = RESULT_FALSE;
719 else if (MOOSIsNumeric(svalue))
720 result = atof(svalue.c_str()) > 0 ? RESULT_TRUE : RESULT_FALSE;
721 }
722 if (result != RESULT_UNSPECIFIED)
723 {
724 refl->SetBool(msg, field_desc, result);
725 ++globals;
726 }
727 break;
728 }
729
730 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
731 {
732 std::string result;
733 if (moos_file_reader.GetValue(moos_global, result))
734 {
735 refl->SetString(msg, field_desc, result);
736 ++globals;
737 }
738
739 break;
740 }
741
742 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
743 {
744 float result;
745 if (moos_file_reader.GetValue(moos_global, result))
746 {
747 refl->SetFloat(msg, field_desc, result);
748 ++globals;
749 }
750
751 break;
752 }
753
754 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
755 {
756 double result;
757 if (moos_file_reader.GetValue(moos_global, result))
758 {
759 refl->SetDouble(msg, field_desc, result);
760 ++globals;
761 }
762 break;
763 }
764
765 case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
766 {
767 std::string result;
768 if (moos_file_reader.GetValue(moos_global, result))
769 {
770 const google::protobuf::EnumValueDescriptor* enum_desc =
771 refl->GetEnum(*msg, field_desc)->type()->FindValueByName(result);
772 if (!enum_desc)
773 throw(std::runtime_error(std::string("invalid enumeration " + result +
774 " for field " + field_desc->name())));
775
776 refl->SetEnum(msg, field_desc, enum_desc);
777 ++globals;
778 }
779 break;
780 }
781 }
782 }
783 return globals;
784}
785
786template <class MOOSAppType>
789{
790 boost::filesystem::path launch_path(argv_[0]);
791
792#if BOOST_FILESYSTEM_VERSION == 3
793 std::string binary_name = launch_path.filename().string();
794#else
795 std::string binary_name = launch_path.filename();
796#endif
797 application_name_ = binary_name;
798
799 //
800 // READ CONFIGURATION
801 //
802
803 boost::program_options::options_description od_all;
804 boost::program_options::variables_map var_map, po_env_var_map;
805 try
806 {
807 boost::program_options::options_description od_cli_only(
808 "Options given on command line only");
809 od_cli_only.add_options()("help,h", "writes this help message")(
810 "moos_file,c", boost::program_options::value<std::string>(&mission_file_),
811 "path to .moos file")("moos_name,a",
812 boost::program_options::value<std::string>(&application_name_),
813 "name to register with MOOS")(
814 "example_config,e", "writes an example .moos ProcessConfig block")(
815 "version,V", "writes the current version");
816
818 boost::program_options::options_description>
819 od_map;
820
821 std::string od_pb_always_desc =
822 "Options typically given in the .moos file, but may be specified on the command line";
823 std::string od_pb_never_desc = "Hidden options";
824 std::string od_pb_advanced_desc = "Advanced options";
825 std::string od_pb_developer_desc = "Developer options";
826 od_map.insert(
828 boost::program_options::options_description(od_pb_always_desc.c_str())));
829 od_map.insert(std::make_pair(
831 boost::program_options::options_description(od_pb_advanced_desc.c_str())));
832 od_map.insert(std::make_pair(
834 boost::program_options::options_description(od_pb_developer_desc.c_str())));
835 od_map.insert(
837 boost::program_options::options_description(od_pb_never_desc.c_str())));
838
839 std::map<std::string, std::string> environmental_var_map;
841 environmental_var_map);
842 std::vector<goby::middleware::ConfigReader::PositionalOption> positional_options;
844 positional_options);
845
846 for (const auto& od_p : od_map) od_all.add(od_p.second);
847 od_all.add(od_cli_only);
848
849 boost::program_options::positional_options_description p;
850 p.add("moos_file", 1);
851 p.add("moos_name", 1);
852 for (const auto& po : positional_options) { p.add(po.name.c_str(), po.position_max_count); }
853
854 boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_)
855 .options(od_all)
856 .positional(p)
857 .run(),
858 var_map);
859
860 if (!environmental_var_map.empty())
861 {
862 boost::program_options::store(
863 boost::program_options::parse_environment(
864 od_all,
865 [&environmental_var_map](const std::string& i_env_var) -> std::string {
866 return environmental_var_map.count(i_env_var)
867 ? environmental_var_map.at(i_env_var)
868 : "";
869 }),
870 po_env_var_map);
871 }
872
873 boost::program_options::notify(var_map);
874 boost::program_options::notify(po_env_var_map);
875
876 if (var_map.count("help"))
877 {
878 std::cerr << "Usage: " << binary_name << " [options] moos_file [moos_name]"
879 << std::endl;
880
881 std::cerr << od_map[goby::GobyFieldOptions::ConfigurationOptions::ALWAYS] << "\n";
882 std::cerr << od_cli_only << "\n";
883 exit(EXIT_SUCCESS);
884 }
885 else if (var_map.count("example_config"))
886 {
887 std::cout << "ProcessConfig = " << application_name_ << "\n{";
889 std::cout << "}" << std::endl;
890 exit(EXIT_SUCCESS);
891 }
892 else if (var_map.count("version"))
893 {
895 exit(EXIT_SUCCESS);
896 }
897
898 goby::glog.set_name(application_name_);
900
901 std::string protobuf_text;
902 std::ifstream fin;
903 fin.open(mission_file_.c_str());
904 if (fin.is_open())
905 {
906 std::string line;
907 bool in_process_config = false;
908 while (getline(fin, line))
909 {
910 std::string no_blanks_line = boost::algorithm::erase_all_copy(line, " ");
911 if (boost::algorithm::iequals(no_blanks_line, "PROCESSCONFIG=" + application_name_))
912 {
913 in_process_config = true;
914 }
915 else if (in_process_config &&
916 !boost::algorithm::ifind_first(line, "PROCESSCONFIG").empty())
917 {
918 break;
919 }
920
921 if (in_process_config)
922 protobuf_text += line + "\n";
923 }
924
925 if (!in_process_config)
926 {
928 goby::glog << "no ProcessConfig block for " << application_name_ << std::endl;
929 }
930
931 // trim off "ProcessConfig = __ {"
932 protobuf_text.erase(0, protobuf_text.find_first_of('{') + 1);
933
934 // trim off last "}" and anything that follows
935 protobuf_text.erase(protobuf_text.find_last_of('}'));
936
937 // convert "//" to "#" for comments
938 boost::algorithm::replace_all(protobuf_text, "//", "#");
939
940 google::protobuf::TextFormat::Parser parser;
941 goby::util::FlexOStreamErrorCollector error_collector(protobuf_text);
942 parser.RecordErrorsTo(&error_collector);
943 parser.AllowPartialMessage(true);
944 parser.ParseFromString(protobuf_text, cfg);
945
946 if (error_collector.has_errors() || error_collector.has_warnings())
947 {
949 goby::glog << "fatal configuration errors (see above)" << std::endl;
950 }
951 }
952 else
953 {
954 goby::glog.is(goby::util::logger::WARN) && goby::glog << "failed to open "
955 << mission_file_ << std::endl;
956 }
957
958 fin.close();
959
960 CMOOSFileReader moos_file_reader;
961 moos_file_reader.SetFile(mission_file_);
962 fetch_moos_globals(cfg, moos_file_reader);
963
964 // add any environmental variable options that don't exist in the cfg file
965 for (const auto& p : po_env_var_map)
966 {
967 // let protobuf deal with the defaults
968 if (!p.second.defaulted())
970 p.second, false);
971 }
972
973 // add / overwrite any options that are specified in the cfg file with those given on the command line
974 for (const auto& p : var_map)
975 {
976 if (!p.second.defaulted())
978 p.second, true);
979 }
980
981 // now the proto message must have all required fields
982 if (!cfg->IsInitialized())
983 {
984 std::vector<std::string> errors;
985 cfg->FindInitializationErrors(&errors);
986
987 std::stringstream err_msg;
988 err_msg << "Configuration is missing required parameters: \n";
989 for (const std::string& s : errors)
990 err_msg << goby::util::esc_red << s << "\n" << goby::util::esc_nocolor;
991
992 err_msg << "Make sure you specified a proper .moos file";
993 throw(goby::middleware::ConfigException(err_msg.str()));
994 }
995 }
997 {
998 // output all the available command line options
999 std::cerr << od_all << "\n";
1000 std::cerr << "Problem parsing command-line configuration: \n" << e.what() << "\n";
1001
1002 throw;
1003 }
1004}
1005
1006template <class MOOSAppType>
1008{
1009 //
1010 // PROCESS CONFIGURATION
1011 //
1012 goby::glog.add_stream(common_cfg_.verbosity(), &std::cout);
1013 if (common_cfg_.show_gui())
1014 {
1016 }
1017
1018 if (common_cfg_.log())
1019 {
1020 if (!common_cfg_.has_log_path())
1021 {
1023 goby::glog << "logging all terminal output to default directory ("
1024 << common_cfg_.log_path() << ")."
1025 << "set log_path for another path " << std::endl;
1026 }
1027
1028 if (!common_cfg_.log_path().empty())
1029 {
1030 using namespace boost::posix_time;
1031 std::string file_name_base = boost::replace_all_copy(application_name_, "/", "_") +
1032 "_" + common_cfg_.community();
1033
1034 std::string file_name =
1035 file_name_base +
1036 (common_cfg_.log_omit_file_timestamp()
1037 ? ""
1038 : std::string("_") + to_iso_string(second_clock::universal_time())) +
1039 ".txt";
1040
1042 goby::glog << "logging output to file: " << file_name << std::endl;
1043
1044 fout_.open(std::string(common_cfg_.log_path() + "/" + file_name).c_str());
1045
1046 if (!common_cfg_.log_omit_latest_symlink())
1047 {
1048 std::string file_symlink = file_name_base + "_latest.txt";
1049 // symlink to "latest.txt"
1050 remove(std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1051 symlink(file_name.c_str(),
1052 std::string(common_cfg_.log_path() + "/" + file_symlink).c_str());
1053 }
1054
1055 // if fails, try logging to this directory
1056 if (!fout_.is_open())
1057 {
1058 fout_.open(std::string("./" + file_name).c_str());
1061 << "logging to current directory because given directory is unwritable!"
1062 << std::endl;
1063 }
1064 // if still no go, quit
1065 if (!fout_.is_open())
1066 {
1068 goby::glog << "cannot write to current directory, so cannot log." << std::endl;
1069 }
1070
1071 goby::glog.add_stream(common_cfg_.log_verbosity(), &fout_);
1072 }
1073 }
1074
1075 goby::moos::set_moos_technique(common_cfg_);
1076
1077 if (common_cfg_.time_warp_multiplier() != 1)
1078 {
1079 goby::time::SimulatorSettings::warp_factor = common_cfg_.time_warp_multiplier();
1082 std::chrono::system_clock::time_point(std::chrono::seconds(0));
1083 start_time_ *= common_cfg_.time_warp_multiplier();
1084 }
1085}
1086
1087// designed to run CMOOSApp derived applications
1088// using the MOOS "convention" of argv[1] == mission file, argv[2] == alternative name
1089template <typename App> int goby::moos::run(int argc, char* argv[])
1090{
1091 App::argc_ = argc;
1092 App::argv_ = argv;
1093
1094 try
1095 {
1096 App* app = App::get_instance();
1097 app->Run(App::application_name_.c_str(), App::mission_file_.c_str());
1098 }
1100 {
1101 // no further warning as the ApplicationBase Ctor handles this
1102 return 1;
1103 }
1104 catch (std::exception& e)
1105 {
1106 // some other exception
1107 goby::glog.is(goby::util::logger::DIE) && goby::glog << "uncaught exception: " << e.what()
1108 << std::endl;
1109 return 2;
1110 }
1111
1112 return 0;
1113}
1114
1115#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:318
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