diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-08-30 16:48:10 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-08-30 16:48:10 +1000 |
| commit | 320fdeb9d8355d3c361d5c6d60de8afc64501af9 (patch) | |
| tree | f0d5a2ab82199c78ad6768c6b18ba1239a0b7ee4 /src/ui | |
| parent | 4247c9fe7d25c921fbfc73fc50e849c8780e7ad6 (diff) | |
| download | tangara-fw-320fdeb9d8355d3c361d5c6d60de8afc64501af9.tar.gz | |
Use a service locator instead of passing around subsets of drivers between FSMs
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/include/lvgl_task.hpp | 27 | ||||
| -rw-r--r-- | src/ui/include/screen_playing.hpp | 4 | ||||
| -rw-r--r-- | src/ui/include/screen_settings.hpp | 6 | ||||
| -rw-r--r-- | src/ui/include/ui_fsm.hpp | 25 | ||||
| -rw-r--r-- | src/ui/include/wheel_encoder.hpp | 4 | ||||
| -rw-r--r-- | src/ui/lvgl_task.cpp | 79 | ||||
| -rw-r--r-- | src/ui/screen_playing.cpp | 6 | ||||
| -rw-r--r-- | src/ui/screen_settings.cpp | 8 | ||||
| -rw-r--r-- | src/ui/ui_fsm.cpp | 101 | ||||
| -rw-r--r-- | src/ui/wheel_encoder.cpp | 15 |
10 files changed, 148 insertions, 127 deletions
diff --git a/src/ui/include/lvgl_task.hpp b/src/ui/include/lvgl_task.hpp index 7e60c4b4..6b7e446e 100644 --- a/src/ui/include/lvgl_task.hpp +++ b/src/ui/include/lvgl_task.hpp @@ -12,15 +12,38 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/timers.h" #include "display.hpp" #include "relative_wheel.hpp" +#include "screen.hpp" #include "themes.hpp" #include "touchwheel.hpp" +#include "wheel_encoder.hpp" namespace ui { -auto StartLvgl(std::weak_ptr<drivers::RelativeWheel> touch_wheel, - std::weak_ptr<drivers::Display> display) -> void; +class UiTask { + public: + static auto Start() -> UiTask*; + + ~UiTask(); + + // FIXME: Once we have more input devices, this function should accept a more + // generic interface. + auto SetInputDevice(std::shared_ptr<TouchWheelEncoder> dev) -> void; + + private: + UiTask(); + + auto Main() -> void; + + std::shared_ptr<TouchWheelEncoder> input_device_; + std::shared_ptr<Screen> current_screen_; + + std::atomic<bool> quit_; + SemaphoreHandle_t frame_semaphore_; + TimerHandle_t frame_timer_; +}; } // namespace ui diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp index c684ddff..f2998c88 100644 --- a/src/ui/include/screen_playing.hpp +++ b/src/ui/include/screen_playing.hpp @@ -29,7 +29,7 @@ namespace screens { class Playing : public Screen { public: explicit Playing(std::weak_ptr<database::Database> db, - audio::TrackQueue* queue); + audio::TrackQueue& queue); ~Playing(); auto Tick() -> void override; @@ -51,7 +51,7 @@ class Playing : public Screen { auto ApplyNextUp(const std::vector<database::Track>& tracks) -> void; std::weak_ptr<database::Database> db_; - audio::TrackQueue* queue_; + audio::TrackQueue& queue_; std::optional<database::Track> track_; std::vector<database::Track> next_tracks_; diff --git a/src/ui/include/screen_settings.hpp b/src/ui/include/screen_settings.hpp index 61375fa9..0ec96d26 100644 --- a/src/ui/include/screen_settings.hpp +++ b/src/ui/include/screen_settings.hpp @@ -37,14 +37,14 @@ class Headphones : public MenuScreen { class Appearance : public MenuScreen { public: - Appearance(drivers::NvsStorage* nvs, drivers::Display* display); + Appearance(drivers::NvsStorage& nvs, drivers::Display& display); auto ChangeBrightness(uint_fast8_t) -> void; auto CommitBrightness() -> void; private: - drivers::NvsStorage* nvs_; - drivers::Display* display_; + drivers::NvsStorage& nvs_; + drivers::Display& display_; lv_obj_t* current_brightness_label_; uint_fast8_t current_brightness_; diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 1fa6bf26..12fe5c69 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -11,9 +11,12 @@ #include "audio_events.hpp" #include "battery.hpp" +#include "gpios.hpp" +#include "lvgl_task.hpp" #include "nvs.hpp" #include "relative_wheel.hpp" #include "screen_playing.hpp" +#include "service_locator.hpp" #include "tinyfsm.hpp" #include "display.hpp" @@ -24,15 +27,13 @@ #include "touchwheel.hpp" #include "track_queue.hpp" #include "ui_events.hpp" +#include "wheel_encoder.hpp" namespace ui { class UiState : public tinyfsm::Fsm<UiState> { public: - static auto Init(drivers::IGpios*, - std::shared_ptr<drivers::NvsStorage>, - audio::TrackQueue*, - std::shared_ptr<battery::Battery>) -> bool; + static auto InitBootSplash(drivers::IGpios&) -> bool; virtual ~UiState() {} @@ -46,7 +47,7 @@ class UiState : public tinyfsm::Fsm<UiState> { /* Fallback event handler. Does nothing. */ void react(const tinyfsm::Event& ev) {} - void react(const system_fsm::BatteryStateChanged&); + virtual void react(const system_fsm::BatteryStateChanged&); virtual void react(const audio::PlaybackStarted&) {} virtual void react(const audio::PlaybackUpdate&) {} @@ -76,15 +77,10 @@ class UiState : public tinyfsm::Fsm<UiState> { void PopScreen(); void UpdateTopBar(); - static drivers::IGpios* sIGpios; - static audio::TrackQueue* sQueue; - - static std::shared_ptr<drivers::TouchWheel> sTouchWheel; - static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel; - static std::shared_ptr<drivers::Display> sDisplay; - static std::shared_ptr<battery::Battery> sBattery; - static std::shared_ptr<drivers::NvsStorage> sNvs; - static std::weak_ptr<database::Database> sDb; + static std::unique_ptr<UiTask> sTask; + static std::shared_ptr<system_fsm::ServiceLocator> sServices; + static std::unique_ptr<drivers::Display> sDisplay; + static std::shared_ptr<TouchWheelEncoder> sEncoder; static std::stack<std::shared_ptr<Screen>> sScreens; static std::shared_ptr<Screen> sCurrentScreen; @@ -97,6 +93,7 @@ class Splash : public UiState { public: void exit() override; void react(const system_fsm::BootComplete&) override; + void react(const system_fsm::BatteryStateChanged&) override{}; using UiState::react; }; diff --git a/src/ui/include/wheel_encoder.hpp b/src/ui/include/wheel_encoder.hpp index c49e5929..fcac5edd 100644 --- a/src/ui/include/wheel_encoder.hpp +++ b/src/ui/include/wheel_encoder.hpp @@ -17,7 +17,7 @@ namespace ui { class TouchWheelEncoder { public: - explicit TouchWheelEncoder(std::weak_ptr<drivers::RelativeWheel> wheel); + explicit TouchWheelEncoder(std::unique_ptr<drivers::RelativeWheel> wheel); auto Read(lv_indev_data_t* data) -> void; auto registration() -> lv_indev_t* { return registration_; } @@ -27,7 +27,7 @@ class TouchWheelEncoder { lv_indev_t* registration_; lv_key_t last_key_; - std::weak_ptr<drivers::RelativeWheel> wheel_; + std::unique_ptr<drivers::RelativeWheel> wheel_; }; } // namespace ui diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp index 9952d0bb..a0f14f7d 100644 --- a/src/ui/lvgl_task.cpp +++ b/src/ui/lvgl_task.cpp @@ -48,67 +48,74 @@ namespace ui { -static const char* kTag = "lv_task"; +static const char* kTag = "ui_task"; static const TickType_t kMaxFrameRate = pdMS_TO_TICKS(100); -static int sTimerId; -static SemaphoreHandle_t sFrameSemaphore; - -auto next_frame(TimerHandle_t) { - xSemaphoreGive(sFrameSemaphore); +static auto next_frame(TimerHandle_t t) { + SemaphoreHandle_t sem = + reinterpret_cast<SemaphoreHandle_t>(pvTimerGetTimerID(t)); + xSemaphoreGive(sem); } -void LvglMain(std::weak_ptr<drivers::RelativeWheel> weak_touch_wheel, - std::weak_ptr<drivers::Display> weak_display) { - ESP_LOGI(kTag, "init lvgl"); - lv_init(); - - sFrameSemaphore = xSemaphoreCreateBinary(); - auto timer = - xTimerCreate("lvgl_frame", kMaxFrameRate, pdTRUE, &sTimerId, next_frame); - xTimerStart(timer, portMAX_DELAY); - - lv_theme_t* base_theme = lv_theme_basic_init(NULL); - lv_disp_set_theme(NULL, base_theme); - themes::Theme::instance()->Apply(); +UiTask::UiTask() + : quit_(false), + frame_semaphore_(xSemaphoreCreateBinary()), + frame_timer_(xTimerCreate("ui_frame", + kMaxFrameRate, + pdTRUE, + frame_semaphore_, + next_frame)) { + xTimerStart(frame_timer_, portMAX_DELAY); +} - TouchWheelEncoder encoder(weak_touch_wheel); +UiTask::~UiTask() { + assert(false); +} - std::shared_ptr<Screen> current_screen; +auto UiTask::Main() -> void { + ESP_LOGI(kTag, "start ui task"); lv_group_t* current_group = nullptr; auto* events = events::queues::Ui(); - while (1) { + while (true) { while (events->Service(0)) { } std::shared_ptr<Screen> screen = UiState::current_screen(); - if (screen != current_screen && screen != nullptr) { - // TODO(jacqueline): animate this sometimes + if (screen != current_screen_ && screen != nullptr) { lv_scr_load(screen->root()); - lv_indev_set_group(encoder.registration(), screen->group()); - current_screen = screen; + if (input_device_) { + lv_indev_set_group(input_device_->registration(), screen->group()); + } + current_screen_ = screen; } - if (current_screen->group() != current_group) { - current_group = current_screen->group(); - lv_indev_set_group(encoder.registration(), current_group); + if (input_device_ && current_screen_->group() != current_group) { + current_group = current_screen_->group(); + lv_indev_set_group(input_device_->registration(), current_group); } - if (current_screen) { - current_screen->Tick(); + if (current_screen_) { + current_screen_->Tick(); } lv_task_handler(); // Wait for the signal to loop again. - xSemaphoreTake(sFrameSemaphore, portMAX_DELAY); + xSemaphoreTake(frame_semaphore_, portMAX_DELAY); + } +} + +auto UiTask::SetInputDevice(std::shared_ptr<TouchWheelEncoder> dev) -> void { + input_device_ = std::move(dev); + if (current_screen_ && input_device_) { + lv_indev_set_group(input_device_->registration(), current_screen_->group()); } } -auto StartLvgl(std::weak_ptr<drivers::RelativeWheel> touch_wheel, - std::weak_ptr<drivers::Display> display) -> void { - tasks::StartPersistent<tasks::Type::kUi>( - 0, [=]() { LvglMain(touch_wheel, display); }); +auto UiTask::Start() -> UiTask* { + UiTask* ret = new UiTask(); + tasks::StartPersistent<tasks::Type::kUi>(0, [=]() { ret->Main(); }); + return ret; } } // namespace ui diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp index 7538d093..2eb4e09e 100644 --- a/src/ui/screen_playing.cpp +++ b/src/ui/screen_playing.cpp @@ -104,7 +104,7 @@ auto Playing::next_up_label(lv_obj_t* parent, const std::string& text) return button; } -Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue) +Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue) : db_(db), queue_(queue), track_(), @@ -204,7 +204,7 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue) Playing::~Playing() {} auto Playing::OnTrackUpdate() -> void { - auto current = queue_->GetCurrent(); + auto current = queue_.GetCurrent(); if (!current) { return; } @@ -230,7 +230,7 @@ auto Playing::OnPlaybackUpdate(uint32_t pos_seconds, uint32_t new_duration) auto Playing::OnQueueUpdate() -> void { OnTrackUpdate(); - auto current = queue_->GetUpcoming(kMaxUpcoming); + auto current = queue_.GetUpcoming(kMaxUpcoming); auto db = db_.lock(); if (!db) { return; diff --git a/src/ui/screen_settings.cpp b/src/ui/screen_settings.cpp index 6bafb9a8..01f40cb5 100644 --- a/src/ui/screen_settings.cpp +++ b/src/ui/screen_settings.cpp @@ -161,7 +161,7 @@ static auto brightness_str(uint_fast8_t percent) -> std::string { return std::to_string(percent) + "%"; } -Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display) +Appearance::Appearance(drivers::NvsStorage& nvs, drivers::Display& display) : MenuScreen("Appearance"), nvs_(nvs), display_(display) { lv_obj_t* toggle_container = settings_container(content_); lv_obj_t* toggle_label = lv_label_create(toggle_container); @@ -170,7 +170,7 @@ Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display) lv_obj_t* toggle = lv_switch_create(toggle_container); lv_group_add_obj(group_, toggle); - uint_fast8_t initial_brightness = nvs_->ScreenBrightness().get(); + uint_fast8_t initial_brightness = nvs_.ScreenBrightness().get(); lv_obj_t* brightness_label = lv_label_create(content_); lv_label_set_text(brightness_label, "Brightness"); @@ -192,13 +192,13 @@ Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display) auto Appearance::ChangeBrightness(uint_fast8_t new_level) -> void { current_brightness_ = new_level; - display_->SetBrightness(new_level); + display_.SetBrightness(new_level); lv_label_set_text(current_brightness_label_, brightness_str(new_level).c_str()); } auto Appearance::CommitBrightness() -> void { - nvs_->ScreenBrightness(current_brightness_); + nvs_.ScreenBrightness(current_brightness_); } InputMethod::InputMethod() : MenuScreen("Input Method") { diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 1febd1c7..0054db23 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -32,6 +32,7 @@ #include "touchwheel.hpp" #include "track_queue.hpp" #include "ui_events.hpp" +#include "wheel_encoder.hpp" #include "widget_top_bar.hpp" namespace ui { @@ -40,51 +41,25 @@ static constexpr char kTag[] = "ui_fsm"; static const std::size_t kRecordsPerPage = 15; -drivers::IGpios* UiState::sIGpios; -audio::TrackQueue* UiState::sQueue; - -std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel; -std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel; -std::shared_ptr<drivers::Display> UiState::sDisplay; -std::shared_ptr<battery::Battery> UiState::sBattery; -std::shared_ptr<drivers::NvsStorage> UiState::sNvs; -std::weak_ptr<database::Database> UiState::sDb; +std::unique_ptr<UiTask> UiState::sTask; +std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices; +std::unique_ptr<drivers::Display> UiState::sDisplay; +std::shared_ptr<TouchWheelEncoder> UiState::sEncoder; std::stack<std::shared_ptr<Screen>> UiState::sScreens; std::shared_ptr<Screen> UiState::sCurrentScreen; std::shared_ptr<Modal> UiState::sCurrentModal; -auto UiState::Init(drivers::IGpios* gpio_expander, - std::shared_ptr<drivers::NvsStorage> nvs, - audio::TrackQueue* queue, - std::shared_ptr<battery::Battery> battery) -> bool { - sIGpios = gpio_expander; - sNvs = nvs; - sQueue = queue; - sBattery = battery; - +auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool { + // Init LVGL first, since the display driver registers itself with LVGL. lv_init(); - sDisplay.reset( - drivers::Display::Create(gpio_expander, drivers::displays::kST7735R)); + sDisplay.reset(drivers::Display::Create(gpios, drivers::displays::kST7735R)); if (sDisplay == nullptr) { return false; } - sDisplay->SetBrightness(nvs->ScreenBrightness().get()); - - sTouchWheel.reset(drivers::TouchWheel::Create()); - if (sTouchWheel != nullptr) { - sRelativeWheel.reset(new drivers::RelativeWheel(sTouchWheel.get())); - } sCurrentScreen.reset(new screens::Splash()); - - // Start the UI task even if init ultimately failed, so that we can show some - // kind of error screen to the user. - StartLvgl(sRelativeWheel, sDisplay); - - if (sTouchWheel == nullptr) { - return false; - } + sTask.reset(UiTask::Start()); return true; } @@ -107,7 +82,8 @@ void UiState::PopScreen() { void UiState::react(const system_fsm::KeyLockChanged& ev) { sDisplay->SetDisplayOn(ev.falling); - sRelativeWheel->SetEnabled(ev.falling); + sTask->SetInputDevice(ev.falling ? sEncoder + : std::shared_ptr<TouchWheelEncoder>()); } void UiState::react(const system_fsm::BatteryStateChanged&) { @@ -115,8 +91,8 @@ void UiState::react(const system_fsm::BatteryStateChanged&) { } void UiState::UpdateTopBar() { - auto battery_state = sBattery->State(); - bool has_queue = sQueue->GetCurrent().has_value(); + auto battery_state = sServices->battery().State(); + bool has_queue = sServices->track_queue().GetCurrent().has_value(); bool is_playing = audio::AudioState::is_in_state<audio::states::Playback>(); widgets::TopBar::State state{ @@ -140,19 +116,40 @@ namespace states { void Splash::exit() { if (sDisplay != nullptr) { - sDisplay->SetDisplayOn(sIGpios->Get(drivers::IGpios::Pin::kKeyLock)); + sDisplay->SetDisplayOn( + sServices->gpios().Get(drivers::IGpios::Pin::kKeyLock)); } } void Splash::react(const system_fsm::BootComplete& ev) { + sServices = ev.services; + + // The system has finished booting! We now need to prepare to show real UI. + // This basically just involves reading and applying the user's preferences. + + lv_theme_t* base_theme = lv_theme_basic_init(NULL); + lv_disp_set_theme(NULL, base_theme); + themes::Theme::instance()->Apply(); + + sDisplay->SetBrightness(sServices->nvs().ScreenBrightness().get()); + + auto touchwheel = sServices->touchwheel(); + if (touchwheel) { + auto relative_wheel = + std::make_unique<drivers::RelativeWheel>(**touchwheel); + sEncoder = std::make_shared<TouchWheelEncoder>(std::move(relative_wheel)); + sTask->SetInputDevice(sEncoder); + } else { + ESP_LOGE(kTag, "no input devices initialised!"); + } + transit<Browse>(); } void Browse::entry() {} void Browse::react(const system_fsm::StorageMounted& ev) { - sDb = ev.db; - auto db = ev.db.lock(); + auto db = sServices->database().lock(); if (!db) { // TODO(jacqueline): Hmm. return; @@ -177,7 +174,7 @@ void Browse::react(const internal::ShowSettingsPage& ev) { screen.reset(new screens::Headphones()); break; case internal::ShowSettingsPage::Page::kAppearance: - screen.reset(new screens::Appearance(sNvs.get(), sDisplay.get())); + screen.reset(new screens::Appearance(sServices->nvs(), *sDisplay)); break; case internal::ShowSettingsPage::Page::kInput: screen.reset(new screens::InputMethod()); @@ -198,7 +195,7 @@ void Browse::react(const internal::ShowSettingsPage& ev) { } void Browse::react(const internal::RecordSelected& ev) { - auto db = sDb.lock(); + auto db = sServices->database().lock(); if (!db) { return; } @@ -206,9 +203,10 @@ void Browse::react(const internal::RecordSelected& ev) { auto record = ev.page->values().at(ev.record); if (record.track()) { ESP_LOGI(kTag, "selected track '%s'", record.text()->c_str()); - sQueue->Clear(); - sQueue->IncludeLast(std::make_shared<playlist::IndexRecordSource>( - sDb, ev.initial_page, 0, ev.page, ev.record)); + auto& queue = sServices->track_queue(); + queue.Clear(); + queue.IncludeLast(std::make_shared<playlist::IndexRecordSource>( + sServices->database(), ev.initial_page, 0, ev.page, ev.record)); transit<Playing>(); } else { ESP_LOGI(kTag, "selected record '%s'", record.text()->c_str()); @@ -218,21 +216,21 @@ void Browse::react(const internal::RecordSelected& ev) { } auto query = db->GetPage(&cont.value()); std::string title = record.text().value_or("TODO"); - PushScreen( - std::make_shared<screens::TrackBrowser>(sDb, title, std::move(query))); + PushScreen(std::make_shared<screens::TrackBrowser>( + sServices->database(), title, std::move(query))); } } void Browse::react(const internal::IndexSelected& ev) { - auto db = sDb.lock(); + auto db = sServices->database().lock(); if (!db) { return; } ESP_LOGI(kTag, "selected index %s", ev.index.name.c_str()); auto query = db->GetTracksByIndex(ev.index, kRecordsPerPage); - PushScreen(std::make_shared<screens::TrackBrowser>(sDb, ev.index.name, - std::move(query))); + PushScreen(std::make_shared<screens::TrackBrowser>( + sServices->database(), ev.index.name, std::move(query))); } void Browse::react(const internal::BackPressed& ev) { @@ -242,7 +240,8 @@ void Browse::react(const internal::BackPressed& ev) { static std::shared_ptr<screens::Playing> sPlayingScreen; void Playing::entry() { - sPlayingScreen.reset(new screens::Playing(sDb, sQueue)); + sPlayingScreen.reset( + new screens::Playing(sServices->database(), sServices->track_queue())); PushScreen(sPlayingScreen); } diff --git a/src/ui/wheel_encoder.cpp b/src/ui/wheel_encoder.cpp index a0e12b7f..2f2e7f68 100644 --- a/src/ui/wheel_encoder.cpp +++ b/src/ui/wheel_encoder.cpp @@ -22,8 +22,8 @@ void encoder_feedback(lv_indev_drv_t* drv, uint8_t event_code) { } TouchWheelEncoder::TouchWheelEncoder( - std::weak_ptr<drivers::RelativeWheel> wheel) - : last_key_(0), wheel_(wheel) { + std::unique_ptr<drivers::RelativeWheel> wheel) + : last_key_(0), wheel_(std::move(wheel)) { lv_indev_drv_init(&driver_); driver_.type = LV_INDEV_TYPE_ENCODER; driver_.read_cb = encoder_read; @@ -34,16 +34,11 @@ TouchWheelEncoder::TouchWheelEncoder( } auto TouchWheelEncoder::Read(lv_indev_data_t* data) -> void { - auto lock = wheel_.lock(); - if (lock == nullptr) { - return; - } + wheel_->Update(); - lock->Update(); - - data->enc_diff = lock->ticks(); + data->enc_diff = wheel_->ticks(); data->state = - lock->is_clicking() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; } } // namespace ui |
