From 50c49023a75d8b4a6e7ac277d4c7acece69ec976 Mon Sep 17 00:00:00 2001 From: Chinsyo Date: Tue, 25 Mar 2025 02:37:43 +0800 Subject: [PATCH] Support adjust volume with knob in SenseCAP Watcher (#399) * fix typo, add missing prefix `CONFIG` to `ESP_TASK_WDT_TIMEOUT_S` * add KNOB gpio spec to sensecap config * create new knob component * implement ajust output volume with knob * modify function name to UpperCamelCase * Tidy up comments and logs --- main/boards/common/knob.cc | 52 +++++++++++++++++++ main/boards/common/knob.h | 25 +++++++++ .../esp32-s3-touch-lcd-1.46.cc | 4 +- main/boards/sensecap-watcher/config.h | 3 +- .../sensecap-watcher/sensecap_watcher.cc | 39 ++++++++++++++ main/idf_component.yml | 1 + sdkconfig.defaults | 2 +- 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 main/boards/common/knob.cc create mode 100644 main/boards/common/knob.h diff --git a/main/boards/common/knob.cc b/main/boards/common/knob.cc new file mode 100644 index 00000000..350fda25 --- /dev/null +++ b/main/boards/common/knob.cc @@ -0,0 +1,52 @@ +#include "knob.h" + +static const char* TAG = "Knob"; + +Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) { + knob_config_t config = { + .default_direction = 0, + .gpio_encoder_a = static_cast(pin_a), + .gpio_encoder_b = static_cast(pin_b), + }; + + esp_err_t err = ESP_OK; + knob_handle_ = iot_knob_create(&config); + if (knob_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create knob instance"); + return; + } + + err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err)); + return; + } + + err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err)); + return; + } + + ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b); +} + +Knob::~Knob() { + if (knob_handle_ != NULL) { + iot_knob_delete(knob_handle_); + knob_handle_ = NULL; + } +} + +void Knob::OnRotate(std::function callback) { + on_rotate_ = callback; +} + +void Knob::knob_callback(void* arg, void* data) { + Knob* knob = static_cast(data); + knob_event_t event = iot_knob_get_event(arg); + + if (knob->on_rotate_) { + knob->on_rotate_(event == KNOB_RIGHT); + } +} \ No newline at end of file diff --git a/main/boards/common/knob.h b/main/boards/common/knob.h new file mode 100644 index 00000000..efea5f56 --- /dev/null +++ b/main/boards/common/knob.h @@ -0,0 +1,25 @@ +#ifndef KNOB_H_ +#define KNOB_H_ + +#include +#include +#include +#include + +class Knob { +public: + Knob(gpio_num_t pin_a, gpio_num_t pin_b); + ~Knob(); + + void OnRotate(std::function callback); + +private: + static void knob_callback(void* arg, void* data); + + knob_handle_t knob_handle_; + gpio_num_t pin_a_; + gpio_num_t pin_b_; + std::function on_rotate_; +}; + +#endif // KNOB_H_ \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc index f5e3430c..eed8b23b 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc +++ b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc @@ -114,7 +114,7 @@ private: ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); } - void Initializespd2010Display() { + void InitializeSpd2010Display() { esp_lcd_panel_io_handle_t panel_io = nullptr; esp_lcd_panel_handle_t panel = nullptr; @@ -224,7 +224,7 @@ public: InitializeI2c(); InitializeTca9554(); InitializeSpi(); - Initializespd2010Display(); + InitializeSpd2010Display(); InitializeButtons(); InitializeIot(); GetBacklight()->RestoreBrightness(); diff --git a/main/boards/sensecap-watcher/config.h b/main/boards/sensecap-watcher/config.h index b7fb2f27..36ce1f7f 100644 --- a/main/boards/sensecap-watcher/config.h +++ b/main/boards/sensecap-watcher/config.h @@ -54,7 +54,8 @@ #define BSP_PWR_START_UP (BSP_PWR_SDCARD | BSP_PWR_LCD | BSP_PWR_SYSTEM | BSP_PWR_AI_CHIP | BSP_PWR_CODEC_PA | BSP_PWR_GROVE | BSP_PWR_BAT_ADC) #define BSP_KNOB_BTN (IO_EXPANDER_PIN_NUM_3) - +#define BSP_KNOB_A_PIN GPIO_NUM_41 +#define BSP_KNOB_B_PIN GPIO_NUM_42 /* QSPI */ #define BSP_SPI3_HOST_PCLK (GPIO_NUM_7) diff --git a/main/boards/sensecap-watcher/sensecap_watcher.cc b/main/boards/sensecap-watcher/sensecap_watcher.cc index cdac9141..31b1b9bf 100644 --- a/main/boards/sensecap-watcher/sensecap_watcher.cc +++ b/main/boards/sensecap-watcher/sensecap_watcher.cc @@ -6,6 +6,7 @@ #include "font_awesome_symbols.h" #include "application.h" #include "button.h" +#include "knob.h" #include "config.h" #include "led/single_led.h" #include "iot/thing_manager.h" @@ -21,6 +22,7 @@ #include #include #include +#include #include #include @@ -34,6 +36,7 @@ class SensecapWatcher : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; LcdDisplay* display_; + std::unique_ptr knob_; esp_io_expander_handle_t io_exp_handle; button_handle_t btns; PowerSaveTimer* power_save_timer_; @@ -115,6 +118,41 @@ private: assert(ret == ESP_OK); } + void OnKnobRotate(bool clockwise) { + auto codec = GetAudioCodec(); + int current_volume = codec->output_volume(); + int new_volume = current_volume + (clockwise ? 5 : -5); + + // 确保音量在有效范围内 + if (new_volume > 100) { + new_volume = 100; + ESP_LOGW(TAG, "Volume reached maximum limit: %d", new_volume); + } else if (new_volume < 0) { + new_volume = 0; + ESP_LOGW(TAG, "Volume reached minimum limit: %d", new_volume); + } + + codec->SetOutputVolume(new_volume); + ESP_LOGI(TAG, "Volume changed from %d to %d", current_volume, new_volume); + + // 显示通知前检查实际变化 + if (new_volume != codec->output_volume()) { + ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d", + new_volume, codec->output_volume()); + } + GetDisplay()->ShowNotification("音量: " + std::to_string(codec->output_volume())); + power_save_timer_->WakeUp(); + } + + void InitializeKnob() { + knob_ = std::make_unique(BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); + knob_->OnRotate([this](bool clockwise) { + ESP_LOGD(TAG, "Knob rotation detected. Clockwise:%s", clockwise ? "true" : "false"); + OnKnobRotate(clockwise); + }); + ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); + } + void InitializeButton() { button_config_t btn_config = { .type = BUTTON_TYPE_CUSTOM, @@ -250,6 +288,7 @@ public: InitializeSpi(); InitializeExpander(); InitializeButton(); + InitializeKnob(); Initializespd2010Display(); InitializeIot(); GetBacklight()->RestoreBrightness(); diff --git a/main/idf_component.yml b/main/idf_component.yml index 1a883547..138021cd 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -16,6 +16,7 @@ dependencies: espressif/esp_codec_dev: "~1.3.2" espressif/esp-sr: "^2.0.2" espressif/button: "^3.3.1" + espressif/knob: "^1.0.0" lvgl/lvgl: "~9.2.2" esp_lvgl_port: "~2.4.4" espressif/esp_io_expander_tca95xx_16bit: "^2.0.0" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 0a512fb1..87077073 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -13,7 +13,7 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 -ESP_TASK_WDT_TIMEOUT_S=10 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y