From ed82063af5f83530afa5cfb5bf5bd516f3d05f2a Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 10 Apr 2024 16:56:10 +1000 Subject: [PATCH 1/2] WIP decompose our giant LVGL driver into smaller classes --- src/drivers/CMakeLists.txt | 3 +- src/drivers/include/relative_wheel.hpp | 52 --- src/drivers/relative_wheel.cpp | 92 ------ src/input/CMakeLists.txt | 11 + src/input/feedback_haptics.cpp | 37 +++ src/input/include/feedback_device.hpp | 32 ++ src/input/include/feedback_haptics.hpp | 26 ++ src/input/include/input_device.hpp | 33 ++ src/input/include/input_touch_dpad.hpp | 30 ++ src/input/include/input_touch_wheel.hpp | 37 +++ src/input/include/input_volume_buttons.hpp | 30 ++ src/input/include/lvgl_input_driver.hpp | 53 +++ src/input/input_touch_dpad.cpp | 35 ++ src/input/input_touch_wheel.cpp | 81 +++++ src/input/input_volume_buttons.cpp | 26 ++ src/input/lvgl_input_driver.cpp | 84 +++++ src/system_fsm/include/system_fsm.hpp | 1 - src/system_fsm/system_fsm.cpp | 1 - src/ui/CMakeLists.txt | 8 +- src/ui/encoder_input.cpp | 358 --------------------- src/ui/include/encoder_input.hpp | 107 ------ src/ui/include/lvgl_task.hpp | 7 +- src/ui/include/ui_fsm.hpp | 5 +- src/ui/lvgl_task.cpp | 15 +- src/ui/ui_fsm.cpp | 122 ++++--- 25 files changed, 584 insertions(+), 702 deletions(-) delete mode 100644 src/drivers/include/relative_wheel.hpp delete mode 100644 src/drivers/relative_wheel.cpp create mode 100644 src/input/CMakeLists.txt create mode 100644 src/input/feedback_haptics.cpp create mode 100644 src/input/include/feedback_device.hpp create mode 100644 src/input/include/feedback_haptics.hpp create mode 100644 src/input/include/input_device.hpp create mode 100644 src/input/include/input_touch_dpad.hpp create mode 100644 src/input/include/input_touch_wheel.hpp create mode 100644 src/input/include/input_volume_buttons.hpp create mode 100644 src/input/include/lvgl_input_driver.hpp create mode 100644 src/input/input_touch_dpad.cpp create mode 100644 src/input/input_touch_wheel.cpp create mode 100644 src/input/input_volume_buttons.cpp create mode 100644 src/input/lvgl_input_driver.cpp delete mode 100644 src/ui/encoder_input.cpp delete mode 100644 src/ui/include/encoder_input.hpp diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 0b7ead94..891115d4 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -5,8 +5,7 @@ idf_component_register( SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "adc.cpp" "storage.cpp" "i2c.cpp" "bluetooth.cpp" "spi.cpp" "display.cpp" "display_init.cpp" - "samd.cpp" "relative_wheel.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" - "spiffs.cpp" + "samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp" INCLUDE_DIRS "include" REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs" "bt" "tinyfsm" "util") diff --git a/src/drivers/include/relative_wheel.hpp b/src/drivers/include/relative_wheel.hpp deleted file mode 100644 index e1106143..00000000 --- a/src/drivers/include/relative_wheel.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2023 jacqueline - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include -#include - -#include "esp_err.h" -#include "result.hpp" - -#include "gpios.hpp" -#include "touchwheel.hpp" - -namespace drivers { - -class RelativeWheel { - public: - explicit RelativeWheel(TouchWheel& touch); - - auto Update() -> void; - auto SetEnabled(bool) -> void; - - auto SetSensitivity(uint8_t) -> void; - auto GetSensitivity() -> uint8_t; - - auto is_clicking() const -> bool; - auto ticks() const -> std::int_fast16_t; - - // Not copyable or movable. - RelativeWheel(const RelativeWheel&) = delete; - RelativeWheel& operator=(const RelativeWheel&) = delete; - - private: - TouchWheel& touch_; - - bool is_enabled_; - uint8_t sensitivity_; - uint8_t threshold_; - - bool is_clicking_; - bool was_clicking_; - bool is_first_read_; - std::int_fast16_t ticks_; - uint8_t last_angle_; -}; - -} // namespace drivers diff --git a/src/drivers/relative_wheel.cpp b/src/drivers/relative_wheel.cpp deleted file mode 100644 index e90143ae..00000000 --- a/src/drivers/relative_wheel.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 jacqueline - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "relative_wheel.hpp" - -#include -#include - -#include "esp_log.h" - -namespace drivers { - -RelativeWheel::RelativeWheel(TouchWheel& touch) - : touch_(touch), - is_enabled_(true), - sensitivity_(128), - threshold_(10), - is_clicking_(false), - was_clicking_(false), - is_first_read_(true), - ticks_(0), - last_angle_(0) {} - -auto RelativeWheel::Update() -> void { - TouchWheelData d = touch_.GetTouchWheelData(); - - is_clicking_ = d.is_button_touched; - - if (is_clicking_) { - ticks_ = 0; - return; - } - - if (!d.is_wheel_touched) { - ticks_ = 0; - is_first_read_ = true; - return; - } - - uint8_t new_angle = d.wheel_position; - if (is_first_read_) { - is_first_read_ = false; - last_angle_ = new_angle; - return; - } - - int delta = 128 - last_angle_; - uint8_t rotated_angle = new_angle + delta; - if (rotated_angle < 128 - threshold_) { - ticks_ = 1; - last_angle_ = new_angle; - } else if (rotated_angle > 128 + threshold_) { - ticks_ = -1; - last_angle_ = new_angle; - } else { - ticks_ = 0; - } -} - -auto RelativeWheel::SetEnabled(bool en) -> void { - is_enabled_ = en; -} - -auto RelativeWheel::SetSensitivity(uint8_t val) -> void { - sensitivity_ = val; - int tmax = 35; - int tmin = 5; - threshold_ = (((255. - sensitivity_)/255.)*(tmax - tmin) + tmin); -} - -auto RelativeWheel::GetSensitivity() -> uint8_t { - return sensitivity_; -} - -auto RelativeWheel::is_clicking() const -> bool { - if (!is_enabled_) { - return false; - } - return is_clicking_; -} - -auto RelativeWheel::ticks() const -> std::int_fast16_t { - if (!is_enabled_) { - return 0; - } - return ticks_; -} - -} // namespace drivers diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt new file mode 100644 index 00000000..92ed3d6c --- /dev/null +++ b/src/input/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright 2023 jacqueline +# +# SPDX-License-Identifier: GPL-3.0-only + +idf_component_register( + SRCS "input_touch_wheel.cpp" "input_touch_dpad.cpp" + "input_volume_buttons.cpp" "lvgl_input_driver.cpp" "feedback_haptics.cpp" + INCLUDE_DIRS "include" + REQUIRES "drivers" "lvgl" "events" "system_fsm") + +target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/input/feedback_haptics.cpp b/src/input/feedback_haptics.cpp new file mode 100644 index 00000000..5e83d0d6 --- /dev/null +++ b/src/input/feedback_haptics.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "feedback_haptics.hpp" + +#include + +#include "lvgl/lvgl.h" + +#include "core/lv_event.h" +#include "esp_log.h" + +#include "haptics.hpp" + +namespace input { + +using Effect = drivers::Haptics::Effect; + +Haptics::Haptics(drivers::Haptics& haptics_) : haptics_(haptics_) {} + +auto Haptics::feedback(uint8_t event_type) -> void { + switch (event_type) { + case LV_EVENT_FOCUSED: + haptics_.PlayWaveformEffect(Effect::kMediumClick1_100Pct); + break; + case LV_EVENT_CLICKED: + haptics_.PlayWaveformEffect(Effect::kSharpClick_100Pct); + break; + default: + break; + } +} + +} // namespace input diff --git a/src/input/include/feedback_device.hpp b/src/input/include/feedback_device.hpp new file mode 100644 index 00000000..4faeeafd --- /dev/null +++ b/src/input/include/feedback_device.hpp @@ -0,0 +1,32 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +namespace input { + +/* + * Interface for providing non-visual feedback to the user as a result of LVGL + * events. 'Feedback Devices' are able to observe all events that are generated + * by LVGL as a result of Input Devices. + * + * Implementations of this interface are a mix of hardware features (e.g. a + * haptic motor buzzing when your selection changes) and firmware features + * (e.g. playing audio feedback that describes the selected element). + */ +class IFeedbackDevice { + public: + virtual ~IFeedbackDevice() {} + + virtual auto feedback(uint8_t event_type) -> void = 0; + + // TODO: Add configuration; likely the same shape of interface that + // IInputDevice uses. +}; + +} // namespace input diff --git a/src/input/include/feedback_haptics.hpp b/src/input/include/feedback_haptics.hpp new file mode 100644 index 00000000..a307a429 --- /dev/null +++ b/src/input/include/feedback_haptics.hpp @@ -0,0 +1,26 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "feedback_device.hpp" +#include "haptics.hpp" + +namespace input { + +class Haptics : public IFeedbackDevice { + public: + Haptics(drivers::Haptics& haptics_); + + auto feedback(uint8_t event_type) -> void override; + + private: + drivers::Haptics& haptics_; +}; + +} // namespace input diff --git a/src/input/include/input_device.hpp b/src/input/include/input_device.hpp new file mode 100644 index 00000000..09cf9193 --- /dev/null +++ b/src/input/include/input_device.hpp @@ -0,0 +1,33 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include + +#include "hal/lv_hal_indev.h" + +namespace input { + +/* + * Interface for all device input methods. Each 'Input Device' is polled by + * LVGL at regular intervals, and can effect the device either via LVGL's input + * device driver API, or by emitting events for other parts of the system to + * react to (e.g. issuing a play/pause event, or altering the volume). + */ +class IInputDevice { + public: + virtual ~IInputDevice() {} + + virtual auto read(lv_indev_data_t* data) -> void = 0; + + // TODO: Add hooks and configuration (or are hooks just one kind of + // configuration?) +}; + +} // namespace input diff --git a/src/input/include/input_touch_dpad.hpp b/src/input/include/input_touch_dpad.hpp new file mode 100644 index 00000000..fdb52db9 --- /dev/null +++ b/src/input/include/input_touch_dpad.hpp @@ -0,0 +1,30 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "touchwheel.hpp" + +namespace input { + +class TouchDPad : public IInputDevice { + public: + TouchDPad(drivers::TouchWheel&); + + auto read(lv_indev_data_t* data) -> void override; + + private: + drivers::TouchWheel& wheel_; +}; + +} // namespace input diff --git a/src/input/include/input_touch_wheel.hpp b/src/input/include/input_touch_wheel.hpp new file mode 100644 index 00000000..a4923f21 --- /dev/null +++ b/src/input/include/input_touch_wheel.hpp @@ -0,0 +1,37 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "touchwheel.hpp" + +namespace input { + +class TouchWheel : public IInputDevice { + public: + TouchWheel(drivers::TouchWheel&); + + auto read(lv_indev_data_t* data) -> void override; + + private: + auto calculate_ticks(const drivers::TouchWheelData& data) -> int8_t; + + drivers::TouchWheel& wheel_; + + bool is_scrolling_; + uint8_t sensitivity_; + uint8_t threshold_; + bool is_first_read_; + uint8_t last_angle_; +}; + +} // namespace input diff --git a/src/input/include/input_volume_buttons.hpp b/src/input/include/input_volume_buttons.hpp new file mode 100644 index 00000000..4ca8c16b --- /dev/null +++ b/src/input/include/input_volume_buttons.hpp @@ -0,0 +1,30 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "gpios.hpp" +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "touchwheel.hpp" + +namespace input { + +class VolumeButtons : public IInputDevice { + public: + VolumeButtons(drivers::IGpios&); + + auto read(lv_indev_data_t* data) -> void override; + + private: + drivers::IGpios& gpios_; +}; + +} // namespace input diff --git a/src/input/include/lvgl_input_driver.hpp b/src/input/include/lvgl_input_driver.hpp new file mode 100644 index 00000000..be452368 --- /dev/null +++ b/src/input/include/lvgl_input_driver.hpp @@ -0,0 +1,53 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include + +#include "core/lv_group.h" +#include "feedback_device.hpp" +#include "gpios.hpp" +#include "hal/lv_hal_indev.h" + +#include "input_device.hpp" +#include "nvs.hpp" +#include "service_locator.hpp" +#include "touchwheel.hpp" + +namespace input { + +/* + * Implementation of an LVGL input device. This class composes multiple + * IInputDevice and IFeedbackDevice instances together into a single LVGL + * device. + */ +class LvglInputDriver { + public: + LvglInputDriver(std::shared_ptr); + + auto read(lv_indev_data_t* data) -> void; + auto feedback(uint8_t) -> void; + + auto registration() -> lv_indev_t* { return registration_; } + auto lock(bool l) -> void { is_locked_ = l; } + + private: + std::shared_ptr services_; + + lv_indev_drv_t driver_; + lv_indev_t* registration_; + + std::vector> inputs_; + std::vector> feedbacks_; + + bool is_locked_; +}; + +} // namespace input diff --git a/src/input/input_touch_dpad.cpp b/src/input/input_touch_dpad.cpp new file mode 100644 index 00000000..a5774ef6 --- /dev/null +++ b/src/input/input_touch_dpad.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_touch_dpad.hpp" + +#include + +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "input_touch_dpad.hpp" +#include "touchwheel.hpp" + +namespace input { + +static inline auto IsAngleWithin(int16_t wheel_angle, + int16_t target_angle, + int threshold) -> bool { + int16_t difference = (wheel_angle - target_angle + 127 + 255) % 255 - 127; + return difference <= threshold && difference >= -threshold; +} + +TouchDPad::TouchDPad(drivers::TouchWheel& wheel) : wheel_(wheel) {} + +auto TouchDPad::read(lv_indev_data_t* data) -> void { + wheel_.Update(); + + // TODO: reimplement +} + +} // namespace input diff --git a/src/input/input_touch_wheel.cpp b/src/input/input_touch_wheel.cpp new file mode 100644 index 00000000..302a17aa --- /dev/null +++ b/src/input/input_touch_wheel.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_touch_wheel.hpp" +#include + +#include + +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "touchwheel.hpp" + +namespace input { + +TouchWheel::TouchWheel(drivers::TouchWheel& wheel) + : wheel_(wheel), + is_scrolling_(false), + sensitivity_(128), + threshold_(10), + is_first_read_(true), + last_angle_(0) {} + +auto TouchWheel::read(lv_indev_data_t* data) -> void { + wheel_.Update(); + auto wheel_data = wheel_.GetTouchWheelData(); + int8_t ticks = calculate_ticks(wheel_data); + + if (!wheel_data.is_wheel_touched) { + // User has released the wheel. + is_scrolling_ = false; + data->enc_diff = 0; + } else if (ticks != 0) { + // User is touching the wheel, and has just passed the sensitivity + // threshold for a scroll tick. + is_scrolling_ = true; + data->enc_diff = ticks; + } else { + // User is touching the wheel, but hasn't moved. + data->enc_diff = 0; + } + + if (!is_scrolling_ && wheel_data.is_button_touched) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +auto TouchWheel::calculate_ticks(const drivers::TouchWheelData& data) + -> int8_t { + if (!data.is_wheel_touched) { + is_first_read_ = true; + return 0; + } + + uint8_t new_angle = data.wheel_position; + if (is_first_read_) { + is_first_read_ = false; + last_angle_ = new_angle; + return 0; + } + + int delta = 128 - last_angle_; + uint8_t rotated_angle = new_angle + delta; + if (rotated_angle < 128 - threshold_) { + last_angle_ = new_angle; + return 1; + } else if (rotated_angle > 128 + threshold_) { + last_angle_ = new_angle; + return -1; + } else { + return 0; + } +} + +} // namespace input diff --git a/src/input/input_volume_buttons.cpp b/src/input/input_volume_buttons.cpp new file mode 100644 index 00000000..bf79ed55 --- /dev/null +++ b/src/input/input_volume_buttons.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_volume_buttons.hpp" +#include "gpios.hpp" + +namespace input { + +VolumeButtons::VolumeButtons(drivers::IGpios& gpios) : gpios_(gpios) {} + +auto VolumeButtons::read(lv_indev_data_t* data) -> void { + bool vol_up = gpios_.Get(drivers::IGpios::Pin::kKeyUp); + if (!vol_up) { + ESP_LOGI("volume", "vol up"); + } + + bool vol_down = gpios_.Get(drivers::IGpios::Pin::kKeyDown); + if (!vol_down) { + ESP_LOGI("volume", "vol down"); + } +} + +} // namespace input diff --git a/src/input/lvgl_input_driver.cpp b/src/input/lvgl_input_driver.cpp new file mode 100644 index 00000000..70b6eb5d --- /dev/null +++ b/src/input/lvgl_input_driver.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "lvgl_input_driver.hpp" +#include + +#include +#include + +#include "feedback_haptics.hpp" +#include "input_touch_wheel.hpp" +#include "input_volume_buttons.hpp" +#include "lvgl.h" +#include "service_locator.hpp" + +[[maybe_unused]] static constexpr char kTag[] = "input"; + +namespace input { + +static void read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) { + LvglInputDriver* instance = + reinterpret_cast(drv->user_data); + instance->read(data); +} + +static void feedback_cb(lv_indev_drv_t* drv, uint8_t event) { + LvglInputDriver* instance = + reinterpret_cast(drv->user_data); + instance->feedback(event); +} + +LvglInputDriver::LvglInputDriver( + std::shared_ptr services) + : services_(services), + driver_(), + registration_(nullptr), + inputs_(), + feedbacks_(), + is_locked_(false) { + lv_indev_drv_init(&driver_); + driver_.type = LV_INDEV_TYPE_ENCODER; + driver_.read_cb = read_cb; + driver_.feedback_cb = feedback_cb; + driver_.user_data = this; + + registration_ = lv_indev_drv_register(&driver_); + + // TODO: Make these devices configurable. I'm thinking each device gets an id + // and then we have: + // - a factory to create instance given an id + // - add/remove device methods on LvglInputDriver that operate on ids + // - the user's enabled devices (+ their configuration) stored in NVS. + auto touchwheel = services_->touchwheel(); + if (touchwheel) { + inputs_.push_back(std::make_unique(**touchwheel)); + } + inputs_.push_back(std::make_unique(services_->gpios())); + feedbacks_.push_back(std::make_unique(services_->haptics())); +} + +auto LvglInputDriver::read(lv_indev_data_t* data) -> void { + // TODO: we should pass lock state on to the individual devices, since they + // may wish to either ignore the lock state, or power down until unlock. + if (is_locked_) { + return; + } + for (auto&& device : inputs_) { + device->read(data); + } +} + +auto LvglInputDriver::feedback(uint8_t event) -> void { + if (is_locked_) { + return; + } + for (auto&& device : feedbacks_) { + device->feedback(event); + } +} + +} // namespace input diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp index e0a0ac7a..f01afb3f 100644 --- a/src/system_fsm/include/system_fsm.hpp +++ b/src/system_fsm/include/system_fsm.hpp @@ -17,7 +17,6 @@ #include "display.hpp" #include "gpios.hpp" #include "nvs.hpp" -#include "relative_wheel.hpp" #include "samd.hpp" #include "service_locator.hpp" #include "storage.hpp" diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp index f502b49a..59d41c73 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -9,7 +9,6 @@ #include "driver/gpio.h" #include "event_queue.hpp" #include "gpios.hpp" -#include "relative_wheel.hpp" #include "service_locator.hpp" #include "system_events.hpp" #include "tag_parser.hpp" diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 6d45fc9f..3814e9d5 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" - "themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp" - "splash.c" "font_fusion_12.c" "font_fusion_10.c" + SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "themes.cpp" + "screen.cpp" "modal.cpp" "screen_lua.cpp" "splash.c" "font_fusion_12.c" + "font_fusion_10.c" INCLUDE_DIRS "include" - REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format") + REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format" "input") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/ui/encoder_input.cpp b/src/ui/encoder_input.cpp deleted file mode 100644 index 3b5af2c1..00000000 --- a/src/ui/encoder_input.cpp +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright 2023 jacqueline - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "encoder_input.hpp" - -#include -#include - -#include "lvgl.h" - -#include "audio_events.hpp" -#include "core/lv_event.h" -#include "core/lv_group.h" -#include "esp_timer.h" -#include "event_queue.hpp" -#include "gpios.hpp" -#include "hal/lv_hal_indev.h" -#include "nvs.hpp" -#include "relative_wheel.hpp" -#include "touchwheel.hpp" -#include "ui_events.hpp" - -[[maybe_unused]] static constexpr char kTag[] = "input"; - -constexpr int kDPadAngleThreshold = 10; -constexpr int kLongPressDelayMs = 500; -constexpr int kRepeatDelayMs = 250; - -static inline auto IsAngleWithin(int16_t wheel_angle, - int16_t target_angle, - int threshold) -> bool { - int16_t difference = (wheel_angle - target_angle + 127 + 255) % 255 - 127; - return difference <= threshold && difference >= -threshold; -} - -namespace ui { - -static void encoder_read(lv_indev_drv_t* drv, lv_indev_data_t* data) { - EncoderInput* instance = reinterpret_cast(drv->user_data); - instance->Read(data); -} - -EncoderInput::EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel) - : gpios_(gpios), - raw_wheel_(wheel), - relative_wheel_(std::make_unique(wheel)), - scroller_(std::make_unique()), - mode_(drivers::NvsStorage::InputModes::kRotatingWheel), - is_locked_(false), - scroll_sensitivity_(10), - is_scrolling_wheel_(false) { - lv_indev_drv_init(&driver_); - driver_.type = LV_INDEV_TYPE_ENCODER; - driver_.read_cb = encoder_read; - driver_.user_data = this; - - registration_ = lv_indev_drv_register(&driver_); -} - -auto EncoderInput::Read(lv_indev_data_t* data) -> void { - if (is_locked_) { - return; - } - - lv_obj_t* active_object = nullptr; - if (registration_ && registration_->group) { - active_object = lv_group_get_focused(registration_->group); - } - - raw_wheel_.Update(); - relative_wheel_->Update(); - // GPIO (for volume buttons) updating is handled by system_fsm. - - uint64_t now_ms = esp_timer_get_time() / 1000; - - // Deal with the potential overflow of our timer. - for (auto& it : touch_time_ms_) { - if (it.second > now_ms) { - // esp_timer overflowed. - it.second = 0; - } - } - - // Check each button. - UpdateKeyState(Keys::kVolumeUp, now_ms, - !gpios_.Get(drivers::IGpios::Pin::kKeyUp)); - UpdateKeyState(Keys::kVolumeDown, now_ms, - !gpios_.Get(drivers::IGpios::Pin::kKeyDown)); - - drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData(); - UpdateKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched); - UpdateKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched); - - UpdateKeyState( - Keys::kDirectionalUp, now_ms, - wheel_data.is_wheel_touched && - IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold)); - UpdateKeyState( - Keys::kDirectionalLeft, now_ms, - wheel_data.is_wheel_touched && - IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold)); - UpdateKeyState( - Keys::kDirectionalDown, now_ms, - wheel_data.is_wheel_touched && - IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold)); - UpdateKeyState( - Keys::kDirectionalRight, now_ms, - wheel_data.is_wheel_touched && - IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold)); - - // When the wheel is being scrolled, we want to ensure that other inputs - // involving the touchwheel don't trigger. This guards again two main issues: - // - hesitating when your thumb is on a cardinal direction, causing an - // unintentional long-press, - // - drifting from the outside of the wheel in a way that causes the centre - // key to be triggered. - if (is_scrolling_wheel_) { - UpdateKeyState(Keys::kTouchWheelCenter, now_ms, false); - UpdateKeyState(Keys::kDirectionalUp, now_ms, false); - UpdateKeyState(Keys::kDirectionalLeft, now_ms, false); - UpdateKeyState(Keys::kDirectionalDown, now_ms, false); - UpdateKeyState(Keys::kDirectionalRight, now_ms, false); - } - - // Now that we've determined the correct state for all keys, we can start - // mapping key states into actions, depending on the current control scheme. - if (mode_ == drivers::NvsStorage::InputModes::kButtonsOnly) { - Trigger trigger; - data->state = LV_INDEV_STATE_RELEASED; - - trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - data->enc_diff = -1; - break; - case Trigger::kLongPress: - events::Ui().Dispatch(internal::BackPressed{}); - break; - } - - trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kLongPress, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - data->enc_diff = 1; - break; - case Trigger::kLongPress: - data->state = LV_INDEV_STATE_PRESSED; - break; - } - } else if (mode_ == drivers::NvsStorage::InputModes::kDirectionalWheel) { - Trigger trigger; - trigger = TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); - data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED - : LV_INDEV_STATE_RELEASED; - - trigger = TriggerKey(Keys::kDirectionalUp, KeyStyle::kRepeat, now_ms); - if (trigger == Trigger::kClick) { - data->enc_diff = scroller_->AddInput(now_ms, -1); - } - - trigger = TriggerKey(Keys::kDirectionalDown, KeyStyle::kRepeat, now_ms); - if (trigger == Trigger::kClick) { - data->enc_diff = scroller_->AddInput(now_ms, 1); - } - - trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kRepeat, now_ms); - if (trigger == Trigger::kClick) { - events::Ui().Dispatch(internal::BackPressed{}); - } - - trigger = TriggerKey(Keys::kDirectionalRight, KeyStyle::kRepeat, now_ms); - if (trigger == Trigger::kClick) { - // TODO: ??? - } - - // Cancel scrolling if the touchpad is released. - if (!touch_time_ms_.contains(Keys::kDirectionalUp) && - !touch_time_ms_.contains(Keys::kDirectionalDown)) { - data->enc_diff = scroller_->AddInput(now_ms, 0); - } - - trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - events::Audio().Dispatch(audio::StepUpVolume{}); - break; - case Trigger::kLongPress: - break; - } - - trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - events::Audio().Dispatch(audio::StepDownVolume{}); - break; - case Trigger::kLongPress: - break; - } - } else if (mode_ == drivers::NvsStorage::InputModes::kRotatingWheel) { - if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) { - // User has released the wheel. - is_scrolling_wheel_ = false; - data->enc_diff = scroller_->AddInput(now_ms, 0); - } else if (relative_wheel_->ticks() != 0) { - // User is touching the wheel, and has just passed the sensitivity - // threshold for a scroll tick. - is_scrolling_wheel_ = true; - data->enc_diff = scroller_->AddInput(now_ms, relative_wheel_->ticks()); - } else { - // User is touching the wheel, but hasn't moved. - data->enc_diff = 0; - } - - Trigger trigger = - TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); - switch (trigger) { - case Trigger::kNone: - data->state = LV_INDEV_STATE_RELEASED; - break; - case Trigger::kClick: - data->state = LV_INDEV_STATE_PRESSED; - break; - case Trigger::kLongPress: - if (active_object) { - lv_event_send(active_object, LV_EVENT_LONG_PRESSED, NULL); - } - break; - } - - trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - events::Audio().Dispatch(audio::StepUpVolume{}); - break; - case Trigger::kLongPress: - break; - } - - trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - events::Audio().Dispatch(audio::StepDownVolume{}); - break; - case Trigger::kLongPress: - break; - } - - trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kLongPress, now_ms); - switch (trigger) { - case Trigger::kNone: - break; - case Trigger::kClick: - break; - case Trigger::kLongPress: - events::Ui().Dispatch(internal::BackPressed{}); - break; - } - } -} - -auto EncoderInput::scroll_sensitivity(uint8_t val) -> void { - scroll_sensitivity_ = val; - relative_wheel_->SetSensitivity(scroll_sensitivity_); -} - -auto EncoderInput::UpdateKeyState(Keys key, uint64_t ms, bool clicked) -> void { - if (clicked) { - if (!touch_time_ms_.contains(key)) { - // Key was just clicked. - touch_time_ms_[key] = ms; - just_released_.erase(key); - fired_.erase(key); - } - return; - } - - // Key is not clicked. - if (touch_time_ms_.contains(key)) { - // Key was just released. - just_released_.insert(key); - touch_time_ms_.erase(key); - } -} - -auto EncoderInput::TriggerKey(Keys key, KeyStyle s, uint64_t ms) -> Trigger { - if (s == KeyStyle::kRepeat) { - bool may_repeat = fired_.contains(key) && touch_time_ms_.contains(key) && - ms - touch_time_ms_[key] >= kRepeatDelayMs; - - // Repeatable keys trigger on press. - if (touch_time_ms_.contains(key) && (!fired_.contains(key) || may_repeat)) { - fired_.insert(key); - return Trigger::kClick; - } else { - return Trigger::kNone; - } - } else if (s == KeyStyle::kLongPress) { - // Long press keys trigger on release, or after holding for a delay. - if (just_released_.contains(key)) { - just_released_.erase(key); - if (!fired_.contains(key)) { - fired_.insert(key); - return Trigger::kClick; - } - } - if (touch_time_ms_.contains(key) && - ms - touch_time_ms_[key] >= kLongPressDelayMs && - !fired_.contains(key)) { - fired_.insert(key); - return Trigger::kLongPress; - } - } - - return Trigger::kNone; -} - -auto Scroller::AddInput(uint64_t ms, int direction) -> int { - bool dir_changed = - ((velocity_ < 0 && direction > 0) || (velocity_ > 0 && direction < 0)); - if (direction == 0 || dir_changed) { - last_input_ms_ = ms; - velocity_ = 0; - return 0; - } - // Decay with time - if (last_input_ms_ > ms) { - last_input_ms_ = 0; - } - uint diff = ms - last_input_ms_; - uint diff_steps = diff / 25; - last_input_ms_ = ms + (last_input_ms_ % 50); - // Use powers of two for our exponential decay so we can implement decay - // trivially via bit shifting. - velocity_ >>= diff_steps; - - velocity_ += direction * 1000; - if (velocity_ > 0) { - return (velocity_ + 500) / 1000; - } else { - return (velocity_ - 500) / 1000; - } -} -} // namespace ui diff --git a/src/ui/include/encoder_input.hpp b/src/ui/include/encoder_input.hpp deleted file mode 100644 index 7dfac071..00000000 --- a/src/ui/include/encoder_input.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 jacqueline - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include -#include -#include - -#include "core/lv_group.h" -#include "gpios.hpp" -#include "hal/lv_hal_indev.h" - -#include "nvs.hpp" -#include "relative_wheel.hpp" -#include "touchwheel.hpp" - -namespace ui { - -class Scroller; - -/* - * Main input device abstracting that handles turning lower-level input device - * drivers into events and LVGL inputs. - * - * As far as LVGL is concerned, this class represents an ordinary rotary - * encoder, supporting only left and right ticks, and clicking. - */ -class EncoderInput { - public: - EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel); - - auto Read(lv_indev_data_t* data) -> void; - auto registration() -> lv_indev_t* { return registration_; } - - auto mode(drivers::NvsStorage::InputModes mode) { mode_ = mode; } - auto scroll_sensitivity(uint8_t val) -> void; // Value between 0-255, used to scale the threshold - auto lock(bool l) -> void { is_locked_ = l; } - - private: - lv_indev_drv_t driver_; - lv_indev_t* registration_; - - drivers::IGpios& gpios_; - drivers::TouchWheel& raw_wheel_; - std::unique_ptr relative_wheel_; - std::unique_ptr scroller_; - - drivers::NvsStorage::InputModes mode_; - bool is_locked_; - uint8_t scroll_sensitivity_; - - // Every kind of distinct input that we could map to an action. - enum class Keys { - kVolumeUp, - kVolumeDown, - kTouchWheel, - kTouchWheelCenter, - kDirectionalUp, - kDirectionalRight, - kDirectionalDown, - kDirectionalLeft, - }; - - // Map from a Key, to the time that it was first touched in ms. If the key is - // currently released, where will be no entry. - std::unordered_map touch_time_ms_; - // Set of keys that were released during the current update. - std::set just_released_; - // Set of keys that have had an event fired for them since being pressed. - std::set fired_; - - bool is_scrolling_wheel_; - - enum class Trigger { - kNone, - // Regular short-click. Triggered on release for long-pressable keys, - // triggered on the initial press for repeatable keys. - kClick, - kLongPress, - }; - - enum class KeyStyle { - kRepeat, - kLongPress, - }; - - auto UpdateKeyState(Keys key, uint64_t ms, bool clicked) -> void; - auto TriggerKey(Keys key, KeyStyle t, uint64_t ms) -> Trigger; -}; - -class Scroller { - public: - Scroller() : last_input_ms_(0), velocity_(0) {} - - auto AddInput(uint64_t, int) -> int; - - private: - uint64_t last_input_ms_; - int velocity_; -}; - -} // namespace ui diff --git a/src/ui/include/lvgl_task.hpp b/src/ui/include/lvgl_task.hpp index f212ab9d..8efcbf35 100644 --- a/src/ui/include/lvgl_task.hpp +++ b/src/ui/include/lvgl_task.hpp @@ -15,8 +15,7 @@ #include "freertos/timers.h" #include "display.hpp" -#include "encoder_input.hpp" -#include "relative_wheel.hpp" +#include "lvgl_input_driver.hpp" #include "screen.hpp" #include "themes.hpp" #include "touchwheel.hpp" @@ -28,14 +27,14 @@ class UiTask { static auto Start() -> UiTask*; ~UiTask(); - auto input(std::shared_ptr input) -> void; + auto input(std::shared_ptr input) -> void; private: UiTask(); auto Main() -> void; - std::shared_ptr input_; + std::shared_ptr input_; std::shared_ptr current_screen_; }; diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 2bab487d..c238a447 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -14,14 +14,13 @@ #include "battery.hpp" #include "db_events.hpp" #include "display.hpp" -#include "encoder_input.hpp" #include "gpios.hpp" #include "lua_thread.hpp" +#include "lvgl_input_driver.hpp" #include "lvgl_task.hpp" #include "modal.hpp" #include "nvs.hpp" #include "property.hpp" -#include "relative_wheel.hpp" #include "screen.hpp" #include "service_locator.hpp" #include "storage.hpp" @@ -92,7 +91,7 @@ class UiState : public tinyfsm::Fsm { static std::unique_ptr sTask; static std::shared_ptr sServices; static std::unique_ptr sDisplay; - static std::shared_ptr sInput; + static std::shared_ptr sInput; static std::stack> sScreens; static std::shared_ptr sCurrentScreen; diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp index f0184766..4cf25c15 100644 --- a/src/ui/lvgl_task.cpp +++ b/src/ui/lvgl_task.cpp @@ -33,11 +33,11 @@ #include "lua.h" #include "lv_api_map.h" #include "lvgl/lvgl.h" +#include "lvgl_input_driver.hpp" #include "misc/lv_color.h" #include "misc/lv_style.h" #include "misc/lv_timer.h" #include "modal.hpp" -#include "relative_wheel.hpp" #include "tasks.hpp" #include "touchwheel.hpp" #include "ui_fsm.hpp" @@ -50,8 +50,6 @@ namespace ui { [[maybe_unused]] static const char* kTag = "ui_task"; -static auto group_focus_cb(lv_group_t *group) -> void; - UiTask::UiTask() {} UiTask::~UiTask() { @@ -78,7 +76,6 @@ auto UiTask::Main() -> void { if (input_ && current_screen_->group() != current_group) { current_group = current_screen_->group(); lv_indev_set_group(input_->registration(), current_group); - lv_group_set_focus_cb(current_group, &group_focus_cb); } TickType_t delay = lv_timer_handler(); @@ -86,10 +83,9 @@ auto UiTask::Main() -> void { } } -auto UiTask::input(std::shared_ptr input) -> void { +auto UiTask::input(std::shared_ptr input) -> void { assert(current_screen_); input_ = input; - lv_indev_set_group(input_->registration(), current_screen_->group()); } auto UiTask::Start() -> UiTask* { @@ -98,11 +94,4 @@ auto UiTask::Start() -> UiTask* { return ret; } -static auto group_focus_cb(lv_group_t *group) -> void { - // TODO(robin): we probably want to vary this, configure this, etc - events::System().Dispatch(system_fsm::HapticTrigger{ - .effect = drivers::Haptics::Effect::kMediumClick1_100Pct, - }); -} - } // namespace ui diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 28733123..a9c1d3a3 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -30,19 +30,18 @@ #include "lauxlib.h" #include "lua_thread.hpp" #include "luavgl.h" +#include "lvgl_input_driver.hpp" #include "memory_resource.hpp" #include "misc/lv_gc.h" #include "audio_events.hpp" #include "display.hpp" -#include "encoder_input.hpp" #include "event_queue.hpp" #include "gpios.hpp" #include "lua_registry.hpp" #include "lvgl_task.hpp" #include "nvs.hpp" #include "property.hpp" -#include "relative_wheel.hpp" #include "samd.hpp" #include "screen.hpp" #include "screen_lua.hpp" @@ -63,7 +62,7 @@ namespace ui { std::unique_ptr UiState::sTask; std::shared_ptr UiState::sServices; std::unique_ptr UiState::sDisplay; -std::shared_ptr UiState::sInput; +std::shared_ptr UiState::sInput; std::stack> UiState::sScreens; std::shared_ptr UiState::sCurrentScreen; @@ -234,51 +233,58 @@ lua::Property UiState::sDisplayBrightness{ return true; }}; -lua::Property UiState::sControlsScheme{ - 0, [](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - drivers::NvsStorage::InputModes mode; - switch (std::get(val)) { - case 0: - mode = drivers::NvsStorage::InputModes::kButtonsOnly; - break; - case 1: - mode = drivers::NvsStorage::InputModes::kButtonsWithWheel; - break; - case 2: - mode = drivers::NvsStorage::InputModes::kDirectionalWheel; - break; - case 3: - mode = drivers::NvsStorage::InputModes::kRotatingWheel; - break; - default: - return false; - } - sServices->nvs().PrimaryInput(mode); - sInput->mode(mode); - return true; - }}; - -lua::Property UiState::sScrollSensitivity{ - 0, [](const lua::LuaValue& val) { - std::optional sensitivity = 0; - std::visit( - [&](auto&& v) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - sensitivity = v; - } - }, - val); - if (!sensitivity) { - return false; - } - sInput->scroll_sensitivity(*sensitivity); - sServices->nvs().ScrollSensitivity(*sensitivity); - return true; - }}; +lua::Property UiState::sControlsScheme{0, [](const lua::LuaValue& val) { + /* + if (!std::holds_alternative(val)) + { return false; + } + drivers::NvsStorage::InputModes mode; + switch (std::get(val)) { + case 0: + mode = + drivers::NvsStorage::InputModes::kButtonsOnly; + break; + case 1: + mode = + drivers::NvsStorage::InputModes::kButtonsWithWheel; + break; + case 2: + mode = + drivers::NvsStorage::InputModes::kDirectionalWheel; + break; + case 3: + mode = + drivers::NvsStorage::InputModes::kRotatingWheel; + break; + default: + return false; + } + sServices->nvs().PrimaryInput(mode); + sInput->mode(mode); + */ + return true; + }}; + +lua::Property UiState::sScrollSensitivity{0, [](const lua::LuaValue& val) { + /* + std::optional sensitivity = 0; + std::visit( + [&](auto&& v) { + using T = + std::decay_t; if + constexpr (std::is_same_v) { + sensitivity = v; + } + }, + val); + if (!sensitivity) { + return false; + } + sInput->scroll_sensitivity(*sensitivity); + sServices->nvs().ScrollSensitivity(*sensitivity); + */ + return true; + }}; lua::Property UiState::sLockSwitch{false}; @@ -372,7 +378,7 @@ void UiState::react(const internal::ControlSchemeChanged&) { if (!sInput) { return; } - sInput->mode(sServices->nvs().PrimaryInput()); + // sInput->mode(sServices->nvs().PrimaryInput()); } void UiState::react(const database::event::UpdateStarted&) { @@ -478,22 +484,8 @@ void Splash::react(const system_fsm::BootComplete& ev) { sDisplayBrightness.Update(brightness); sDisplay->SetBrightness(brightness); - auto touchwheel = sServices->touchwheel(); - if (touchwheel) { - sInput = std::make_shared(sServices->gpios(), **touchwheel); - - auto mode = sServices->nvs().PrimaryInput(); - sInput->mode(mode); - sControlsScheme.Update(static_cast(mode)); - - auto sensitivity = sServices->nvs().ScrollSensitivity(); - sInput->scroll_sensitivity(sensitivity); - sScrollSensitivity.Update(static_cast(sensitivity)); - - sTask->input(sInput); - } else { - ESP_LOGE(kTag, "no input devices initialised!"); - } + sInput = std::make_shared(sServices); + sTask->input(sInput); } void Splash::react(const system_fsm::StorageMounted&) { From 33919e9e3f419e13318fa6b8217d8c8dcd86c1eb Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 11 Apr 2024 15:16:35 +1000 Subject: [PATCH 2/2] Migrate all existing control schemes to the cool new world --- src/drivers/include/touchwheel.hpp | 4 + src/drivers/touchwheel.cpp | 7 ++ src/input/CMakeLists.txt | 3 +- src/input/device_factory.cpp | 58 ++++++++++++++ src/input/include/device_factory.hpp | 39 ++++++++++ src/input/include/input_device.hpp | 6 ++ src/input/include/input_nav_buttons.hpp | 34 +++++++++ src/input/include/input_touch_dpad.hpp | 6 ++ src/input/include/input_touch_wheel.hpp | 20 ++++- src/input/include/input_trigger.hpp | 37 +++++++++ src/input/include/input_volume_buttons.hpp | 4 + src/input/include/lvgl_input_driver.hpp | 17 +++-- src/input/input_nav_buttons.cpp | 44 +++++++++++ src/input/input_touch_dpad.cpp | 42 ++++++++++- src/input/input_touch_wheel.cpp | 76 +++++++++++++++++-- src/input/input_trigger.cpp | 72 ++++++++++++++++++ src/input/input_volume_buttons.cpp | 17 ++++- src/input/lvgl_input_driver.cpp | 59 ++++++++++----- src/ui/include/ui_events.hpp | 1 - src/ui/include/ui_fsm.hpp | 9 ++- src/ui/ui_fsm.cpp | 88 ++++++---------------- 21 files changed, 532 insertions(+), 111 deletions(-) create mode 100644 src/input/device_factory.cpp create mode 100644 src/input/include/device_factory.hpp create mode 100644 src/input/include/input_nav_buttons.hpp create mode 100644 src/input/include/input_trigger.hpp create mode 100644 src/input/input_nav_buttons.cpp create mode 100644 src/input/input_trigger.cpp diff --git a/src/drivers/include/touchwheel.hpp b/src/drivers/include/touchwheel.hpp index 9d002156..18ace2b4 100644 --- a/src/drivers/include/touchwheel.hpp +++ b/src/drivers/include/touchwheel.hpp @@ -24,6 +24,10 @@ struct TouchWheelData { class TouchWheel { public: + static auto isAngleWithin(int16_t wheel_angle, + int16_t target_angle, + int threshold) -> bool; + static auto Create() -> TouchWheel* { return new TouchWheel(); } TouchWheel(); ~TouchWheel(); diff --git a/src/drivers/touchwheel.cpp b/src/drivers/touchwheel.cpp index a20f434b..41b9a6af 100644 --- a/src/drivers/touchwheel.cpp +++ b/src/drivers/touchwheel.cpp @@ -28,6 +28,13 @@ namespace drivers { static const uint8_t kTouchWheelAddress = 0x1C; static const gpio_num_t kIntPin = GPIO_NUM_25; +auto TouchWheel::isAngleWithin(int16_t wheel_angle, + int16_t target_angle, + int threshold) -> bool { + int16_t difference = (wheel_angle - target_angle + 127 + 255) % 255 - 127; + return difference <= threshold && difference >= -threshold; +} + TouchWheel::TouchWheel() { gpio_config_t int_config{ .pin_bit_mask = 1ULL << kIntPin, diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 92ed3d6c..3bc3e39d 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -3,8 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "input_touch_wheel.cpp" "input_touch_dpad.cpp" + SRCS "input_touch_wheel.cpp" "input_touch_dpad.cpp" "input_trigger.cpp" "input_volume_buttons.cpp" "lvgl_input_driver.cpp" "feedback_haptics.cpp" + "device_factory.cpp" "input_nav_buttons.cpp" INCLUDE_DIRS "include" REQUIRES "drivers" "lvgl" "events" "system_fsm") diff --git a/src/input/device_factory.cpp b/src/input/device_factory.cpp new file mode 100644 index 00000000..65f4d785 --- /dev/null +++ b/src/input/device_factory.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "device_factory.hpp" + +#include + +#include "feedback_haptics.hpp" +#include "input_device.hpp" +#include "input_nav_buttons.hpp" +#include "input_touch_dpad.hpp" +#include "input_touch_wheel.hpp" +#include "input_volume_buttons.hpp" + +namespace input { + +DeviceFactory::DeviceFactory( + std::shared_ptr services) + : services_(services) { + if (services->touchwheel()) { + wheel_ = + std::make_shared(services->nvs(), **services->touchwheel()); + } +} + +auto DeviceFactory::createInputs(drivers::NvsStorage::InputModes mode) + -> std::vector> { + std::vector> ret; + switch (mode) { + case drivers::NvsStorage::InputModes::kButtonsOnly: + ret.push_back(std::make_shared(services_->gpios())); + break; + case drivers::NvsStorage::InputModes::kDirectionalWheel: + ret.push_back(std::make_shared(services_->gpios())); + if (services_->touchwheel()) { + ret.push_back(std::make_shared(**services_->touchwheel())); + } + break; + case drivers::NvsStorage::InputModes::kRotatingWheel: + default: // Don't break input over a bad enum value. + ret.push_back(std::make_shared(services_->gpios())); + if (wheel_) { + ret.push_back(wheel_); + } + break; + } + return ret; +} + +auto DeviceFactory::createFeedbacks() + -> std::vector> { + return {std::make_shared(services_->haptics())}; +} + +} // namespace input diff --git a/src/input/include/device_factory.hpp b/src/input/include/device_factory.hpp new file mode 100644 index 00000000..dd9c7133 --- /dev/null +++ b/src/input/include/device_factory.hpp @@ -0,0 +1,39 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "feedback_device.hpp" +#include "input_device.hpp" +#include "input_touch_wheel.hpp" +#include "nvs.hpp" +#include "service_locator.hpp" + +namespace input { + +class DeviceFactory { + public: + DeviceFactory(std::shared_ptr); + + auto createInputs(drivers::NvsStorage::InputModes mode) + -> std::vector>; + + auto createFeedbacks() -> std::vector>; + + auto touch_wheel() -> std::shared_ptr { return wheel_; } + + private: + std::shared_ptr services_; + + // HACK: the touchwheel is current a special case, since it's the only input + // device that has some kind of setting/configuration; scroll sensitivity. + std::shared_ptr wheel_; +}; + +} // namespace input diff --git a/src/input/include/input_device.hpp b/src/input/include/input_device.hpp index 09cf9193..5fc3e066 100644 --- a/src/input/include/input_device.hpp +++ b/src/input/include/input_device.hpp @@ -11,6 +11,7 @@ #include #include "hal/lv_hal_indev.h" +#include "property.hpp" namespace input { @@ -28,6 +29,11 @@ class IInputDevice { // TODO: Add hooks and configuration (or are hooks just one kind of // configuration?) + + virtual auto settings() + -> std::vector> { + return {}; + } }; } // namespace input diff --git a/src/input/include/input_nav_buttons.hpp b/src/input/include/input_nav_buttons.hpp new file mode 100644 index 00000000..29a19a16 --- /dev/null +++ b/src/input/include/input_nav_buttons.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "gpios.hpp" +#include "hal/lv_hal_indev.h" + +#include "haptics.hpp" +#include "input_device.hpp" +#include "input_trigger.hpp" +#include "touchwheel.hpp" + +namespace input { + +class NavButtons : public IInputDevice { + public: + NavButtons(drivers::IGpios&); + + auto read(lv_indev_data_t* data) -> void override; + + private: + drivers::IGpios& gpios_; + + Trigger up_; + Trigger down_; +}; + +} // namespace input diff --git a/src/input/include/input_touch_dpad.hpp b/src/input/include/input_touch_dpad.hpp index fdb52db9..03936acb 100644 --- a/src/input/include/input_touch_dpad.hpp +++ b/src/input/include/input_touch_dpad.hpp @@ -13,6 +13,7 @@ #include "haptics.hpp" #include "input_device.hpp" +#include "input_trigger.hpp" #include "touchwheel.hpp" namespace input { @@ -25,6 +26,11 @@ class TouchDPad : public IInputDevice { private: drivers::TouchWheel& wheel_; + + Trigger up_; + Trigger right_; + Trigger down_; + Trigger left_; }; } // namespace input diff --git a/src/input/include/input_touch_wheel.hpp b/src/input/include/input_touch_wheel.hpp index a4923f21..c81cbb1a 100644 --- a/src/input/include/input_touch_wheel.hpp +++ b/src/input/include/input_touch_wheel.hpp @@ -6,29 +6,43 @@ #pragma once +#include #include #include "hal/lv_hal_indev.h" #include "haptics.hpp" #include "input_device.hpp" +#include "input_trigger.hpp" +#include "nvs.hpp" +#include "property.hpp" #include "touchwheel.hpp" namespace input { class TouchWheel : public IInputDevice { public: - TouchWheel(drivers::TouchWheel&); + TouchWheel(drivers::NvsStorage&, drivers::TouchWheel&); auto read(lv_indev_data_t* data) -> void override; + auto sensitivity() -> lua::Property&; + private: - auto calculate_ticks(const drivers::TouchWheelData& data) -> int8_t; + auto calculateTicks(const drivers::TouchWheelData& data) -> int8_t; + auto calculateThreshold(uint8_t sensitivity) -> uint8_t; + drivers::NvsStorage& nvs_; drivers::TouchWheel& wheel_; + lua::Property sensitivity_; + + Trigger up_; + Trigger right_; + Trigger down_; + Trigger left_; + bool is_scrolling_; - uint8_t sensitivity_; uint8_t threshold_; bool is_first_read_; uint8_t last_angle_; diff --git a/src/input/include/input_trigger.hpp b/src/input/include/input_trigger.hpp new file mode 100644 index 00000000..599b796b --- /dev/null +++ b/src/input/include/input_trigger.hpp @@ -0,0 +1,37 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "hal/lv_hal_indev.h" + +namespace input { + +const uint16_t kLongPressDelayMs = LV_INDEV_DEF_LONG_PRESS_TIME; +const uint16_t kRepeatDelayMs = LV_INDEV_DEF_LONG_PRESS_REP_TIME; + +class Trigger { + public: + enum class State { + kNone, + kClick, + kLongPress, + kRepeatPress, + }; + + Trigger(); + + auto update(bool is_pressed) -> State; + + private: + std::optional touch_time_ms_; + uint16_t times_fired_; +}; + +} // namespace input diff --git a/src/input/include/input_volume_buttons.hpp b/src/input/include/input_volume_buttons.hpp index 4ca8c16b..162ca8d9 100644 --- a/src/input/include/input_volume_buttons.hpp +++ b/src/input/include/input_volume_buttons.hpp @@ -13,6 +13,7 @@ #include "haptics.hpp" #include "input_device.hpp" +#include "input_trigger.hpp" #include "touchwheel.hpp" namespace input { @@ -25,6 +26,9 @@ class VolumeButtons : public IInputDevice { private: drivers::IGpios& gpios_; + + Trigger up_; + Trigger down_; }; } // namespace input diff --git a/src/input/include/lvgl_input_driver.hpp b/src/input/include/lvgl_input_driver.hpp index be452368..257ccb28 100644 --- a/src/input/include/lvgl_input_driver.hpp +++ b/src/input/include/lvgl_input_driver.hpp @@ -6,19 +6,20 @@ #pragma once -#include +#include #include #include #include #include "core/lv_group.h" +#include "device_factory.hpp" #include "feedback_device.hpp" #include "gpios.hpp" #include "hal/lv_hal_indev.h" #include "input_device.hpp" #include "nvs.hpp" -#include "service_locator.hpp" +#include "property.hpp" #include "touchwheel.hpp" namespace input { @@ -30,7 +31,9 @@ namespace input { */ class LvglInputDriver { public: - LvglInputDriver(std::shared_ptr); + LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); + + auto mode() -> lua::Property& { return mode_; } auto read(lv_indev_data_t* data) -> void; auto feedback(uint8_t) -> void; @@ -39,13 +42,15 @@ class LvglInputDriver { auto lock(bool l) -> void { is_locked_ = l; } private: - std::shared_ptr services_; + drivers::NvsStorage& nvs_; + DeviceFactory& factory_; + lua::Property mode_; lv_indev_drv_t driver_; lv_indev_t* registration_; - std::vector> inputs_; - std::vector> feedbacks_; + std::vector> inputs_; + std::vector> feedbacks_; bool is_locked_; }; diff --git a/src/input/input_nav_buttons.cpp b/src/input/input_nav_buttons.cpp new file mode 100644 index 00000000..d83568c8 --- /dev/null +++ b/src/input/input_nav_buttons.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_nav_buttons.hpp" + +#include "event_queue.hpp" +#include "gpios.hpp" +#include "hal/lv_hal_indev.h" + +namespace input { + +NavButtons::NavButtons(drivers::IGpios& gpios) : gpios_(gpios) {} + +auto NavButtons::read(lv_indev_data_t* data) -> void { + bool vol_up = gpios_.Get(drivers::IGpios::Pin::kKeyUp); + switch (up_.update(!vol_up)) { + case Trigger::State::kClick: + data->enc_diff = -1; + break; + case Trigger::State::kLongPress: + events::Ui().Dispatch(ui::internal::BackPressed{}); + break; + default: + break; + } + + bool vol_down = gpios_.Get(drivers::IGpios::Pin::kKeyDown); + switch (down_.update(!vol_down)) { + case Trigger::State::kClick: + data->enc_diff = 1; + break; + case Trigger::State::kLongPress: + data->state = LV_INDEV_STATE_PRESSED; + break; + default: + data->state = LV_INDEV_STATE_RELEASED; + break; + } +} + +} // namespace input diff --git a/src/input/input_touch_dpad.cpp b/src/input/input_touch_dpad.cpp index a5774ef6..828d6b59 100644 --- a/src/input/input_touch_dpad.cpp +++ b/src/input/input_touch_dpad.cpp @@ -10,6 +10,7 @@ #include "hal/lv_hal_indev.h" +#include "event_queue.hpp" #include "haptics.hpp" #include "input_device.hpp" #include "input_touch_dpad.hpp" @@ -28,8 +29,47 @@ TouchDPad::TouchDPad(drivers::TouchWheel& wheel) : wheel_(wheel) {} auto TouchDPad::read(lv_indev_data_t* data) -> void { wheel_.Update(); + auto wheel_data = wheel_.GetTouchWheelData(); - // TODO: reimplement + if (wheel_data.is_button_touched) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + + switch (up_.update( + wheel_data.is_wheel_touched && + drivers::TouchWheel::isAngleWithin(wheel_data.wheel_position, 0, 32))) { + case Trigger::State::kNone: + break; + default: + data->enc_diff = -1; + break; + } + switch (right_.update( + wheel_data.is_wheel_touched && + drivers::TouchWheel::isAngleWithin(wheel_data.wheel_position, 192, 32))) { + default: + break; + } + switch (down_.update( + wheel_data.is_wheel_touched && + drivers::TouchWheel::isAngleWithin(wheel_data.wheel_position, 128, 32))) { + case Trigger::State::kNone: + break; + default: + data->enc_diff = 1; + break; + } + switch (left_.update( + wheel_data.is_wheel_touched && + drivers::TouchWheel::isAngleWithin(wheel_data.wheel_position, 64, 32))) { + case Trigger::State::kLongPress: + events::Ui().Dispatch(ui::internal::BackPressed{}); + break; + default: + break; + } } } // namespace input diff --git a/src/input/input_touch_wheel.cpp b/src/input/input_touch_wheel.cpp index 302a17aa..7670e342 100644 --- a/src/input/input_touch_wheel.cpp +++ b/src/input/input_touch_wheel.cpp @@ -8,27 +8,46 @@ #include #include +#include +#include "event_queue.hpp" #include "hal/lv_hal_indev.h" #include "haptics.hpp" #include "input_device.hpp" +#include "input_trigger.hpp" +#include "nvs.hpp" +#include "property.hpp" #include "touchwheel.hpp" +#include "ui_events.hpp" namespace input { -TouchWheel::TouchWheel(drivers::TouchWheel& wheel) - : wheel_(wheel), +TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel) + : nvs_(nvs), + wheel_(wheel), + sensitivity_(static_cast(nvs.ScrollSensitivity()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + int int_val = std::get(val); + if (int_val < 0 || int_val > UINT8_MAX) { + return false; + } + nvs.ScrollSensitivity(int_val); + threshold_ = calculateThreshold(int_val); + return true; + }), is_scrolling_(false), - sensitivity_(128), - threshold_(10), + threshold_(calculateThreshold(nvs.ScrollSensitivity())), is_first_read_(true), last_angle_(0) {} auto TouchWheel::read(lv_indev_data_t* data) -> void { wheel_.Update(); auto wheel_data = wheel_.GetTouchWheelData(); - int8_t ticks = calculate_ticks(wheel_data); + int8_t ticks = calculateTicks(wheel_data); if (!wheel_data.is_wheel_touched) { // User has released the wheel. @@ -49,10 +68,47 @@ auto TouchWheel::read(lv_indev_data_t* data) -> void { } else { data->state = LV_INDEV_STATE_RELEASED; } + + // If the user is touching the wheel but not scrolling, then they may be + // clicking on one of the wheel's cardinal directions. + bool pressing = wheel_data.is_wheel_touched && !is_scrolling_; + + switch (up_.update(pressing && drivers::TouchWheel::isAngleWithin( + wheel_data.wheel_position, 0, 32))) { + case Trigger::State::kLongPress: + data->enc_diff = INT16_MIN; + break; + default: + break; + } + switch (right_.update(pressing && drivers::TouchWheel::isAngleWithin( + wheel_data.wheel_position, 192, 32))) { + default: + break; + } + switch (down_.update(pressing && drivers::TouchWheel::isAngleWithin( + wheel_data.wheel_position, 128, 32))) { + case Trigger::State::kLongPress: + data->enc_diff = INT16_MAX; + break; + default: + break; + } + switch (left_.update(pressing && drivers::TouchWheel::isAngleWithin( + wheel_data.wheel_position, 64, 32))) { + case Trigger::State::kLongPress: + events::Ui().Dispatch(ui::internal::BackPressed{}); + break; + default: + break; + } +} + +auto TouchWheel::sensitivity() -> lua::Property& { + return sensitivity_; } -auto TouchWheel::calculate_ticks(const drivers::TouchWheelData& data) - -> int8_t { +auto TouchWheel::calculateTicks(const drivers::TouchWheelData& data) -> int8_t { if (!data.is_wheel_touched) { is_first_read_ = true; return 0; @@ -78,4 +134,10 @@ auto TouchWheel::calculate_ticks(const drivers::TouchWheelData& data) } } +auto TouchWheel::calculateThreshold(uint8_t sensitivity) -> uint8_t { + int tmax = 35; + int tmin = 5; + return (((255. - sensitivity) / 255.) * (tmax - tmin) + tmin); +} + } // namespace input diff --git a/src/input/input_trigger.cpp b/src/input/input_trigger.cpp new file mode 100644 index 00000000..9485ecb4 --- /dev/null +++ b/src/input/input_trigger.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_trigger.hpp" +#include + +#include +#include "esp_log.h" +#include "esp_timer.h" + +namespace input { + +Trigger::Trigger() : touch_time_ms_(), times_fired_(0) {} + +auto Trigger::update(bool is_pressed) -> State { + // Bail out early if we're in a steady-state of not pressed. + if (!is_pressed && !touch_time_ms_) { + return State::kNone; + } + + uint64_t now_ms = esp_timer_get_time() / 1000; + + // Initial press of this key: record the current time, and report that we + // haven't triggered yet. + if (is_pressed && !touch_time_ms_) { + touch_time_ms_ = now_ms; + times_fired_ = 0; + return State::kNone; + } + + // The key was released. If there were no long-press events fired during the + // press, then this was a standard click. + if (!is_pressed && touch_time_ms_) { + touch_time_ms_.reset(); + if (times_fired_ == 0) { + return State::kClick; + } else { + return State::kNone; + } + } + + // Now the more complicated case: the user is continuing to press the button. + if (times_fired_ == 0) { + // We haven't fired yet, so we wait for the long-press event. + if (now_ms - *touch_time_ms_ >= kLongPressDelayMs) { + times_fired_++; + return State::kLongPress; + } + } else { + // We've already fired at least once. How long has the user been holding + // the key for? + uint64_t time_since_long_press = + now_ms - (*touch_time_ms_ + kLongPressDelayMs); + + // How many times should we have fired? + // 1 initial fire (for the long-press), plus one additional fire every + // kRepeatDelayMs since the long-press event. + uint16_t expected_times_fired = + 1 + (time_since_long_press / kRepeatDelayMs); + if (times_fired_ < expected_times_fired) { + times_fired_++; + return State::kRepeatPress; + } + } + + return State::kNone; +} + +} // namespace input diff --git a/src/input/input_volume_buttons.cpp b/src/input/input_volume_buttons.cpp index bf79ed55..0413222c 100644 --- a/src/input/input_volume_buttons.cpp +++ b/src/input/input_volume_buttons.cpp @@ -5,6 +5,7 @@ */ #include "input_volume_buttons.hpp" +#include "event_queue.hpp" #include "gpios.hpp" namespace input { @@ -13,13 +14,21 @@ VolumeButtons::VolumeButtons(drivers::IGpios& gpios) : gpios_(gpios) {} auto VolumeButtons::read(lv_indev_data_t* data) -> void { bool vol_up = gpios_.Get(drivers::IGpios::Pin::kKeyUp); - if (!vol_up) { - ESP_LOGI("volume", "vol up"); + switch (up_.update(!vol_up)) { + case Trigger::State::kNone: + break; + default: + events::Audio().Dispatch(audio::StepUpVolume{}); + break; } bool vol_down = gpios_.Get(drivers::IGpios::Pin::kKeyDown); - if (!vol_down) { - ESP_LOGI("volume", "vol down"); + switch (down_.update(!vol_down)) { + case Trigger::State::kNone: + break; + default: + events::Audio().Dispatch(audio::StepDownVolume{}); + break; } } diff --git a/src/input/lvgl_input_driver.cpp b/src/input/lvgl_input_driver.cpp index 70b6eb5d..9698aa79 100644 --- a/src/input/lvgl_input_driver.cpp +++ b/src/input/lvgl_input_driver.cpp @@ -9,12 +9,16 @@ #include #include +#include +#include "device_factory.hpp" #include "feedback_haptics.hpp" #include "input_touch_wheel.hpp" +#include "input_trigger.hpp" #include "input_volume_buttons.hpp" #include "lvgl.h" -#include "service_locator.hpp" +#include "nvs.hpp" +#include "property.hpp" [[maybe_unused]] static constexpr char kTag[] = "input"; @@ -32,33 +36,52 @@ static void feedback_cb(lv_indev_drv_t* drv, uint8_t event) { instance->feedback(event); } -LvglInputDriver::LvglInputDriver( - std::shared_ptr services) - : services_(services), +auto intToMode(int raw) -> std::optional { + switch (raw) { + case 0: + return drivers::NvsStorage::InputModes::kButtonsOnly; + case 1: + return drivers::NvsStorage::InputModes::kButtonsWithWheel; + case 2: + return drivers::NvsStorage::InputModes::kDirectionalWheel; + case 3: + return drivers::NvsStorage::InputModes::kRotatingWheel; + default: + return {}; + } +} + +LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, + DeviceFactory& factory) + : nvs_(nvs), + factory_(factory), + mode_(static_cast(nvs.PrimaryInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToMode(std::get(val)); + if (!mode) { + return false; + } + nvs.PrimaryInput(*mode); + inputs_ = factory.createInputs(*mode); + return true; + }), driver_(), registration_(nullptr), - inputs_(), - feedbacks_(), + inputs_(factory.createInputs(nvs.PrimaryInput())), + feedbacks_(factory.createFeedbacks()), is_locked_(false) { lv_indev_drv_init(&driver_); driver_.type = LV_INDEV_TYPE_ENCODER; driver_.read_cb = read_cb; driver_.feedback_cb = feedback_cb; driver_.user_data = this; + driver_.long_press_time = kLongPressDelayMs; + driver_.long_press_repeat_time = kRepeatDelayMs; registration_ = lv_indev_drv_register(&driver_); - - // TODO: Make these devices configurable. I'm thinking each device gets an id - // and then we have: - // - a factory to create instance given an id - // - add/remove device methods on LvglInputDriver that operate on ids - // - the user's enabled devices (+ their configuration) stored in NVS. - auto touchwheel = services_->touchwheel(); - if (touchwheel) { - inputs_.push_back(std::make_unique(**touchwheel)); - } - inputs_.push_back(std::make_unique(services_->gpios())); - feedbacks_.push_back(std::make_unique(services_->haptics())); } auto LvglInputDriver::read(lv_indev_data_t* data) -> void { diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp index 5c033b0c..81e0543a 100644 --- a/src/ui/include/ui_events.hpp +++ b/src/ui/include/ui_events.hpp @@ -32,7 +32,6 @@ struct DumpLuaStack : tinyfsm::Event {}; namespace internal { -struct ControlSchemeChanged : tinyfsm::Event {}; struct ReindexDatabase : tinyfsm::Event {}; struct BackPressed : tinyfsm::Event {}; diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index c238a447..8eafc6e0 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -13,8 +13,12 @@ #include "audio_events.hpp" #include "battery.hpp" #include "db_events.hpp" +#include "device_factory.hpp" #include "display.hpp" +#include "feedback_haptics.hpp" #include "gpios.hpp" +#include "input_touch_wheel.hpp" +#include "input_volume_buttons.hpp" #include "lua_thread.hpp" #include "lvgl_input_driver.hpp" #include "lvgl_task.hpp" @@ -67,7 +71,6 @@ class UiState : public tinyfsm::Fsm { void react(const system_fsm::SamdUsbStatusChanged&); void react(const internal::DismissAlerts&); - void react(const internal::ControlSchemeChanged&); void react(const database::event::UpdateStarted&); void react(const database::event::UpdateProgress&){}; @@ -91,7 +94,9 @@ class UiState : public tinyfsm::Fsm { static std::unique_ptr sTask; static std::shared_ptr sServices; static std::unique_ptr sDisplay; + static std::shared_ptr sInput; + static std::unique_ptr sDeviceFactory; static std::stack> sScreens; static std::shared_ptr sCurrentScreen; @@ -125,8 +130,6 @@ class UiState : public tinyfsm::Fsm { static lua::Property sDisplayBrightness; - static lua::Property sControlsScheme; - static lua::Property sScrollSensitivity; static lua::Property sLockSwitch; static lua::Property sDatabaseUpdating; diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index a9c1d3a3..eaaef450 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -12,9 +12,14 @@ #include "bluetooth_types.hpp" #include "db_events.hpp" +#include "device_factory.hpp" #include "display_init.hpp" +#include "feedback_haptics.hpp" #include "freertos/portmacro.h" #include "freertos/projdefs.h" +#include "input_device.hpp" +#include "input_touch_wheel.hpp" +#include "input_volume_buttons.hpp" #include "lua.h" #include "lua.hpp" @@ -62,7 +67,9 @@ namespace ui { std::unique_ptr UiState::sTask; std::shared_ptr UiState::sServices; std::unique_ptr UiState::sDisplay; + std::shared_ptr UiState::sInput; +std::unique_ptr UiState::sDeviceFactory; std::stack> UiState::sScreens; std::shared_ptr UiState::sCurrentScreen; @@ -233,59 +240,6 @@ lua::Property UiState::sDisplayBrightness{ return true; }}; -lua::Property UiState::sControlsScheme{0, [](const lua::LuaValue& val) { - /* - if (!std::holds_alternative(val)) - { return false; - } - drivers::NvsStorage::InputModes mode; - switch (std::get(val)) { - case 0: - mode = - drivers::NvsStorage::InputModes::kButtonsOnly; - break; - case 1: - mode = - drivers::NvsStorage::InputModes::kButtonsWithWheel; - break; - case 2: - mode = - drivers::NvsStorage::InputModes::kDirectionalWheel; - break; - case 3: - mode = - drivers::NvsStorage::InputModes::kRotatingWheel; - break; - default: - return false; - } - sServices->nvs().PrimaryInput(mode); - sInput->mode(mode); - */ - return true; - }}; - -lua::Property UiState::sScrollSensitivity{0, [](const lua::LuaValue& val) { - /* - std::optional sensitivity = 0; - std::visit( - [&](auto&& v) { - using T = - std::decay_t; if - constexpr (std::is_same_v) { - sensitivity = v; - } - }, - val); - if (!sensitivity) { - return false; - } - sInput->scroll_sensitivity(*sensitivity); - sServices->nvs().ScrollSensitivity(*sensitivity); - */ - return true; - }}; - lua::Property UiState::sLockSwitch{false}; lua::Property UiState::sDatabaseUpdating{false}; @@ -374,13 +328,6 @@ void UiState::react(const system_fsm::SamdUsbStatusChanged& ev) { drivers::Samd::UsbStatus::kAttachedBusy); } -void UiState::react(const internal::ControlSchemeChanged&) { - if (!sInput) { - return; - } - // sInput->mode(sServices->nvs().PrimaryInput()); -} - void UiState::react(const database::event::UpdateStarted&) { sDatabaseUpdating.Update(true); } @@ -484,7 +431,10 @@ void Splash::react(const system_fsm::BootComplete& ev) { sDisplayBrightness.Update(brightness); sDisplay->SetBrightness(brightness); - sInput = std::make_shared(sServices); + sDeviceFactory = std::make_unique(sServices); + sInput = std::make_shared(sServices->nvs(), + *sDeviceFactory); + sTask->input(sInput); } @@ -542,12 +492,16 @@ void Lua::entry() { {"brightness", &sDisplayBrightness}, }); - registry.AddPropertyModule("controls", - { - {"scheme", &sControlsScheme}, - {"scroll_sensitivity", &sScrollSensitivity}, - {"lock_switch", &sLockSwitch}, - }); + registry.AddPropertyModule("controls", { + {"scheme", &sInput->mode()}, + {"lock_switch", &sLockSwitch}, + }); + + if (sDeviceFactory->touch_wheel()) { + registry.AddPropertyModule( + "controls", {{"scroll_sensitivity", + &sDeviceFactory->touch_wheel()->sensitivity()}}); + } registry.AddPropertyModule( "backstack",