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. 219
      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; std::optional<database::TrackId> AudioState::sCurrentTrack;
void AudioState::react(const system_fsm::KeyUpChanged& ev) { void AudioState::react(const StepUpVolume& ev) {
if (ev.falling && sOutput->AdjustVolumeUp()) { if (sOutput->AdjustVolumeUp()) {
ESP_LOGI(kTag, "volume up!");
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{});
} }
} }
void AudioState::react(const system_fsm::KeyDownChanged& ev) { void AudioState::react(const StepDownVolume& ev) {
if (ev.falling && sOutput->AdjustVolumeDown()) { if (sOutput->AdjustVolumeDown()) {
ESP_LOGI(kTag, "volume down!");
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{});
} }
} }

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

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

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

@ -31,28 +31,14 @@ void SystemState::react(const FatalError& err) {
void SystemState::react(const internal::GpioInterrupt&) { void SystemState::react(const internal::GpioInterrupt&) {
auto& gpios = sServices->gpios(); 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_key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
bool prev_has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect); bool prev_has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect);
gpios.Read(); 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 key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
bool has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect); 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) { if (key_lock != prev_key_lock) {
KeyLockChanged ev{.falling = key_lock}; KeyLockChanged ev{.falling = key_lock};
events::System().Dispatch(ev); events::System().Dispatch(ev);

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

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

Loading…
Cancel
Save