Neaten up the various kinds of inputs, and move volumes to The New Way

custom
jacqueline 2 years ago
parent c851b789fa
commit 5c04e2ad8d
  1. 10
      src/audio/audio_fsm.cpp
  2. 2
      src/audio/include/audio_events.hpp
  3. 4
      src/audio/include/audio_fsm.hpp
  4. 6
      src/system_fsm/include/system_events.hpp
  5. 14
      src/system_fsm/system_fsm.cpp
  6. 243
      src/ui/encoder_input.cpp
  7. 29
      src/ui/include/encoder_input.hpp

@ -50,16 +50,14 @@ std::shared_ptr<IAudioOutput> AudioState::sOutput;
std::optional<database::TrackId> AudioState::sCurrentTrack;
void AudioState::react(const system_fsm::KeyUpChanged& ev) {
if (ev.falling && sOutput->AdjustVolumeUp()) {
ESP_LOGI(kTag, "volume up!");
void AudioState::react(const StepUpVolume& ev) {
if (sOutput->AdjustVolumeUp()) {
events::Ui().Dispatch(VolumeChanged{});
}
}
void AudioState::react(const system_fsm::KeyDownChanged& ev) {
if (ev.falling && sOutput->AdjustVolumeDown()) {
ESP_LOGI(kTag, "volume down!");
void AudioState::react(const StepDownVolume& ev) {
if (sOutput->AdjustVolumeDown()) {
events::Ui().Dispatch(VolumeChanged{});
}
}

@ -34,6 +34,8 @@ struct PlayFile : tinyfsm::Event {
std::pmr::string filename;
};
struct StepUpVolume : tinyfsm::Event {};
struct StepDownVolume : tinyfsm::Event {};
struct VolumeChanged : tinyfsm::Event {};
struct ChangeMaxVolume : tinyfsm::Event {
uint16_t new_max;

@ -41,8 +41,8 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
/* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {}
void react(const system_fsm::KeyUpChanged&);
void react(const system_fsm::KeyDownChanged&);
void react(const StepUpVolume&);
void react(const StepDownVolume&);
void react(const system_fsm::HasPhonesChanged&);
void react(const ChangeMaxVolume&);
void react(const OutputModeChanged&);

@ -41,12 +41,6 @@ struct StorageMounted : tinyfsm::Event {};
struct StorageError : tinyfsm::Event {};
struct KeyUpChanged : tinyfsm::Event {
bool falling;
};
struct KeyDownChanged : tinyfsm::Event {
bool falling;
};
struct KeyLockChanged : tinyfsm::Event {
bool falling;
};

@ -31,28 +31,14 @@ void SystemState::react(const FatalError& err) {
void SystemState::react(const internal::GpioInterrupt&) {
auto& gpios = sServices->gpios();
bool prev_key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp);
bool prev_key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown);
bool prev_key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
bool prev_has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect);
gpios.Read();
bool key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp);
bool key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown);
bool key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
bool has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect);
if (key_up != prev_key_up) {
KeyUpChanged ev{.falling = prev_key_up};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
if (key_down != prev_key_down) {
KeyDownChanged ev{.falling = prev_key_down};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
if (key_lock != prev_key_lock) {
KeyLockChanged ev{.falling = key_lock};
events::System().Dispatch(ev);

@ -9,13 +9,16 @@
#include <sys/_stdint.h>
#include <memory>
#include "audio_events.hpp"
#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"
constexpr int kDPadAngleThreshold = 20;
constexpr int kLongPressDelayMs = 500;
@ -70,83 +73,139 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void {
}
// Check each button.
HandleKey(Keys::kVolumeUp, now_ms, !gpios_.Get(drivers::IGpios::Pin::kKeyUp));
HandleKey(Keys::kVolumeDown, now_ms,
!gpios_.Get(drivers::IGpios::Pin::kKeyDown));
HandleKeyState(Keys::kVolumeUp, now_ms,
!gpios_.Get(drivers::IGpios::Pin::kKeyUp));
HandleKeyState(Keys::kVolumeDown, now_ms,
!gpios_.Get(drivers::IGpios::Pin::kKeyDown));
drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData();
HandleKey(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched);
HandleKey(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched);
HandleKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched);
HandleKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched);
HandleKey(
HandleKeyState(
Keys::kDirectionalUp, now_ms,
wheel_data.is_wheel_touched &&
IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold));
HandleKey(
HandleKeyState(
Keys::kDirectionalLeft, now_ms,
wheel_data.is_wheel_touched &&
IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold));
HandleKey(
HandleKeyState(
Keys::kDirectionalDown, now_ms,
wheel_data.is_wheel_touched &&
IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold));
HandleKey(
HandleKeyState(
Keys::kDirectionalRight, now_ms,
wheel_data.is_wheel_touched &&
IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold));
// We now have enough information to give LVGL its update.
Trigger trigger;
switch (mode_) {
case drivers::NvsStorage::InputModes::kButtonsOnly:
data->state = LV_INDEV_STATE_RELEASED;
if (ShortPressTrigger(Keys::kVolumeUp)) {
data->enc_diff = -1;
} else if (ShortPressTrigger(Keys::kVolumeDown)) {
data->enc_diff = 1;
} else if (LongPressTrigger(Keys::kVolumeDown, now_ms)) {
data->state = LV_INDEV_STATE_PRESSED;
} else if (LongPressTrigger(Keys::kVolumeUp, now_ms)) {
// TODO: Back button event
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;
}
break;
case drivers::NvsStorage::InputModes::kButtonsWithWheel:
data->state = ShortPressTrigger(Keys::kTouchWheel)
? LV_INDEV_STATE_PRESSED
: LV_INDEV_STATE_RELEASED;
if (ShortPressTriggerRepeating(Keys::kVolumeUp, now_ms)) {
trigger = TriggerKey(Keys::kTouchWheel, KeyStyle::kLongPress, now_ms);
data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED
: LV_INDEV_STATE_RELEASED;
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms);
if (trigger == Trigger::kClick) {
data->enc_diff = scroller_->AddInput(now_ms, -1);
} else if (ShortPressTriggerRepeating(Keys::kVolumeDown, now_ms)) {
}
trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms);
if (trigger == Trigger::kClick) {
data->enc_diff = scroller_->AddInput(now_ms, 1);
}
// Cancel scrolling if the buttons are released.
if (!touch_time_ms_.contains(Keys::kVolumeDown) &&
!touch_time_ms_.contains(Keys::kVolumeUp)) {
data->enc_diff = scroller_->AddInput(now_ms, 0);
}
// TODO: Long-press events.
break;
case drivers::NvsStorage::InputModes::kDirectionalWheel:
data->state = ShortPressTrigger(Keys::kTouchWheelCenter)
? LV_INDEV_STATE_PRESSED
: LV_INDEV_STATE_RELEASED;
if (!ShortPressTriggerRepeating(Keys::kTouchWheel, now_ms)) {
break;
}
if (ShortPressTriggerRepeating(Keys::kDirectionalUp, now_ms)) {
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);
} else if (ShortPressTriggerRepeating(Keys::kDirectionalDown, now_ms)) {
}
trigger = TriggerKey(Keys::kDirectionalDown, KeyStyle::kRepeat, now_ms);
if (trigger == Trigger::kClick) {
data->enc_diff = scroller_->AddInput(now_ms, 1);
} else if (ShortPressTrigger(Keys::kDirectionalRight)) {
}
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: ???
} else if (ShortPressTrigger(Keys::kDirectionalLeft)) {
// TODO: Back button event.
}
// 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);
}
// TODO: Long-press events.
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, 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::kLongPress, now_ms);
switch (trigger) {
case Trigger::kNone:
break;
case Trigger::kClick:
events::Audio().Dispatch(audio::StepDownVolume{});
break;
case Trigger::kLongPress:
break;
}
break;
case drivers::NvsStorage::InputModes::kRotatingWheel:
if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) {
@ -156,53 +215,97 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void {
} else {
data->enc_diff = 0;
}
data->state = relative_wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED
: LV_INDEV_STATE_RELEASED;
// TODO: Long-press events.
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:
// TODO: ???
data->state = LV_INDEV_STATE_PRESSED;
break;
}
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, 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::kLongPress, now_ms);
switch (trigger) {
case Trigger::kNone:
break;
case Trigger::kClick:
events::Audio().Dispatch(audio::StepDownVolume{});
break;
case Trigger::kLongPress:
break;
}
break;
}
// TODO: Apply inertia / acceleration.
}
auto EncoderInput::HandleKey(Keys key, uint64_t ms, bool clicked) -> void {
if (!clicked) {
touch_time_ms_.erase(key);
short_press_fired_.erase(key);
long_press_fired_.erase(key);
auto EncoderInput::HandleKeyState(Keys key, uint64_t ms, bool clicked) -> void {
if (clicked) {
if (!touch_time_ms_.contains(key)) {
// Key was just pressed
touch_time_ms_[key] = ms;
just_released_.erase(key);
fired_.erase(key);
}
return;
}
if (!touch_time_ms_.contains(key)) {
touch_time_ms_[key] = ms;
}
}
auto EncoderInput::ShortPressTrigger(Keys key) -> bool {
if (touch_time_ms_.contains(key) && !short_press_fired_.contains(key)) {
short_press_fired_[key] = true;
return true;
// Key is not clicked.
if (touch_time_ms_.contains(key)) {
// Key was just released.
just_released_.insert(key);
touch_time_ms_.erase(key);
}
return false;
}
auto EncoderInput::ShortPressTriggerRepeating(Keys key, uint64_t ms) -> bool {
if (touch_time_ms_.contains(key) &&
(!short_press_fired_.contains(key) ||
ms - touch_time_ms_[key] >= kRepeatDelayMs)) {
touch_time_ms_[key] = ms;
short_press_fired_[key] = true;
return true;
}
return false;
}
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;
auto EncoderInput::LongPressTrigger(Keys key, uint64_t ms) -> bool {
if (touch_time_ms_.contains(key) && !long_press_fired_.contains(key) &&
ms - touch_time_ms_[key] >= kLongPressDelayMs) {
long_press_fired_[key] = true;
return true;
// 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 false;
return Trigger::kNone;
}
auto Scroller::AddInput(uint64_t ms, int direction) -> int {

@ -9,6 +9,7 @@
#include <stdint.h>
#include <deque>
#include <memory>
#include <set>
#include "core/lv_group.h"
#include "gpios.hpp"
@ -51,6 +52,7 @@ class EncoderInput {
drivers::NvsStorage::InputModes mode_;
bool is_locked_;
// Every kind of distinct input that we could map to an action.
enum class Keys {
kVolumeUp,
kVolumeDown,
@ -62,14 +64,29 @@ class EncoderInput {
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<Keys, uint64_t> touch_time_ms_;
std::unordered_map<Keys, bool> short_press_fired_;
std::unordered_map<Keys, bool> long_press_fired_;
// Set of keys that were released during the current update.
std::set<Keys> just_released_;
// Set of keys that have had an event fired for them since being pressed.
std::set<Keys> fired_;
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 HandleKey(Keys key, uint64_t ms, bool clicked) -> void;
auto ShortPressTrigger(Keys key) -> bool;
auto ShortPressTriggerRepeating(Keys key, uint64_t ms) -> bool;
auto LongPressTrigger(Keys key, uint64_t ms) -> bool;
auto HandleKeyState(Keys key, uint64_t ms, bool clicked) -> void;
auto TriggerKey(Keys key, KeyStyle t, uint64_t ms) -> Trigger;
};
class Scroller {

Loading…
Cancel
Save