diff --git a/.gitignore b/.gitignore index d955937..7e67e61 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build *.user .vscode +.vs/ +out/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a903ab2..e270f12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,8 +75,9 @@ link_directories( add_definitions(${PCL_DEFINITIONS}) - + set(client_SRCS + src/common/notifications.cpp src/modules/polar_to_cart_converter.cpp src/modules/distance_filter.cpp src/modules/ring_intensity_filter.cpp @@ -142,7 +143,8 @@ if (PACKAGE_FOR_DEV) if(WIN32) install(TARGETS quanergy_client EXPORT QuanergyClientTargets - RUNTIME DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib) + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT shlib) else() install(TARGETS quanergy_client EXPORT QuanergyClientTargets diff --git a/apps/dynamic_connection.cpp b/apps/dynamic_connection.cpp index 4ec3835..8113945 100644 --- a/apps/dynamic_connection.cpp +++ b/apps/dynamic_connection.cpp @@ -18,6 +18,9 @@ // sensor pipeline #include +// handle notifications +#include + int main(int argc, char** argv) { namespace po = boost::program_options; diff --git a/apps/visualizer.cpp b/apps/visualizer.cpp index 77db584..ba134ed 100644 --- a/apps/visualizer.cpp +++ b/apps/visualizer.cpp @@ -17,6 +17,9 @@ // sensor pipeline #include +// handle notifications +#include + int main(int argc, char** argv) { namespace po = boost::program_options; diff --git a/include/quanergy/client/exceptions.h b/include/quanergy/client/exceptions.h index f7de1f1..681cf58 100644 --- a/include/quanergy/client/exceptions.h +++ b/include/quanergy/client/exceptions.h @@ -35,15 +35,27 @@ namespace quanergy }; /** \brief error parsing header */ - struct InvalidHeaderError : public std::exception + struct ParseTimeoutError : public std::exception { - virtual const char* what() const throw() { return "Invalid header"; } + virtual const char* what() const throw() { return "Parse timeout error"; } + }; + + /** \brief error parsing header */ + struct InvalidHeaderError : public std::runtime_error + { + explicit InvalidHeaderError(const std::string& message) + : std::runtime_error("Invalid header! Details: " + message) {} + explicit InvalidHeaderError() + : std::runtime_error("Invalid header!") {} }; /** \brief packet size doesn't match data description */ - struct SizeMismatchError : public std::exception + struct SizeMismatchError : public std::runtime_error { - virtual const char* what() const throw() { return "Packet sizes don't match"; } + explicit SizeMismatchError(const std::string& message) + : std::runtime_error("Packet sizes don't match! Details: " + message) {} + explicit SizeMismatchError() + : std::runtime_error("Packet sizes don't match!") {} }; /** \brief Invalid packet */ diff --git a/include/quanergy/client/impl/tcp_client.hpp b/include/quanergy/client/impl/tcp_client.hpp index 223a21e..a78446a 100755 --- a/include/quanergy/client/impl/tcp_client.hpp +++ b/include/quanergy/client/impl/tcp_client.hpp @@ -115,8 +115,8 @@ namespace quanergy template void TCPClient
::startDataConnect() { - std::cout << "Attempting to connect (" << host_query_.host_name() - << ":" << host_query_.service_name() << ")..." << std::endl; + log.info << "Attempting to connect (" << host_query_.host_name() + << ":" << host_query_.service_name() << ")..." << std::endl; boost::asio::ip::tcp::resolver resolver(io_service_); try @@ -138,23 +138,25 @@ namespace quanergy } else if (error) { - std::cerr << "Unable to bind to socket (" << host_query_.host_name() - << ":" << host_query_.service_name() << ")! " - << error.message() << std::endl; + log.error << "Unable to bind to socket (" << host_query_.host_name() + << ":" << host_query_.service_name() << ")! " + << error.message() << std::endl; + throw SocketBindError(error.message()); } else { - std::cout << "Connection established" << std::endl; + log.info << "Connection established" << std::endl; + startDataRead(); } }); } catch (boost::system::system_error& e) { - std::cerr << "Unable to resolve host (" << host_query_.host_name() - << ":" << host_query_.service_name() << ")! " - << e.what() << std::endl; + log.error << "Unable to resolve host (" << host_query_.host_name() + << ":" << host_query_.service_name() << ")! " + << e.what() << std::endl; throw SocketBindError(e.what()); } } @@ -177,8 +179,8 @@ namespace quanergy } else if (error) { - std::cerr << "Error reading header: " - << error.message() << std::endl; + log.error << "Error reading header: " + << error.message() << std::endl; throw SocketReadError(error.message()); } else @@ -213,8 +215,9 @@ namespace quanergy } else if (error) { - std::cerr << "Error reading body: " - << error.message() << std::endl; + log.error << "Error reading body: " + << error.message() << std::endl; + throw SocketReadError(error.message()); } else @@ -227,7 +230,7 @@ namespace quanergy while (buff_queue_.size() > max_queue_size_) { buff_queue_.pop(); - std::cout << "Warning: Client dropped packet due to full buffer" << std::endl; + log.warn << "Client dropped packet due to full buffer" << std::endl; } lk.unlock(); diff --git a/include/quanergy/client/packet_header.h b/include/quanergy/client/packet_header.h index 12d9abe..6f6057e 100644 --- a/include/quanergy/client/packet_header.h +++ b/include/quanergy/client/packet_header.h @@ -24,6 +24,8 @@ #include +#include + #include namespace quanergy @@ -98,9 +100,8 @@ namespace quanergy { if (deserialize(object.signature) != SIGNATURE) { - std::cerr << "Invalid header signature: " << std::hex << std::showbase - << object.signature << std::dec << std::noshowbase << std::endl; - + log.error << "Invalid header signature: " << std::hex << std::showbase + << object.signature << std::dec << std::noshowbase << std::endl; return false; } diff --git a/include/quanergy/common/notifications.h b/include/quanergy/common/notifications.h new file mode 100644 index 0000000..02bd0ec --- /dev/null +++ b/include/quanergy/common/notifications.h @@ -0,0 +1,120 @@ +/**************************************************************** + ** ** + ** Copyright(C) 2020 Quanergy Systems. All Rights Reserved. ** + ** Contact: http://www.quanergy.com ** + ** ** + ****************************************************************/ + + /** \file notifications.h + * \brief Define classes for handling notifications. + * + */ + +#ifndef QUANERGY_NOTIFICATIONS_H +#define QUANERGY_NOTIFICATIONS_H + +#include +#include + +#include + +namespace quanergy +{ + /** \brief severity level of the notification */ + enum class NotificationLevel { Trace, Debug, Info, Warn, Error }; + + /** \brief string buffer for notifier */ + class DLLEXPORT NotifierBuf : public std::stringbuf + { + public: + /** \brief constructor + * \param name notifier name printed in output + * \param level notifier level printed in output + */ + NotifierBuf(std::string name, NotificationLevel level); + + /** \brief called on flush allowing us to forward to sink_ */ + virtual int sync() override; + + /** \brief set the downstream sink */ + void SetSink(std::ostream* sink) { sink_ = sink; } + + /** \brief enable formatting on output (time, level, name) */ + void EnableFormatting(bool enable = true) { add_formatting_ = enable; } + + protected: + /** \brief sink to stream to on flush */ + std::ostream* sink_ = nullptr; + + /** \brief flag determining whether to include time, level, and name */ + bool add_formatting_ = true; + + /** \brief name used for formatting */ + std::string name_; + + /** \brief level string used for formatting */ + std::string level_string_; + }; + + /** \brief ostream for notifier */ + class DLLEXPORT NotifierStream : public std::ostream + { + public: + /** \brief constructor + * \param name notifier name printed in output + * \param level notifier level printed in output + */ + NotifierStream(std::string name, NotificationLevel level); + + /** \brief set the downstream sink */ + void SetSink(std::ostream* sink) { buf_.SetSink(sink); } + + /** \brief enable formatting on output (time, level, name) */ + void EnableFormatting(bool enable = true) { buf_.EnableFormatting(enable); } + + protected: + /** \brief the notification buffer for this stream */ + NotifierBuf buf_; + }; + + /** \brief class to keep the various streams + * by default, error streams to cerr and info and warn stream to cout + */ + class DLLEXPORT Notifier + { + public: + /** \brief constructor + * \param name notifier name printed in output + */ + Notifier(std::string name); + + /** \brief set the downstream sink for one or more streams */ + void SetSinks(std::ostream* sink, NotificationLevel minLevel = NotificationLevel::Trace, NotificationLevel maxLevel = NotificationLevel::Error); + /** \brief set the downstream sink for one stream */ + void SetSink(std::ostream* sink, NotificationLevel level); + + /** \brief clear the downstream sink for one or more streams */ + void ClearSinks(NotificationLevel minLevel = NotificationLevel::Trace, NotificationLevel maxLevel = NotificationLevel::Error); + /** \brief clear the downstream sink for one stream */ + void ClearSink(NotificationLevel level); + + /** \brief enable formatting on output (time, level, name) */ + void EnableFormatting(bool enable = true); + + /** \brief the error stream */ + NotifierStream error; + /** \brief the warn stream */ + NotifierStream warn; + /** \brief the info stream */ + NotifierStream info; + /** \brief the debug stream */ + NotifierStream debug; + /** \brief the info stream */ + NotifierStream trace; + }; + + static Notifier log {"QuanergyClient"}; + +} // namespace quanergy + +#endif \ No newline at end of file diff --git a/include/quanergy/parsers/data_packet_01.h b/include/quanergy/parsers/data_packet_01.h index ff2a6b0..4cf10d8 100644 --- a/include/quanergy/parsers/data_packet_01.h +++ b/include/quanergy/parsers/data_packet_01.h @@ -91,9 +91,10 @@ namespace quanergy sizeof(DataHeader01) + object.data_header.point_count * sizeof(DataPoint01)) { - std::cerr << "Invalid sizes: " << object.data_header.point_count + std::stringstream ss; + ss << "Invalid sizes: " << object.data_header.point_count << " points and " << object.packet_header.size << " bytes" << std::endl; - throw SizeMismatchError(); + throw SizeMismatchError(ss.str()); } object.data_points.resize(object.data_header.point_count); diff --git a/include/quanergy/pipelines/async.h b/include/quanergy/pipelines/async.h index 5e09cad..5437f15 100644 --- a/include/quanergy/pipelines/async.h +++ b/include/quanergy/pipelines/async.h @@ -22,6 +22,8 @@ #include #include +#include + namespace quanergy { namespace pipeline @@ -88,7 +90,7 @@ namespace quanergy // while shouldn't be necessary but doesn't hurt just to be sure while (input_queue_.size() > max_queue_size_) { - std::cerr << "Warning: AsyncModule dropped input due to full buffer" << std::endl; + log.warn << "AsyncModule dropped input due to full buffer" << std::endl; input_queue_.pop(); } diff --git a/src/client/device_info.cpp b/src/client/device_info.cpp index a2407d9..ed74acf 100644 --- a/src/client/device_info.cpp +++ b/src/client/device_info.cpp @@ -17,6 +17,9 @@ #include #include +// Handle notifications +#include + using namespace quanergy::client; DeviceInfo::DeviceInfo(const std::string& host) @@ -25,7 +28,7 @@ DeviceInfo::DeviceInfo(const std::string& host) std::stringstream device_info_stream; // get deviceInfo from sensor for calibration - std::cout << "Attempting to get device info from " << host << std::endl; + quanergy::log.info << "Attempting to get device info from " << host << "..." << std::endl; http_client.read(device_info_path_, device_info_stream); boost::property_tree::ptree device_info_tree; boost::property_tree::read_xml(device_info_stream, device_info_tree); @@ -63,7 +66,7 @@ DeviceInfo::DeviceInfo(const std::string& host) } // if laser data } // if cal data - + quanergy::log.info << "Device info retrieval complete." << std::endl; } // constructor diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp new file mode 100644 index 0000000..f50d1a9 --- /dev/null +++ b/src/common/notifications.cpp @@ -0,0 +1,138 @@ +/**************************************************************** + ** ** + ** Copyright(C) 2020 Quanergy Systems. All Rights Reserved. ** + ** Contact: http://www.quanergy.com ** + ** ** + ****************************************************************/ + +#include + +#include +#include + +using namespace quanergy; + +static inline bool checkLevel(NotificationLevel level, NotificationLevel min, NotificationLevel max) +{ + return (level >= min && level <= max); +} + +static std::string getTime() +{ + auto now = std::chrono::system_clock::now(); + auto itt = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&itt), "%Y-%m-%d %H:%M:%S"); + return ss.str(); +} + +NotifierBuf::NotifierBuf(std::string name, NotificationLevel level) +: name_(name) +{ + switch (level) + { + case NotificationLevel::Trace: + level_string_ = "Trace"; + break; + case NotificationLevel::Debug: + level_string_ = "Debug"; + break; + case NotificationLevel::Info: + level_string_ = "Info"; + break; + case NotificationLevel::Warn: + level_string_ = "Warn"; + break; + case NotificationLevel::Error: + level_string_ = "Error"; + break; + default: + throw std::runtime_error("NotifierBuf invalid notification level"); + } +} + +int NotifierBuf::sync() +{ + if (sink_ && !this->str().empty()) + { + if (add_formatting_) + { + (*sink_) << getTime() << " [" << level_string_ << "] " << name_ << ": " << this->str(); + } + else + { + (*sink_) << this->str(); + } + } + + this->str(""); + return 0; +} + +NotifierStream::NotifierStream(std::string name, NotificationLevel level) +: std::ostream(nullptr) +, buf_(name, level) +{ + this->rdbuf(&buf_); +} + +Notifier::Notifier(std::string name) +: error(name, NotificationLevel::Error) +, warn(name, NotificationLevel::Warn) +, info(name, NotificationLevel::Info) +, debug(name, NotificationLevel::Debug) +, trace(name, NotificationLevel::Trace) +{ + // default to Info & Warn on cout and Error on cerr + SetSinks(&std::cout, NotificationLevel::Info, NotificationLevel::Warn); + SetSink(&std::cerr, NotificationLevel::Error); +} + +void Notifier::SetSinks(std::ostream* sink, NotificationLevel minLevel, NotificationLevel maxLevel) +{ + if (checkLevel(NotificationLevel::Trace, minLevel, maxLevel)) + { + trace.SetSink(sink); + } + if (checkLevel(NotificationLevel::Debug, minLevel, maxLevel)) + { + debug.SetSink(sink); + } + if (checkLevel(NotificationLevel::Info, minLevel, maxLevel)) + { + info.SetSink(sink); + } + if (checkLevel(NotificationLevel::Warn, minLevel, maxLevel)) + { + warn.SetSink(sink); + } + if (checkLevel(NotificationLevel::Error, minLevel, maxLevel)) + { + error.SetSink(sink); + } +} + +void Notifier::SetSink(std::ostream* sink, NotificationLevel level) +{ + SetSinks(sink, level, level); +} + +void Notifier::ClearSinks(NotificationLevel minLevel, NotificationLevel maxLevel) +{ + SetSinks(nullptr, minLevel, maxLevel); +} + +void Notifier::ClearSink(NotificationLevel level) +{ + ClearSinks(level, level); +} + +void Notifier::EnableFormatting(bool enable) +{ + trace.EnableFormatting(enable); + debug.EnableFormatting(enable); + info.EnableFormatting(enable); + warn.EnableFormatting(enable); + error.EnableFormatting(enable); +} \ No newline at end of file diff --git a/src/modules/encoder_angle_calibration.cpp b/src/modules/encoder_angle_calibration.cpp index dc52112..b5631bb 100644 --- a/src/modules/encoder_angle_calibration.cpp +++ b/src/modules/encoder_angle_calibration.cpp @@ -14,6 +14,8 @@ #include +#include + #include namespace quanergy @@ -117,7 +119,7 @@ namespace quanergy { if (!started_calibration_) { - std::cout << "QuanergyClient: Starting encoder calibration. This may take up to " + log.info << "QuanergyClient: Starting encoder calibration. This may take up to " << std::chrono::duration_cast(timeout_).count() << " seconds to complete..." << std::endl; started_calibration_ = true; @@ -140,7 +142,7 @@ namespace quanergy std::stringstream msg; msg << "QuanergyClient: Encoder calibration not required for this sensor.\n" "Average amplitude calculated: " << ba::mean(amplitude_accumulator_); - std::cout << msg.str() << std::endl; + log.info << msg.str() << std::endl; setParams(0., 0.); applyCalibration(cloud_ptr); @@ -278,7 +280,7 @@ namespace quanergy { if (first_run_) { - std::cout << "QuanergyClient: AMPLITUDE(rads), PHASE(rads)" << std::endl; + log.info << "QuanergyClient: AMPLITUDE(rads), PHASE(rads)" << std::endl; first_run_ = false; } @@ -286,7 +288,7 @@ namespace quanergy output << sine_parameters.first << "," << sine_parameters.second << std::endl; - std::cout << output.str(); + log.info << output.str(); continue; } @@ -323,7 +325,7 @@ namespace quanergy { setParams(ba::mean(amplitude_accumulator_), phase_averager_.avg()); - std::cout << "QuanergyClient: Calibration complete." << std::endl + log.info << "QuanergyClient: Calibration complete." << std::endl << " amplitude : " << amplitude_ << std::endl << " phase : " << phase_ << std::endl; diff --git a/src/modules/ring_intensity_filter.cpp b/src/modules/ring_intensity_filter.cpp index 7d54d89..7316665 100644 --- a/src/modules/ring_intensity_filter.cpp +++ b/src/modules/ring_intensity_filter.cpp @@ -7,6 +7,8 @@ #include +#include + namespace quanergy { namespace client @@ -97,7 +99,7 @@ namespace quanergy { if (laser_beam >= M_SERIES_NUM_LASERS) { - std::cerr << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; + log.error << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; return std::numeric_limits::quiet_NaN(); } @@ -110,7 +112,7 @@ namespace quanergy { if (laser_beam >= M_SERIES_NUM_LASERS) { - std::cerr << "Index out of bound! Beam index should be between 0 and " + log.error << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; } else @@ -124,7 +126,7 @@ namespace quanergy { if (laser_beam >= M_SERIES_NUM_LASERS) { - std::cerr << "Index out of bound! Beam index should be between 0 and " + log.error << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; return -1; } @@ -138,7 +140,7 @@ namespace quanergy { if (laser_beam >= M_SERIES_NUM_LASERS) { - std::cerr << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; + log.error << "Index out of bound! Beam index should be between 0 and " << M_SERIES_NUM_LASERS << std::endl; } else { diff --git a/src/parsers/data_packet_parser_m_series.cpp b/src/parsers/data_packet_parser_m_series.cpp index 1d0b948..7a14a0f 100644 --- a/src/parsers/data_packet_parser_m_series.cpp +++ b/src/parsers/data_packet_parser_m_series.cpp @@ -7,6 +7,8 @@ #include +#include + namespace quanergy { namespace client @@ -123,7 +125,7 @@ namespace quanergy if (status != previous_status_) { - std::cerr << "Sensor status: " << std::uint16_t(status) << std::endl; + log.error << "Sensor status: " << std::uint16_t(status) << std::endl; previous_status_ = status; } @@ -191,7 +193,7 @@ namespace quanergy if(cloudfull) { - std::cout << "Warning: Maximum cloud size limit of (" + log.warn << "Maximum cloud size limit of (" << maximum_cloud_size_ << ") exceeded" << std::endl; } @@ -217,7 +219,7 @@ namespace quanergy } else if(current_cloud_->size() > 0) { - std::cout << "Warning: Minimum cloud size limit of (" << minimum_cloud_size_ + log.warn << "Minimum cloud size limit of (" << minimum_cloud_size_ << ") not reached (" << current_cloud_->size() << ")" << std::endl; } diff --git a/src/pipelines/sensor_pipeline.cpp b/src/pipelines/sensor_pipeline.cpp index 094e933..4ee7489 100644 --- a/src/pipelines/sensor_pipeline.cpp +++ b/src/pipelines/sensor_pipeline.cpp @@ -10,6 +10,8 @@ #include #include +#include + namespace quanergy { namespace pipeline @@ -21,7 +23,7 @@ namespace quanergy // get sensor type auto model = device_info.model(); - std::cout << "got model from device info: " << model << std::endl; + log.info << "got model from device info: " << model << std::endl; // 'model.rfind(sub, 0) == 0' checks only the first position (the beginning) of model for sub // and is true if sub was found there @@ -34,22 +36,22 @@ namespace quanergy // encoder params if (settings.calibrate) { - std::cout << "Encoder calibration will be performed" << std::endl; + log.info << "Encoder calibration will be performed" << std::endl; encoder_corrector.setFrameRate(settings.frame_rate); } else if (settings.override_encoder_params) { - std::cout << "Encoder calibration parameters provided will be applied" << std::endl; + log.info << "Encoder calibration parameters provided will be applied" << std::endl; encoder_corrector.setParams(settings.amplitude, settings.phase); } else if (device_info.amplitude() && device_info.phase()) { - std::cout << "Encoder calibration parameters from the sensor will be applied" << std::endl; + log.info << "Encoder calibration parameters from the sensor will be applied" << std::endl; encoder_corrector.setParams(*device_info.amplitude(), *device_info.phase()); } else { - std::cout << "No encoder calibration will be applied" << std::endl; + log.info << "No encoder calibration will be applied" << std::endl; encoder_corrector.setParams(0.f, 0.f); // turns off calibration procedure } @@ -67,7 +69,7 @@ namespace quanergy } else if (model.rfind("M8", 0) == 0) { - std::cout << "No vertical angle calibration information available on sensor, proceeding with M8 defaults" << std::endl; + log.warn << "No vertical angle calibration information available on sensor, proceeding with M8 defaults" << std::endl; // tell parsers to use M8 defaults parser.get().setVerticalAngles(quanergy::client::SensorType::M8);