Goby Underwater Autonomy Project
Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
|
00001 // t. schneider tes@mit.edu 07.26.10 00002 // ocean engineering graudate student - mit / whoi joint program 00003 // massachusetts institute of technology (mit) 00004 // laboratory for autonomous marine sensing systems (lamss) 00005 // 00006 // This program is free software: you can redistribute it and/or modify 00007 // it under the terms of the GNU General Public License as published by 00008 // the Free Software Foundation, either version 3 of the License, or 00009 // (at your option) any later version. 00010 // 00011 // This software is distributed in the hope that it will be useful, 00012 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 // GNU General Public License for more details. 00015 // 00016 // You should have received a copy of the GNU General Public License 00017 // along with this software. If not, see <http://www.gnu.org/licenses/>. 00018 00019 #ifndef TESMOOSAPP20100726H 00020 #define TESMOOSAPP20100726H 00021 00022 #include "MOOSLIB/MOOSApp.h" 00023 00024 #include <boost/date_time/posix_time/posix_time.hpp> 00025 #include <map> 00026 #include <boost/function.hpp> 00027 #include <boost/algorithm/string.hpp> 00028 #include <boost/bind.hpp> 00029 #include <boost/filesystem.hpp> 00030 00031 #include "dynamic_moos_vars.h" 00032 #include "goby/util/logger.h" 00033 #include "tes_moos_app.pb.h" 00034 #include "goby/core/libcore/configuration_reader.h" 00035 #include "goby/core/libcore/exception.h" 00036 #include "moos_protobuf_helpers.h" 00037 #include "goby/version.h" 00038 00039 namespace goby 00040 { 00041 namespace moos 00042 { 00043 template<typename App> 00044 int run(int argc, char* argv[]); 00045 } 00046 } 00047 00048 class TesMoosApp : public CMOOSApp 00049 { 00050 protected: 00051 typedef boost::function<void (const CMOOSMsg& msg)> InboxFunc; 00052 00053 template<typename ProtobufConfig> 00054 explicit TesMoosApp(ProtobufConfig* cfg); 00055 00056 00057 virtual ~TesMoosApp() { } 00058 00059 void publish(CMOOSMsg& msg) 00060 { 00061 if(connected_) 00062 m_Comms.Post(msg); 00063 else 00064 msg_buffer_.push_back(msg); 00065 } 00066 00067 void publish(const std::string& key, const std::string& value) 00068 { 00069 CMOOSMsg msg(MOOS_NOTIFY, key, value); 00070 publish(msg); 00071 } 00072 00073 void publish(const std::string& key, double value) 00074 { 00075 CMOOSMsg msg(MOOS_NOTIFY, key, value); 00076 publish(msg); 00077 } 00078 00079 tes::DynamicMOOSVars& dynamic_vars() { return dynamic_vars_; } 00080 double start_time() const { return start_time_; } 00081 00082 void subscribe(const std::string& var, 00083 InboxFunc handler, 00084 int blackout = 0); 00085 00086 template<typename V, typename A1> 00087 void subscribe(const std::string& var, 00088 void(V::*mem_func)(A1), 00089 V* obj, 00090 int blackout = 0) 00091 { subscribe(var, boost::bind(mem_func, obj, _1), blackout); } 00092 00093 00094 00095 template<typename App> 00096 friend int ::goby::moos::run(int argc, char* argv[]); 00097 00098 virtual void loop() = 0; 00099 00100 bool ignore_stale() { return ignore_stale_; } 00101 void set_ignore_stale(bool b) { ignore_stale_ = b; } 00102 00103 00104 private: 00105 // from CMOOSApp 00106 bool Iterate(); 00107 bool OnStartUp(); 00108 bool OnConnectToServer(); 00109 bool OnNewMail(MOOSMSG_LIST &NewMail); 00110 void try_subscribing(); 00111 void do_subscriptions(); 00112 00113 void fetch_moos_globals(google::protobuf::Message* msg, 00114 CMOOSFileReader& moos_file_reader); 00115 00116 private: 00117 00118 // when we started (seconds since UNIX) 00119 double start_time_; 00120 00121 // have we read the configuration file fully? 00122 bool configuration_read_; 00123 bool cout_cleared_; 00124 00125 std::ofstream fout_; 00126 00127 // allows direct reading of newest publish to a given MOOS variable 00128 tes::DynamicMOOSVars dynamic_vars_; 00129 00130 std::map<std::string, boost::function<void (const CMOOSMsg& msg)> > mail_handlers_; 00131 00132 // CMOOSApp::OnConnectToServer() 00133 bool connected_; 00134 // CMOOSApp::OnStartUp() 00135 bool started_up_; 00136 00137 std::deque<CMOOSMsg> msg_buffer_; 00138 00139 // MOOS Variable name, blackout time 00140 std::deque<std::pair<std::string, int> > pending_subscriptions_; 00141 00142 TesMoosAppConfig common_cfg_; 00143 00144 bool ignore_stale_; 00145 00146 static int argc_; 00147 static char** argv_; 00148 static std::string mission_file_; 00149 static std::string application_name_; 00150 }; 00151 00152 00153 template<typename ProtobufConfig> 00154 TesMoosApp::TesMoosApp(ProtobufConfig* cfg) 00155 : start_time_(MOOSTime()), 00156 configuration_read_(false), 00157 cout_cleared_(false), 00158 connected_(false), 00159 started_up_(false), 00160 ignore_stale_(true) 00161 { 00162 using goby::util::glogger; 00163 00164 boost::filesystem::path launch_path(argv_[0]); 00165 00166 #if BOOST_FILESYSTEM_VERSION == 3 00167 application_name_ = launch_path.filename().string(); 00168 #else 00169 application_name_ = launch_path.filename(); 00170 #endif 00171 // 00172 // READ CONFIGURATION 00173 // 00174 00175 boost::program_options::options_description od_all; 00176 boost::program_options::variables_map var_map; 00177 try 00178 { 00179 00180 boost::program_options::options_description od_cli_only("Given on command line only"); 00181 od_cli_only.add_options() 00182 ("help,h", "writes this help message") 00183 ("moos_file,c", boost::program_options::value<std::string>(&mission_file_), "path to .moos file") 00184 ("moos_name,a", boost::program_options::value<std::string>(&application_name_), "name to register with MOOS") 00185 ("example_config,e", "writes an example .moos ProcessConfig block") 00186 ("version,V", "writes the current version"); 00187 00188 00189 boost::program_options::options_description od_both("Typically given in the .moos file, but may be specified on the command line"); 00190 00191 goby::core::ConfigReader::get_protobuf_program_options(od_both, cfg->GetDescriptor()); 00192 od_all.add(od_both); 00193 od_all.add(od_cli_only); 00194 00195 boost::program_options::positional_options_description p; 00196 p.add("moos_file", 1); 00197 p.add("moos_name", 2); 00198 00199 boost::program_options::store(boost::program_options::command_line_parser(argc_, argv_). 00200 options(od_all).positional(p).run(), var_map); 00201 00202 00203 boost::program_options::notify(var_map); 00204 00205 if (var_map.count("help")) 00206 { 00207 goby::ConfigException e(""); 00208 e.set_error(false); 00209 throw(e); 00210 } 00211 else if(var_map.count("example_config")) 00212 { 00213 std::cout << "ProcessConfig = " << application_name_ << "\n{"; 00214 goby::core::ConfigReader::get_example_cfg_file(cfg, &std::cout, " "); 00215 std::cout << "}" << std::endl; 00216 exit(EXIT_SUCCESS); 00217 } 00218 else if(var_map.count("version")) 00219 { 00220 std::cout << "This is Version " << goby::VERSION_STRING 00221 << " of the Goby Underwater Autonomy Project released on " 00222 << goby::VERSION_DATE 00223 << ".\nSee https://launchpad.net/goby to search for updates." << std::endl; 00224 exit(EXIT_SUCCESS); 00225 } 00226 00227 glogger().set_name(application_name_); 00228 glogger().add_stream("verbose", &std::cout); 00229 00230 00231 std::string protobuf_text; 00232 std::ifstream fin; 00233 fin.open(mission_file_.c_str()); 00234 if(fin.is_open()) 00235 { 00236 std::string line; 00237 bool in_process_config = false; 00238 while(!getline(fin, line).eof()) 00239 { 00240 std::string no_blanks_line = boost::algorithm::erase_all_copy(line, " "); 00241 if(boost::algorithm::istarts_with(no_blanks_line, "PROCESSCONFIG=" + application_name_)) 00242 { 00243 in_process_config = true; 00244 } 00245 else if(in_process_config && 00246 !boost::algorithm::ifind_first(line, "PROCESSCONFIG").empty()) 00247 { 00248 break; 00249 } 00250 00251 if(in_process_config) 00252 protobuf_text += line + "\n"; 00253 } 00254 00255 if(!in_process_config) 00256 glogger() << die << "no ProcessConfig block for " << application_name_ << std::endl; 00257 00258 // trim off "ProcessConfig = __ {" 00259 protobuf_text.erase(0, protobuf_text.find_first_of('{')+1); 00260 00261 // trim off last "}" and anything that follows 00262 protobuf_text.erase(protobuf_text.find_last_of('}')); 00263 00264 // convert "//" to "#" for comments 00265 boost::algorithm::replace_all(protobuf_text, "//", "#"); 00266 00267 google::protobuf::TextFormat::Parser parser; 00268 FlexOStreamErrorCollector error_collector(protobuf_text); 00269 parser.RecordErrorsTo(&error_collector); 00270 parser.AllowPartialMessage(true); 00271 parser.ParseFromString(protobuf_text, cfg); 00272 if(error_collector.has_errors()) 00273 glogger() << die << "fatal configuration errors (see above)" << std::endl; 00274 00275 } 00276 else 00277 { 00278 glogger() << warn << "failed to open " << mission_file_ << std::endl; 00279 } 00280 00281 fin.close(); 00282 00283 CMOOSFileReader moos_file_reader; 00284 moos_file_reader.SetFile(mission_file_); 00285 fetch_moos_globals(cfg, moos_file_reader); 00286 00287 00288 // add / overwrite any options that are specified in the cfg file with those given on the command line 00289 typedef std::pair<std::string, boost::program_options::variable_value> P; 00290 BOOST_FOREACH(const P&p, var_map) 00291 { 00292 // let protobuf deal with the defaults 00293 if(!p.second.defaulted()) 00294 goby::core::ConfigReader::set_protobuf_program_option(var_map, *cfg, p.first, p.second); 00295 } 00296 00297 // now the proto message must have all required fields 00298 if(!cfg->IsInitialized()) 00299 { 00300 std::vector< std::string > errors; 00301 cfg->FindInitializationErrors(&errors); 00302 00303 std::stringstream err_msg; 00304 err_msg << "Configuration is missing required parameters: \n"; 00305 BOOST_FOREACH(const std::string& s, errors) 00306 err_msg << goby::util::esc_red << s << "\n" << goby::util::esc_nocolor; 00307 00308 err_msg << "Make sure you specified a proper .moos file"; 00309 throw(goby::ConfigException(err_msg.str())); 00310 } 00311 00312 } 00313 catch(goby::ConfigException& e) 00314 { 00315 // output all the available command line options 00316 std::cerr << od_all << "\n"; 00317 if(e.error()) 00318 std::cerr << "Problem parsing command-line configuration: \n" 00319 << e.what() << "\n"; 00320 00321 throw; 00322 } 00323 00324 00325 00326 // keep a copy for ourselves 00327 common_cfg_ = cfg->common(); 00328 configuration_read_ = true; 00329 00330 00331 // 00332 // PROCESS CONFIGURATION 00333 // 00334 switch(cfg->common().verbosity()) 00335 { 00336 case TesMoosAppConfig::VERBOSITY_VERBOSE: 00337 glogger().add_stream(goby::util::Logger::verbose, &std::cout); 00338 break; 00339 case TesMoosAppConfig::VERBOSITY_WARN: 00340 glogger().add_stream(goby::util::Logger::warn, &std::cout); 00341 break; 00342 case TesMoosAppConfig::VERBOSITY_DEBUG: 00343 glogger().add_stream(goby::util::Logger::debug, &std::cout); 00344 break; 00345 case TesMoosAppConfig::VERBOSITY_GUI: 00346 glogger().add_stream(goby::util::Logger::gui, &std::cout); 00347 break; 00348 case TesMoosAppConfig::VERBOSITY_QUIET: 00349 glogger().add_stream(goby::util::Logger::quiet, &std::cout); 00350 break; 00351 } 00352 00353 00354 if(cfg->common().log()) 00355 { 00356 if(!cfg->common().has_log_path()) 00357 { 00358 glogger() << warn << "logging all terminal output to default directory (" << cfg->common().log_path() << ")." << "set log_path for another path " << std::endl; 00359 } 00360 00361 if(!cfg->common().log_path().empty()) 00362 { 00363 using namespace boost::posix_time; 00364 std::string file_name = application_name_ + "_" + cfg->common().community() + "_" + to_iso_string(second_clock::universal_time()) + ".txt"; 00365 00366 glogger() << "logging output to file: " << file_name << std::endl; 00367 00368 fout_.open(std::string(cfg->common().log_path() + "/" + file_name).c_str()); 00369 00370 // if fails, try logging to this directory 00371 if(!fout_.is_open()) 00372 { 00373 fout_.open(std::string("./" + file_name).c_str()); 00374 glogger() << warn << "logging to current directory because given directory is unwritable!" << std::endl; 00375 } 00376 // if still no go, quit 00377 if(!fout_.is_open()) 00378 glogger() << die << "cannot write to current directory, so cannot log." << std::endl; 00379 00380 glogger().add_stream(goby::util::Logger::verbose, &fout_); 00381 } 00382 } 00383 00384 00385 glogger() << cfg->DebugString() << std::endl; 00386 } 00387 00388 00389 // designed to run CMOOSApp derived applications 00390 // using the MOOS "convention" of argv[1] == mission file, argv[2] == alternative name 00391 template<typename App> 00392 int goby::moos::run(int argc, char* argv[]) 00393 { 00394 App::argc_ = argc; 00395 App::argv_ = argv; 00396 00397 try 00398 { 00399 App* app = App::get_instance(); 00400 app->Run(App::application_name_.c_str(), App::mission_file_.c_str()); 00401 } 00402 catch(goby::ConfigException& e) 00403 { 00404 // no further warning as the ApplicationBase Ctor handles this 00405 return 1; 00406 } 00407 catch(std::exception& e) 00408 { 00409 // some other exception 00410 std::cerr << "uncaught exception: " << e.what() << std::endl; 00411 return 2; 00412 } 00413 00414 return 0; 00415 } 00416 00417 00418 00419 #endif