summaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-04-10 16:56:10 +1000
committerjacqueline <me@jacqueline.id.au>2024-04-10 16:56:10 +1000
commited82063af5f83530afa5cfb5bf5bd516f3d05f2a (patch)
treef8beba0e107d57bc0324b930d5200fdc405c96c4 /src/input
parent2e59325c22605873bba62c078c196d99c1664590 (diff)
downloadtangara-fw-ed82063af5f83530afa5cfb5bf5bd516f3d05f2a.tar.gz
WIP decompose our giant LVGL driver into smaller classes
Diffstat (limited to 'src/input')
-rw-r--r--src/input/CMakeLists.txt11
-rw-r--r--src/input/feedback_haptics.cpp37
-rw-r--r--src/input/include/feedback_device.hpp32
-rw-r--r--src/input/include/feedback_haptics.hpp26
-rw-r--r--src/input/include/input_device.hpp33
-rw-r--r--src/input/include/input_touch_dpad.hpp30
-rw-r--r--src/input/include/input_touch_wheel.hpp37
-rw-r--r--src/input/include/input_volume_buttons.hpp30
-rw-r--r--src/input/include/lvgl_input_driver.hpp53
-rw-r--r--src/input/input_touch_dpad.cpp35
-rw-r--r--src/input/input_touch_wheel.cpp81
-rw-r--r--src/input/input_volume_buttons.cpp26
-rw-r--r--src/input/lvgl_input_driver.cpp84
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