From ae9724d9232637c874d4b5ee44b9d9e79646452f Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sun, 11 Feb 2024 18:33:01 -0600 Subject: [PATCH] feat(base_component): add new base component and improve I2C class + example (#149) * feat(base_component): add new base component * Add base component which contains logger with some methods for setting logging level and tag for the logger * Update i2c to subclass base component and add some extra methods for writing / reading with std::vector. Also added some logging to the i2c component * Add i2c format helpers for logging the i2c bus which is initialized * Add missing i2c destructor * Update i2c example to test new i2c destructor * Add i2c menu to i2c example for allowing interactive control / query of the i2c bus using cli component * Update i2c and cli component to have s3 defualts file configuring the console output port to be usb serial/jtag * added missing base component * minor update to i2c menu to enable easier reuse * minor update to deinit * feat(i2c): change naming of new functions so that older std::bind based peripheral code still compiles * doc: update * doc: rebuild * set logger verbosity to default level so that it doesnt have to be provided * update to change BMI formatting * update non-peripheral components to use new base_component class * doc: rebuild * fix(controller): fix typo from refactor to base component * fix(base_component): make constructors with arguments explicit * add explicit cast * fix(rtsp): fix out of order BMI member use * doc: rebuild --- .clang-format | 2 +- components/adc/CMakeLists.txt | 2 +- components/adc/include/continuous_adc.hpp | 21 +- components/adc/include/oneshot_adc.hpp | 7 +- components/base_component/CMakeLists.txt | 3 + .../base_component/include/base_component.hpp | 51 +++ components/bldc_driver/CMakeLists.txt | 2 +- .../bldc_driver/include/bldc_driver.hpp | 20 +- components/bldc_haptics/CMakeLists.txt | 2 +- .../bldc_haptics/include/bldc_haptics.hpp | 20 +- components/bldc_motor/CMakeLists.txt | 2 +- components/bldc_motor/include/bldc_motor.hpp | 37 ++- components/button/CMakeLists.txt | 2 +- components/button/include/button.hpp | 13 +- .../cli/example/sdkconfig.defaults.esp32s3 | 3 + components/controller/CMakeLists.txt | 2 +- components/controller/include/controller.hpp | 13 +- components/display/CMakeLists.txt | 2 +- components/display/include/display.hpp | 61 ++-- components/encoder/CMakeLists.txt | 2 +- components/encoder/include/abi_encoder.hpp | 7 +- components/event_manager/CMakeLists.txt | 2 +- .../event_manager/include/event_manager.hpp | 15 +- components/file_system/CMakeLists.txt | 2 +- .../file_system/include/file_system.hpp | 11 +- components/ftp/CMakeLists.txt | 2 +- components/ftp/include/ftp_client.hpp | 8 +- components/ftp/include/ftp_client_session.hpp | 15 +- components/ftp/include/ftp_server.hpp | 13 +- components/i2c/CMakeLists.txt | 2 +- components/i2c/example/CMakeLists.txt | 2 +- components/i2c/example/main/i2c_example.cpp | 83 +++-- components/i2c/example/main/i2c_menu.hpp | 168 ++++++++++ components/i2c/example/sdkconfig.defaults | 3 + .../i2c/example/sdkconfig.defaults.esp32s3 | 3 + components/i2c/include/i2c.hpp | 84 ++++- components/i2c/include/i2c_format_helpers.hpp | 21 ++ components/input_drivers/CMakeLists.txt | 2 +- .../input_drivers/include/encoder_input.hpp | 8 +- .../input_drivers/include/keypad_input.hpp | 8 +- .../input_drivers/include/touchpad_input.hpp | 12 +- components/joystick/CMakeLists.txt | 2 +- components/joystick/include/joystick.hpp | 14 +- components/led/CMakeLists.txt | 2 +- components/led/include/led.hpp | 12 +- components/led_strip/CMakeLists.txt | 2 +- components/led_strip/include/led_strip.hpp | 13 +- components/logger/include/logger.hpp | 11 +- components/monitor/CMakeLists.txt | 2 +- components/monitor/include/task_monitor.hpp | 8 +- components/pid/CMakeLists.txt | 2 +- components/pid/include/pid.hpp | 9 +- components/rmt/CMakeLists.txt | 2 +- components/rmt/include/rmt.hpp | 9 +- components/rtsp/CMakeLists.txt | 2 +- components/rtsp/include/rtsp_client.hpp | 22 +- components/rtsp/include/rtsp_server.hpp | 15 +- components/rtsp/include/rtsp_session.hpp | 22 +- components/socket/CMakeLists.txt | 2 +- components/socket/include/socket.hpp | 11 +- components/task/CMakeLists.txt | 2 +- components/task/include/task.hpp | 24 +- components/thermistor/CMakeLists.txt | 2 +- components/thermistor/include/thermistor.hpp | 16 +- components/timer/CMakeLists.txt | 2 +- components/timer/include/timer.hpp | 16 +- components/wifi/CMakeLists.txt | 2 +- components/wifi/include/wifi.hpp | 66 ++++ components/wifi/include/wifi_ap.hpp | 8 +- components/wifi/include/wifi_sta.hpp | 13 +- doc/Doxyfile | 1 + doc/en/base_component.rst | 16 + doc/en/index.rst | 1 + docs/_sources/base_component.rst.txt | 16 + docs/_sources/base_peripheral.rst.txt | 20 ++ docs/_sources/index.rst.txt | 1 + docs/adc/adc_types.html | 7 +- docs/adc/ads1x15.html | 5 +- docs/adc/ads7138.html | 5 +- docs/adc/continuous_adc.html | 83 ++++- docs/adc/index.html | 3 +- docs/adc/oneshot_adc.html | 83 ++++- docs/adc/tla2528.html | 5 +- docs/base_component.html | 293 +++++++++++++++++ docs/base_peripheral.html | 190 +++++++++++ docs/battery/bldc_driver.html | 83 ++++- docs/battery/bldc_motor.html | 87 ++++- docs/battery/index.html | 7 +- docs/battery/max1704x.html | 5 +- docs/bldc/bldc_driver.html | 83 ++++- docs/bldc/bldc_motor.html | 87 ++++- docs/bldc/index.html | 3 +- docs/button.html | 83 ++++- docs/cli.html | 7 +- docs/color.html | 5 +- docs/controller.html | 83 ++++- docs/csv.html | 5 +- docs/display/display.html | 87 ++++- docs/display/display_drivers.html | 7 +- docs/display/index.html | 3 +- docs/encoder/abi_encoder.html | 83 ++++- docs/encoder/as5600.html | 5 +- docs/encoder/encoder_types.html | 3 +- docs/encoder/index.html | 3 +- docs/encoder/mt6701.html | 5 +- docs/event_manager.html | 78 ++++- docs/file_system.html | 83 ++++- docs/filters/biquad.html | 5 +- docs/filters/butterworth.html | 5 +- docs/filters/index.html | 3 +- docs/filters/lowpass.html | 5 +- docs/filters/sos.html | 5 +- docs/filters/transfer_function.html | 3 +- docs/ftp/ftp_server.html | 163 ++++++++- docs/ftp/index.html | 3 +- docs/genindex.html | 309 +++++++++++++++++- docs/haptics/bldc_haptics.html | 87 ++++- docs/haptics/drv2605.html | 5 +- docs/haptics/index.html | 3 +- docs/i2c.html | 234 ++++++++++--- docs/index.html | 7 +- docs/input/encoder_input.html | 83 ++++- docs/input/ft5x06.html | 5 +- docs/input/gt911.html | 5 +- docs/input/index.html | 3 +- docs/input/keypad_input.html | 83 ++++- docs/input/t_keyboard.html | 5 +- docs/input/touchpad_input.html | 83 ++++- docs/input/tt21100.html | 5 +- docs/io_expander/aw9523.html | 5 +- docs/io_expander/index.html | 3 +- docs/io_expander/mcp23x17.html | 5 +- docs/joystick.html | 83 ++++- docs/led.html | 83 ++++- docs/led_strip.html | 83 ++++- docs/logger.html | 20 +- docs/math/bezier.html | 5 +- docs/math/fast_math.html | 3 +- docs/math/gaussian.html | 5 +- docs/math/index.html | 3 +- docs/math/range_mapper.html | 5 +- docs/math/vector2d.html | 5 +- docs/monitor.html | 86 ++++- docs/network/index.html | 3 +- docs/network/socket.html | 83 ++++- docs/network/tcp_socket.html | 81 ++++- docs/network/udp_socket.html | 81 ++++- docs/nfc/index.html | 3 +- docs/nfc/ndef.html | 5 +- docs/nfc/st25dv.html | 5 +- docs/objects.inv | Bin 61948 -> 64792 bytes docs/pid.html | 83 ++++- docs/qwiicnes.html | 5 +- docs/rmt.html | 85 ++++- docs/rtc/bm8563.html | 5 +- docs/rtc/index.html | 3 +- docs/rtsp.html | 253 +++++++++++++- docs/search.html | 1 + docs/searchindex.js | 2 +- docs/serialization.html | 5 +- docs/state_machine.html | 11 +- docs/tabulate.html | 5 +- docs/task.html | 83 ++++- docs/thermistor.html | 83 ++++- docs/timer.html | 83 ++++- docs/wifi/index.html | 3 +- docs/wifi/wifi_ap.html | 83 ++++- docs/wifi/wifi_sta.html | 83 ++++- 168 files changed, 4748 insertions(+), 567 deletions(-) create mode 100644 components/base_component/CMakeLists.txt create mode 100644 components/base_component/include/base_component.hpp create mode 100644 components/cli/example/sdkconfig.defaults.esp32s3 create mode 100644 components/i2c/example/main/i2c_menu.hpp create mode 100644 components/i2c/example/sdkconfig.defaults.esp32s3 create mode 100644 components/i2c/include/i2c_format_helpers.hpp create mode 100644 components/wifi/include/wifi.hpp create mode 100644 doc/en/base_component.rst create mode 100644 docs/_sources/base_component.rst.txt create mode 100644 docs/_sources/base_peripheral.rst.txt create mode 100644 docs/base_component.html create mode 100644 docs/base_peripheral.html diff --git a/.clang-format b/.clang-format index 0e0bcba93..e49f21ef6 100644 --- a/.clang-format +++ b/.clang-format @@ -40,7 +40,7 @@ BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializersBeforeComma: true BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true diff --git a/components/adc/CMakeLists.txt b/components/adc/CMakeLists.txt index 6c1087e11..b7d911cd4 100644 --- a/components/adc/CMakeLists.txt +++ b/components/adc/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger task esp_adc) + REQUIRES base_component task esp_adc) diff --git a/components/adc/include/continuous_adc.hpp b/components/adc/include/continuous_adc.hpp index be356b42e..78be586f0 100644 --- a/components/adc/include/continuous_adc.hpp +++ b/components/adc/include/continuous_adc.hpp @@ -10,7 +10,7 @@ #include "esp_adc/adc_continuous.h" #include "adc_types.hpp" -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" namespace espp { @@ -27,7 +27,7 @@ namespace espp { * \section adc_continuous_ex1 Continuous ADC Example * \snippet adc_example.cpp continuous adc example */ -class ContinuousAdc { +class ContinuousAdc : public espp::BaseComponent { public: /** * @brief Configure the sample rate (globally applied to each channel), @@ -50,12 +50,14 @@ class ContinuousAdc { * @param config Config used to initialize the reader. */ explicit ContinuousAdc(const Config &config) - : sample_rate_hz_(config.sample_rate_hz), window_size_bytes_(config.window_size_bytes), - num_channels_(config.channels.size()), conv_mode_(config.convert_mode), - result_data_(window_size_bytes_, 0xcc), - logger_({.tag = "Continuous Adc", - .rate_limit = std::chrono::milliseconds(100), - .level = config.log_level}) { + : BaseComponent("ContinuousAdc", config.log_level) + , sample_rate_hz_(config.sample_rate_hz) + , window_size_bytes_(config.window_size_bytes) + , num_channels_(config.channels.size()) + , conv_mode_(config.convert_mode) + , result_data_(window_size_bytes_, 0xcc) { + // set the rate limit for the logger + logger_.set_rate_limit(std::chrono::milliseconds(100)); // initialize the adc continuous subsystem init(config.channels); // and start the task @@ -203,7 +205,7 @@ class ContinuousAdc { #if !CONFIG_IDF_TARGET_ESP32 if (output_format_ == ADC_DIGI_OUTPUT_FORMAT_TYPE2) { if (check_valid_data(p)) { - auto unit = p->type2.unit; + auto unit = (adc_unit_t)p->type2.unit; auto channel = (adc_channel_t)p->type2.channel; auto data = p->type2.data; auto id = get_id(unit, channel); @@ -398,7 +400,6 @@ class ContinuousAdc { adc_digi_convert_mode_t conv_mode_; adc_digi_output_format_t output_format_; std::vector result_data_; - Logger logger_; std::unique_ptr task_; TaskHandle_t task_handle_{NULL}; std::mutex data_mutex_; diff --git a/components/adc/include/oneshot_adc.hpp b/components/adc/include/oneshot_adc.hpp index acce851ef..6bbe91c1f 100644 --- a/components/adc/include/oneshot_adc.hpp +++ b/components/adc/include/oneshot_adc.hpp @@ -10,7 +10,7 @@ #include "esp_adc/adc_oneshot.h" #include "adc_types.hpp" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -22,7 +22,7 @@ namespace espp { * \section adc_oneshot_ex1 Oneshot ADC Example * \snippet adc_example.cpp oneshot adc example */ -class OneshotAdc { +class OneshotAdc : public BaseComponent { public: /** * @brief Configure the unit for which to read adc values from the provided @@ -44,7 +44,7 @@ class OneshotAdc { * @param config Config used to initialize the reader. */ explicit OneshotAdc(const Config &config) - : logger_({.tag = "Oneshot Adc", .level = config.log_level}) { + : BaseComponent("OneShotAdc", config.log_level) { init(config); } @@ -185,6 +185,5 @@ class OneshotAdc { adc_oneshot_unit_handle_t adc_handle_; adc_cali_handle_t adc_cali_handle_; std::atomic calibrated_{false}; - Logger logger_; }; } // namespace espp diff --git a/components/base_component/CMakeLists.txt b/components/base_component/CMakeLists.txt new file mode 100644 index 000000000..1d8b9a5e8 --- /dev/null +++ b/components/base_component/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register( + INCLUDE_DIRS "include" + REQUIRES logger) diff --git a/components/base_component/include/base_component.hpp b/components/base_component/include/base_component.hpp new file mode 100644 index 000000000..4170255e9 --- /dev/null +++ b/components/base_component/include/base_component.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "logger.hpp" + +namespace espp { +/// Base class for all components +/// Provides a logger and some basic logging configuration +class BaseComponent { +public: + /// Set the tag for the logger + /// \param tag The tag to use for the logger + void set_log_tag(const std::string_view &tag) { logger_.set_tag(tag); } + + /// Set the log level for the logger + /// \param level The verbosity level to use for the logger + /// \sa Logger::Verbosity + /// \sa Logger::set_verbosity + void set_log_level(Logger::Verbosity level) { logger_.set_verbosity(level); } + + /// Set the log verbosity for the logger + /// \param level The verbosity level to use for the logger + /// \note This is a convenience method that calls set_log_level + /// \sa set_log_level + /// \sa Logger::Verbosity + /// \sa Logger::set_verbosity + void set_log_verbosity(Logger::Verbosity level) { set_log_level(level); } + + /// Set the rate limit for the logger + /// \param rate_limit The rate limit to use for the logger + /// \note Only calls to the logger that have _rate_limit suffix will be rate limited + /// \sa Logger::set_rate_limit + void set_log_rate_limit(std::chrono::duration rate_limit) { + logger_.set_rate_limit(rate_limit); + } + +protected: + BaseComponent() = default; + + explicit BaseComponent(std::string_view tag, Logger::Verbosity level = Logger::Verbosity::WARN) + : logger_({.tag = tag, .level = level}) {} + + explicit BaseComponent(const Logger::Config &logger_config) + : logger_(logger_config) {} + + /// The logger for this component + Logger logger_ = espp::Logger({.tag = "BaseComponent", .level = Logger::Verbosity::INFO}); +}; +} // namespace espp diff --git a/components/bldc_driver/CMakeLists.txt b/components/bldc_driver/CMakeLists.txt index 86f338bb0..4e90d84c0 100644 --- a/components/bldc_driver/CMakeLists.txt +++ b/components/bldc_driver/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger driver) + REQUIRES base_component driver) diff --git a/components/bldc_driver/include/bldc_driver.hpp b/components/bldc_driver/include/bldc_driver.hpp index 1fd092316..d5bb8f297 100644 --- a/components/bldc_driver/include/bldc_driver.hpp +++ b/components/bldc_driver/include/bldc_driver.hpp @@ -7,7 +7,7 @@ #include "driver/gpio.h" #include "driver/mcpwm_prelude.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -16,7 +16,7 @@ namespace espp { * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html * peripheral. */ -class BldcDriver { +class BldcDriver : public BaseComponent { public: static constexpr size_t TIMER_RESOLUTION_HZ = 80 * 1000 * 1000; // 80 MHz static constexpr size_t FREQUENCY_HZ = 20 * 1000; // 20 KHz @@ -48,11 +48,16 @@ class BldcDriver { * @param config Config used to initialize the driver. */ explicit BldcDriver(const Config &config) - : gpio_ah_((gpio_num_t)config.gpio_a_h), gpio_al_((gpio_num_t)config.gpio_a_l), - gpio_bh_((gpio_num_t)config.gpio_b_h), gpio_bl_((gpio_num_t)config.gpio_b_l), - gpio_ch_((gpio_num_t)config.gpio_c_h), gpio_cl_((gpio_num_t)config.gpio_c_l), - gpio_en_(config.gpio_enable), gpio_fault_(config.gpio_fault), dead_zone_(config.dead_zone), - logger_({.tag = "BLDC Driver", .level = config.log_level}) { + : BaseComponent("BldcDriver", config.log_level) + , gpio_ah_((gpio_num_t)config.gpio_a_h) + , gpio_al_((gpio_num_t)config.gpio_a_l) + , gpio_bh_((gpio_num_t)config.gpio_b_h) + , gpio_bl_((gpio_num_t)config.gpio_b_l) + , gpio_ch_((gpio_num_t)config.gpio_c_h) + , gpio_cl_((gpio_num_t)config.gpio_c_l) + , gpio_en_(config.gpio_enable) + , gpio_fault_(config.gpio_fault) + , dead_zone_(config.dead_zone) { configure_power(config.power_supply_voltage, config.limit_voltage); init(config); } @@ -411,6 +416,5 @@ class BldcDriver { std::array operators_; std::array comparators_; std::array generators_; - Logger logger_; }; } // namespace espp diff --git a/components/bldc_haptics/CMakeLists.txt b/components/bldc_haptics/CMakeLists.txt index cb71043a1..a4fc9a32e 100644 --- a/components/bldc_haptics/CMakeLists.txt +++ b/components/bldc_haptics/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger math pid task bldc_motor + REQUIRES base_component math pid task bldc_motor ) diff --git a/components/bldc_haptics/include/bldc_haptics.hpp b/components/bldc_haptics/include/bldc_haptics.hpp index 048957f90..3ba617ba3 100644 --- a/components/bldc_haptics/include/bldc_haptics.hpp +++ b/components/bldc_haptics/include/bldc_haptics.hpp @@ -4,10 +4,10 @@ #include #include +#include "base_component.hpp" #include "bldc_motor.hpp" #include "detent_config.hpp" #include "haptic_config.hpp" -#include "logger.hpp" #include "task.hpp" namespace espp { @@ -93,7 +93,7 @@ concept MotorConcept = requires { /// \snippet bldc_haptics_example.cpp bldc_haptics_example_1 /// \section bldc_haptics_ex2 Example 2: Playing a haptic click / buzz /// \snippet bldc_haptics_example.cpp bldc_haptics_example_2 -template class BldcHaptics { +template class BldcHaptics : public BaseComponent { public: /// @brief Configuration for the haptic motor struct Config { @@ -113,19 +113,19 @@ template class BldcHaptics { /// @brief Constructor for the haptic motor /// @param config Configuration for the haptic motor explicit BldcHaptics(const Config &config) - : detent_pid_({.kp = 0, // will be set later (motor_task) + : BaseComponent("BldcHaptics", config.log_level) + , detent_pid_({.kp = 0, // will be set later (motor_task) .ki = 0, // not configurable for now .kd = 0, // will be set later (update_detent_config) .integrator_min = 0, // not configurable for now .integrator_max = 0, // not configurable for now .output_min = -1, // go ahead and set some bounds (operates on current) .output_max = 1}) // go ahead and set some bounds (operates on current) - , - kp_factor_(config.kp_factor), kd_factor_min_(config.kd_factor_min), - kd_factor_max_(config.kd_factor_max), motor_(config.motor), - logger_({.tag = "BldcHaptics", - .rate_limit = std::chrono::milliseconds(100), - .level = config.log_level}) { + , kp_factor_(config.kp_factor) + , kd_factor_min_(config.kd_factor_min) + , kd_factor_max_(config.kd_factor_max) + , motor_(config.motor) { + logger_.set_rate_limit(std::chrono::milliseconds(100)); logger_.info("Initializing haptic motor\n" "\tkp_factor: {}\n" "\tkd_factor_min: {}\n" @@ -432,7 +432,5 @@ template class BldcHaptics { std::reference_wrapper motor_; ///< Pointer to the motor to use for haptics std::unique_ptr motor_task_; ///< Task which runs the haptic motor - - Logger logger_; ///< Logger for the haptics }; } // namespace espp diff --git a/components/bldc_motor/CMakeLists.txt b/components/bldc_motor/CMakeLists.txt index 98f4ad03e..dec80c1be 100644 --- a/components/bldc_motor/CMakeLists.txt +++ b/components/bldc_motor/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger math pid task + REQUIRES base_component math pid task ) diff --git a/components/bldc_motor/include/bldc_motor.hpp b/components/bldc_motor/include/bldc_motor.hpp index a4790548f..6dc973480 100644 --- a/components/bldc_motor/include/bldc_motor.hpp +++ b/components/bldc_motor/include/bldc_motor.hpp @@ -2,8 +2,8 @@ #include +#include "base_component.hpp" #include "fast_math.hpp" -#include "logger.hpp" #include "pid.hpp" #include "task.hpp" @@ -65,7 +65,7 @@ struct DummyCurrentSense { * @snippet bldc_motor_example.cpp bldc_motor example */ template -class BldcMotor { +class BldcMotor : public BaseComponent { public: /** * @brief Filter the raw input sample and return it. @@ -130,17 +130,27 @@ class BldcMotor { * necessary sensor calibration. */ explicit BldcMotor(const Config &config) - : num_pole_pairs_(config.num_pole_pairs), phase_resistance_(config.phase_resistance), - phase_inductance_(config.phase_inductance), kv_rating_(config.kv_rating * _SQRT2), - current_limit_(config.current_limit), velocity_limit_(config.velocity_limit), - sensor_direction_(config.sensor_direction), foc_type_(config.foc_type), - torque_control_type_(config.torque_controller), driver_(config.driver), - sensor_(config.sensor), current_sense_(config.current_sense), - pid_current_q_(config.current_pid_config), pid_current_d_(config.current_pid_config), - pid_velocity_(config.current_pid_config), pid_angle_(config.current_pid_config), - q_current_filter_(config.q_current_filter), d_current_filter_(config.d_current_filter), - velocity_filter_(config.velocity_filter), angle_filter_(config.angle_filter), - logger_({.tag = "BldcMotor", .level = config.log_level}) { + : BaseComponent("BldcMotor", config.log_level) + , num_pole_pairs_(config.num_pole_pairs) + , phase_resistance_(config.phase_resistance) + , phase_inductance_(config.phase_inductance) + , kv_rating_(config.kv_rating * _SQRT2) + , current_limit_(config.current_limit) + , velocity_limit_(config.velocity_limit) + , sensor_direction_(config.sensor_direction) + , foc_type_(config.foc_type) + , torque_control_type_(config.torque_controller) + , driver_(config.driver) + , sensor_(config.sensor) + , current_sense_(config.current_sense) + , pid_current_q_(config.current_pid_config) + , pid_current_d_(config.current_pid_config) + , pid_velocity_(config.current_pid_config) + , pid_angle_(config.current_pid_config) + , q_current_filter_(config.q_current_filter) + , d_current_filter_(config.d_current_filter) + , velocity_filter_(config.velocity_filter) + , angle_filter_(config.angle_filter) { // initialize the voltage limit voltage_limit_ = driver_->get_voltage_limit(); voltage_sensor_align_ = voltage_limit_ / 4.0f; @@ -948,7 +958,6 @@ class BldcMotor { filter_fn angle_filter_{nullptr}; std::atomic enabled_{false}; Status status_{Status::UNINITIALIZED}; - Logger logger_; }; } // namespace espp diff --git a/components/button/CMakeLists.txt b/components/button/CMakeLists.txt index c3e49703e..b5464f5ff 100644 --- a/components/button/CMakeLists.txt +++ b/components/button/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES driver logger task) + REQUIRES driver base_component task) diff --git a/components/button/include/button.hpp b/components/button/include/button.hpp index c45a2f54a..e7b902a8b 100644 --- a/components/button/include/button.hpp +++ b/components/button/include/button.hpp @@ -8,7 +8,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/queue.h" -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" namespace espp { @@ -19,7 +19,7 @@ namespace espp { /// /// \section button_ex1 Button Example /// \snippet button_example.cpp button example -class Button { +class Button : public BaseComponent { public: /// \brief The event for the button struct Event { @@ -65,9 +65,11 @@ class Button { /// \brief Construct a button /// \param config The configuration for the button explicit Button(const Config &config) - : gpio_num_(config.gpio_num), callback_(config.callback), active_level_(config.active_level), - event_queue_(xQueueCreate(10, sizeof(EventData))), - logger_({.tag = config.name, .level = config.log_level}) { + : BaseComponent(config.name, config.log_level) + , gpio_num_(config.gpio_num) + , callback_(config.callback) + , active_level_(config.active_level) + , event_queue_(xQueueCreate(10, sizeof(EventData))) { // configure the GPIO for an interrupt gpio_config_t io_conf; memset(&io_conf, 0, sizeof(io_conf)); @@ -178,6 +180,5 @@ class Button { HandlerArgs handler_args_; std::atomic pressed_{false}; std::unique_ptr task_; - espp::Logger logger_; }; } // namespace espp diff --git a/components/cli/example/sdkconfig.defaults.esp32s3 b/components/cli/example/sdkconfig.defaults.esp32s3 new file mode 100644 index 000000000..eaee743c2 --- /dev/null +++ b/components/cli/example/sdkconfig.defaults.esp32s3 @@ -0,0 +1,3 @@ +# on the ESP32S3, which has native USB, we need to set the console so that the +# CLI can be configured correctly: +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/controller/CMakeLists.txt b/components/controller/CMakeLists.txt index 50dd73b45..e01474da2 100644 --- a/components/controller/CMakeLists.txt +++ b/components/controller/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES driver joystick + REQUIRES driver base_component joystick ) diff --git a/components/controller/include/controller.hpp b/components/controller/include/controller.hpp index dc702983a..f2ad534a5 100644 --- a/components/controller/include/controller.hpp +++ b/components/controller/include/controller.hpp @@ -7,8 +7,8 @@ #include "driver/dedic_gpio.h" #include "driver/gpio.h" +#include "base_component.hpp" #include "joystick.hpp" -#include "logger.hpp" namespace espp { /** @@ -31,7 +31,7 @@ namespace espp { * \section controller_ex3 I2C Analog Controller Example * \snippet controller_example.cpp i2c analog controller example */ -class Controller { +class Controller : public BaseComponent { public: /** * @brief The buttons that the controller supports. @@ -137,7 +137,7 @@ class Controller { * @brief Create a Digital controller. */ explicit Controller(const DigitalConfig &config) - : logger_({.tag = "Digital Controller", .level = config.log_level}) { + : BaseComponent("Digital Controller", config.log_level) { gpio_.assign((int)Button::LAST_UNUSED, -1); input_state_.assign((int)Button::LAST_UNUSED, false); gpio_[(int)Button::A] = config.gpio_a; @@ -157,8 +157,8 @@ class Controller { * @brief Create an analog joystick controller. */ explicit Controller(const AnalogJoystickConfig &config) - : joystick_(std::make_unique(config.joystick_config)), - logger_({.tag = "Analog Joystick Controller", .level = config.log_level}) { + : BaseComponent("Analog Joystick Controller", config.log_level) + , joystick_(std::make_unique(config.joystick_config)) { gpio_.assign((int)Button::LAST_UNUSED, -1); input_state_.assign((int)Button::LAST_UNUSED, false); gpio_[(int)Button::A] = config.gpio_a; @@ -175,7 +175,7 @@ class Controller { * @brief Create a dual d-pad + analog joystick controller. */ explicit Controller(const DualConfig &config) - : logger_({.tag = "Dual Digital Controller", .level = config.log_level}) { + : BaseComponent("Dual Digital Controller", config.log_level) { gpio_.assign((int)Button::LAST_UNUSED, -1); input_state_.assign((int)Button::LAST_UNUSED, false); gpio_[(int)Button::A] = config.gpio_a; @@ -322,6 +322,5 @@ class Controller { std::vector input_state_; dedic_gpio_bundle_handle_t gpio_bundle_{NULL}; std::unique_ptr joystick_; - espp::Logger logger_; }; } // namespace espp diff --git a/components/display/CMakeLists.txt b/components/display/CMakeLists.txt index 50b268d16..31a343098 100644 --- a/components/display/CMakeLists.txt +++ b/components/display/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES driver led lvgl logger task) + REQUIRES driver led lvgl base_component task) # lvgl generates deprecated enum-enum conversion warnings, suppress them target_compile_options(${COMPONENT_LIB} INTERFACE "-Wno-deprecated-enum-enum-conversion") add_definitions(-DLV_CONF_INCLUDE_SIMPLE) diff --git a/components/display/include/display.hpp b/components/display/include/display.hpp index c383dfcb8..498b5e786 100644 --- a/components/display/include/display.hpp +++ b/components/display/include/display.hpp @@ -8,6 +8,7 @@ #include +#include "base_component.hpp" #include "led.hpp" #include "task.hpp" @@ -23,7 +24,7 @@ namespace espp { * For more information, see * https://docs.lvgl.io/8.3/porting/display.html#display-interface */ -class Display { +class Display : public BaseComponent { public: /** * @brief Callback for lvgl to flush segments of pixel data from the pixel @@ -57,8 +58,7 @@ class Display { gpio_num_t backlight_pin; /**< GPIO pin for the backlight. */ bool backlight_on_value{ true}; /**< Value to write to the backlight pin to turn the backlight on. */ - size_t stack_size_bytes{ - 4096}; /**< Size of the display task stack in bytes. */ + size_t stack_size_bytes{4096}; /**< Size of the display task stack in bytes. */ std::chrono::duration update_period{ 0.01}; /**< How frequently to run the update function. */ bool double_buffered{ @@ -87,8 +87,7 @@ class Display { gpio_num_t backlight_pin; /**< GPIO pin for the backlight. */ bool backlight_on_value{ true}; /**< Value to write to the backlight pin to turn the backlight on. */ - size_t stack_size_bytes{ - 4096}; /**< Size of the display task stack in bytes. */ + size_t stack_size_bytes{4096}; /**< Size of the display task stack in bytes. */ std::chrono::duration update_period{ 0.01}; /**< How frequently to run the update function. */ Rotation rotation{Rotation::LANDSCAPE}; /**< Default / Initial rotation of the display. */ @@ -104,19 +103,20 @@ class Display { * callback. */ explicit Display(const AllocatingConfig &config) - : width_(config.width), height_(config.height), - display_buffer_px_size_(config.pixel_buffer_size), - led_channel_configs_( + : BaseComponent("Display", config.log_level) + , width_(config.width) + , height_(config.height) + , display_buffer_px_size_(config.pixel_buffer_size) + , led_channel_configs_( std::vector{{.gpio = (size_t)config.backlight_pin, .channel = LEDC_CHANNEL_0, .timer = LEDC_TIMER_0, - .output_invert = !config.backlight_on_value}}), - backlight_(Led::Config{.timer = LEDC_TIMER_0, + .output_invert = !config.backlight_on_value}}) + , backlight_(Led::Config{.timer = LEDC_TIMER_0, .frequency_hz = 5000, .channels = led_channel_configs_, - .duty_resolution = LEDC_TIMER_10_BIT}), - update_period_(config.update_period), - logger_({.tag = "Display", .level = config.log_level}) { + .duty_resolution = LEDC_TIMER_10_BIT}) + , update_period_(config.update_period) { logger_.debug("Initializing with allocating config!"); // create the display buffers vram_0_ = (lv_color_t *)heap_caps_malloc(vram_size_bytes(), config.allocation_flags); @@ -126,7 +126,8 @@ class Display { assert(vram_1_ != NULL); } created_vram_ = true; - init(config.flush_callback, config.software_rotation_enabled, config.rotation, config.stack_size_bytes); + init(config.flush_callback, config.software_rotation_enabled, config.rotation, + config.stack_size_bytes); set_brightness(1.0f); } @@ -136,21 +137,25 @@ class Display { * memory, the pixel buffer size and flush callback. */ explicit Display(const NonAllocatingConfig &config) - : width_(config.width), height_(config.height), - display_buffer_px_size_(config.pixel_buffer_size), vram_0_(config.vram0), - vram_1_(config.vram1), led_channel_configs_(std::vector{ - {.gpio = (size_t)config.backlight_pin, - .channel = LEDC_CHANNEL_0, - .timer = LEDC_TIMER_0, - .output_invert = !config.backlight_on_value}}), - backlight_(Led::Config{.timer = LEDC_TIMER_0, + : BaseComponent("Display", config.log_level) + , width_(config.width) + , height_(config.height) + , display_buffer_px_size_(config.pixel_buffer_size) + , vram_0_(config.vram0) + , vram_1_(config.vram1) + , led_channel_configs_( + std::vector{{.gpio = (size_t)config.backlight_pin, + .channel = LEDC_CHANNEL_0, + .timer = LEDC_TIMER_0, + .output_invert = !config.backlight_on_value}}) + , backlight_(Led::Config{.timer = LEDC_TIMER_0, .frequency_hz = 5000, .channels = led_channel_configs_, - .duty_resolution = LEDC_TIMER_10_BIT}), - update_period_(config.update_period), - logger_({.tag = "Display", .level = config.log_level}) { + .duty_resolution = LEDC_TIMER_10_BIT}) + , update_period_(config.update_period) { logger_.debug("Initializing with non-allocating config!"); - init(config.flush_callback, config.software_rotation_enabled, config.rotation, config.stack_size_bytes); + init(config.flush_callback, config.software_rotation_enabled, config.rotation, + config.stack_size_bytes); } /** @@ -258,7 +263,8 @@ class Display { * @param rotation Default / initial rotation of the display. * @param stack_size_bytes Size of the task stack in bytes. */ - void init(flush_fn flush_callback, bool sw_rotation_enabled, Rotation rotation, size_t stack_size_bytes = 4096) { + void init(flush_fn flush_callback, bool sw_rotation_enabled, Rotation rotation, + size_t stack_size_bytes = 4096) { lv_init(); // Configure the LVGL display buffer with our pixel buffers @@ -327,6 +333,5 @@ class Display { std::chrono::duration update_period_; lv_disp_draw_buf_t disp_buffer_; lv_disp_drv_t disp_driver_; - Logger logger_; }; } // namespace espp diff --git a/components/encoder/CMakeLists.txt b/components/encoder/CMakeLists.txt index 86f338bb0..4e90d84c0 100644 --- a/components/encoder/CMakeLists.txt +++ b/components/encoder/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger driver) + REQUIRES base_component driver) diff --git a/components/encoder/include/abi_encoder.hpp b/components/encoder/include/abi_encoder.hpp index c04fe9752..acea583e5 100644 --- a/components/encoder/include/abi_encoder.hpp +++ b/components/encoder/include/abi_encoder.hpp @@ -5,8 +5,8 @@ #include "driver/gpio.h" #include "driver/pulse_cnt.h" +#include "base_component.hpp" #include "encoder_types.hpp" -#include "logger.hpp" namespace espp { /** @@ -20,7 +20,7 @@ namespace espp { * \section encoder_ex2 AbiEncoder (LINEAR) Example * \snippet encoder_example.cpp abi encoder linear example */ -template class AbiEncoder { +template class AbiEncoder : public BaseComponent { public: struct Config { int a_gpio; /**< GPIO number for the a channel pulse. */ @@ -48,7 +48,7 @@ template class AbiEncoder { */ template explicit AbiEncoder(const Config &config) - : logger_({.tag = "AbiEncoder", .level = config.log_level}) { + : BaseComponent("AbiEncoder", config.log_level) { // we only care about counts_per_revolution if it is EncoderType::ROTATIONAL if constexpr (type == EncoderType::ROTATIONAL) { if (config.counts_per_revolution == 0) { @@ -275,6 +275,5 @@ template class AbiEncoder { pcnt_unit_handle_t pcnt_unit_{nullptr}; pcnt_channel_handle_t pcnt_channel_a_{nullptr}; pcnt_channel_handle_t pcnt_channel_b_{nullptr}; - Logger logger_; }; } // namespace espp diff --git a/components/event_manager/CMakeLists.txt b/components/event_manager/CMakeLists.txt index b34800578..cb1e7ccab 100644 --- a/components/event_manager/CMakeLists.txt +++ b/components/event_manager/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES logger task) + REQUIRES base_component task) diff --git a/components/event_manager/include/event_manager.hpp b/components/event_manager/include/event_manager.hpp index 9113d4acb..5a80c2cf5 100644 --- a/components/event_manager/include/event_manager.hpp +++ b/components/event_manager/include/event_manager.hpp @@ -6,8 +6,8 @@ #include #include +#include "base_component.hpp" #include "event_map.hpp" -#include "logger.hpp" #include "task.hpp" namespace espp { @@ -34,7 +34,7 @@ namespace espp { * \section event_manager_ex1 Event Manager Example * \snippet event_manager_example.cpp event manager example */ -class EventManager { +class EventManager : public BaseComponent { public: /** * @brief Function definition for function prototypes to be called when @@ -110,14 +110,9 @@ class EventManager { */ bool remove_subscriber(const std::string &topic, const std::string &component); - /** - * @brief Set the logger verbosity for the EventManager. - * @param level new Logger::Verbosity level to use. - */ - void set_log_level(Logger::Verbosity level) { logger_.set_verbosity(level); } - protected: - EventManager() : logger_({.tag = "Event Manager", .level = Logger::Verbosity::WARN}) {} + EventManager() + : BaseComponent("Event Manager") {} struct SubscriberData { std::mutex m; @@ -139,7 +134,5 @@ class EventManager { std::recursive_mutex data_mutex_; std::unordered_map subscriber_data_; - - Logger logger_; }; } // namespace espp diff --git a/components/file_system/CMakeLists.txt b/components/file_system/CMakeLists.txt index 8026e3c96..e91d440e7 100644 --- a/components/file_system/CMakeLists.txt +++ b/components/file_system/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger esp_littlefs spi_flash) + REQUIRES base_component esp_littlefs spi_flash) diff --git a/components/file_system/include/file_system.hpp b/components/file_system/include/file_system.hpp index b2bd18a95..9ffb1d085 100644 --- a/components/file_system/include/file_system.hpp +++ b/components/file_system/include/file_system.hpp @@ -19,7 +19,7 @@ #include -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /// @brief File system class @@ -43,7 +43,7 @@ namespace espp { /// \snippet file_system_example.cpp file_system posix example /// \section fs_ex3 File System Info std::filesystem Example /// \snippet file_system_example.cpp file_system std filesystem example -class FileSystem { +class FileSystem : public BaseComponent { public: /// @brief Access the singleton instance of the file system /// @return Reference to the file system instance @@ -276,7 +276,10 @@ class FileSystem { /// @brief Constructor /// @details /// The constructor is private to ensure that the class is a singleton. - FileSystem() : logger_({.tag = "FileSystem", .level = Logger::Verbosity::WARN}) { init(); } + FileSystem() + : BaseComponent("FileSystem") { + init(); + } /// @brief Initialize the file system /// @details @@ -321,7 +324,5 @@ class FileSystem { std::filesystem::create_directory(root_path); } } - - Logger logger_; }; } // namespace espp diff --git a/components/ftp/CMakeLists.txt b/components/ftp/CMakeLists.txt index c0119cae2..bf0d7d50b 100644 --- a/components/ftp/CMakeLists.txt +++ b/components/ftp/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger task socket) + REQUIRES base_component task socket) diff --git a/components/ftp/include/ftp_client.hpp b/components/ftp/include/ftp_client.hpp index d14f05b3b..522252288 100644 --- a/components/ftp/include/ftp_client.hpp +++ b/components/ftp/include/ftp_client.hpp @@ -6,14 +6,14 @@ #include #include -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "tcp_socket.hpp" #include "udp_socket.hpp" namespace espp { /// \brief A class which implements a simple FTP client. -class FtpClient { +class FtpClient : public BaseComponent { public: /// \brief A struct which represents an FTP response. struct Response { @@ -38,7 +38,8 @@ class FtpClient { }; /// \brief Constructs a new FtpClient object. - FtpClient() : logger_({.tag = "FtpClient", .level = Logger::Verbosity::WARN}) {} + FtpClient() + : BaseComponent("FtpClient") {} /// \brief Connects to the FTP server. /// \param host The hostname or IP address of the FTP server. @@ -291,6 +292,5 @@ class FtpClient { std::string response_; std::string message_; std::unique_ptr task_; - Logger logger_; }; } // namespace espp diff --git a/components/ftp/include/ftp_client_session.hpp b/components/ftp/include/ftp_client_session.hpp index 1b38c89b6..7a014dc36 100644 --- a/components/ftp/include/ftp_client_session.hpp +++ b/components/ftp/include/ftp_client_session.hpp @@ -19,7 +19,7 @@ #include #endif -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "tcp_socket.hpp" @@ -41,15 +41,17 @@ template std::time_t to_time_t(TP tp) { namespace espp { /// Class representing a client that is connected to the FTP server. This /// class is used by the FtpServer class to handle the client's requests. -class FtpClientSession { +class FtpClientSession : public BaseComponent { public: explicit FtpClientSession(int id, std::string_view local_address, std::unique_ptr socket, const std::filesystem::path &root_path) - : id_(id), local_ip_address_(local_address), current_directory_(root_path), - socket_(std::move(socket)), passive_socket_({.log_level = Logger::Verbosity::WARN}), - logger_( - {.tag = "FtpClientSession " + std::to_string(id), .level = Logger::Verbosity::WARN}) { + : BaseComponent("FtpClientSession " + std::to_string(id)) + , id_(id) + , local_ip_address_(local_address) + , current_directory_(root_path) + , socket_(std::move(socket)) + , passive_socket_({.log_level = Logger::Verbosity::WARN}) { logger_.debug("Client session {} created", id_); send_welcome_message(); using namespace std::placeholders; @@ -1163,7 +1165,6 @@ class FtpClientSession { uint16_t data_port_{0}; std::unique_ptr task_; - Logger logger_; }; } // namespace espp diff --git a/components/ftp/include/ftp_server.hpp b/components/ftp/include/ftp_server.hpp index 7d88f472f..42a953af7 100644 --- a/components/ftp/include/ftp_server.hpp +++ b/components/ftp/include/ftp_server.hpp @@ -15,7 +15,7 @@ #include #endif -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "tcp_socket.hpp" @@ -23,7 +23,7 @@ namespace espp { /// \brief A class that implements a FTP server. -class FtpServer { +class FtpServer : public BaseComponent { public: /// \brief A class that implements a FTP server. /// \note The IP Address is not currently used to select the right @@ -33,8 +33,11 @@ class FtpServer { /// \param port The port to listen on. /// \param root The root directory of the FTP server. FtpServer(std::string_view ip_address, uint16_t port, const std::filesystem::path &root) - : ip_address_(ip_address), port_(port), server_({.log_level = Logger::Verbosity::WARN}), - root_(root), logger_({.tag = "FtpServer", .level = Logger::Verbosity::WARN}) {} + : BaseComponent("FtpServer") + , ip_address_(ip_address) + , port_(port) + , server_({.log_level = Logger::Verbosity::WARN}) + , root_(root) {} /// \brief Destroy the FTP server. ~FtpServer() { stop(); } @@ -154,7 +157,5 @@ class FtpServer { std::mutex clients_mutex_; std::unordered_map> clients_; - - Logger logger_; }; } // namespace espp diff --git a/components/i2c/CMakeLists.txt b/components/i2c/CMakeLists.txt index d4ce87c49..1f7ada26d 100644 --- a/components/i2c/CMakeLists.txt +++ b/components/i2c/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "driver" "logger" + REQUIRES "driver" "base_component" ) diff --git a/components/i2c/example/CMakeLists.txt b/components/i2c/example/CMakeLists.txt index e887c19d4..beec22e9e 100644 --- a/components/i2c/example/CMakeLists.txt +++ b/components/i2c/example/CMakeLists.txt @@ -11,7 +11,7 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py i2c logger" + "main esptool_py cli i2c logger" CACHE STRING "List of components to include" ) diff --git a/components/i2c/example/main/i2c_example.cpp b/components/i2c/example/main/i2c_example.cpp index 384d2d0d4..35aba63e7 100644 --- a/components/i2c/example/main/i2c_example.cpp +++ b/components/i2c/example/main/i2c_example.cpp @@ -5,45 +5,72 @@ #include "i2c.hpp" #include "logger.hpp" +#include "i2c_menu.hpp" + using namespace std::chrono_literals; extern "C" void app_main(void) { espp::Logger logger({.tag = "I2C Example", .level = espp::Logger::Verbosity::INFO}); logger.info("Starting"); - //! [i2c example] - espp::I2c i2c({ - .port = I2C_NUM_0, - .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, - .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, + + { + //! [i2c menu example] + espp::I2c i2c({ + .port = I2C_NUM_0, + .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, + .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, + .log_level = espp::Logger::Verbosity::INFO, }); + // now make a menu for the auth object + I2cMenu i2c_menu(i2c); + cli::Cli cli(i2c_menu.get()); + cli::SetColor(); + cli.ExitAction([](auto &out) { out << "Goodbye and thanks for all the fish.\n"; }); + espp::Cli input(cli); + input.SetInputHistorySize(10); - // probe the bus for all addresses and store the ones that were found / - // responded - std::vector found_addresses; - for (uint8_t address = 0; address < 128; address++) { - if (i2c.probe_device(address)) { - found_addresses.push_back(address); - } + input.Start(); // As this is in the primary thread, we hold here until cli is complete. + //! [i2c menu example] } - // print out the addresses that were found - logger.info("Found devices at addresses: {::#02x}", found_addresses); - - static constexpr uint8_t device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR; - static constexpr uint8_t register_address = CONFIG_EXAMPLE_I2C_DEVICE_REG_ADDR; - bool device_found = i2c.probe_device(device_address); - if (device_found) { - logger.info("Found device with address {:#02x}", device_address); - std::vector read_data(CONFIG_EXAMPLE_I2C_DEVICE_REG_SIZE, 0); - bool success = i2c.read_at_register(device_address, register_address, read_data.data(), read_data.size()); - if (success) { - logger.info("read data: {::#02x}", read_data); + + { + //! [i2c example] + espp::I2c i2c({ + .port = I2C_NUM_0, + .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, + .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, + }); + + // probe the bus for all addresses and store the ones that were found / + // responded + std::vector found_addresses; + for (uint8_t address = 0; address < 128; address++) { + if (i2c.probe_device(address)) { + found_addresses.push_back(address); + } + } + // print out the addresses that were found + logger.info("Found devices at addresses: {::#02x}", found_addresses); + + static constexpr uint8_t device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR; + static constexpr uint8_t register_address = CONFIG_EXAMPLE_I2C_DEVICE_REG_ADDR; + bool device_found = i2c.probe_device(device_address); + if (device_found) { + logger.info("Found device with address {:#02x}", device_address); + std::vector read_data(CONFIG_EXAMPLE_I2C_DEVICE_REG_SIZE, 0); + bool success = i2c.read_at_register(device_address, register_address, read_data.data(), + read_data.size()); + if (success) { + logger.info("read data: {::#02x}", read_data); + } else { + logger.error("read failed"); + } } else { - logger.error("read failed"); + logger.error("Could not find device with address {:#02x}", device_address); } - } else { - logger.error("Could not find device with address {:#02x}", device_address); + //! [i2c example] } - //! [i2c example] + logger.info("I2C example complete!"); while (true) { diff --git a/components/i2c/example/main/i2c_menu.hpp b/components/i2c/example/main/i2c_menu.hpp new file mode 100644 index 000000000..4314d5080 --- /dev/null +++ b/components/i2c/example/main/i2c_menu.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include + +#include "cli.hpp" +#include "format.hpp" +#include "i2c.hpp" + +class I2cMenu { +public: + explicit I2cMenu(std::reference_wrapper i2c) : i2c_(i2c) {} + + std::unique_ptr get(std::string_view name = "i2c", + std::string_view description = "I2c menu") { + auto i2c_menu = std::make_unique(std::string(name), std::string(description)); + + // set the log verbosity for the i2c bus + i2c_menu->Insert( + "log", + [this](std::ostream &out, const std::string &verbosity) -> void { + if (verbosity == "debug") { + i2c_.get().set_log_level(espp::Logger::Verbosity::DEBUG); + } else if (verbosity == "info") { + i2c_.get().set_log_level(espp::Logger::Verbosity::INFO); + } else if (verbosity == "warn") { + i2c_.get().set_log_level(espp::Logger::Verbosity::WARN); + } else if (verbosity == "error") { + i2c_.get().set_log_level(espp::Logger::Verbosity::ERROR); + } else if (verbosity == "none") { + i2c_.get().set_log_level(espp::Logger::Verbosity::NONE); + } else { + out << "Invalid log level.\n"; + return; + } + out << fmt::format("Set I2c log level to {}.\n", verbosity); + }, + "Set the log verbosity for the I2c bus."); + + // scan the bus for devices + i2c_menu->Insert( + "scan", + [this](std::ostream &out) -> void { + out << "Scanning I2c bus...\n"; + std::vector found_addresses; + for (uint8_t address = 1; address < 128; address++) { + if (i2c_.get().probe_device(address)) { + found_addresses.push_back(address); + } + } + if (found_addresses.empty()) { + out << "No devices found.\n"; + } else { + std::string log = fmt::format("Found devices at addresses: {::#02x}", found_addresses); + out << log << "\n"; + } + }, + "Scan the I2c bus for devices."); + + // probe for a device (hexadecimal address string) + i2c_menu->Insert( + "probe", {"address (hex)"}, + [this](std::ostream &out, const std::string &address_string) -> void { + // convert address_string to a uint8_t + uint8_t address = std::stoi(address_string, nullptr, 16); + if (i2c_.get().probe_device(address)) { + out << fmt::format("Device found at address {:#02x}.\n", address); + } else { + out << fmt::format("No device found at address {:#02x}.\n", address); + } + }, + "Probe for a device at a specific address, given as a hexadecimal string."); + + // read from a device + i2c_menu->Insert( + "read", {"address (hex)", "register"}, + [this](std::ostream &out, const std::string &address_string, uint8_t reg) -> void { + // convert address_string to a uint8_t + uint8_t address = std::stoi(address_string, nullptr, 16); + uint8_t data; + std::string log; + if (i2c_.get().read_at_register(address, reg, &data, 1)) { + log = fmt::format("Read from address {:#02x} at register {:#02x}: {:#02x}", address, + reg, data); + } else { + log = + fmt::format("Error reading from address {:#02x} at register {:#02x}", address, reg); + } + out << log << "\n"; + }, + "Read a byte from a device at a specific address and register."); + + // read from a device + i2c_menu->Insert( + "read", {"address (hex)", "register", "length (number of bytes to read)"}, + [this](std::ostream &out, const std::string &address_string, uint8_t reg, + uint8_t len) -> void { + // convert address_string to a uint8_t + uint8_t address = std::stoi(address_string, nullptr, 16); + std::vector data(len); + std::string log; + if (i2c_.get().read_at_register_vector(address, reg, data)) { + log = fmt::format("Read {} bytes from address {:#02x} at register {:#02x}: {::#02x}", + data.size(), address, reg, data); + } else { + log = + fmt::format("Error reading from address {:#02x} at register {:#02x}", address, reg); + } + out << log << "\n"; + }, + "Read len bytes from a device at a specific address and register."); + + // write to a device + i2c_menu->Insert( + "write", {"address (hex)", "register", "data byte"}, + [this](std::ostream &out, const std::string &address_string, uint8_t reg, + uint8_t data) -> void { + // convert address_string to a uint8_t + uint8_t address = std::stoi(address_string, nullptr, 16); + std::vector data_vec = {reg, data}; + std::string log; + if (i2c_.get().write_vector(address, data_vec)) { + log = fmt::format("Wrote data {:#02x} to address {:#02x} at register {:#02x}", data, + address, reg); + } else { + log = fmt::format("Error writing data {:#02x} to address {:#02x} at register {:#02x}", + data, address, reg); + } + out << log << "\n"; + }, + "Write a byte to a device at a specific address and register."); + + // write to a device + i2c_menu->Insert( + "write", {"address (hex)", "register (hex)", "data byte (hex)", "data byte (hex)", "..."}, + [this](std::ostream &out, const std::vector &args) -> void { + // parse the args into address, reg, and data + if (args.size() < 3) { + out << "Not enough arguments.\n"; + return; + } + // convert address_string to a uint8_t + uint8_t address = std::stoi(args[0], nullptr, 16); + // remove the address byte (first element) and convert the rest of the + // vector of strings into a vector of bytes + std::vector data; + std::transform(args.begin() + 1, args.end(), std::back_inserter(data), + [](const std::string &s) -> uint8_t { return std::stoi(s, nullptr, 0); }); + uint8_t reg = data[0]; + std::string log; + if (i2c_.get().write_vector(address, data)) { + log = fmt::format("Wrote {} bytes to address {:#02x} at register {:#02x}: {::#02x}", + data.size(), address, reg, data); + } else { + log = fmt::format("Error writing {} bytes to address {:#02x} at register {:#02x}", + data.size(), address, reg); + } + out << log << "\n"; + }, + "Write bytes to a device at a specific address and register."); + + return i2c_menu; + } + +protected: + std::reference_wrapper i2c_; +}; diff --git a/components/i2c/example/sdkconfig.defaults b/components/i2c/example/sdkconfig.defaults index c3667f3e3..4ff973154 100644 --- a/components/i2c/example/sdkconfig.defaults +++ b/components/i2c/example/sdkconfig.defaults @@ -2,3 +2,6 @@ # CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 + +# the cli library requires exceptions right now... +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/i2c/example/sdkconfig.defaults.esp32s3 b/components/i2c/example/sdkconfig.defaults.esp32s3 new file mode 100644 index 000000000..eaee743c2 --- /dev/null +++ b/components/i2c/example/sdkconfig.defaults.esp32s3 @@ -0,0 +1,3 @@ +# on the ESP32S3, which has native USB, we need to set the console so that the +# CLI can be configured correctly: +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/i2c/include/i2c.hpp b/components/i2c/include/i2c.hpp index a1905b3ce..fe2136acb 100644 --- a/components/i2c/include/i2c.hpp +++ b/components/i2c/include/i2c.hpp @@ -1,10 +1,13 @@ #pragma once #include +#include #include -#include "logger.hpp" +#include "base_component.hpp" + +#include "i2c_format_helpers.hpp" namespace espp { /// @brief I2C driver @@ -13,7 +16,7 @@ namespace espp { /// /// \section Example /// \snippet i2c_example.cpp i2c example -class I2c { +class I2c : public espp::BaseComponent { public: /// Configuration for I2C struct Config { @@ -30,8 +33,7 @@ class I2c { /// Construct I2C driver /// \param config Configuration for I2C - explicit I2c(const Config &config) - : config_(config), logger_({.tag = "I2C", .level = config.log_level}) { + explicit I2c(const Config &config) : BaseComponent("I2C", config.log_level), config_(config) { if (config.auto_init) { std::error_code ec; init(ec); @@ -41,6 +43,15 @@ class I2c { } } + /// Destructor + ~I2c() { + std::error_code ec; + deinit(ec); + if (ec) { + logger_.error("deinit failed"); + } + } + /// Initialize I2C driver void init(std::error_code &ec) { if (initialized_) { @@ -71,6 +82,8 @@ class I2c { return; } + logger_.info("I2C initialized on port {}", config_.port); + initialized_ = true; } @@ -78,7 +91,8 @@ class I2c { void deinit(std::error_code &ec) { if (!initialized_) { logger_.warn("not initialized"); - ec = std::make_error_code(std::errc::protocol_error); + // dont make this an error + ec.clear(); return; } @@ -90,6 +104,8 @@ class I2c { return; } + ec.clear(); + logger_.info("I2C deinitialized on port {}", config_.port); initialized_ = false; } @@ -98,12 +114,13 @@ class I2c { /// \param data Data to write /// \param data_len Length of data to write /// \return True if successful - bool write(uint8_t dev_addr, uint8_t *data, size_t data_len) { + bool write(const uint8_t dev_addr, const uint8_t *data, const size_t data_len) { if (!initialized_) { logger_.error("not initialized"); return false; } + logger_.debug("write {} bytes to address {:#02x}", data_len, dev_addr); std::lock_guard lock(mutex_); auto err = i2c_master_write_to_device(config_.port, dev_addr, data, data_len, config_.timeout_ms / portTICK_PERIOD_MS); @@ -115,6 +132,14 @@ class I2c { return true; } + /// Write data to I2C device + /// \param dev_addr I2C device address + /// \param data Data to write + /// \return True if successful + bool write_vector(const uint8_t dev_addr, const std::vector &data) { + return write(dev_addr, data.data(), data.size()); + } + /// Write to and read data from I2C device /// \param dev_addr I2C device address /// \param write_data Data to write @@ -122,13 +147,15 @@ class I2c { /// \param read_data Data to read /// \param read_size Length of data to read /// \return True if successful - bool write_read(uint8_t dev_addr, uint8_t *write_data, size_t write_size, uint8_t *read_data, - size_t read_size) { + bool write_read(const uint8_t dev_addr, const uint8_t *write_data, const size_t write_size, + uint8_t *read_data, size_t read_size) { if (!initialized_) { logger_.error("not initialized"); return false; } + logger_.debug("write {} bytes and read {} bytes from address {:#02x}", write_size, read_size, + dev_addr); std::lock_guard lock(mutex_); auto err = i2c_master_write_read_device(config_.port, dev_addr, write_data, write_size, read_data, @@ -141,18 +168,32 @@ class I2c { return true; } + /// Write data to and read data from I2C device + /// \param dev_addr I2C device address + /// \param write_data Data to write + /// \param read_data Data to read + /// \return True if successful + bool write_read_vector(const uint8_t dev_addr, const std::vector &write_data, + std::vector &read_data) { + return write_read(dev_addr, write_data.data(), write_data.size(), read_data.data(), + read_data.size()); + } + /// Read data from I2C device at register /// \param dev_addr I2C device address /// \param reg_addr Register address /// \param data Data to read /// \param data_len Length of data to read /// \return True if successful - bool read_at_register(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, size_t data_len) { + bool read_at_register(const uint8_t dev_addr, const uint8_t reg_addr, uint8_t *data, + size_t data_len) { if (!initialized_) { logger_.error("not initialized"); return false; } + logger_.debug("read {} bytes from address {:#02x} at register {}", data_len, dev_addr, + reg_addr); std::lock_guard lock(mutex_); auto err = i2c_master_write_read_device(config_.port, dev_addr, ®_addr, 1, data, data_len, config_.timeout_ms / portTICK_PERIOD_MS); @@ -165,17 +206,28 @@ class I2c { return true; } + /// Read data from I2C device at register + /// \param dev_addr I2C device address + /// \param reg_addr Register address + /// \param data Data to read + /// \return True if successful + bool read_at_register_vector(const uint8_t dev_addr, const uint8_t reg_addr, + std::vector &data) { + return read_at_register(dev_addr, reg_addr, data.data(), data.size()); + } + /// Read data from I2C device /// \param dev_addr I2C device address /// \param data Data to read /// \param data_len Length of data to read /// \return True if successful - bool read(uint8_t dev_addr, uint8_t *data, size_t data_len) { + bool read(const uint8_t dev_addr, uint8_t *data, size_t data_len) { if (!initialized_) { logger_.error("not initialized"); return false; } + logger_.debug("read {} bytes from address {:#02x}", data_len, dev_addr); std::lock_guard lock(mutex_); auto err = i2c_master_read_from_device(config_.port, dev_addr, data, data_len, config_.timeout_ms / portTICK_PERIOD_MS); @@ -187,6 +239,14 @@ class I2c { return true; } + /// Read data from I2C device + /// \param dev_addr I2C device address + /// \param data Data to read + /// \return True if successful + bool read_vector(const uint8_t dev_addr, std::vector &data) { + return read(dev_addr, data.data(), data.size()); + } + /// Probe I2C device /// \param dev_addr I2C device address /// \return True if successful @@ -194,8 +254,9 @@ class I2c { /// This function sends a start condition, writes the device address, and then /// sends a stop condition. If the device acknowledges the address, then it is /// present on the bus. - bool probe_device(uint8_t dev_addr) { + bool probe_device(const uint8_t dev_addr) { bool success = false; + logger_.debug("probe device {:#02x}", dev_addr); std::lock_guard lock(mutex_); auto cmd = i2c_cmd_link_create(); i2c_master_start(cmd); @@ -212,6 +273,5 @@ class I2c { Config config_; bool initialized_ = false; std::mutex mutex_; - espp::Logger logger_; }; } // namespace espp diff --git a/components/i2c/include/i2c_format_helpers.hpp b/components/i2c/include/i2c_format_helpers.hpp new file mode 100644 index 000000000..66f0958f9 --- /dev/null +++ b/components/i2c/include/i2c_format_helpers.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "format.hpp" + +// for printing of i2c_port_t with libfmt +template <> struct fmt::formatter { + constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); } + + template auto format(const i2c_port_t &p, FormatContext &ctx) { + switch (p) { + case I2C_NUM_0: + return fmt::format_to(ctx.out(), "I2C_NUM_0"); + case I2C_NUM_1: + return fmt::format_to(ctx.out(), "I2C_NUM_1"); + default: + return fmt::format_to(ctx.out(), "Unknown"); + } + } +}; diff --git a/components/input_drivers/CMakeLists.txt b/components/input_drivers/CMakeLists.txt index f81d5861d..e2dd8771d 100644 --- a/components/input_drivers/CMakeLists.txt +++ b/components/input_drivers/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger ) + REQUIRES base_component ) diff --git a/components/input_drivers/include/encoder_input.hpp b/components/input_drivers/include/encoder_input.hpp index 0f70a91ad..01d64f30f 100644 --- a/components/input_drivers/include/encoder_input.hpp +++ b/components/input_drivers/include/encoder_input.hpp @@ -6,14 +6,14 @@ #include "lvgl.h" #include "sdkconfig.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** * @brief Light wrapper around LVGL input device driver, specifically * designed for encoders with optional home buttons. */ -class EncoderInput { +class EncoderInput : public BaseComponent { public: typedef std::function read_fn; @@ -33,7 +33,8 @@ class EncoderInput { * @param config Configuration structure for the EncoderInput. */ explicit EncoderInput(const Config &config) - : read_(config.read), logger_({.tag = "EncoderInput", .level = config.log_level}) { + : BaseComponent("EncoderInput", config.log_level) + , read_(config.read) { init(); } @@ -118,6 +119,5 @@ class EncoderInput { lv_indev_t *indev_encoder_; lv_indev_drv_t indev_drv_btn_; lv_indev_t *indev_button_; - Logger logger_; }; } // namespace espp diff --git a/components/input_drivers/include/keypad_input.hpp b/components/input_drivers/include/keypad_input.hpp index 4f1a671c1..938429d17 100644 --- a/components/input_drivers/include/keypad_input.hpp +++ b/components/input_drivers/include/keypad_input.hpp @@ -6,14 +6,14 @@ #include "lvgl.h" #include "sdkconfig.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** * @brief Light wrapper around LVGL input device driver, specifically * designed for keypads. */ -class KeypadInput { +class KeypadInput : public BaseComponent { public: typedef std::function @@ -35,7 +35,8 @@ class KeypadInput { * @param config Configuration structure for the KeypadInput. */ explicit KeypadInput(const Config &config) - : read_(config.read), logger_({.tag = "KeypadInput", .level = config.log_level}) { + : BaseComponent("KeypadInput", config.log_level) + , read_(config.read) { init(); } @@ -104,6 +105,5 @@ class KeypadInput { read_fn read_; lv_indev_drv_t indev_drv_keypad_; lv_indev_t *indev_keypad_; - Logger logger_; }; } // namespace espp diff --git a/components/input_drivers/include/touchpad_input.hpp b/components/input_drivers/include/touchpad_input.hpp index 928eb84e0..1e93e204d 100644 --- a/components/input_drivers/include/touchpad_input.hpp +++ b/components/input_drivers/include/touchpad_input.hpp @@ -6,14 +6,14 @@ #include "lvgl.h" #include "sdkconfig.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** * @brief Light wrapper around LVGL input device driver, specifically * designed for touchpads with optional home buttons. */ -class TouchpadInput { +class TouchpadInput : public BaseComponent { public: /** * @brief Function prototype for getting the latest input data from the @@ -48,8 +48,11 @@ class TouchpadInput { * @param config Configuration structure for the TouchpadInput. */ explicit TouchpadInput(const Config &config) - : touchpad_read_(config.touchpad_read), swap_xy_(config.swap_xy), invert_x_(config.invert_x), - invert_y_(config.invert_y), logger_({.tag = "TouchpadInput", .level = config.log_level}) { + : BaseComponent("TouchpadInput", config.log_level) + , touchpad_read_(config.touchpad_read) + , swap_xy_(config.swap_xy) + , invert_x_(config.invert_x) + , invert_y_(config.invert_y) { init(); } @@ -155,6 +158,5 @@ class TouchpadInput { lv_indev_t *indev_touchpad_; lv_indev_drv_t indev_drv_btn_; lv_indev_t *indev_button_; - Logger logger_; }; } // namespace espp diff --git a/components/joystick/CMakeLists.txt b/components/joystick/CMakeLists.txt index 851edd130..e24070f55 100644 --- a/components/joystick/CMakeLists.txt +++ b/components/joystick/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger math) + REQUIRES base_component math) diff --git a/components/joystick/include/joystick.hpp b/components/joystick/include/joystick.hpp index 64235f128..2d36a033e 100644 --- a/components/joystick/include/joystick.hpp +++ b/components/joystick/include/joystick.hpp @@ -2,7 +2,7 @@ #include -#include "logger.hpp" +#include "base_component.hpp" #include "range_mapper.hpp" #include "vector2d.hpp" @@ -13,7 +13,7 @@ namespace espp { * \section joystick_ex1 ADC Joystick Example * \snippet joystick_example.cpp adc joystick example */ -class Joystick { +class Joystick : public BaseComponent { public: /** * @brief Types of deadzones the joystick can have. @@ -61,9 +61,12 @@ class Joystick { * @param config Config structure with initialization information. */ explicit Joystick(const Config &config) - : x_mapper_(config.x_calibration), y_mapper_(config.y_calibration), - deadzone_(config.deadzone), deadzone_radius_(config.deadzone_radius), - get_values_(config.get_values), logger_({.tag = "Joystick", .level = config.log_level}) {} + : BaseComponent("Joystick", config.log_level) + , x_mapper_(config.x_calibration) + , y_mapper_(config.y_calibration) + , deadzone_(config.deadzone) + , deadzone_radius_(config.deadzone_radius) + , get_values_(config.get_values) {} /** * @brief Sets the deadzone type and radius. @@ -164,7 +167,6 @@ class Joystick { Deadzone deadzone_; float deadzone_radius_; get_values_fn get_values_; - Logger logger_; }; } // namespace espp diff --git a/components/led/CMakeLists.txt b/components/led/CMakeLists.txt index 86f338bb0..4e90d84c0 100644 --- a/components/led/CMakeLists.txt +++ b/components/led/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger driver) + REQUIRES base_component driver) diff --git a/components/led/include/led.hpp b/components/led/include/led.hpp index 88c28a125..d1228b57b 100644 --- a/components/led/include/led.hpp +++ b/components/led/include/led.hpp @@ -9,7 +9,7 @@ #include #include -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -21,7 +21,7 @@ namespace espp { * \section led_ex2 Breathing LED Example * \snippet led_example.cpp breathing led example */ -class Led { +class Led : public BaseComponent { public: /** * Represents one LED channel. @@ -61,9 +61,10 @@ class Led { * @param config The configuration structure for the LEDC subsystem. */ explicit Led(const Config &config) - : duty_resolution_(config.duty_resolution), - max_raw_duty_((uint32_t)(std::pow(2, (int)duty_resolution_) - 1)), - channels_(config.channels), logger_({.tag = "Led", .level = config.log_level}) { + : BaseComponent("Led", config.log_level) + , duty_resolution_(config.duty_resolution) + , max_raw_duty_((uint32_t)(std::pow(2, (int)duty_resolution_) - 1)) + , channels_(config.channels) { logger_.info("Initializing timer"); ledc_timer_config_t ledc_timer; @@ -242,6 +243,5 @@ class Led { uint32_t max_raw_duty_; std::vector fade_semaphores_; std::vector channels_; - Logger logger_; }; } // namespace espp diff --git a/components/led_strip/CMakeLists.txt b/components/led_strip/CMakeLists.txt index 6107a4e58..9faa14ad2 100644 --- a/components/led_strip/CMakeLists.txt +++ b/components/led_strip/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES "logger" "color" + REQUIRES "base_component" "color" ) diff --git a/components/led_strip/include/led_strip.hpp b/components/led_strip/include/led_strip.hpp index b28dfdde9..93915f8ed 100644 --- a/components/led_strip/include/led_strip.hpp +++ b/components/led_strip/include/led_strip.hpp @@ -7,8 +7,8 @@ #include #include +#include "base_component.hpp" #include "color.hpp" -#include "logger.hpp" namespace espp { /// \brief Class to control LED strips @@ -31,7 +31,7 @@ namespace espp { /// /// \section led_strip_ex1 Example 1: APA102 via SPI /// \snippet led_strip_example.cpp led strip ex1 -class LedStrip { +class LedStrip : public BaseComponent { public: /// \brief Function to write data to the strip /// \details This function is used to write data to the strip. It @@ -75,9 +75,11 @@ class LedStrip { /// \brief Constructor /// \param config Configuration for the LedStrip class explicit LedStrip(const Config &config) - : num_leds_(config.num_leds), send_brightness_(config.send_brightness), - byte_order_(config.byte_order), write_(config.write), - logger_({.tag = "LedStrip", .level = config.log_level}) { + : BaseComponent("LedStrip", config.log_level) + , num_leds_(config.num_leds) + , send_brightness_(config.send_brightness) + , byte_order_(config.byte_order) + , write_(config.write) { // set the color data size pixel_size_ = send_brightness_ ? 4 : 3; data_.resize(num_leds_ * pixel_size_ + config.start_frame.size() + config.end_frame.size()); @@ -254,6 +256,5 @@ class LedStrip { size_t end_offset_{0}; std::vector data_; write_fn write_; - Logger logger_; }; } // namespace espp diff --git a/components/logger/include/logger.hpp b/components/logger/include/logger.hpp index 18481e647..25177838d 100644 --- a/components/logger/include/logger.hpp +++ b/components/logger/include/logger.hpp @@ -50,7 +50,9 @@ class Logger { * @param config configuration for the logger. */ explicit Logger(const Config &config) - : tag_(config.tag), rate_limit_(config.rate_limit), level_(config.level) {} + : tag_(config.tag) + , rate_limit_(config.rate_limit) + , level_(config.level) {} /** * @brief Change the verbosity for the logger. \sa Logger::Verbosity @@ -67,6 +69,13 @@ class Logger { tag_ = tag; } + /** + * @brief Change the rate limit for the logger. + * @param rate_limit The new rate limit. + * @note Only calls that have _rate_limited suffixed will be rate limited. + */ + void set_rate_limit(const std::chrono::duration rate_limit) { rate_limit_ = rate_limit; } + /** * @brief Format args into string according to format string. From: * https://en.cppreference.com/w/cpp/utility/format/format diff --git a/components/monitor/CMakeLists.txt b/components/monitor/CMakeLists.txt index 6aa80205c..6a7b3ecc5 100644 --- a/components/monitor/CMakeLists.txt +++ b/components/monitor/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - PRIV_REQUIRES logger task) + PRIV_REQUIRES base_component task) diff --git a/components/monitor/include/task_monitor.hpp b/components/monitor/include/task_monitor.hpp index 138285d65..a74658321 100644 --- a/components/monitor/include/task_monitor.hpp +++ b/components/monitor/include/task_monitor.hpp @@ -4,7 +4,7 @@ #include "sdkconfig.h" -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "esp_freertos_hooks.h" @@ -34,7 +34,7 @@ namespace espp { * \section task_monitor_ex2 get_latest_info() Example * \snippet monitor_example.cpp get_latest_info example */ -class TaskMonitor { +class TaskMonitor : public BaseComponent { public: struct Config { std::chrono::duration period; /**< Period (s) the TaskMonitor::task_callback runs at. */ @@ -43,7 +43,8 @@ class TaskMonitor { }; explicit TaskMonitor(const Config &config) - : period_(config.period), logger_({.tag = "TaskMonitor"}) { + : BaseComponent("TaskMonitor") + , period_(config.period) { #if CONFIG_FREERTOS_USE_TRACE_FACILITY && CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS using namespace std::placeholders; task_ = Task::make_unique({.name = "TaskMonitor Task", @@ -164,7 +165,6 @@ class TaskMonitor { } std::chrono::duration period_; - Logger logger_; std::unique_ptr task_; }; } // namespace espp diff --git a/components/pid/CMakeLists.txt b/components/pid/CMakeLists.txt index 1d8b9a5e8..704e9cb79 100644 --- a/components/pid/CMakeLists.txt +++ b/components/pid/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger) + REQUIRES base_component) diff --git a/components/pid/include/pid.hpp b/components/pid/include/pid.hpp index 4fd49c685..9eda15c59 100644 --- a/components/pid/include/pid.hpp +++ b/components/pid/include/pid.hpp @@ -4,7 +4,7 @@ #include #include -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -19,7 +19,7 @@ namespace espp { * \section pid_ex2 Complex PID Example * \snippet pid_example.cpp complex pid example */ -class Pid { +class Pid : public BaseComponent { public: struct Config { float kp; /**< Proportional gain. */ @@ -42,8 +42,8 @@ class Pid { * @brief Create the PID controller. */ explicit Pid(const Config &config) - : prev_ts_(std::chrono::high_resolution_clock::now()), - logger_({.tag = "PID", .level = config.log_level}) { + : BaseComponent("PID", config.log_level) + , prev_ts_(std::chrono::high_resolution_clock::now()) { change_gains(config); } @@ -157,7 +157,6 @@ class Pid { std::atomic integrator_{0}; std::chrono::time_point prev_ts_; std::recursive_mutex mutex_; ///< For protecting the config - Logger logger_; }; } // namespace espp diff --git a/components/rmt/CMakeLists.txt b/components/rmt/CMakeLists.txt index 1e4d0cfcb..a069fc182 100644 --- a/components/rmt/CMakeLists.txt +++ b/components/rmt/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger driver + REQUIRES base_component driver ) diff --git a/components/rmt/include/rmt.hpp b/components/rmt/include/rmt.hpp index 78c275935..fa6795eec 100644 --- a/components/rmt/include/rmt.hpp +++ b/components/rmt/include/rmt.hpp @@ -4,7 +4,7 @@ #include -#include "logger.hpp" +#include "base_component.hpp" #include "rmt_encoder.hpp" namespace espp { @@ -20,7 +20,7 @@ namespace espp { /// /// \section rmt_ex1 Example 1: Transmitting data /// \snippet rmt_example.cpp rmt example -class Rmt { +class Rmt : public BaseComponent { public: /// \brief Configuration for the RMT class struct Config { @@ -39,7 +39,8 @@ class Rmt { /// \brief Constructor /// \param config Configuration for this class - explicit Rmt(const Config &config) : logger_({.tag = "RMT", .level = config.log_level}) { + explicit Rmt(const Config &config) + : BaseComponent("RMT", config.log_level) { init(config); } @@ -118,7 +119,5 @@ class Rmt { rmt_channel_handle_t channel_; ///< RMT channel handle std::unique_ptr encoder_; - - Logger logger_; ///< Logger for this class }; } // namespace espp diff --git a/components/rtsp/CMakeLists.txt b/components/rtsp/CMakeLists.txt index c0119cae2..bf0d7d50b 100644 --- a/components/rtsp/CMakeLists.txt +++ b/components/rtsp/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger task socket) + REQUIRES base_component task socket) diff --git a/components/rtsp/include/rtsp_client.hpp b/components/rtsp/include/rtsp_client.hpp index 2d8fb5c6d..30b3c3685 100644 --- a/components/rtsp/include/rtsp_client.hpp +++ b/components/rtsp/include/rtsp_client.hpp @@ -5,7 +5,7 @@ #include #include -#include "logger.hpp" +#include "base_component.hpp" #include "tcp_socket.hpp" #include "udp_socket.hpp" @@ -25,7 +25,7 @@ namespace espp { /// /// \section RtspClient Example /// \snippet rtsp_example.cpp rtsp_client_example -class RtspClient { +class RtspClient : public BaseComponent { public: /// Function type for the callback to call when a JPEG frame is received using jpeg_frame_callback_t = std::function jpeg_frame)>; @@ -45,13 +45,15 @@ class RtspClient { /// Constructor /// \param config The configuration for the RTSP client explicit RtspClient(const Config &config) - : server_address_(config.server_address), rtsp_port_(config.rtsp_port), - rtsp_socket_({.log_level = espp::Logger::Verbosity::WARN}), - rtp_socket_({.log_level = espp::Logger::Verbosity::WARN}), - rtcp_socket_({.log_level = espp::Logger::Verbosity::WARN}), - on_jpeg_frame_(config.on_jpeg_frame), cseq_(0), - path_("rtsp://" + server_address_ + ":" + std::to_string(rtsp_port_) + config.path), - logger_({.tag = "RtspClient", .level = config.log_level}) {} + : BaseComponent("RtspClient", config.log_level) + , server_address_(config.server_address) + , rtsp_port_(config.rtsp_port) + , rtsp_socket_({.log_level = espp::Logger::Verbosity::WARN}) + , rtp_socket_({.log_level = espp::Logger::Verbosity::WARN}) + , rtcp_socket_({.log_level = espp::Logger::Verbosity::WARN}) + , on_jpeg_frame_(config.on_jpeg_frame) + , cseq_(0) + , path_("rtsp://" + server_address_ + ":" + std::to_string(rtsp_port_) + config.path) {} /// Destructor /// Disconnects from the RTSP server @@ -480,8 +482,6 @@ class RtspClient { int video_payload_type_ = 0; std::string path_; std::string session_id_; - - espp::Logger logger_; }; } // namespace espp diff --git a/components/rtsp/include/rtsp_server.hpp b/components/rtsp/include/rtsp_server.hpp index 15d40eec5..205a74d8a 100644 --- a/components/rtsp/include/rtsp_server.hpp +++ b/components/rtsp/include/rtsp_server.hpp @@ -11,7 +11,7 @@ #include #endif -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "tcp_socket.hpp" #include "udp_socket.hpp" @@ -32,7 +32,7 @@ namespace espp { /// /// \section RtspServer example /// \snippet rtsp_example.cpp rtsp_server_example -class RtspServer { +class RtspServer : public BaseComponent { public: /// @brief Configuration for the RTSP server struct Config { @@ -50,10 +50,12 @@ class RtspServer { /// @brief Construct an RTSP server /// @param config The configuration for the RTSP server explicit RtspServer(const Config &config) - : server_address_(config.server_address), port_(config.port), path_(config.path), - rtsp_socket_({.log_level = espp::Logger::Verbosity::WARN}), - max_data_size_(config.max_data_size), - logger_({.tag = "RTSP Server", .level = config.log_level}) { + : BaseComponent("RTSP Server", config.log_level) + , server_address_(config.server_address) + , port_(config.port) + , path_(config.path) + , rtsp_socket_({.log_level = espp::Logger::Verbosity::WARN}) + , max_data_size_(config.max_data_size) { // generate a random ssrc #if defined(ESP_PLATFORM) ssrc_ = esp_random(); @@ -327,7 +329,6 @@ class RtspServer { std::mutex session_mutex_; std::unordered_map> sessions_; - Logger logger_; std::unique_ptr accept_task_; std::unique_ptr session_task_; }; diff --git a/components/rtsp/include/rtsp_session.hpp b/components/rtsp/include/rtsp_session.hpp index 443a138f5..51b61d826 100644 --- a/components/rtsp/include/rtsp_session.hpp +++ b/components/rtsp/include/rtsp_session.hpp @@ -11,7 +11,7 @@ #include #endif -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" #include "tcp_socket.hpp" #include "udp_socket.hpp" @@ -22,7 +22,7 @@ namespace espp { /// Class that reepresents an RTSP session, which is uniquely identified by a /// session id and sends frame data over RTP and RTCP to the client -class RtspSession { +class RtspSession : public BaseComponent { public: /// Configuration for the RTSP session struct Config { @@ -35,12 +35,16 @@ class RtspSession { /// @param control_socket The control socket of the session /// @param config The configuration of the session explicit RtspSession(std::unique_ptr control_socket, const Config &config) - : control_socket_(std::move(control_socket)), - rtp_socket_({.log_level = Logger::Verbosity::WARN}), - rtcp_socket_({.log_level = Logger::Verbosity::WARN}), session_id_(generate_session_id()), - server_address_(config.server_address), rtsp_path_(config.rtsp_path), - client_address_(control_socket_->get_remote_info().address), - logger_({.tag = "RtspSession " + std::to_string(session_id_), .level = config.log_level}) { + : BaseComponent("RtspSession", config.log_level) + , control_socket_(std::move(control_socket)) + , rtp_socket_({.log_level = Logger::Verbosity::WARN}) + , rtcp_socket_({.log_level = Logger::Verbosity::WARN}) + , session_id_(generate_session_id()) + , server_address_(config.server_address) + , rtsp_path_(config.rtsp_path) + , client_address_(control_socket_->get_remote_info().address) { + // set the logger tag to include the session id + logger_.set_tag("RtspSession " + std::to_string(session_id_)); // start the session task to handle RTSP commands using namespace std::placeholders; control_task_ = std::make_unique(Task::Config{ @@ -515,7 +519,5 @@ class RtspSession { int client_rtcp_port_; std::unique_ptr control_task_; - - Logger logger_; }; } // namespace espp diff --git a/components/socket/CMakeLists.txt b/components/socket/CMakeLists.txt index 07bc8baa9..dd3a9d46e 100644 --- a/components/socket/CMakeLists.txt +++ b/components/socket/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - PRIV_REQUIRES logger task lwip) + PRIV_REQUIRES base_component task lwip) diff --git a/components/socket/include/socket.hpp b/components/socket/include/socket.hpp index bfde73e1f..728cf4e8e 100644 --- a/components/socket/include/socket.hpp +++ b/components/socket/include/socket.hpp @@ -16,15 +16,15 @@ #include +#include "base_component.hpp" #include "format.hpp" -#include "logger.hpp" namespace espp { /** * @brief Class for a generic socket with some helper functions for * configuring the socket. */ -class Socket { +class Socket : public BaseComponent { public: enum class Type : int { RAW = SOCK_RAW, /**< Only IP headers, no TCP or UDP headers as well. */ @@ -156,7 +156,8 @@ class Socket { * @param logger_config configuration for the logger associated with the * socket. */ - explicit Socket(int socket_fd, const Logger::Config &logger_config) : logger_(logger_config) { + explicit Socket(int socket_fd, const Logger::Config &logger_config) + : BaseComponent(logger_config) { socket_ = socket_fd; } @@ -166,7 +167,8 @@ class Socket { * @param logger_config configuration for the logger associated with the * socket. */ - explicit Socket(Type type, const Logger::Config &logger_config) : logger_(logger_config) { + explicit Socket(Type type, const Logger::Config &logger_config) + : BaseComponent(logger_config) { init(type); } @@ -416,7 +418,6 @@ class Socket { static constexpr int ip_protocol_{IPPROTO_IP}; int socket_; - Logger logger_; }; } // namespace espp diff --git a/components/task/CMakeLists.txt b/components/task/CMakeLists.txt index e31f5e4b2..0d74279ec 100644 --- a/components/task/CMakeLists.txt +++ b/components/task/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger pthread) + REQUIRES base_component pthread) diff --git a/components/task/include/task.hpp b/components/task/include/task.hpp index 785b0ccc4..5415a7b0c 100644 --- a/components/task/include/task.hpp +++ b/components/task/include/task.hpp @@ -12,7 +12,7 @@ #include #endif -#include "logger.hpp" +#include "base_component.hpp" namespace espp { @@ -32,7 +32,7 @@ namespace espp { * \section task_ex5 Task Request Stop Example * \snippet task_example.cpp Task Request Stop example */ -class Task { +class Task : public BaseComponent { public: /** * @brief Task callback function signature. @@ -104,18 +104,24 @@ class Task { * @param config Config struct to initialize the Task with. */ explicit Task(const Config &config) - : name_(config.name), callback_(config.callback), stack_size_bytes_(config.stack_size_bytes), - priority_(config.priority), core_id_(config.core_id), - logger_({.tag = name_, .level = config.log_level}) {} + : BaseComponent(config.name, config.log_level) + , name_(config.name) + , callback_(config.callback) + , stack_size_bytes_(config.stack_size_bytes) + , priority_(config.priority) + , core_id_(config.core_id) {} /** * @brief Construct a new Task object using the SimpleConfig struct. * @param config SimpleConfig struct to initialize the Task with. */ explicit Task(const SimpleConfig &config) - : name_(config.name), simple_callback_(config.callback), - stack_size_bytes_(config.stack_size_bytes), priority_(config.priority), - core_id_(config.core_id), logger_({.tag = name_, .level = config.log_level}) {} + : BaseComponent(config.name, config.log_level) + , name_(config.name) + , simple_callback_(config.callback) + , stack_size_bytes_(config.stack_size_bytes) + , priority_(config.priority) + , core_id_(config.core_id) {} /** * @brief Get a unique pointer to a new task created with \p config. @@ -324,8 +330,6 @@ class Task { */ int core_id_; - Logger logger_; - std::atomic started_{false}; std::condition_variable cv_; std::mutex cv_m_; diff --git a/components/thermistor/CMakeLists.txt b/components/thermistor/CMakeLists.txt index 851edd130..e24070f55 100644 --- a/components/thermistor/CMakeLists.txt +++ b/components/thermistor/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger math) + REQUIRES base_component math) diff --git a/components/thermistor/include/thermistor.hpp b/components/thermistor/include/thermistor.hpp index ef3bcf22c..fdc0ac974 100644 --- a/components/thermistor/include/thermistor.hpp +++ b/components/thermistor/include/thermistor.hpp @@ -2,8 +2,8 @@ #include +#include "base_component.hpp" #include "fast_math.hpp" -#include "logger.hpp" namespace espp { /// @brief Thermistor class @@ -19,7 +19,7 @@ namespace espp { /// /// \section thermistor_ex2 ADC Example /// \snippet thermistor_example.cpp thermistor adc example -class Thermistor { +class Thermistor : public BaseComponent { public: /// @brief Function type for reading voltage /// @return Voltage in millivolts @@ -46,10 +46,13 @@ class Thermistor { /// @brief Constructor /// @param config Configuration struct explicit Thermistor(const Config &config) - : divider_config_(config.divider_config), b_(config.beta), - r0_(config.nominal_resistance_ohms), r2_(config.fixed_resistance_ohms), - supply_mv_(config.supply_mv), read_mv_(config.read_mv), - logger_({.tag = "Thermistor", .level = config.log_level}) { + : BaseComponent("Thermistor", config.log_level) + , divider_config_(config.divider_config) + , b_(config.beta) + , r0_(config.nominal_resistance_ohms) + , r2_(config.fixed_resistance_ohms) + , supply_mv_(config.supply_mv) + , read_mv_(config.read_mv) { if (b_ == 0.0f) { logger_.error("b_ is 0"); } else { @@ -138,6 +141,5 @@ class Thermistor { float r2_; ///< Resistance of the fixed resistor in the voltage divider float supply_mv_; ///< Supply voltage of the voltage divider (in mv) read_mv_fn read_mv_{nullptr}; ///< Function for reading voltage (in mv) - Logger logger_; }; } // namespace espp diff --git a/components/timer/CMakeLists.txt b/components/timer/CMakeLists.txt index b9596b4ef..7114c1f57 100644 --- a/components/timer/CMakeLists.txt +++ b/components/timer/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES logger task) + REQUIRES base_component task) diff --git a/components/timer/include/timer.hpp b/components/timer/include/timer.hpp index 31c9b0ef3..f219a5dce 100644 --- a/components/timer/include/timer.hpp +++ b/components/timer/include/timer.hpp @@ -4,7 +4,7 @@ #include #include -#include "logger.hpp" +#include "base_component.hpp" #include "task.hpp" namespace espp { @@ -45,7 +45,7 @@ namespace espp { /// \snippet timer_example.cpp timer cancel itself example /// \section timer_ex5 Oneshot Timer Cancel Itself Then Start again with Delay Example /// \snippet timer_example.cpp timer oneshot restart example -class Timer { +class Timer : public BaseComponent { public: typedef std::function callback_fn; ///< The callback function type. Return true to cancel the timer. @@ -69,11 +69,12 @@ class Timer { /// @brief Construct a new Timer object /// @param config The configuration for the timer. explicit Timer(const Config &config) - : period_(std::chrono::duration_cast(config.period)), - delay_(std::chrono::duration_cast(config.delay)), - callback_(config.callback), logger_({.tag = config.name, - .rate_limit = std::chrono::milliseconds(100), - .level = config.log_level}) { + : BaseComponent(config.name, config.log_level) + , period_(std::chrono::duration_cast(config.period)) + , delay_(std::chrono::duration_cast(config.delay)) + , callback_(config.callback) { + // set the logger rate limit + logger_.set_rate_limit(std::chrono::milliseconds(100)); // make the task task_ = espp::Task::make_unique({ .name = std::string(config.name) + "_task", @@ -211,6 +212,5 @@ class Timer { float delay_float; callback_fn callback_; ///< The callback function to call when the timer expires. std::unique_ptr task_; ///< The task that runs the timer. - espp::Logger logger_; ///< The logger for the timer. }; } // namespace espp diff --git a/components/wifi/CMakeLists.txt b/components/wifi/CMakeLists.txt index 5f61cca10..e8872079a 100644 --- a/components/wifi/CMakeLists.txt +++ b/components/wifi/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - PRIV_REQUIRES logger esp_wifi) + PRIV_REQUIRES base_component esp_wifi) diff --git a/components/wifi/include/wifi.hpp b/components/wifi/include/wifi.hpp new file mode 100644 index 000000000..74360bc22 --- /dev/null +++ b/components/wifi/include/wifi.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "esp_wifi.h" + +#include "wifi_ap.hpp" +#include "wifi_sta.hpp" + +namespace espp { + +/// @brief The Wifi class provides access to the ESP32 Wifi functionality. +/// @details The Wifi class is a singleton class that provides access to the +/// ESP32 Wifi functionality. The Wifi class is a wrapper around the ESP32 +/// Wifi API. The Wifi class provides access to the Wifi AP and Wifi STA +/// functionality. +class Wifi { +public: + /// @brief Access the singleton instance of the Wifi class. + /// @return The singleton instance of the Wifi class. + static Wifi &instance() { + static Wifi wifi; + return wifi; + } + + Wifi(const Wifi &) = delete; + Wifi &operator=(const Wifi &) = delete; + Wifi(Wifi &&) = delete; + Wifi &operator=(Wifi &&) = delete; + + /// @brief Get the IP address of the Wifi AP or Wifi STA interface. + /// @param ip_address The IP address of the Wifi AP or Wifi STA interface. + /// @return True if the IP address was retrieved, false otherwise. + bool get_ip_address(std::string &ip_address) { + esp_netif_ip_info_t ip; + if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_WIFI_AP), &ip) != ESP_OK) { + if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_WIFI_STA), &ip) != ESP_OK) { + return false; + } + } + + ip_address = ip4addr_ntoa(&ip.ip); + return true; + } + +protected: + /// @brief Construct a new Wifi object. + /// @details The Wifi object is a singleton object and can only be + /// constructed once. + Wifi() { + /* + esp_netif_init(); + esp_event_loop_create_default(); + esp_netif_create_default_wifi_ap(); + esp_netif_create_default_wifi_sta(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&cfg); + esp_wifi_set_storage(WIFI_STORAGE_RAM); + esp_wifi_set_mode(WIFI_MODE_APSTA); + esp_wifi_start(); + */ + } + + std::unique_ptr ap_; + std::unique_ptr sta_; +}; + +} // namespace espp diff --git a/components/wifi/include/wifi_ap.hpp b/components/wifi/include/wifi_ap.hpp index de4fa3302..b3f572688 100644 --- a/components/wifi/include/wifi_ap.hpp +++ b/components/wifi/include/wifi_ap.hpp @@ -7,7 +7,7 @@ #include "esp_wifi.h" #include "nvs_flash.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -23,7 +23,7 @@ namespace espp { * \section wifiap_ex1 WiFi Access Point Example * \snippet wifi_example.cpp wifi ap example */ -class WifiAp { +class WifiAp : public BaseComponent { public: struct Config { std::string ssid; /**< SSID for the access point. */ @@ -38,7 +38,8 @@ class WifiAp { * @brief Initialize the WiFi Access Point (AP) * @param config WifiAp::Config structure with initialization information. */ - explicit WifiAp(const Config &config) : logger_({.tag = "WifiAp", .level = config.log_level}) { + explicit WifiAp(const Config &config) + : BaseComponent("WifiAp", config.log_level) { // Code below is modified from: // https://github.com/espressif/esp-idf/blob/master/examples/wifi/getting_started/softAP/main/softap_example_main.c // NOTE: Init phase @@ -149,7 +150,6 @@ class WifiAp { } } - Logger logger_; esp_event_handler_instance_t *event_handler_instance_{nullptr}; }; } // namespace espp diff --git a/components/wifi/include/wifi_sta.hpp b/components/wifi/include/wifi_sta.hpp index a60f1e7d4..edd511c92 100644 --- a/components/wifi/include/wifi_sta.hpp +++ b/components/wifi/include/wifi_sta.hpp @@ -9,7 +9,7 @@ #include "esp_wifi.h" #include "nvs_flash.h" -#include "logger.hpp" +#include "base_component.hpp" namespace espp { /** @@ -25,7 +25,7 @@ namespace espp { * \section wifista_ex1 WiFi Station Example * \snippet wifi_example.cpp wifi sta example */ -class WifiSta { +class WifiSta : public BaseComponent { public: /** * @brief called when the WiFi station connects to an access point. @@ -66,9 +66,11 @@ class WifiSta { * @param config WifiSta::Config structure with initialization information. */ explicit WifiSta(const Config &config) - : num_retries_(config.num_connect_retries), connect_callback_(config.on_connected), - disconnect_callback_(config.on_disconnected), ip_callback_(config.on_got_ip), - logger_({.tag = "WifiSta", .level = config.log_level}) { + : BaseComponent("WifiSta", config.log_level) + , num_retries_(config.num_connect_retries) + , connect_callback_(config.on_connected) + , disconnect_callback_(config.on_disconnected) + , ip_callback_(config.on_got_ip) { // Code below is modified from: // https://github.com/espressif/esp-idf/blob/1c84cfde14dcffdc77d086a5204ce8a548dce935/examples/wifi/getting_started/station/main/station_example_main.c esp_err_t err; @@ -215,7 +217,6 @@ class WifiSta { disconnect_callback disconnect_callback_{nullptr}; ip_callback ip_callback_{nullptr}; std::atomic connected_{false}; - Logger logger_; esp_event_handler_instance_t *event_handler_instance_any_id_{nullptr}; esp_event_handler_instance_t *event_handler_instance_got_ip_{nullptr}; }; diff --git a/doc/Doxyfile b/doc/Doxyfile index ae2f6f6d7..32a7edceb 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -84,6 +84,7 @@ INPUT += $(PROJECT_PATH)/components/ads1x15/include/ads1x15.hpp INPUT += $(PROJECT_PATH)/components/ads7138/include/ads7138.hpp INPUT += $(PROJECT_PATH)/components/as5600/include/as5600.hpp INPUT += $(PROJECT_PATH)/components/aw9523/include/aw9523.hpp +INPUT += $(PROJECT_PATH)/components/base_component/include/base_component.hpp INPUT += $(PROJECT_PATH)/components/bldc_driver/include/bldc_driver.hpp INPUT += $(PROJECT_PATH)/components/bldc_haptics/include/bldc_haptics.hpp INPUT += $(PROJECT_PATH)/components/bldc_haptics/include/detent_config.hpp diff --git a/doc/en/base_component.rst b/doc/en/base_component.rst new file mode 100644 index 000000000..7de4a262c --- /dev/null +++ b/doc/en/base_component.rst @@ -0,0 +1,16 @@ +Base Compoenent +*************** + +The `espp::BaseComponent` provides a simple base class for all components in the +ESP-CPP package. It provides a simple interface for the component to be +initialized and to be updated. + +It is not required to use the `BaseComponent` class, but it is recommended to +use as it provides a standardized API for logging and log management. + +.. ---------------------------- API Reference ---------------------------------- + +API Reference +------------- + +.. include-build-file:: inc/base_component.inc diff --git a/doc/en/index.rst b/doc/en/index.rst index eeb4db2e4..85689db81 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -11,6 +11,7 @@ This is the documentation for esp-idf c++ components, ESPP (`espp - + @@ -90,6 +90,7 @@ +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -146,7 +147,7 @@
  • ADC APIs »
  • ADC Types
  • - Edit on GitHub + Edit on GitHub

  • @@ -174,7 +175,7 @@

    Header File - +
    diff --git a/docs/adc/ads1x15.html b/docs/adc/ads1x15.html index 5b9a69f98..e1970fa6a 100644 --- a/docs/adc/ads1x15.html +++ b/docs/adc/ads1x15.html @@ -91,6 +91,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -147,7 +148,7 @@
  • ADC APIs »
  • ADS1x15 I2C ADC
  • - Edit on GitHub + Edit on GitHub

  • @@ -164,7 +165,7 @@

    API Reference

    Header File

    diff --git a/docs/adc/ads7138.html b/docs/adc/ads7138.html index c57f0a24d..fee30e20e 100644 --- a/docs/adc/ads7138.html +++ b/docs/adc/ads7138.html @@ -91,6 +91,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -147,7 +148,7 @@
  • ADC APIs »
  • ADS7138 I2C ADC
  • - Edit on GitHub + Edit on GitHub

  • @@ -169,7 +170,7 @@

    API Reference

    Header File

    diff --git a/docs/adc/continuous_adc.html b/docs/adc/continuous_adc.html index e785c1ceb..30e5e1de5 100644 --- a/docs/adc/continuous_adc.html +++ b/docs/adc/continuous_adc.html @@ -91,6 +91,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -147,7 +148,7 @@
  • ADC APIs »
  • Continuous ADC
  • - Edit on GitHub + Edit on GitHub

  • @@ -169,14 +170,14 @@

    API Reference

    Header File

    Classes

    -class espp::ContinuousAdc
    +class espp::ContinuousAdc : public espp::BaseComponent

    ContinuousAdc provides a wrapper around the ESP-IDF continuous adc subsystem, enabling high-frequency, filtered measurements of analog values. The get_mv() function will always return the most up to date value, without needing to perform additional reads (therefore it is non-blocking).

    Continuous ADC Example

    @@ -294,6 +295,82 @@

    Continuous ADC Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/adc/index.html b/docs/adc/index.html index 07dd150a1..625745817 100644 --- a/docs/adc/index.html +++ b/docs/adc/index.html @@ -84,6 +84,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -139,7 +140,7 @@
  • »
  • ADC APIs
  • - Edit on GitHub + Edit on GitHub

  • diff --git a/docs/adc/oneshot_adc.html b/docs/adc/oneshot_adc.html index 92892379a..c5213c5ed 100644 --- a/docs/adc/oneshot_adc.html +++ b/docs/adc/oneshot_adc.html @@ -91,6 +91,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -147,7 +148,7 @@
  • ADC APIs »
  • Oneshot ADC
  • - Edit on GitHub + Edit on GitHub

  • @@ -168,14 +169,14 @@

    API Reference

    Header File

    Classes

    -class espp::OneshotAdc
    +class espp::OneshotAdc : public espp::BaseComponent

    OneshotAdc provides a wrapper around the ESP-IDF oneshot adc subsystem, enabling simple, direct (blocking / slow) measurements of analog values. The read_mv() function will always take a new measurement (therefore it is blocking).

    Oneshot ADC Example

    @@ -258,6 +259,82 @@

    Oneshot ADC Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/adc/tla2528.html b/docs/adc/tla2528.html index dd983da15..92b911a5f 100644 --- a/docs/adc/tla2528.html +++ b/docs/adc/tla2528.html @@ -91,6 +91,7 @@
  • ADC Types
  • +
  • Base Compoenent
  • Battery APIs
  • BLDC APIs
  • Button APIs
  • @@ -147,7 +148,7 @@
  • ADC APIs »
  • TLA2528 I2C ADC
  • - Edit on GitHub + Edit on GitHub

  • @@ -169,7 +170,7 @@

    API Reference

    Header File

    diff --git a/docs/base_component.html b/docs/base_component.html new file mode 100644 index 000000000..6f535f87a --- /dev/null +++ b/docs/base_component.html @@ -0,0 +1,293 @@ + + + + + + + Base Compoenent - ESP32 - — ESPP latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Base Compoenent

    +

    The espp::BaseComponent provides a simple base class for all components in the +ESP-CPP package. It provides a simple interface for the component to be +initialized and to be updated.

    +

    It is not required to use the BaseComponent class, but it is recommended to +use as it provides a standardized API for logging and log management.

    +
    +

    API Reference

    +
    +

    Header File

    + +
    +
    +

    Classes

    +
    +
    +class espp::BaseComponent
    +

    Base class for all components Provides a logger and some basic logging configuration

    +

    Subclassed by espp::AbiEncoder< T >, espp::BldcDriver, espp::BldcHaptics< M >, espp::BldcMotor< D, S, CS >, espp::Button, espp::ContinuousAdc, espp::Controller, espp::Display, espp::EncoderInput, espp::EventManager, espp::FileSystem, espp::FtpClientSession, espp::FtpServer, espp::I2c, espp::Joystick, espp::KeypadInput, espp::Led, espp::LedStrip, espp::OneshotAdc, espp::Pid, espp::Rmt, espp::RtspClient, espp::RtspServer, espp::RtspSession, espp::Socket, espp::Task, espp::TaskMonitor, espp::Thermistor, espp::Timer, espp::TouchpadInput, espp::WifiAp, espp::WifiSta

    +
    +

    Public Functions

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/docs/base_peripheral.html b/docs/base_peripheral.html new file mode 100644 index 000000000..ec3804c69 --- /dev/null +++ b/docs/base_peripheral.html @@ -0,0 +1,190 @@ + + + + + + + Base Peripheral - ESP32 - — ESPP latest documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Base Peripheral

    +

    The espp::BasePeripheral class is a base class for all peripherals. It +provides a common interface for all peripherals and is used to access the +peripheral’s registers. It is primarily designed to be used as a base class for +peripheral classes that communicate using I2C (address-based) or SPI (CS-based) +protocols.

    +

    The base class provides an interface for specifying different communications +functions that the peripheral may use, as well as providing some base +implementations for common functionality such as reading / writing u8 and u16 +values from / to a register.

    +
    +

    API Reference

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/docs/battery/bldc_driver.html b/docs/battery/bldc_driver.html index 5254f1888..ad9ad43b1 100644 --- a/docs/battery/bldc_driver.html +++ b/docs/battery/bldc_driver.html @@ -74,6 +74,7 @@

    Classes

    -class espp::BldcDriver
    +class espp::BldcDriver : public espp::BaseComponent

    Interface for controlling the 6 PWMs (high/low sides for phases A,B,C) of a brushless dc motor (BLDC motor). Wraps around the ESP32 https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html peripheral.

    Public Functions

    @@ -305,6 +306,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/battery/bldc_motor.html b/docs/battery/bldc_motor.html index ba7ec9be5..b80f51893 100644 --- a/docs/battery/bldc_motor.html +++ b/docs/battery/bldc_motor.html @@ -74,6 +74,7 @@

    Classes

    -template<DriverConcept D, SensorConcept S, CurrentSensorConcept CS = DummyCurrentSense>
    class espp::BldcMotor
    +template<DriverConcept D, SensorConcept S, CurrentSensorConcept CS = DummyCurrentSense>
    class espp::BldcMotor : public espp::BaseComponent

    Motor control class for a Brushless DC (BLDC) motor, implementing the field-oriented control (FOC) algorithm. Must be provided a driver object / type, and optionally a position/velocity sensor object/type and optionally a current sensor object / type.

    Example Usage

    @@ -425,6 +426,82 @@

    Example Usage +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -555,13 +632,13 @@

    Example Usage

    Header File

    Header File

    diff --git a/docs/battery/index.html b/docs/battery/index.html index 779603365..5b3f18531 100644 --- a/docs/battery/index.html +++ b/docs/battery/index.html @@ -39,7 +39,7 @@ - + @@ -76,6 +76,7 @@ diff --git a/docs/battery/max1704x.html b/docs/battery/max1704x.html index 959c8e22d..7e8229406 100644 --- a/docs/battery/max1704x.html +++ b/docs/battery/max1704x.html @@ -76,6 +76,7 @@
    diff --git a/docs/bldc/bldc_motor.html b/docs/bldc/bldc_motor.html index 0231267a8..b491f7b22 100644 --- a/docs/bldc/bldc_motor.html +++ b/docs/bldc/bldc_motor.html @@ -76,6 +76,7 @@

    Motor control class for a Brushless DC (BLDC) motor, implementing the field-oriented control (FOC) algorithm. Must be provided a driver object / type, and optionally a position/velocity sensor object/type and optionally a current sensor object / type.

    Example Usage

    @@ -441,6 +442,82 @@

    Example Usage +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -571,13 +648,13 @@

    Example Usage

    Header File

    Header File

    diff --git a/docs/bldc/index.html b/docs/bldc/index.html index 0e02716cf..7e8241a73 100644 --- a/docs/bldc/index.html +++ b/docs/bldc/index.html @@ -76,6 +76,7 @@

    A class to handle a button connected to a GPIO.

    This class uses the ESP-IDF GPIO interrupt handler to detect button presses and releases. It then calls the callback function with the event.

    @@ -343,6 +344,82 @@

    Button Example

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/cli.html b/docs/cli.html index 4433c07a7..78d6415fa 100644 --- a/docs/cli.html +++ b/docs/cli.html @@ -76,6 +76,7 @@

    Class for managing controller input.

    The controller can be configured to either use a digital d-pad or an analog 2-axis joystick with select button.

    Digital configuration can support ABXY, start, select, and 4 digital directional inputs.

    @@ -549,6 +550,82 @@

    I2C Analog Controller Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/csv.html b/docs/csv.html index b560d17c5..7a82d541c 100644 --- a/docs/csv.html +++ b/docs/csv.html @@ -76,6 +76,7 @@

    Wrapper class around LVGL display buffer and display driver.

    Optionally allocates and owns the memory associated with the pixel display buffers. Initializes the LVGL subsystem then starts and maintains a task which runs the high priority lv_tick_inc() function every update period (default = 10 ms).

    For more information, see https://docs.lvgl.io/8.3/porting/display.html#display-interface

    @@ -376,6 +377,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -421,7 +498,7 @@

    Classes
    -size_t stack_size_bytes{4096}
    +size_t stack_size_bytes = {4096}

    Size of the display task stack in bytes.

    @@ -520,7 +597,7 @@

    Classes
    -size_t stack_size_bytes{4096}
    +size_t stack_size_bytes = {4096}

    Size of the display task stack in bytes.

    diff --git a/docs/display/display_drivers.html b/docs/display/display_drivers.html index 9990ff988..8b0effad6 100644 --- a/docs/display/display_drivers.html +++ b/docs/display/display_drivers.html @@ -76,6 +76,7 @@
    diff --git a/docs/encoder/as5600.html b/docs/encoder/as5600.html index 2e66c807e..892b675e9 100644 --- a/docs/encoder/as5600.html +++ b/docs/encoder/as5600.html @@ -76,6 +76,7 @@

    Singleton class for managing events. Provides mechanisms for anonymous publish / subscribe interactions - enabling one to one, one to many, many to one, and many to many data distribution with loose coupling and low overhead. Each topic runs a thread for that topic’s subscribers, executing all the callbacks in sequence and then going to sleep again until new data is published.

    Event Manager Example

    @@ -426,13 +427,78 @@

    Event Manager Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    +
    -inline void set_log_level(Logger::Verbosity level)
    -

    Set the logger verbosity for the EventManager.

    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    Parameters
    -

    level – new Logger::Verbosity level to use.

    +

    rate_limit – The rate limit to use for the logger

    diff --git a/docs/file_system.html b/docs/file_system.html index 66c44469a..8295c0d83 100644 --- a/docs/file_system.html +++ b/docs/file_system.html @@ -76,6 +76,7 @@

    Public Static Functions

    diff --git a/docs/filters/biquad.html b/docs/filters/biquad.html index d703bdb3e..a1a1c2bf3 100644 --- a/docs/filters/biquad.html +++ b/docs/filters/biquad.html @@ -77,6 +77,7 @@
    @@ -227,7 +304,7 @@

    Classes

    Header File

    @@ -247,7 +324,7 @@

    Functions

    -class espp::FtpClientSession
    +class espp::FtpClientSession : public espp::BaseComponent

    Class representing a client that is connected to the FTP server. This class is used by the FtpServer class to handle the client’s requests.

    Public Functions

    @@ -309,6 +386,82 @@

    Classes

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    + diff --git a/docs/ftp/index.html b/docs/ftp/index.html index 967bc48a6..f91e2a342 100644 --- a/docs/ftp/index.html +++ b/docs/ftp/index.html @@ -76,6 +76,7 @@

    Header File

    Header File

    Classes

    -template<MotorConcept M>
    class espp::BldcHaptics
    +template<MotorConcept M>
    class espp::BldcHaptics : public espp::BaseComponent

    Class which creates haptic feedback for the user by vibrating the motor This class is based on the work at https://github.com/scottbez1/smartknob to use a small BLDC gimbal motor as a haptic feedback device. It does so by varying the control type, setpoints, and gains of the motor to create a vibration. The motor is driven using the ESP32’s MCPWM peripheral. The motor is driven in a closed loop using the encoder feedback.

    The haptics provided by this class enable configuration of:

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/haptics/drv2605.html b/docs/haptics/drv2605.html index 9acb8439b..c59ca4a44 100644 --- a/docs/haptics/drv2605.html +++ b/docs/haptics/drv2605.html @@ -76,6 +76,7 @@
    diff --git a/docs/haptics/index.html b/docs/haptics/index.html index 1800549da..1f4fad352 100644 --- a/docs/haptics/index.html +++ b/docs/haptics/index.html @@ -76,6 +76,7 @@

    Classes

    -class espp::I2c
    +class espp::I2c : public espp::BaseComponent

    I2C driver.

    This class is a wrapper around the ESP-IDF I2C driver.

    Example

    -

      espp::I2c i2c({
    -      .port = I2C_NUM_0,
    -      .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO,
    -      .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO,
    +

        espp::I2c i2c({
    +        .port = I2C_NUM_0,
    +        .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO,
    +        .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO,
         });
     
    -  // probe the bus for all addresses and store the ones that were found /
    -  // responded
    -  std::vector<uint8_t> found_addresses;
    -  for (uint8_t address = 0; address < 128; address++) {
    -    if (i2c.probe_device(address)) {
    -      found_addresses.push_back(address);
    +    // probe the bus for all addresses and store the ones that were found /
    +    // responded
    +    std::vector<uint8_t> found_addresses;
    +    for (uint8_t address = 0; address < 128; address++) {
    +      if (i2c.probe_device(address)) {
    +        found_addresses.push_back(address);
    +      }
         }
    -  }
    -  // print out the addresses that were found
    -  logger.info("Found devices at addresses: {::#02x}", found_addresses);
    -
    -  static constexpr uint8_t device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR;
    -  static constexpr uint8_t register_address = CONFIG_EXAMPLE_I2C_DEVICE_REG_ADDR;
    -  bool device_found = i2c.probe_device(device_address);
    -  if (device_found) {
    -    logger.info("Found device with address {:#02x}", device_address);
    -    std::vector<uint8_t> read_data(CONFIG_EXAMPLE_I2C_DEVICE_REG_SIZE, 0);
    -    bool success = i2c.read_at_register(device_address, register_address, read_data.data(), read_data.size());
    -    if (success) {
    -      logger.info("read data: {::#02x}", read_data);
    +    // print out the addresses that were found
    +    logger.info("Found devices at addresses: {::#02x}", found_addresses);
    +
    +    static constexpr uint8_t device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR;
    +    static constexpr uint8_t register_address = CONFIG_EXAMPLE_I2C_DEVICE_REG_ADDR;
    +    bool device_found = i2c.probe_device(device_address);
    +    if (device_found) {
    +      logger.info("Found device with address {:#02x}", device_address);
    +      std::vector<uint8_t> read_data(CONFIG_EXAMPLE_I2C_DEVICE_REG_SIZE, 0);
    +      bool success = i2c.read_at_register(device_address, register_address, read_data.data(),
    +                                          read_data.size());
    +      if (success) {
    +        logger.info("read data: {::#02x}", read_data);
    +      } else {
    +        logger.error("read failed");
    +      }
         } else {
    -      logger.error("read failed");
    +      logger.error("Could not find device with address {:#02x}", device_address);
         }
    -  } else {
    -    logger.error("Could not find device with address {:#02x}", device_address);
    -  }
     

    @@ -216,6 +218,12 @@

    Example +
    +inline ~I2c()
    +

    Destructor.

    +

    +
    inline void init(std::error_code &ec)
    @@ -229,8 +237,8 @@

    Example -
    -inline bool write(uint8_t dev_addr, uint8_t *data, size_t data_len)
    +
    +inline bool write(const uint8_t dev_addr, const uint8_t *data, const size_t data_len)

    Write data to I2C device

    Parameters
    @@ -247,8 +255,25 @@

    Example -
    -inline bool write_read(uint8_t dev_addr, uint8_t *write_data, size_t write_size, uint8_t *read_data, size_t read_size)
    +
    +inline bool write_vector(const uint8_t dev_addr, const std::vector<uint8_t> &data)
    +

    Write data to I2C device

    +
    +
    Parameters
    +
      +
    • dev_addr – I2C device address

    • +
    • data – Data to write

    • +
    +
    +
    Returns
    +

    True if successful

    +
    +
    +

    + +
    +
    +inline bool write_read(const uint8_t dev_addr, const uint8_t *write_data, const size_t write_size, uint8_t *read_data, size_t read_size)

    Write to and read data from I2C device

    Parameters
    @@ -267,8 +292,26 @@

    Example -
    -inline bool read_at_register(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, size_t data_len)
    +
    +inline bool write_read_vector(const uint8_t dev_addr, const std::vector<uint8_t> &write_data, std::vector<uint8_t> &read_data)
    +

    Write data to and read data from I2C device

    +
    +
    Parameters
    +
      +
    • dev_addr – I2C device address

    • +
    • write_data – Data to write

    • +
    • read_data – Data to read

    • +
    +
    +
    Returns
    +

    True if successful

    +
    +
    +

    + +
    +
    +inline bool read_at_register(const uint8_t dev_addr, const uint8_t reg_addr, uint8_t *data, size_t data_len)

    Read data from I2C device at register

    Parameters
    @@ -286,8 +329,26 @@

    Example -
    -inline bool read(uint8_t dev_addr, uint8_t *data, size_t data_len)
    +
    +inline bool read_at_register_vector(const uint8_t dev_addr, const uint8_t reg_addr, std::vector<uint8_t> &data)
    +

    Read data from I2C device at register

    +
    +
    Parameters
    +
      +
    • dev_addr – I2C device address

    • +
    • reg_addr – Register address

    • +
    • data – Data to read

    • +
    +
    +
    Returns
    +

    True if successful

    +
    +
    +

    + +
    +
    +inline bool read(const uint8_t dev_addr, uint8_t *data, size_t data_len)

    Read data from I2C device

    Parameters
    @@ -304,8 +365,25 @@

    Example -
    -inline bool probe_device(uint8_t dev_addr)
    +
    +inline bool read_vector(const uint8_t dev_addr, std::vector<uint8_t> &data)
    +

    Read data from I2C device

    +
    +
    Parameters
    +
      +
    • dev_addr – I2C device address

    • +
    • data – Data to read

    • +
    +
    +
    Returns
    +

    True if successful

    +
    +
    +

    + +
    +
    +inline bool probe_device(const uint8_t dev_addr)

    Probe I2C device

    This function sends a start condition, writes the device address, and then sends a stop condition. If the device acknowledges the address, then it is present on the bus.

    @@ -319,6 +397,82 @@

    Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/index.html b/docs/index.html index 3767d9b7a..712a7dcd9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -75,6 +75,7 @@

    Classes

    -class espp::EncoderInput
    +class espp::EncoderInput : public espp::BaseComponent

    Light wrapper around LVGL input device driver, specifically designed for encoders with optional home buttons.

    Public Functions

    @@ -216,6 +217,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/input/ft5x06.html b/docs/input/ft5x06.html index dcf362db2..eb24f0fe3 100644 --- a/docs/input/ft5x06.html +++ b/docs/input/ft5x06.html @@ -76,6 +76,7 @@
    diff --git a/docs/input/gt911.html b/docs/input/gt911.html index 3b787252e..a1140cbb0 100644 --- a/docs/input/gt911.html +++ b/docs/input/gt911.html @@ -76,6 +76,7 @@
    diff --git a/docs/input/index.html b/docs/input/index.html index 9ace24c83..0f512e559 100644 --- a/docs/input/index.html +++ b/docs/input/index.html @@ -76,6 +76,7 @@

    Classes

    -class espp::KeypadInput
    +class espp::KeypadInput : public espp::BaseComponent

    Light wrapper around LVGL input device driver, specifically designed for keypads.

    Public Functions

    @@ -206,6 +207,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/input/t_keyboard.html b/docs/input/t_keyboard.html index 44e284bfd..6c95d012f 100644 --- a/docs/input/t_keyboard.html +++ b/docs/input/t_keyboard.html @@ -76,6 +76,7 @@
    diff --git a/docs/input/touchpad_input.html b/docs/input/touchpad_input.html index 73774882a..2e7ab4c2f 100644 --- a/docs/input/touchpad_input.html +++ b/docs/input/touchpad_input.html @@ -76,6 +76,7 @@

    Classes

    -class espp::TouchpadInput
    +class espp::TouchpadInput : public espp::BaseComponent

    Light wrapper around LVGL input device driver, specifically designed for touchpads with optional home buttons.

    Public Types

    @@ -239,6 +240,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/input/tt21100.html b/docs/input/tt21100.html index 85b6c80af..a81cf7d8a 100644 --- a/docs/input/tt21100.html +++ b/docs/input/tt21100.html @@ -76,6 +76,7 @@
    diff --git a/docs/io_expander/aw9523.html b/docs/io_expander/aw9523.html index 6c2f30729..53f4bb825 100644 --- a/docs/io_expander/aw9523.html +++ b/docs/io_expander/aw9523.html @@ -76,6 +76,7 @@
    diff --git a/docs/io_expander/index.html b/docs/io_expander/index.html index 9ca325ec6..05739c030 100644 --- a/docs/io_expander/index.html +++ b/docs/io_expander/index.html @@ -76,6 +76,7 @@
    diff --git a/docs/joystick.html b/docs/joystick.html index 6b15adb2e..958d59ad7 100644 --- a/docs/joystick.html +++ b/docs/joystick.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Joystick
    +class espp::Joystick : public espp::BaseComponent

    2-axis Joystick with axis mapping / calibration.

    ADC Joystick Example

    @@ -379,6 +380,82 @@

    ADC Joystick Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/led.html b/docs/led.html index 9c23c2006..405914c68 100644 --- a/docs/led.html +++ b/docs/led.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Led
    +class espp::Led : public espp::BaseComponent

    Provides a wrapper around the LEDC peripheral in ESP-IDF which allows for thread-safe control over one or more channels of LEDs using a simpler API.

    Linear LED Example

    @@ -335,6 +336,82 @@

    Breathing LED Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/led_strip.html b/docs/led_strip.html index 56a87d420..0627c5317 100644 --- a/docs/led_strip.html +++ b/docs/led_strip.html @@ -76,6 +76,7 @@

    Classes

    -class espp::LedStrip
    +class espp::LedStrip : public espp::BaseComponent

    Class to control LED strips.

    This class is used to control LED strips. It is designed to be used with a write function that can be used to write data to the strip. This allows it to be used with different hardware interfaces (e.g. SPI, I2C, RMT, etc.).

    This class is designed to be used to control various LED strips, which may be using different protocols. The following protocols are supported:

    @@ -559,6 +560,82 @@

    Example 1: APA102 via SPI +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Attributes

    diff --git a/docs/logger.html b/docs/logger.html index 297365e6a..cb2810b1e 100644 --- a/docs/logger.html +++ b/docs/logger.html @@ -76,6 +76,7 @@
    @@ -324,6 +325,21 @@

    Threaded Logging and Verbosity Example +
    +inline void set_rate_limit(const std::chrono::duration<float> rate_limit)
    +

    Change the rate limit for the logger.

    +
    +

    Note

    +

    Only calls that have _rate_limited suffixed will be rate limited.

    +
    +
    +
    Parameters
    +

    rate_limit – The new rate limit.

    +
    +
    +
    +
    template<typename ...Args>
    inline std::string format(std::string_view rt_fmt_str, Args&&... args)
    diff --git a/docs/math/bezier.html b/docs/math/bezier.html index 9a77257aa..382fe39ed 100644 --- a/docs/math/bezier.html +++ b/docs/math/bezier.html @@ -76,6 +76,7 @@

    diff --git a/docs/math/fast_math.html b/docs/math/fast_math.html index 98c7a5d58..013f11834 100644 --- a/docs/math/fast_math.html +++ b/docs/math/fast_math.html @@ -76,6 +76,7 @@
    diff --git a/docs/math/index.html b/docs/math/index.html index bced029af..de946c8a9 100644 --- a/docs/math/index.html +++ b/docs/math/index.html @@ -76,6 +76,7 @@
    diff --git a/docs/math/vector2d.html b/docs/math/vector2d.html index a776336d9..708cd54c9 100644 --- a/docs/math/vector2d.html +++ b/docs/math/vector2d.html @@ -76,6 +76,7 @@
    diff --git a/docs/monitor.html b/docs/monitor.html index 0b233ba23..d5d031ac0 100644 --- a/docs/monitor.html +++ b/docs/monitor.html @@ -76,6 +76,7 @@

    Classes

    -class espp::TaskMonitor
    +class espp::TaskMonitor : public espp::BaseComponent

    Class which monitors the currently running tasks in the system and periodically logs their states. See also FreeRTOS::vTaskGetInfo().

    Basic Task Monitor Example

    @@ -249,6 +250,85 @@

    get_latest_info() Example

    Note

    You can use the static TaskMonitor::get_latest_info() to get a string with the latest info without needing to construct a class / start the task.

    + +
    +

    Public Functions

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Functions

    diff --git a/docs/network/index.html b/docs/network/index.html index 4cc76058b..510bbd5e4 100644 --- a/docs/network/index.html +++ b/docs/network/index.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Socket
    +class espp::Socket : public espp::BaseComponent

    Class for a generic socket with some helper functions for configuring the socket.

    Subclassed by espp::TcpSocket, espp::UdpSocket

    @@ -328,6 +329,82 @@

    Classes +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Functions

    diff --git a/docs/network/tcp_socket.html b/docs/network/tcp_socket.html index 221c2473d..c2e4dea44 100644 --- a/docs/network/tcp_socket.html +++ b/docs/network/tcp_socket.html @@ -76,6 +76,7 @@
    @@ -665,6 +666,82 @@

    TCP Server Response Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Functions

    diff --git a/docs/network/udp_socket.html b/docs/network/udp_socket.html index c14dd51e4..fe12f0014 100644 --- a/docs/network/udp_socket.html +++ b/docs/network/udp_socket.html @@ -76,6 +76,7 @@
    @@ -554,6 +555,82 @@

    UDP Multicast Server Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Functions

    diff --git a/docs/nfc/index.html b/docs/nfc/index.html index b61cf38bf..448c412be 100644 --- a/docs/nfc/index.html +++ b/docs/nfc/index.html @@ -76,6 +76,7 @@

    diff --git a/docs/nfc/st25dv.html b/docs/nfc/st25dv.html index f8ac8612f..307ed12ee 100644 --- a/docs/nfc/st25dv.html +++ b/docs/nfc/st25dv.html @@ -76,6 +76,7 @@
    diff --git a/docs/objects.inv b/docs/objects.inv index 775e098ef1794068f4fab79daa1b2243fef569d7..3906dfbd9d8fa6fd4aa25cb77387f17c2fa64e69 100644 GIT binary patch literal 64792 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%0A5_?-Omd{Teu0p+J$w4=A`Pu-QXwphS95 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 literal 61948 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 ET}JlpQ~&?~ diff --git a/docs/pid.html b/docs/pid.html index befc5c70e..1cad2571c 100644 --- a/docs/pid.html +++ b/docs/pid.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Pid
    +class espp::Pid : public espp::BaseComponent

    Simple PID (proportional, integral, derivative) controller class with integrator clamping, output clamping, and prevention of integrator windup during output saturation. This class is thread-safe, so you can update(), clear(), and change_gains() from multiple threads if needed.

    Basic PID Example

    @@ -340,6 +341,82 @@

    Complex PID Example

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/qwiicnes.html b/docs/qwiicnes.html index 85fb74486..ede85e347 100644 --- a/docs/qwiicnes.html +++ b/docs/qwiicnes.html @@ -76,6 +76,7 @@
    diff --git a/docs/rmt.html b/docs/rmt.html index bb33b0539..557333780 100644 --- a/docs/rmt.html +++ b/docs/rmt.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Rmt
    +class espp::Rmt : public espp::BaseComponent

    Class wrapping the RMT peripheral on the ESP32.

    The RMT (Remote Control Transceiver) peripheral is used to generate precise timing pulses on a GPIO pin. It can be used to drive a WS2812B or similar LED strip which uses a 1-wire protocol such as the WS2812B. The RMT peripheral is also used by the ESP32 to drive the IR transmitter.

    @@ -273,6 +274,82 @@

    Example 1: Transmitting data +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -331,7 +408,7 @@

    Example 1: Transmitting data

    Header File

    diff --git a/docs/rtc/bm8563.html b/docs/rtc/bm8563.html index bce14f71c..528f9d76b 100644 --- a/docs/rtc/bm8563.html +++ b/docs/rtc/bm8563.html @@ -76,6 +76,7 @@
    diff --git a/docs/rtc/index.html b/docs/rtc/index.html index 3caeca4b2..bc64d20bf 100644 --- a/docs/rtc/index.html +++ b/docs/rtc/index.html @@ -76,6 +76,7 @@

    Classes

    -class espp::RtspClient
    +class espp::RtspClient : public espp::BaseComponent

    A class for interacting with an RTSP server using RTP and RTCP over UDP

    This class is used to connect to an RTSP server and receive JPEG frames over RTP. It uses the TCP socket to send RTSP requests and receive RTSP responses. It uses the UDP socket to receive RTP and RTCP packets.

    The RTSP client is designed to be used with the RTSP server in the [camera-streamer]https://github.com/esp-cpp/camera-streamer) project, but it should work with any RTSP server that sends JPEG frames over RTP.

    @@ -409,6 +410,82 @@

    Example

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -455,14 +532,14 @@

    Example

    Header File

    Classes

    -class espp::RtspServer
    +class espp::RtspServer : public espp::BaseComponent

    Class for streaming MJPEG data from a camera using RTSP + RTP Starts a TCP socket to listen for RTSP connections, and then spawns off a new RTSP session for each connection.

    See also

    RtspSession

    @@ -567,6 +644,82 @@

    example

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -613,14 +766,14 @@

    example

    Header File

    Classes

    -class espp::RtspSession
    +class espp::RtspSession : public espp::BaseComponent

    Class that reepresents an RTSP session, which is uniquely identified by a session id and sends frame data over RTP and RTCP to the client

    Public Functions

    @@ -736,6 +889,82 @@

    Classes

    +
    +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    @@ -770,7 +999,7 @@

    Classes

    Header File

    @@ -912,7 +1141,7 @@

    Classes

    Header File

    @@ -1211,7 +1440,7 @@

    Classes

    Header File

    @@ -1227,7 +1456,7 @@

    Classes

    Header File

    @@ -1314,7 +1543,7 @@

    Classes

    Header File

    diff --git a/docs/search.html b/docs/search.html index 9f290a029..eeb1cf671 100644 --- a/docs/search.html +++ b/docs/search.html @@ -76,6 +76,7 @@
    diff --git a/docs/state_machine.html b/docs/state_machine.html index 295179b8c..2c84efd44 100644 --- a/docs/state_machine.html +++ b/docs/state_machine.html @@ -76,6 +76,7 @@
    @@ -324,7 +325,7 @@

    Running the HFSM Test Bench on a Real Device:

    Header File

    @@ -464,7 +465,7 @@

    Classes

    Header File

    @@ -595,7 +596,7 @@

    Classes

    Header File

    diff --git a/docs/tabulate.html b/docs/tabulate.html index f70e42964..0e233ec14 100644 --- a/docs/tabulate.html +++ b/docs/tabulate.html @@ -76,6 +76,7 @@
    diff --git a/docs/task.html b/docs/task.html index 8d352863c..38f119b46 100644 --- a/docs/task.html +++ b/docs/task.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Task
    +class espp::Task : public espp::BaseComponent

    Task provides an abstraction over std::thread which optionally includes memory / priority configuration on ESP systems. It allows users to easily stop the task, and will automatically stop itself if destroyed.

    Basic Task Example

    @@ -445,6 +446,82 @@

    Task Request Stop Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +

    Public Static Functions

    diff --git a/docs/thermistor.html b/docs/thermistor.html index 8431b58e2..da318f32e 100644 --- a/docs/thermistor.html +++ b/docs/thermistor.html @@ -77,6 +77,7 @@

    Classes

    -class espp::Thermistor
    +class espp::Thermistor : public espp::BaseComponent

    Thermistor class.

    Reads voltage from a thermistor and converts it to temperature using the Steinhart-Hart equation. This class is designed to be used with a NTC (negative temperature coefficient) thermistor in a voltage divider configuration.

    See also

    @@ -423,6 +424,82 @@

    ADC Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/timer.html b/docs/timer.html index 7892bd6c7..b3e7961bc 100644 --- a/docs/timer.html +++ b/docs/timer.html @@ -76,6 +76,7 @@

    Classes

    -class espp::Timer
    +class espp::Timer : public espp::BaseComponent

    A timer that can be used to schedule tasks to run at a later time.

    A timer can be used to schedule a task to run at a later time. The timer will run in the background and will call the task when the time is up. The timer can be canceled at any time. A timer can be configured to run once or to repeat.

    The timer uses a task to run in the background. The task will sleep until the timer is ready to run. When the timer is ready to run, the task will call the callback function. The callback function can return true to cancel the timer or false to keep the timer running. If the timer is configured to repeat, then the callback function will be called again after the period has elapsed. If the timer is configured to run once, then the callback function will only be called once.

    @@ -362,6 +363,82 @@

    Oneshot Timer Cancel Itself Then Start again with Delay Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/wifi/index.html b/docs/wifi/index.html index 33402946c..4bd114017 100644 --- a/docs/wifi/index.html +++ b/docs/wifi/index.html @@ -76,6 +76,7 @@

    Classes

    -class espp::WifiAp
    +class espp::WifiAp : public espp::BaseComponent

    WiFi Access Point (AP)

    see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#esp32-wi-fi-ap-general-scenario

    @@ -193,6 +194,82 @@

    WiFi Access Point Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +
    diff --git a/docs/wifi/wifi_sta.html b/docs/wifi/wifi_sta.html index 5cd8b1f2c..2b44b7cf9 100644 --- a/docs/wifi/wifi_sta.html +++ b/docs/wifi/wifi_sta.html @@ -76,6 +76,7 @@

    Classes

    -class espp::WifiSta
    +class espp::WifiSta : public espp::BaseComponent

    WiFi Station (STA)

    see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#esp32-wi-fi-station-general-scenario

    @@ -248,6 +249,82 @@

    WiFi Station Example +
    +inline void set_log_tag(const std::string_view &tag)
    +

    Set the tag for the logger

    +
    +
    Parameters
    +

    tag – The tag to use for the logger

    +
    +
    +

    + +
    +
    +inline void set_log_level(Logger::Verbosity level)
    +

    Set the log level for the logger

    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_verbosity(Logger::Verbosity level)
    +

    Set the log verbosity for the logger

    +

    See also

    +

    set_log_level

    +
    +
    +

    See also

    +

    Logger::Verbosity

    +
    +
    +

    See also

    +

    Logger::set_verbosity

    +
    +

    +
    +

    Note

    +

    This is a convenience method that calls set_log_level

    +
    +
    +
    Parameters
    +

    level – The verbosity level to use for the logger

    +
    +
    +
    + +
    +
    +inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
    +

    Set the rate limit for the logger

    +

    +
    +

    Note

    +

    Only calls to the logger that have _rate_limit suffix will be rate limited

    +
    +
    +
    Parameters
    +

    rate_limit – The rate limit to use for the logger

    +
    +
    +
    +