Goby3 3.7.0
2026.11.12
Loading...
Searching...
No Matches
interface.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//
8//
9// This file is part of the Goby Underwater Autonomy Project Libraries
10// ("The Goby Libraries").
11//
12// The Goby Libraries are free software: you can redistribute them and/or modify
13// them under the terms of the GNU Lesser General Public License as published by
14// the Free Software Foundation, either version 2.1 of the License, or
15// (at your option) any later version.
16//
17// The Goby Libraries are distributed in the hope that they will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public License
23// along with Goby. If not, see <http://www.gnu.org/licenses/>.
24
25#ifndef GOBY_MIDDLEWARE_APPLICATION_INTERFACE_H
26#define GOBY_MIDDLEWARE_APPLICATION_INTERFACE_H
27
28#include <chrono>
29#include <csignal>
30#include <cstdlib>
31#include <iostream>
32#include <sys/types.h>
33#include <unistd.h>
34
35#include <boost/format.hpp>
36
37#include "goby/exception.h"
42#include "goby/time.h"
44#include "goby/util/geodesy.h"
45
46namespace goby
47{
54template <typename App>
56
63template <typename App,
64 typename Configurator = middleware::ProtobufConfigurator<typename App::ConfigType>>
65int run(int argc, char* argv[])
66{
67 return run<App>(Configurator(argc, argv));
68}
69
70namespace middleware
71{
72
73namespace julia
74{
75template <typename App> class ApplicationWrapper;
76}
77
78namespace python
79{
80template <typename App> class ApplicationWrapper;
81}
82
84template <typename Config> class Application
85{
86 public:
88 virtual ~Application()
89 {
90 goby::glog.is_debug2() && goby::glog << "Application: destructing cleanly" << std::endl;
91 }
92
93 using ConfigType = Config;
94
95 protected:
97 virtual void pre_initialize() {};
98
100 virtual void initialize() {};
101
103 virtual void post_initialize() {};
104
106 virtual void run() = 0;
107
109 virtual void pre_finalize() {};
110
112 virtual void finalize() {};
113
115 virtual void post_finalize() {};
116
120 void quit(int return_value = 0)
121 {
122 alive_ = false;
123 return_value_ = return_value;
124 }
125
127 const Config& app_cfg() { return *app_cfg_; }
128
131 {
132 if (geodesy_)
133 return *geodesy_;
134 else
135 throw(goby::Exception("No lat_origin and lon_origin defined for requested UTMGeodesy"));
136 }
137
139 bool has_geodesy() { return geodesy_ ? true : false; }
140
141 std::string app_name() { return app3_base_configuration_->name(); }
142
143 bool app_alive() { return alive_; }
144
145 boost::units::quantity<boost::units::si::frequency>
146 choose_loop_freq(boost::units::quantity<boost::units::si::frequency> compiled_loop_freq)
147 {
148 if (app3_base_configuration_->has_loop_frequency())
149 return app3_base_configuration_->loop_frequency_with_units();
150 else
151 return compiled_loop_freq;
152 }
153
154 protected:
156
157 private:
158 template <typename App>
159 friend int ::goby::run(
161
162 template <typename App> friend class goby::middleware::julia::ApplicationWrapper;
163 template <typename App> friend class goby::middleware::python::ApplicationWrapper;
164
165 // main loop that exits on quit(); returns the desired return value
166 int __run();
167
168 void run_one() { run(); }
169
170 void configure_logger();
171 void configure_glog_file();
172 void configure_intervehicle();
173 void load_shared_libraries();
174 void check_rotate_glog_file();
175
176 private:
177 // sets configuration (before Application construction)
178 static std::unique_ptr<Config> app_cfg_;
179 static std::unique_ptr<protobuf::AppConfig> app3_base_configuration_;
180
181 bool alive_;
182 int return_value_;
183
184 // static here allows fout_ to live until program exit to log glog output
185 static std::unique_ptr<std::ofstream> fout_;
186
187 goby::time::SteadyClock::time_point next_log_rotate_time_;
188
189 std::unique_ptr<goby::util::UTMGeodesy> geodesy_;
190};
191} // namespace middleware
192
193} // namespace goby
194
195template <typename Config>
196std::unique_ptr<std::ofstream> goby::middleware::Application<Config>::fout_;
197
198template <typename Config> std::unique_ptr<Config> goby::middleware::Application<Config>::app_cfg_;
199
200template <typename Config>
201std::unique_ptr<goby::middleware::protobuf::AppConfig>
203
204template <typename Config> goby::middleware::Application<Config>::Application() : alive_(true)
205{
206 using goby::glog;
207
208 configure_logger();
209
210 load_shared_libraries();
211
212 if (app3_base_configuration_->has_geodesy())
213 configure_geodesy({app3_base_configuration_->geodesy().lat_origin_with_units(),
214 app3_base_configuration_->geodesy().lon_origin_with_units()});
215
216 if (app3_base_configuration_->has_intervehicle_cfg())
217 configure_intervehicle();
218
219 if (!app3_base_configuration_->IsInitialized())
220 throw(middleware::ConfigException("Invalid base configuration"));
221
222 glog.is_debug2() && glog << "Application: constructed with PID: " << getpid() << std::endl;
223 glog.is_debug1() && glog << "App name is " << app3_base_configuration_->name() << std::endl;
224 glog.is_debug2() && glog << "Configuration is: " << app_cfg_->DebugString() << std::endl;
225}
226
227template <typename Config> void goby::middleware::Application<Config>::configure_logger()
228{
229 using goby::glog;
230
231 // set up the logger
232 glog.set_name(app3_base_configuration_->name());
234 app3_base_configuration_->glog_config().tty_verbosity()),
235 &std::cout);
236
237 if (app3_base_configuration_->glog_config().show_gui())
239
240 if (app3_base_configuration_->glog_config().has_file_log())
241 configure_glog_file();
242
243 if (app3_base_configuration_->glog_config().show_dccl_log())
245}
246
248{
249 using goby::glog;
251
252 try
253 {
254 if (const char* env_libs = getenv("GOBY_LOAD_SHARED_LIBRARY"))
255 DCCLSerializerParserHelperBase::load_library(std::string(env_libs));
256
257 for (const auto& lib : app3_base_configuration_->load_shared_library())
258 DCCLSerializerParserHelperBase::load_library(lib);
259 }
260 catch (const std::exception& e)
261 {
262 glog.is_die() && glog << e.what() << std::endl;
263 }
264}
265
267{
269 app3_base_configuration_->intervehicle_cfg();
270
271 if (intervehicle_cfg.has_dccl_passphrase())
272 {
274 intervehicle_cfg.dccl_passphrase());
275 }
276}
277
279{
280 using goby::glog;
281 if (app3_base_configuration_->glog_config().has_file_log() &&
282 app3_base_configuration_->glog_config().file_log().has_log_rotate_sec() &&
283 goby::time::SteadyClock::now() > next_log_rotate_time_)
284 {
285 glog.remove_stream(fout_.get());
286 configure_glog_file();
287 }
288}
289
291{
292 const auto& file_log = app3_base_configuration_->glog_config().file_log();
293 std::string file_format_str;
294
295 std::string file_name_format = file_log.file_name();
296
297 if (!file_log.has_file_name() && file_log.omit().file_timestamp())
298 file_name_format = "%2%.txt";
299
300 if (file_log.has_file_dir() && !file_log.file_dir().empty())
301 {
302 auto file_dir = file_log.file_dir();
303 if (file_dir.back() != '/')
304 file_dir += "/";
305 file_format_str = file_dir + file_name_format;
306 }
307 else
308 {
309 file_format_str = file_name_format;
310 }
311
312 boost::format file_format(file_format_str);
313
314 if (!file_log.omit().file_timestamp())
315 {
316 if (file_format_str.find("%1") == std::string::npos)
317 glog.is_die() &&
318 glog << "file_name string must contain \"%1%\" which is expanded to the current "
319 "application start time (e.g. 20190201T184925), unless omit.file_timestamp "
320 "== true. Erroneous file_name is: "
321 << file_format_str << std::endl;
322 }
323
324 file_format.exceptions(boost::io::all_error_bits ^
325 (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
326
327 std::string file_name =
328 (file_format % goby::time::file_str() % app3_base_configuration_->name()).str();
329 glog.is_verbose() && glog << "logging output to file: " << file_name << std::endl;
330
331 fout_.reset(new std::ofstream(file_name.c_str()));
332
333 if (!fout_->is_open())
334 glog.is_die() && glog << "cannot write glog output to requested file: " << file_name
335 << std::endl;
336
337 if (!file_log.omit().latest_symlink())
338 {
339 std::string file_symlink =
340 (file_format % "latest" % app3_base_configuration_->name()).str();
341 remove(file_symlink.c_str());
342 char* resolved = realpath(file_name.c_str(), nullptr);
343 if (resolved != nullptr)
344 {
345 int result = symlink(resolved, file_symlink.c_str());
346 free(resolved);
347 if (result != 0)
348 glog.is_warn() &&
349 glog << "Cannot create symlink to latest file. Continuing onwards anyway"
350 << std::endl;
351 }
352 }
353
354 glog.add_stream(file_log.verbosity(), fout_.get());
355
356 if (file_log.has_log_rotate_sec())
357 next_log_rotate_time_ =
358 goby::time::SteadyClock::now() + std::chrono::seconds(file_log.log_rotate_sec());
359}
360
361template <typename Config>
367
368template <typename Config> int goby::middleware::Application<Config>::__run()
369{
370 // block SIGWINCH (change window size) in all threads
371 sigset_t signal_mask;
372 sigemptyset(&signal_mask);
373 sigaddset(&signal_mask, SIGWINCH);
374 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
375
376 this->pre_initialize();
377 this->initialize();
378 this->post_initialize();
379 // continue to run while we are alive (quit() has not been called)
380 while (alive_)
381 {
382 this->run_one();
383 this->check_rotate_glog_file();
384 }
385 this->pre_finalize();
386 this->finalize();
387 this->post_finalize();
388 return return_value_;
389}
390
391template <typename App>
393{
394 int return_value = 0;
395 try
396 {
397 try
398 {
399 cfgtor.validate();
400 }
402 {
403 cfgtor.handle_config_error(e);
404 return 1;
405 }
406
407 // simply print the configuration and exit
408 if (cfgtor.app_configuration().debug_cfg())
409 {
410 std::cout << cfgtor.str() << std::endl;
411 exit(EXIT_SUCCESS);
412 }
413
414 // set configuration
415 App::app_cfg_.reset(new typename App::ConfigType(cfgtor.cfg()));
416 App::app3_base_configuration_.reset(
418
419 // set up simulation time
420 middleware::detail::configure_simulation_time(*App::app3_base_configuration_);
421
422 // instantiate the application (with the configuration already set)
423 App app;
424 return_value = app.__run();
425 if (return_value == 0 && cfgtor.invalid_command())
426 return_value = EXIT_FAILURE;
427 }
428 catch (std::exception& e)
429 {
430 // some other exception
431 std::cerr << "Application:: uncaught exception: " << e.what() << std::endl;
432 throw;
433 }
434
435 goby::glog.is_debug2() && goby::glog << "goby::run: exiting cleanly with code: " << return_value
436 << std::endl;
437 return return_value;
438}
439
440#endif
simple exception class for goby applications
Definition exception.h:35
Base class for Goby applications. Generally you will want to use SingleThreadApplication or MultiThre...
Definition interface.h:85
const util::UTMGeodesy & geodesy()
Accesses the geodetic conversion tool if lat_origin and lon_origin were provided.
Definition interface.h:130
virtual void pre_finalize()
Called just before finalize.
Definition interface.h:109
virtual void post_initialize()
Called just after initialize.
Definition interface.h:103
virtual void initialize()
Perform any initialize tasks that couldn't be done in the constructor.
Definition interface.h:100
virtual void pre_initialize()
Called just before initialize.
Definition interface.h:97
virtual void run()=0
Runs once.
bool has_geodesy()
Returns if the geodesy tool is configured with a datum.
Definition interface.h:139
virtual void finalize()
Perform any final cleanup actions just before the destructor is called.
Definition interface.h:112
void quit(int return_value=0)
Requests a clean exit.
Definition interface.h:120
void configure_geodesy(goby::util::UTMGeodesy::LatLonPoint datum)
Definition interface.h:362
boost::units::quantity< boost::units::si::frequency > choose_loop_freq(boost::units::quantity< boost::units::si::frequency > compiled_loop_freq)
Definition interface.h:146
virtual void post_finalize()
Called just after finalize.
Definition interface.h:115
const Config & app_cfg()
Accesses configuration object passed at launch.
Definition interface.h:127
indicates a problem with the runtime command line or .cfg file configuration (or –help was given)
Defines the interface to a "configurator", a class that can read command line parameters (argc,...
virtual void validate() const
Override to validate the configuration.
virtual bool invalid_command() const
True if the command line was invalid and a fallback (usually help) is run instead,...
virtual void handle_config_error(middleware::ConfigException &e) const
Override to customize how ConfigException errors are handled.
virtual const protobuf::AppConfig & app_configuration() const
Subset of the configuration used to configure the Application itself.
const Config & cfg() const
The configuration object produced from the command line parameters.
virtual std::string str() const =0
Override to output the configuration object as a string.
The class exposed to Python; user application classes subclass this.
void remove_stream(std::ostream *os=nullptr)
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.
void configure_simulation_time(const protobuf::AppConfig &app_cfg)
Applies the simulation time settings from an application configuration to goby::time::SimulatorSettin...
std::string str(TimeType value=SystemClock::now< TimeType >())
Returns the provided time (or current time if omitted) as a human-readable string.
Definition convert.h:187
std::string file_str(TimeType value=SystemClock::now< TimeType >())
Returns the provided time (or current time if omitted) as an ISO string suitable for file names (no s...
Definition convert.h:200
The global namespace for the Goby project.
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:392
Wraps a dccl::Codec in a thread-safe way to make it usable by SerializerParserHelper.
static void set_crypto_passphrase(const std::string &passphrase, const std::set< dccl::int32 > &do_not_encrypt_ids=std::set< dccl::int32 >())
static void setup_dlog()
Enable dlog output to glog using same verbosity settings as glog.
std::chrono::time_point< SteadyClock > time_point
static time_point now() noexcept
Returns the current steady time unless SimulatorSettings::using_sim_time == true in which case a simu...