27 #ifndef GOBY_MOOS_GOBY_MOOS_APP_H
28 #define GOBY_MOOS_GOBY_MOOS_APP_H
44 #include <MOOS/libMOOS/App/MOOSApp.h>
45 #include <MOOS/libMOOS/Comms/CommsTypes.h>
46 #include <MOOS/libMOOS/Comms/MOOSMsg.h>
47 #include <MOOS/libMOOS/Utils/MOOSFileReader.h>
48 #include <MOOS/libMOOS/Utils/MOOSUtilityFunctions.h>
49 #include <boost/algorithm/string/erase.hpp>
50 #include <boost/algorithm/string/find.hpp>
51 #include <boost/algorithm/string/predicate.hpp>
52 #include <boost/algorithm/string/replace.hpp>
53 #include <boost/algorithm/string/trim.hpp>
54 #include <boost/bind/bind.hpp>
55 #include <boost/date_time/posix_time/posix_time_config.hpp>
56 #include <boost/date_time/posix_time/posix_time_types.hpp>
57 #include <boost/date_time/posix_time/time_formatters.hpp>
58 #include <boost/filesystem.hpp>
59 #include <boost/function.hpp>
60 #include <boost/lexical_cast/bad_lexical_cast.hpp>
61 #include <boost/program_options/options_description.hpp>
62 #include <boost/program_options/parsers.hpp>
63 #include <boost/program_options/positional_options.hpp>
64 #include <boost/program_options/value_semantic.hpp>
65 #include <boost/program_options/variables_map.hpp>
66 #include <boost/range/iterator_range_core.hpp>
67 #include <boost/signals2/signal.hpp>
68 #include <boost/smart_ptr/shared_ptr.hpp>
69 #include <boost/units/quantity.hpp>
71 #include <google/protobuf/descriptor.h>
72 #include <google/protobuf/descriptor.pb.h>
74 #include <google/protobuf/text_format.h>
100 template <
typename App>
int run(
int argc,
char* argv[]);
102 template <
typename ProtobufMessage>
104 boost::function<
void(
const ProtobufMessage&
msg)> handler)
106 ProtobufMessage pb_msg;
119 bool OnNewMail(MOOSMSG_LIST& )
override {
return true; }
129 template <
typename ProtobufConfig>
131 : start_time_(MOOSTime()),
132 configuration_read_(false),
133 cout_cleared_(false),
137 dynamic_moos_vars_enabled_(true)
141 read_configuration(cfg);
144 common_cfg_ = cfg->common();
145 configuration_read_ =
true;
147 process_configuration();
154 template <
typename ProtobufMessage>
157 std::string serialized;
161 msg.GetDescriptor()->full_name());
167 if (connected_ && started_up_)
168 MOOSAppType::m_Comms.Post(
msg);
170 msg_buffer_.push_back(
msg);
173 void publish(
const std::string& key,
const std::string& value)
179 void publish(
const std::string& key,
double value)
189 double blackout = 0);
191 template <
typename V,
typename A1>
192 void subscribe(
const std::string& var,
void (V::*mem_func)(A1), V* obj,
double blackout = 0)
198 void subscribe(
const std::string& var_pattern,
const std::string& app_pattern,
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)
209 template <
typename V,
typename ProtobufMessage>
210 void subscribe_pb(
const std::string& var,
void (V::*mem_func)(
const ProtobufMessage&), V* obj,
213 subscribe_pb<ProtobufMessage>(var,
boost::bind(mem_func, obj, boost::placeholders::_1),
217 template <
typename ProtobufMessage>
219 boost::function<
void(
const ProtobufMessage&
msg)> handler,
223 boost::bind(&goby::moos::protobuf_inbox<ProtobufMessage>, boost::placeholders::_1,
230 int now = (goby::time::SystemClock::now<goby::time::SITime>() / boost::units::si::seconds) /
232 now *= period_seconds;
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);
241 template <
typename V>
void register_timer(
int period_seconds,
void (V::*mem_func)(), V* obj)
248 virtual void loop() = 0;
256 std::pair<std::string, goby::moos::protobuf::TranslatorEntry::ParserSerializerTechnique>
259 std::string protobuf_type;
261 if (!type_and_technique.empty())
263 std::string::size_type colon_pos = type_and_technique.find(
':');
265 if (colon_pos != std::string::npos)
267 protobuf_type = type_and_technique.substr(0, colon_pos);
268 std::string str_technique = type_and_technique.substr(colon_pos + 1);
271 str_technique, &technique))
272 throw(std::runtime_error(
"Invalid technique string"));
276 throw std::runtime_error(
"Missing colon (:)");
278 return std::make_pair(protobuf_type, technique);
282 throw std::runtime_error(
"Empty technique string");
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();
299 void process_configuration();
306 bool configuration_read_;
314 std::map<std::string, std::shared_ptr<boost::signals2::signal<
void(
const CMOOSMsg&
msg)>>>
317 std::map<std::pair<std::string, std::string>,
319 wildcard_mail_handlers_;
326 std::deque<CMOOSMsg> msg_buffer_;
329 std::deque<std::pair<std::string, double>> pending_subscriptions_;
330 std::deque<std::pair<std::string, double>> existing_subscriptions_;
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_;
342 boost::function<
void()> handler;
345 std::vector<SynchronousLoop> synchronous_loops_;
347 protobuf::GobyMOOSAppConfig common_cfg_;
351 bool dynamic_moos_vars_enabled_;
355 static std::string mission_file_;
356 static std::string application_name_;
362 template <
typename ProtobufConfig>
370 template <
class MOOSAppType>
373 template <
class MOOSAppType>
381 MOOSAppType::Iterate();
383 if (!configuration_read_)
391 cout_cleared_ =
true;
394 while (!msg_buffer_.empty() && (connected_ && started_up_))
397 goby::glog <<
"writing from buffer: " << msg_buffer_.front().GetKey() <<
": "
398 << msg_buffer_.front().GetAsString() << std::endl;
400 MOOSAppType::m_Comms.Post(msg_buffer_.front());
401 msg_buffer_.pop_front();
406 if (synchronous_loops_.size())
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();
414 if (loop.unix_next <= now)
417 loop.unix_next += loop.period_seconds;
420 if (loop.unix_next < now)
421 loop.unix_next = now + loop.period_seconds;
425 if (loop.unix_next > (now + 2 * loop.period_seconds))
426 loop.unix_next = now + loop.period_seconds;
433 template <
class MOOSAppType>
437 MOOSAppType::OnNewMail(NewMail);
439 for (
const auto&
msg : NewMail)
442 goby::glog <<
"Received mail: " <<
msg.GetKey() <<
", time: " << std::setprecision(15)
443 <<
msg.GetTime() << std::endl;
447 if (dynamic_moos_vars_enabled_)
448 dynamic_vars().update_moos_vars(
msg);
450 if (
msg.GetTime() < start_time_ && ignore_stale_)
454 <<
" from before we started (dynamics still updated)" << std::endl;
456 else if (mail_handlers_.count(
msg.GetKey()))
457 (*mail_handlers_[
msg.GetKey()])(
msg);
459 for (
auto& wildcard_mail_handler : wildcard_mail_handlers_)
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);
470 template <
class MOOSAppType>
473 std::cout << MOOSAppType::m_MissionReader.GetAppName() <<
", disconnected from server."
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();
488 std::cout << MOOSAppType::m_MissionReader.GetAppName() <<
", connected to server." << std::endl;
492 for (
const auto& ini : common_cfg_.initializer())
494 if (ini.has_global_cfg_var())
497 if (MOOSAppType::m_MissionReader.GetValue(ini.global_cfg_var(), result))
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)
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());
519 MOOSAppType::OnStartUp();
521 std::cout << MOOSAppType::m_MissionReader.GetAppName() <<
", starting ..." << std::endl;
522 CMOOSApp::SetCommsFreq(common_cfg_.comm_tick());
523 CMOOSApp::SetAppFreq(common_cfg_.app_tick());
529 template <
class MOOSAppType>
535 goby::glog <<
"subscribing for MOOS variable: " << var <<
" @ " << blackout << std::endl;
537 pending_subscriptions_.emplace_back(var, blackout);
540 if (!mail_handlers_[var])
541 mail_handlers_[var].reset(
new boost::signals2::signal<
void(
const CMOOSMsg&
msg)>);
544 mail_handlers_[var]->connect(handler);
547 template <
class MOOSAppType>
549 const std::string& app_pattern,
554 goby::glog <<
"wildcard subscribing for MOOS variable pattern: " << var_pattern
555 <<
", app pattern: " << app_pattern <<
" @ " << blackout << std::endl;
557 std::pair<std::string, std::string> key = std::make_pair(var_pattern, app_pattern);
558 wildcard_pending_subscriptions_.emplace_back(key, blackout);
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)>>()));
566 wildcard_mail_handlers_[key]->connect(handler);
571 if (connected_ && started_up_)
577 MOOSAppType::RegisterVariables();
579 while (!pending_subscriptions_.empty())
582 if (MOOSAppType::m_Comms.Register(pending_subscriptions_.front().first,
583 pending_subscriptions_.front().second))
586 goby::glog <<
"subscribed for: " << pending_subscriptions_.front().first
592 goby::glog <<
"failed to subscribe for: " << pending_subscriptions_.front().first
595 existing_subscriptions_.push_back(pending_subscriptions_.front());
596 pending_subscriptions_.pop_front();
599 while (!wildcard_pending_subscriptions_.empty())
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))
608 << wildcard_pending_subscriptions_.front().first.first <<
":"
609 << wildcard_pending_subscriptions_.front().first.second << std::endl;
615 << wildcard_pending_subscriptions_.front().first.first <<
":"
616 << wildcard_pending_subscriptions_.front().first.second << std::endl;
619 wildcard_existing_subscriptions_.push_back(wildcard_pending_subscriptions_.front());
620 wildcard_pending_subscriptions_.pop_front();
624 template <
class MOOSAppType>
629 const google::protobuf::Descriptor* desc =
msg->GetDescriptor();
632 for (
int i = 0, n = desc->field_count(); i < n; ++i)
634 const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
637 if (field_desc->is_repeated() || field_desc->containing_oneof())
640 std::string moos_global = field_desc->options().GetExtension(
goby::field).moos_global();
642 switch (field_desc->cpp_type())
644 case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
646 bool message_was_empty = !refl->
HasField(*
msg, field_desc);
649 if (set_globals == 0 && message_was_empty)
655 case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
658 if (moos_file_reader.GetValue(moos_global, result))
667 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
670 if (moos_file_reader.GetValue(moos_global, result))
678 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
681 if (moos_file_reader.GetValue(moos_global, result))
690 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
693 if (moos_file_reader.GetValue(moos_global, result))
701 case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
707 RESULT_UNSPECIFIED = -1
710 Result result = RESULT_UNSPECIFIED;
713 if (moos_file_reader.GetValue(moos_global, svalue))
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;
722 if (result != RESULT_UNSPECIFIED)
730 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
733 if (moos_file_reader.GetValue(moos_global, result))
742 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
745 if (moos_file_reader.GetValue(moos_global, result))
754 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
757 if (moos_file_reader.GetValue(moos_global, result))
765 case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
768 if (moos_file_reader.GetValue(moos_global, result))
770 const google::protobuf::EnumValueDescriptor* enum_desc =
771 refl->
GetEnum(*
msg, field_desc)->type()->FindValueByName(result);
773 throw(std::runtime_error(std::string(
"invalid enumeration " + result +
774 " for field " + field_desc->name())));
786 template <
class MOOSAppType>
790 boost::filesystem::path launch_path(argv_[0]);
792 #if BOOST_FILESYSTEM_VERSION == 3
793 std::string binary_name = launch_path.filename().string();
795 std::string binary_name = launch_path.filename();
797 application_name_ = binary_name;
803 boost::program_options::options_description od_all;
804 boost::program_options::variables_map var_map, po_env_var_map;
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");
818 boost::program_options::options_description>
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";
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())));
837 boost::program_options::options_description(od_pb_never_desc.c_str())));
839 std::map<std::string, std::string> environmental_var_map;
841 environmental_var_map);
842 std::vector<goby::middleware::ConfigReader::PositionalOption> positional_options;
846 for (
const auto& od_p : od_map) od_all.add(od_p.second);
847 od_all.add(od_cli_only);
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); }
854 boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_)
860 if (!environmental_var_map.empty())
862 boost::program_options::store(
863 boost::program_options::parse_environment(
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)
873 boost::program_options::notify(var_map);
874 boost::program_options::notify(po_env_var_map);
876 if (var_map.count(
"help"))
878 std::cerr <<
"Usage: " << binary_name <<
" [options] moos_file [moos_name]"
882 std::cerr << od_cli_only <<
"\n";
885 else if (var_map.count(
"example_config"))
887 std::cout <<
"ProcessConfig = " << application_name_ <<
"\n{";
889 std::cout <<
"}" << std::endl;
892 else if (var_map.count(
"version"))
901 std::string protobuf_text;
903 fin.open(mission_file_.c_str());
907 bool in_process_config =
false;
908 while (getline(fin, line))
910 std::string no_blanks_line = boost::algorithm::erase_all_copy(line,
" ");
911 if (boost::algorithm::iequals(no_blanks_line,
"PROCESSCONFIG=" + application_name_))
913 in_process_config =
true;
915 else if (in_process_config &&
916 !boost::algorithm::ifind_first(line,
"PROCESSCONFIG").empty())
921 if (in_process_config)
922 protobuf_text += line +
"\n";
925 if (!in_process_config)
928 goby::glog <<
"no ProcessConfig block for " << application_name_ << std::endl;
932 protobuf_text.erase(0, protobuf_text.find_first_of(
'{') + 1);
935 protobuf_text.erase(protobuf_text.find_last_of(
'}'));
938 boost::algorithm::replace_all(protobuf_text,
"//",
"#");
940 google::protobuf::TextFormat::Parser parser;
942 parser.RecordErrorsTo(&error_collector);
943 parser.AllowPartialMessage(
true);
944 parser.ParseFromString(protobuf_text, cfg);
946 if (error_collector.has_errors() || error_collector.has_warnings())
949 goby::glog <<
"fatal configuration errors (see above)" << std::endl;
955 << mission_file_ << std::endl;
960 CMOOSFileReader moos_file_reader;
961 moos_file_reader.SetFile(mission_file_);
962 fetch_moos_globals(cfg, moos_file_reader);
965 for (
const auto& p : po_env_var_map)
968 if (!p.second.defaulted())
974 for (
const auto& p : var_map)
976 if (!p.second.defaulted())
984 std::vector<std::string> errors;
987 std::stringstream err_msg;
988 err_msg <<
"Configuration is missing required parameters: \n";
989 for (
const std::string& s : errors)
992 err_msg <<
"Make sure you specified a proper .moos file";
999 std::cerr << od_all <<
"\n";
1000 std::cerr <<
"Problem parsing command-line configuration: \n" <<
e.what() <<
"\n";
1006 template <
class MOOSAppType>
1013 if (common_cfg_.show_gui())
1018 if (common_cfg_.log())
1020 if (!common_cfg_.has_log_path())
1023 goby::glog <<
"logging all terminal output to default directory ("
1024 << common_cfg_.log_path() <<
")."
1025 <<
"set log_path for another path " << std::endl;
1028 if (!common_cfg_.log_path().empty())
1030 using namespace boost::posix_time;
1031 std::string file_name_base = boost::replace_all_copy(application_name_,
"/",
"_") +
1032 "_" + common_cfg_.community();
1034 std::string file_name =
1036 (common_cfg_.log_omit_file_timestamp()
1038 : std::string(
"_") + to_iso_string(second_clock::universal_time())) +
1042 goby::glog <<
"logging output to file: " << file_name << std::endl;
1044 fout_.open(std::string(common_cfg_.log_path() +
"/" + file_name).c_str());
1046 if (!common_cfg_.log_omit_latest_symlink())
1048 std::string file_symlink = file_name_base +
"_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());
1056 if (!fout_.is_open())
1058 fout_.open(std::string(
"./" + file_name).c_str());
1061 <<
"logging to current directory because given directory is unwritable!"
1065 if (!fout_.is_open())
1068 goby::glog <<
"cannot write to current directory, so cannot log." << std::endl;
1077 if (common_cfg_.time_warp_multiplier() != 1)
1082 std::chrono::system_clock::time_point(std::chrono::seconds(0));
1083 start_time_ *= common_cfg_.time_warp_multiplier();
1096 App* app = App::get_instance();
1097 app->Run(App::application_name_.c_str(), App::mission_file_.c_str());
1104 catch (std::exception&
e)