Note: Goby version 1 (shown here) is now considered obsolete. Please use version 2 for new projects, and consider upgrading old projects.

Goby Underwater Autonomy Project  Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
moos/libmoos_util/tes_moos_app.h
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
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends