diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-04-10 16:56:10 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-04-10 16:56:10 +1000 |
| commit | ed82063af5f83530afa5cfb5bf5bd516f3d05f2a (patch) | |
| tree | f8beba0e107d57bc0324b930d5200fdc405c96c4 /src/input | |
| parent | 2e59325c22605873bba62c078c196d99c1664590 (diff) | |
| download | tangara-fw-ed82063af5f83530afa5cfb5bf5bd516f3d05f2a.tar.gz | |
WIP decompose our giant LVGL driver into smaller classes
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | src/input/feedback_haptics.cpp | 37 | ||||
| -rw-r--r-- | src/input/include/feedback_device.hpp | 32 | ||||
| -rw-r--r-- | src/input/include/feedback_haptics.hpp | 26 | ||||
| -rw-r--r-- | src/input/include/input_device.hpp | 33 | ||||
| -rw-r--r-- | src/input/include/input_touch_dpad.hpp | 30 | ||||
| -rw-r--r-- | src/input/include/input_touch_wheel.hpp | 37 | ||||
| -rw-r--r-- | src/input/include/input_volume_buttons.hpp | 30 | ||||
| -rw-r--r-- | src/input/include/lvgl_input_driver.hpp | 53 | ||||
| -rw-r--r-- | src/input/input_touch_dpad.cpp | 35 | ||||
| -rw-r--r-- | src/input/input_touch_wheel.cpp | 81 | ||||
| -rw-r--r-- | src/input/input_volume_buttons.cpp | 26 | ||||
| -rw-r--r-- | src/input/lvgl_input_driver.cpp | 84 |
13 files changed, 515 insertions, 0 deletions
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 <me@jacqueline.id.au> +# +# 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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "feedback_haptics.hpp" + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <cstdint> + +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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <functional> +#include <string> +#include <vector> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <stdint.h> +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include <stdint.h> +#include <deque> +#include <memory> +#include <set> + +#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<system_fsm::ServiceLocator>); + + 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<system_fsm::ServiceLocator> services_; + + lv_indev_drv_t driver_; + lv_indev_t* registration_; + + std::vector<std::unique_ptr<IInputDevice>> inputs_; + std::vector<std::unique_ptr<IFeedbackDevice>> 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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_touch_dpad.hpp" + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input_touch_wheel.hpp" +#include <stdint.h> + +#include <cstdint> + +#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 <me@jacqueline.id.au> + * + * 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 <me@jacqueline.id.au> + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "lvgl_input_driver.hpp" +#include <stdint.h> + +#include <cstdint> +#include <memory> + +#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<LvglInputDriver*>(drv->user_data); + instance->read(data); +} + +static void feedback_cb(lv_indev_drv_t* drv, uint8_t event) { + LvglInputDriver* instance = + reinterpret_cast<LvglInputDriver*>(drv->user_data); + instance->feedback(event); +} + +LvglInputDriver::LvglInputDriver( + std::shared_ptr<system_fsm::ServiceLocator> 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>(**touchwheel)); + } + inputs_.push_back(std::make_unique<VolumeButtons>(services_->gpios())); + feedbacks_.push_back(std::make_unique<Haptics>(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 |
