24#ifndef GOBY_MIDDLEWARE_LANGUAGES_PYTHON_APPLICATION_H
25#define GOBY_MIDDLEWARE_LANGUAGES_PYTHON_APPLICATION_H
37#include <google/protobuf/text_format.h>
39#include <pybind11/pybind11.h>
40#include <pybind11/stl.h>
66namespace py = pybind11;
76inline void invoke_callback(
const py::object& callback,
const std::vector<char>& bytes)
78 py::gil_scoped_acquire gil;
79 callback(py::bytes(bytes.data(), bytes.size()));
111 std::signal(signum, SIG_DFL);
137 if (previous_sigint_ != SIG_ERR)
138 std::signal(SIGINT, previous_sigint_);
139 if (previous_sigterm_ != SIG_ERR)
140 std::signal(SIGTERM, previous_sigterm_);
151 void (*previous_sigint_)(int){SIG_ERR};
152 void (*previous_sigterm_)(int){SIG_ERR};
164 using AppBase::AppBase;
166 using Hook = std::function<void()>;
168 using HealthHook = std::function<std::string(
const std::string&)>;
172 loop_ = std::move(
loop);
175 health_ = std::move(
health);
198 health.SerializeToString(&before);
200 std::string after = health_(before);
205 if (updated.ParseFromString(after))
209 "could not be parsed; keeping the Goby one"
218 if (initialize_ && !initialize_called_)
220 initialize_called_ =
true;
227 if (finalize_ && !finalize_called_)
229 finalize_called_ =
true;
239 bool initialize_called_{
false};
240 bool finalize_called_{
false};
259 "Goby configuration has not been read. Applications must be started with "
260 "goby.run(), which parses the command line before constructing the application."));
262 app_ptr_.reset(
new App(loop_frequency_hertz));
263 app_ptr_->set_hooks([
this]() { this->
loop(); }, [
this]() { this->
initialize(); },
265 [
this](
const std::string& serialized) -> std::string
269 py::gil_scoped_acquire gil;
270 return this->
health(py::bytes(serialized));
283 throw(
goby::Exception(
"loop() must be overridden when loop_frequency is non-zero"));
296 virtual std::string
health(py::bytes serialized) {
return std::string(serialized); }
306 static py::bytes
configure(
const std::vector<std::string>& argv)
308 using ConfigType =
typename App::ConfigType;
310 std::vector<char*> argv_c;
311 argv_c.reserve(argv.size() + 1);
312 for (
const auto& arg : argv) argv_c.push_back(
const_cast<char*
>(arg.c_str()));
313 argv_c.push_back(
nullptr);
334 std::cout << cfgtor.
str() << std::endl;
335 std::exit(EXIT_SUCCESS);
338 App::app_cfg_.reset(
new ConfigType(cfgtor.
cfg()));
339 App::app3_base_configuration_.reset(
344 return set_configuration(cfgtor.
cfg());
355 typename App::ConfigType cfg;
357 google::protobuf::TextFormat::Parser parser;
358 goby::util::FlexOStreamErrorCollector error_collector(config_text);
359 parser.RecordErrorsTo(&error_collector);
360 parser.AllowPartialMessage(
false);
362 if (!parser.ParseFromString(config_text, &cfg))
364 "Failed to parse the configuration as Protobuf TextFormat"));
366 return set_configuration(cfg);
374 typename App::ConfigType cfg;
376 if (!cfg.ParseFromString(config_bytes))
379 return set_configuration(cfg);
390 py::gil_scoped_release release_gil;
391 return app_ptr_->__run();
395 void quit(
int return_value = 0) { app_ptr_->quit(return_value); }
398 std::string
app_name() {
return app_ptr_->app_name(); }
403 std::string serialized;
404 app_ptr_->app_cfg().SerializeToString(&serialized);
405 return py::bytes(serialized);
410 app_ptr_->set_loop_frequency_hertz(loop_frequency_hertz);
415 const std::string& data)
426 std::move(callback));
432 static py::bytes set_configuration(
const typename App::ConfigType& cfg)
436 App::app_cfg_.reset(
new typename App::ConfigType(cfg));
441 std::string serialized;
442 cfg.SerializeToString(&serialized);
443 return py::bytes(serialized);
446 static void signal_quit(
void* self)
448 static_cast<ApplicationWrapper*
>(self)->app_ptr_->quit();
452 std::unique_ptr<App> app_ptr_;
464 std::string
health(py::bytes serialized)
override
481inline void glog_write(
int verbosity,
const std::string&
group,
const std::string& text)
483 py::gil_scoped_release release_gil;
512template <
typename App>
517 py::register_exception_translator(
518 [](std::exception_ptr p)
523 std::rethrow_exception(p);
527 py::object goby_module = py::module_::import(
"goby");
528 PyErr_SetString(goby_module.attr(
"ConfigError").ptr(), e.what());
533 .def(py::init<double>(), py::arg(
"loop_frequency_hertz") = 0)
541 py::arg(
"scheme"), py::arg(
"group"), py::arg(
"data"))
543 py::arg(
"type_name"), py::arg(
"scheme"), py::arg(
"group"), py::arg(
"callback"))
548 py::arg(
"config_text"))
549 .def_static(
"_configure_from_serialized",
558 return py::make_tuple(
561 static_cast<std::int64_t
>(
563 std::chrono::microseconds(1)));
569 m.def(
"_glog_add_group",
570 [](
const std::string& name,
const std::string& description)
573 m.attr(
"_GOBY_APPLICATION_NAME") = app_name;
584#define GOBY_PYTHON_IF_PUBLICATION(SCHEME, LAYER_ENUM, LAYER_FUNCTION, GROUP, GROUP_KEY, TYPE) \
585 if (id == goby::middleware::python::Identifier{ \
586 goby::middleware::python::PubSubLayer::LAYER_ENUM, TYPE::descriptor()->name(), \
587 goby::middleware::MarshallingScheme::SCHEME, GROUP_KEY}) \
589 decltype(data.end()) actual_end; \
590 auto msg = goby::middleware::SerializerParserHelper< \
591 TYPE, goby::middleware::MarshallingScheme::SCHEME>::parse(data.begin(), data.end(), \
593 LAYER_FUNCTION().publish<GROUP>(msg); \
597#define GOBY_PYTHON_IF_SUBSCRIPTION(SCHEME, LAYER_ENUM, LAYER_FUNCTION, GROUP, GROUP_KEY, TYPE) \
598 if (id == goby::middleware::python::Identifier{ \
599 goby::middleware::python::PubSubLayer::LAYER_ENUM, TYPE::descriptor()->name(), \
600 goby::middleware::MarshallingScheme::SCHEME, GROUP_KEY}) \
602 LAYER_FUNCTION().subscribe<GROUP>( \
603 [callback](const TYPE& pb) \
605 std::vector<char> bytes = goby::middleware::SerializerParserHelper< \
606 TYPE, goby::middleware::MarshallingScheme::SCHEME>::serialize(pb); \
607 goby::middleware::python::detail::invoke_callback(callback, bytes); \
612#define GOBY_PYTHON_FAIL(PUBLISH_OR_SUBSCRIBE) \
613 goby::glog.is_die() && \
614 goby::glog << PUBLISH_OR_SUBSCRIBE " not defined for these parameters: [" << id \
615 << "]. Please include in interface.yml and re-generate to include them." \
619#define GOBY_PYTHON_QUOTE(name) #name
620#define GOBY_PYTHON_DEFINE_MODULE(APPLICATION_NAME, MODULE_NAME) \
621 PYBIND11_MODULE(MODULE_NAME, goby_python_module) \
623 goby::middleware::python::define_python_module<APPLICATION_NAME>( \
624 goby_python_module, GOBY_PYTHON_QUOTE(APPLICATION_NAME)); \
simple exception class for goby applications
indicates a problem with the runtime command line or .cfg file configuration (or –help was given)
static void check_required_cfg(const google::protobuf::Message &message, const std::string &binary)
Checks that all required fields are set (either via the command line or the configuration file) in th...
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 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.
Implementation of ConfiguratorInterface for Google Protocol buffers.
void Swap(ThreadHealth *other)
Lets Python subclasses override the Goby virtual methods.
void loop() override
Override in Python to do work at loop_frequency Hz.
void initialize() override
Override in Python for work that can't be done in the constructor.
void finalize() override
Override in Python for cleanup just before the application exits.
std::string health(py::bytes serialized) override
Override in Python to answer goby_coroner's health request.
The class exposed to Python; user application classes subclass this.
ApplicationWrapper & operator=(const ApplicationWrapper &)=delete
std::string app_name()
The application name, as set by –app_name or derived from argv[0].
static py::bytes configure_from_text(const std::string &config_text)
Reads the application configuration from a Protobuf TextFormat string.
ApplicationWrapper(const ApplicationWrapper &)=delete
py::bytes cfg_serialized()
The configuration the application was started with, serialized as Protobuf.
static py::bytes configure_from_serialized(const std::string &config_bytes)
Reads the application configuration from a serialized Protobuf message.
virtual void loop()
Override in Python to do work at loop_frequency Hz.
virtual void finalize()
Override in Python for cleanup just before the application exits.
void publish(int layer, const std::string &type_name, int scheme, const std::string &group, const std::string &data)
Publishes already-marshalled bytes; dispatched by the generated glue code.
static py::bytes configure(const std::vector< std::string > &argv)
Reads the command line into the application configuration.
int run()
Runs the Goby event loop until quit() is called or the application is terminated.
virtual ~ApplicationWrapper()=default
virtual void initialize()
Override in Python for work that can't be done in the constructor.
void set_loop_frequency_hertz(double loop_frequency_hertz)
ApplicationWrapper(double loop_frequency_hertz=0)
Constructs the underlying Goby application.
void subscribe(int layer, const std::string &type_name, int scheme, const std::string &group, py::object callback)
Subscribes a Python callable; dispatched by the generated glue code.
void quit(int return_value=0)
Requests a clean exit.
virtual std::string health(py::bytes serialized)
Override in Python to answer goby_coroner's health request.
Base class for the generated C++ glue class, forwarding the Goby virtual methods to Python.
void set_hooks(Hook loop, Hook initialize, Hook finalize, HealthHook health)
std::function< void()> Hook
void initialize() override
std::function< std::string(const std::string &)> HealthHook
Takes the serialized ThreadHealth Goby filled in and returns the one Python wrote.
void health(goby::middleware::protobuf::ThreadHealth &health) override
Hands the report goby_coroner asked for to Python, as serialized Protobuf.
Installs the quit-on-signal handlers for the duration of a run() call.
SignalGuard(void(*quit_fn)(void *), void *quit_ctx)
SignalGuard(const SignalGuard &)=delete
SignalGuard & operator=(const SignalGuard &)=delete
logger::Verbosity highest_verbosity() const
void add_group(const std::string &name, Colors::Color color=Colors::nocolor, const std::string &description="")
Add another group to the logger. A group provides related manipulator for categorizing log messages.
goby::util::logger::GroupSetter group(std::string n)
detail namespace with internal helper functions
void configure_simulation_time(const protobuf::AppConfig &app_cfg)
Applies the simulation time settings from an application configuration to goby::time::SimulatorSettin...
PubSubLayer
The publish/subscribe layer used for a given publication or subscription.
void signal_handler(int signum)
void glog_write(int verbosity, const std::string &group, const std::string &text)
Writes one already-formatted line to goby::glog at the given verbosity.
SignalState & signal_state()
bool glog_is(int verbosity)
Whether anything is listening at this verbosity.
void invoke_callback(const py::object &callback, const std::vector< char > &bytes)
Hands marshalled bytes to a Python callable, acquiring the GIL first.
void define_python_module(py::module_ &m, const std::string &app_name)
Defines the contents of the generated extension module.
constexpr int scheme()
Placeholder to provide an interface for the scheme() function family.
The global namespace for the Goby project.
util::FlexOstream glog
Access the Goby logger through this object.
Fully identifies a publication or subscription crossing the language boundary.
State used to turn SIGINT/SIGTERM into a clean Application::quit()
volatile std::sig_atomic_t requested
static bool using_sim_time
Enables simulation time if true (if false, none of the remaining parameters are used)
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...
static int warp_factor
Warp factor to speed up (or slow time) the time values returned by SteadyClock::now() and SystemClock...