diff options
Diffstat (limited to 'src/ui/ui_fsm.cpp')
| -rw-r--r-- | src/ui/ui_fsm.cpp | 693 |
1 files changed, 0 insertions, 693 deletions
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp deleted file mode 100644 index 1cbf1be4..00000000 --- a/src/ui/ui_fsm.cpp +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "ui_fsm.hpp" - -#include <memory> -#include <memory_resource> -#include <variant> - -#include "bluetooth_types.hpp" -#include "db_events.hpp" -#include "device_factory.hpp" -#include "display_init.hpp" -#include "esp_spp_api.h" -#include "feedback_haptics.hpp" -#include "freertos/portmacro.h" -#include "freertos/projdefs.h" -#include "input_device.hpp" -#include "input_touch_wheel.hpp" -#include "input_volume_buttons.hpp" -#include "lua.h" -#include "lua.hpp" - -#include "audio_fsm.hpp" -#include "battery.hpp" -#include "core/lv_group.h" -#include "core/lv_obj.h" -#include "core/lv_obj_tree.h" -#include "database.hpp" -#include "esp_heap_caps.h" -#include "esp_timer.h" -#include "haptics.hpp" -#include "lauxlib.h" -#include "lua_thread.hpp" -#include "luavgl.h" -#include "lvgl_input_driver.hpp" -#include "memory_resource.hpp" -#include "misc/lv_gc.h" - -#include "audio_events.hpp" -#include "display.hpp" -#include "event_queue.hpp" -#include "gpios.hpp" -#include "lua_registry.hpp" -#include "lvgl_task.hpp" -#include "nvs.hpp" -#include "property.hpp" -#include "samd.hpp" -#include "screen.hpp" -#include "screen_lua.hpp" -#include "screen_splash.hpp" -#include "spiffs.hpp" -#include "storage.hpp" -#include "system_events.hpp" -#include "tinyfsm.hpp" -#include "touchwheel.hpp" -#include "track_queue.hpp" -#include "ui_events.hpp" -#include "widgets/lv_label.h" - -namespace ui { - -[[maybe_unused]] static constexpr char kTag[] = "ui_fsm"; - -std::unique_ptr<UiTask> UiState::sTask; -std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices; -std::unique_ptr<drivers::Display> UiState::sDisplay; - -std::shared_ptr<input::LvglInputDriver> UiState::sInput; -std::unique_ptr<input::DeviceFactory> UiState::sDeviceFactory; - -std::stack<std::shared_ptr<Screen>> UiState::sScreens; -std::shared_ptr<Screen> UiState::sCurrentScreen; -std::shared_ptr<Modal> UiState::sCurrentModal; -std::shared_ptr<lua::LuaThread> UiState::sLua; - -static TimerHandle_t sAlertTimer; -static lv_obj_t* sAlertContainer; - -static void alert_timer_callback(TimerHandle_t timer) { - events::Ui().Dispatch(internal::DismissAlerts{}); -} - -lua::Property UiState::sBatteryPct{0}; -lua::Property UiState::sBatteryMv{0}; -lua::Property UiState::sBatteryCharging{false}; - -lua::Property UiState::sBluetoothEnabled{ - false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - if (std::get<bool>(val)) { - sServices->nvs().OutputMode(drivers::NvsStorage::Output::kBluetooth); - sServices->bluetooth().Enable(); - } else { - sServices->nvs().OutputMode(drivers::NvsStorage::Output::kHeadphones); - sServices->bluetooth().Disable(); - } - events::Audio().Dispatch(audio::OutputModeChanged{}); - return true; - }}; - -lua::Property UiState::sBluetoothConnected{false}; -lua::Property UiState::sBluetoothPairedDevice{ - std::monostate{}, [](const lua::LuaValue& val) { - if (std::holds_alternative<drivers::bluetooth::Device>(val)) { - auto dev = std::get<drivers::bluetooth::Device>(val); - sServices->bluetooth().SetPreferredDevice( - drivers::bluetooth::MacAndName{ - .mac = dev.address, - .name = {dev.name.data(), dev.name.size()}, - }); - } - return false; - }}; -lua::Property UiState::sBluetoothDevices{ - std::vector<drivers::bluetooth::Device>{}}; - -lua::Property UiState::sPlaybackPlaying{ - false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool new_val = std::get<bool>(val); - events::Audio().Dispatch(audio::TogglePlayPause{.set_to = new_val}); - return true; - }}; - -lua::Property UiState::sPlaybackTrack{}; -lua::Property UiState::sPlaybackPosition{ - 0, [](const lua::LuaValue& val) { - int current_val = std::get<int>(sPlaybackPosition.get()); - if (!std::holds_alternative<int>(val)) { - return false; - } - int new_val = std::get<int>(val); - if (current_val != new_val) { - auto track = sPlaybackTrack.get(); - if (!std::holds_alternative<audio::TrackInfo>(track)) { - return false; - } - events::Audio().Dispatch(audio::SetTrack{ - .new_track = std::get<audio::TrackInfo>(track).uri, - .seek_to_second = (uint32_t)new_val, - }); - } - return true; - }}; - -lua::Property UiState::sQueuePosition{0}; -lua::Property UiState::sQueueSize{0}; -lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool new_val = std::get<bool>(val); - sServices->track_queue().repeat(new_val); - return true; - }}; -lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool new_val = std::get<bool>(val); - sServices->track_queue().replay(new_val); - return true; - }}; -lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool new_val = std::get<bool>(val); - sServices->track_queue().random(new_val); - return true; - }}; - -lua::Property UiState::sVolumeCurrentPct{ - 0, [](const lua::LuaValue& val) { - if (!std::holds_alternative<int>(val)) { - return false; - } - events::Audio().Dispatch(audio::SetVolume{ - .percent = std::get<int>(val), - .db = {}, - }); - return true; - }}; -lua::Property UiState::sVolumeCurrentDb{ - 0, [](const lua::LuaValue& val) { - if (!std::holds_alternative<int>(val)) { - return false; - } - events::Audio().Dispatch(audio::SetVolume{ - .percent = {}, - .db = std::get<int>(val), - }); - return true; - }}; -lua::Property UiState::sVolumeLeftBias{ - 0, [](const lua::LuaValue& val) { - if (!std::holds_alternative<int>(val)) { - return false; - } - events::Audio().Dispatch(audio::SetVolumeBalance{ - .left_bias = std::get<int>(val), - }); - return true; - }}; -lua::Property UiState::sVolumeLimit{ - 0, [](const lua::LuaValue& val) { - if (!std::holds_alternative<int>(val)) { - return false; - } - int limit = std::get<int>(val); - events::Audio().Dispatch(audio::SetVolumeLimit{ - .limit_db = limit, - }); - return true; - }}; - -lua::Property UiState::sDisplayBrightness{ - 0, [](const lua::LuaValue& val) { - std::optional<int> brightness = 0; - std::visit( - [&](auto&& v) { - using T = std::decay_t<decltype(v)>; - if constexpr (std::is_same_v<T, int>) { - brightness = v; - } - }, - val); - if (!brightness) { - return false; - } - sDisplay->SetBrightness(*brightness); - sServices->nvs().ScreenBrightness(*brightness); - return true; - }}; - -lua::Property UiState::sLockSwitch{false}; - -lua::Property UiState::sDatabaseUpdating{false}; -lua::Property UiState::sDatabaseAutoUpdate{ - false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - sServices->nvs().DbAutoIndex(std::get<bool>(val)); - return true; - }}; - -lua::Property UiState::sUsbMassStorageEnabled{ - false, [](const lua::LuaValue& val) { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool enable = std::get<bool>(val); - // FIXME: Check for system busy. - events::System().Dispatch(system_fsm::SamdUsbMscChanged{.en = enable}); - return true; - }}; - -lua::Property UiState::sUsbMassStorageBusy{false}; - -auto UiState::InitBootSplash(drivers::IGpios& gpios, drivers::NvsStorage& nvs) - -> bool { - events::Ui().Dispatch(internal::InitDisplay{ - .gpios = gpios, - .nvs = nvs, - }); - sTask.reset(UiTask::Start()); - return true; -} - -void UiState::react(const internal::InitDisplay& ev) { - // Init LVGL first, since the display driver registers itself with LVGL. - lv_init(); - - drivers::displays::InitialisationData init_data = drivers::displays::kST7735R; - - // HACK: correct the display size for our prototypes. - // nvs.DisplaySize({161, 130}); - - auto actual_size = ev.nvs.DisplaySize(); - init_data.width = actual_size.first.value_or(init_data.width); - init_data.height = actual_size.second.value_or(init_data.height); - sDisplay.reset(drivers::Display::Create(ev.gpios, init_data)); - - sCurrentScreen.reset(new screens::Splash()); - - // Display will only actually come on after LVGL finishes its first flush. - sDisplay->SetDisplayOn(!ev.gpios.IsLocked()); -} - -void UiState::PushScreen(std::shared_ptr<Screen> screen) { - lv_obj_set_parent(sAlertContainer, screen->alert()); - - if (sCurrentScreen) { - sCurrentScreen->onHidden(); - sScreens.push(sCurrentScreen); - } - sCurrentScreen = screen; - sCurrentScreen->onShown(); -} - -int UiState::PopScreen() { - if (sScreens.empty()) { - return 0; - } - lv_obj_set_parent(sAlertContainer, sScreens.top()->alert()); - - sCurrentScreen->onHidden(); - - sCurrentScreen = sScreens.top(); - sScreens.pop(); - - sCurrentScreen->onShown(); - - return sScreens.size(); -} - -void UiState::react(const system_fsm::KeyLockChanged& ev) { - sDisplay->SetDisplayOn(!ev.locking); - sInput->lock(ev.locking); - sLockSwitch.setDirect(ev.locking); -} - -void UiState::react(const system_fsm::SamdUsbStatusChanged& ev) { - sUsbMassStorageBusy.setDirect(ev.new_status == - drivers::Samd::UsbStatus::kAttachedBusy); -} - -void UiState::react(const database::event::UpdateStarted&) { - sDatabaseUpdating.setDirect(true); -} - -void UiState::react(const database::event::UpdateFinished&) { - sDatabaseUpdating.setDirect(false); -} - -void UiState::react(const internal::DismissAlerts&) { - lv_obj_clean(sAlertContainer); -} - -void UiState::react(const system_fsm::BatteryStateChanged& ev) { - sBatteryPct.setDirect(static_cast<int>(ev.new_state.percent)); - sBatteryMv.setDirect(static_cast<int>(ev.new_state.millivolts)); - sBatteryCharging.setDirect(ev.new_state.is_charging); -} - -void UiState::react(const audio::QueueUpdate&) { - auto& queue = sServices->track_queue(); - sQueueSize.setDirect(static_cast<int>(queue.totalSize())); - - int current_pos = queue.currentPosition(); - if (queue.current()) { - current_pos++; - } - sQueuePosition.setDirect(current_pos); - sQueueRandom.setDirect(queue.random()); - sQueueRepeat.setDirect(queue.repeat()); - sQueueReplay.setDirect(queue.replay()); -} - -void UiState::react(const audio::PlaybackUpdate& ev) { - if (ev.current_track) { - sPlaybackTrack.setDirect(*ev.current_track); - } else { - sPlaybackTrack.setDirect(std::monostate{}); - } - sPlaybackPlaying.setDirect(!ev.paused); - sPlaybackPosition.setDirect(static_cast<int>(ev.track_position.value_or(0))); -} - -void UiState::react(const audio::VolumeChanged& ev) { - sVolumeCurrentPct.setDirect(static_cast<int>(ev.percent)); - sVolumeCurrentDb.setDirect(static_cast<int>(ev.db)); -} - -void UiState::react(const audio::VolumeBalanceChanged& ev) { - sVolumeLeftBias.setDirect(ev.left_bias); -} - -void UiState::react(const audio::VolumeLimitChanged& ev) { - sVolumeLimit.setDirect(ev.new_limit_db); -} - -void UiState::react(const system_fsm::BluetoothEvent& ev) { - auto bt = sServices->bluetooth(); - auto dev = bt.ConnectedDevice(); - switch (ev.event) { - case drivers::bluetooth::Event::kKnownDevicesChanged: - sBluetoothDevices.setDirect(bt.KnownDevices()); - break; - case drivers::bluetooth::Event::kConnectionStateChanged: - sBluetoothConnected.setDirect(bt.IsConnected()); - if (dev) { - sBluetoothPairedDevice.setDirect(drivers::bluetooth::Device{ - .address = dev->mac, - .name = {dev->name.data(), dev->name.size()}, - .class_of_device = 0, - .signal_strength = 0, - }); - } else { - sBluetoothPairedDevice.setDirect(std::monostate{}); - } - break; - case drivers::bluetooth::Event::kPreferredDeviceChanged: - break; - } -} - -namespace states { - -void Splash::exit() { - // buzz a bit to tell the user we're done booting - events::System().Dispatch(system_fsm::HapticTrigger{ - .effect = drivers::Haptics::Effect::kLongDoubleSharpTick1_100Pct, - }); -} - -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(); - - int brightness = sServices->nvs().ScreenBrightness(); - sDisplayBrightness.setDirect(brightness); - sDisplay->SetBrightness(brightness); - - sDeviceFactory = std::make_unique<input::DeviceFactory>(sServices); - sInput = std::make_shared<input::LvglInputDriver>(sServices->nvs(), - *sDeviceFactory); - - sTask->input(sInput); -} - -void Splash::react(const system_fsm::StorageMounted&) { - transit<Lua>(); -} - -void Lua::entry() { - if (!sLua) { - sAlertTimer = xTimerCreate("ui_alerts", pdMS_TO_TICKS(1000), false, NULL, - alert_timer_callback); - sAlertContainer = lv_obj_create(sCurrentScreen->alert()); - lv_obj_set_style_bg_opa(sAlertContainer, LV_OPA_TRANSP, 0); - - auto& registry = lua::Registry::instance(*sServices); - sLua = registry.uiThread(); - registry.AddPropertyModule("power", { - {"battery_pct", &sBatteryPct}, - {"battery_millivolts", &sBatteryMv}, - {"plugged_in", &sBatteryCharging}, - }); - registry.AddPropertyModule("bluetooth", - { - {"enabled", &sBluetoothEnabled}, - {"connected", &sBluetoothConnected}, - {"paired_device", &sBluetoothPairedDevice}, - {"devices", &sBluetoothDevices}, - }); - registry.AddPropertyModule("playback", { - {"playing", &sPlaybackPlaying}, - {"track", &sPlaybackTrack}, - {"position", &sPlaybackPosition}, - }); - registry.AddPropertyModule( - "queue", - { - {"next", [&](lua_State* s) { return QueueNext(s); }}, - {"previous", [&](lua_State* s) { return QueuePrevious(s); }}, - {"position", &sQueuePosition}, - {"size", &sQueueSize}, - {"replay", &sQueueReplay}, - {"repeat_track", &sQueueRepeat}, - {"random", &sQueueRandom}, - }); - registry.AddPropertyModule("volume", - { - {"current_pct", &sVolumeCurrentPct}, - {"current_db", &sVolumeCurrentDb}, - {"left_bias", &sVolumeLeftBias}, - {"limit_db", &sVolumeLimit}, - }); - - registry.AddPropertyModule("display", - { - {"brightness", &sDisplayBrightness}, - }); - - registry.AddPropertyModule( - "controls", - { - {"scheme", &sInput->mode()}, - {"lock_switch", &sLockSwitch}, - {"hooks", [&](lua_State* L) { return sInput->pushHooks(L); }}, - }); - - if (sDeviceFactory->touch_wheel()) { - registry.AddPropertyModule( - "controls", {{"scroll_sensitivity", - &sDeviceFactory->touch_wheel()->sensitivity()}}); - } - - registry.AddPropertyModule( - "backstack", - { - {"push", [&](lua_State* s) { return PushLuaScreen(s); }}, - {"pop", [&](lua_State* s) { return PopLuaScreen(s); }}, - }); - registry.AddPropertyModule( - "alerts", { - {"show", [&](lua_State* s) { return ShowAlert(s); }}, - {"hide", [&](lua_State* s) { return HideAlert(s); }}, - }); - - registry.AddPropertyModule( - "time", { - {"ticks", [&](lua_State* s) { return Ticks(s); }}, - }); - registry.AddPropertyModule("database", - { - {"updating", &sDatabaseUpdating}, - {"auto_update", &sDatabaseAutoUpdate}, - }); - registry.AddPropertyModule("usb", - { - {"msc_enabled", &sUsbMassStorageEnabled}, - {"msc_busy", &sUsbMassStorageBusy}, - }); - - sDatabaseAutoUpdate.setDirect(sServices->nvs().DbAutoIndex()); - - auto bt = sServices->bluetooth(); - sBluetoothEnabled.setDirect(bt.IsEnabled()); - sBluetoothConnected.setDirect(bt.IsConnected()); - sBluetoothDevices.setDirect(bt.KnownDevices()); - - sCurrentScreen.reset(); - sLua->RunScript("/sdcard/config.lua"); - sLua->RunScript("/lua/main.lua"); - } -} - -auto Lua::PushLuaScreen(lua_State* s) -> int { - // Ensure the arg looks right before continuing. - luaL_checktype(s, 1, LUA_TTABLE); - - // First, create a new plain old Screen object. We will use its root and - // group for the Lua screen. Allocate it in external ram so that arbitrarily - // deep screen stacks don't cause too much memory pressure. - auto new_screen = - std::allocate_shared<screens::Lua, - std::pmr::polymorphic_allocator<screens::Lua>>( - &memory::kSpiRamResource); - - // Tell lvgl about the new roots. - luavgl_set_root(s, new_screen->content()); - lv_group_set_default(new_screen->group()); - - // Call the constructor for this screen. - // lua_settop(s, 1); // Make sure the screen is actually at top of stack - lua_pushliteral(s, "createUi"); - if (lua_gettable(s, 1) == LUA_TFUNCTION) { - lua_pushvalue(s, 1); - lua::CallProtected(s, 1, 0); - } - - // Store the reference for this screen's table. - lua_settop(s, 1); - new_screen->SetObjRef(s); - - // Finally, push the now-initialised screen as if it were a regular C++ - // screen. - PushScreen(new_screen); - - return 0; -} - -auto Lua::QueueNext(lua_State*) -> int { - sServices->track_queue().next(); - return 0; -} - -auto Lua::QueuePrevious(lua_State*) -> int { - sServices->track_queue().previous(); - return 0; -} - -auto Lua::PopLuaScreen(lua_State* s) -> int { - if (!sCurrentScreen->canPop()) { - return 0; - } - PopScreen(); - luavgl_set_root(s, sCurrentScreen->content()); - lv_group_set_default(sCurrentScreen->group()); - return 0; -} - -auto Lua::Ticks(lua_State* s) -> int { - lua_pushinteger(s, esp_timer_get_time() / 1000); - return 1; -} - -auto Lua::ShowAlert(lua_State* s) -> int { - if (!sCurrentScreen) { - return 0; - } - xTimerReset(sAlertTimer, portMAX_DELAY); - tinyfsm::FsmList<UiState>::dispatch(internal::DismissAlerts{}); - - lv_group_t* prev_group = lv_group_get_default(); - - luavgl_set_root(s, sAlertContainer); - lv_group_t* catchall = lv_group_create(); - lv_group_set_default(catchall); - - // Call the constructor for the alert. - lua_settop(s, 1); // Make sure the function is actually at top of stack - lua::CallProtected(s, 0, 1); - - // Restore the previous group and default object. - luavgl_set_root(s, sCurrentScreen->content()); - lv_group_set_default(prev_group); - - lv_group_del(catchall); - - return 0; -} - -auto Lua::HideAlert(lua_State* s) -> int { - xTimerStop(sAlertTimer, portMAX_DELAY); - tinyfsm::FsmList<UiState>::dispatch(internal::DismissAlerts{}); - return 0; -} - -auto Lua::SetRandom(const lua::LuaValue& val) -> bool { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool b = std::get<bool>(val); - sServices->track_queue().random(b); - return true; -} - -auto Lua::SetRepeat(const lua::LuaValue& val) -> bool { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool b = std::get<bool>(val); - sServices->track_queue().repeat(b); - return true; -} - -auto Lua::SetReplay(const lua::LuaValue& val) -> bool { - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool b = std::get<bool>(val); - sServices->track_queue().replay(b); - return true; -} - -void Lua::exit() { - lv_group_set_default(NULL); -} - -void Lua::react(const OnLuaError& err) { - ESP_LOGE("lua", "%s", err.message.c_str()); -} - -void Lua::react(const DumpLuaStack& ev) { - sLua->DumpStack(); -} - -void Lua::react(const internal::BackPressed& ev) { - PopLuaScreen(sLua->state()); -} - -} // namespace states -} // namespace ui - -FSM_INITIAL_STATE(ui::UiState, ui::states::Splash) |
