add an alerts module for lua, and implement a volume indicator with it

custom
jacqueline 1 year ago
parent 938ba62f57
commit 34cae4e6e4
  1. 34
      lua/main.lua
  2. 12
      src/audio/audio_fsm.cpp
  3. 1
      src/audio/i2s_audio_output.cpp
  4. 5
      src/audio/include/audio_events.hpp
  5. 13
      src/lua/stubs/alerts.lua
  6. 14
      src/lua/stubs/volume.lua
  7. 2
      src/ui/include/screen.hpp
  8. 8
      src/ui/include/ui_events.hpp
  9. 11
      src/ui/include/ui_fsm.hpp
  10. 4
      src/ui/screen.cpp
  11. 72
      src/ui/ui_fsm.cpp

@ -1,4 +1,36 @@
require("font") local font = require("font")
local vol = require("volume")
-- Set up property bindings that are used across every screen.
GLOBAL_BINDINGS = {
vol.current_pct:bind(function(pct)
require("alerts").show(function()
local container = lvgl.Object(nil, {
w = lvgl.PCT(80),
h = lvgl.SIZE_CONTENT,
flex = {
flex_direction = "column",
justify_content = "center",
align_items = "center",
align_content = "center",
},
bg_opa = lvgl.OPA(100),
bg_color = "#fafafa",
})
container:Label {
text = string.format("Volume %i%%", pct),
text_font = font.fusion_10
}
container:Bar {
w = lvgl.PCT(100),
h = 8,
range = { min = 0, max = 100 },
value = pct,
}
container:center()
end)
end),
}
local backstack = require("backstack") local backstack = require("backstack")
local main_menu = require("main_menu") local main_menu = require("main_menu")

@ -58,13 +58,19 @@ void AudioState::react(const system_fsm::KeyLockChanged& ev) {
void AudioState::react(const StepUpVolume& ev) { void AudioState::react(const StepUpVolume& ev) {
if (sOutput->AdjustVolumeUp()) { if (sOutput->AdjustVolumeUp()) {
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
});
} }
} }
void AudioState::react(const StepDownVolume& ev) { void AudioState::react(const StepDownVolume& ev) {
if (sOutput->AdjustVolumeDown()) { if (sOutput->AdjustVolumeDown()) {
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
});
} }
} }
@ -77,7 +83,7 @@ void AudioState::react(const system_fsm::HasPhonesChanged& ev) {
} }
void AudioState::react(const ChangeMaxVolume& ev) { void AudioState::react(const ChangeMaxVolume& ev) {
ESP_LOGI(kTag, "new max volume %u db", ESP_LOGI(kTag, "new max volume %i db",
(ev.new_max - drivers::wm8523::kLineLevelReferenceVolume) / 4); (ev.new_max - drivers::wm8523::kLineLevelReferenceVolume) / 4);
sI2SOutput->SetMaxVolume(ev.new_max); sI2SOutput->SetMaxVolume(ev.new_max);
sServices->nvs().AmpMaxVolume(ev.new_max); sServices->nvs().AmpMaxVolume(ev.new_max);

@ -93,7 +93,6 @@ auto I2SAudioOutput::SetMaxVolume(uint16_t max) -> void {
auto I2SAudioOutput::SetVolume(uint16_t vol) -> void { auto I2SAudioOutput::SetVolume(uint16_t vol) -> void {
current_volume_ = std::clamp(vol, kMinVolume, max_volume_); current_volume_ = std::clamp(vol, kMinVolume, max_volume_);
ESP_LOGI(kTag, "set volume to %u%% = %idB", GetVolumePct(), GetVolumeDb());
int32_t left_unclamped = current_volume_ + left_difference_; int32_t left_unclamped = current_volume_ + left_difference_;
uint16_t left = std::clamp<int32_t>(left_unclamped, kMinVolume, max_volume_); uint16_t left = std::clamp<int32_t>(left_unclamped, kMinVolume, max_volume_);

@ -47,7 +47,10 @@ struct PlayFile : tinyfsm::Event {
struct StepUpVolume : tinyfsm::Event {}; struct StepUpVolume : tinyfsm::Event {};
struct StepDownVolume : tinyfsm::Event {}; struct StepDownVolume : tinyfsm::Event {};
struct VolumeChanged : tinyfsm::Event {}; struct VolumeChanged : tinyfsm::Event {
uint_fast8_t percent;
int db;
};
struct ChangeMaxVolume : tinyfsm::Event { struct ChangeMaxVolume : tinyfsm::Event {
uint16_t new_max; uint16_t new_max;
}; };

@ -0,0 +1,13 @@
--- Module for interacting with playback volume. The Bluetooth and wired outputs store their current volume separately; this API only allows interacting with the volume of the currently used output device.
-- @module alerts
local alerts = {}
--- Returns the current volume as a percentage of the current volume limit.
-- @tparam function constructor Called to create the UI for the alert. A new default root object and group will be set before calling this function.i Alerts are non-interactable; the group created for the constructor will not be granted focus.
function alerts.show(constructor) end
--- Dismisses any visible alerts, removing them from the screen.
function alerts.hide() end
return alerts

@ -0,0 +1,14 @@
--- Module for interacting with playback volume. The Bluetooth and wired outputs store their current volume separately; this API only allows interacting with the volume of the currently used output device.
-- @module volume
local volume = {}
--- Returns the current volume as a percentage of the current volume limit.
-- @treturn types.Property an integer property
function volume.current_pct() end
--- Returns the current volume in terms of dB from line level.
-- @treturn types.Property an integer property
function volume.current_db() end
return volume

@ -40,6 +40,7 @@ class Screen {
auto root() -> lv_obj_t* { return root_; } auto root() -> lv_obj_t* { return root_; }
auto content() -> lv_obj_t* { return content_; } auto content() -> lv_obj_t* { return content_; }
auto alert() -> lv_obj_t* { return alert_; }
auto modal_content() -> lv_obj_t* { return modal_content_; } auto modal_content() -> lv_obj_t* { return modal_content_; }
auto modal_group(lv_group_t* g) -> void { modal_group_ = g; } auto modal_group(lv_group_t* g) -> void { modal_group_ = g; }
@ -68,6 +69,7 @@ class Screen {
lv_obj_t* const root_; lv_obj_t* const root_;
lv_obj_t* content_; lv_obj_t* content_;
lv_obj_t* modal_content_; lv_obj_t* modal_content_;
lv_obj_t* alert_;
lv_group_t* const group_; lv_group_t* const group_;
lv_group_t* modal_group_; lv_group_t* modal_group_;

@ -24,9 +24,9 @@ struct OnStorageChange : tinyfsm::Event {
struct OnSystemError : tinyfsm::Event {}; struct OnSystemError : tinyfsm::Event {};
struct OnLuaError : tinyfsm::Event { struct OnLuaError : tinyfsm::Event {
std::string message; std::string message;
}; };
namespace internal { namespace internal {
@ -54,6 +54,8 @@ struct OnboardingNavigate : tinyfsm::Event {
struct ModalConfirmPressed : tinyfsm::Event {}; struct ModalConfirmPressed : tinyfsm::Event {};
struct ModalCancelPressed : tinyfsm::Event {}; struct ModalCancelPressed : tinyfsm::Event {};
struct DismissAlerts : tinyfsm::Event {};
} // namespace internal } // namespace internal
} // namespace ui } // namespace ui

@ -60,6 +60,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
virtual void react(const audio::PlaybackFinished&); virtual void react(const audio::PlaybackFinished&);
virtual void react(const audio::PlaybackUpdate&); virtual void react(const audio::PlaybackUpdate&);
virtual void react(const audio::QueueUpdate&); virtual void react(const audio::QueueUpdate&);
virtual void react(const audio::VolumeChanged&){};
virtual void react(const system_fsm::KeyLockChanged&); virtual void react(const system_fsm::KeyLockChanged&);
virtual void react(const OnLuaError&) {} virtual void react(const OnLuaError&) {}
@ -76,6 +77,8 @@ class UiState : public tinyfsm::Fsm<UiState> {
void react(const internal::ControlSchemeChanged&); void react(const internal::ControlSchemeChanged&);
virtual void react(const internal::ReindexDatabase&){}; virtual void react(const internal::ReindexDatabase&){};
void react(const internal::DismissAlerts&);
virtual void react(const database::event::UpdateStarted&){}; virtual void react(const database::event::UpdateStarted&){};
virtual void react(const database::event::UpdateProgress&){}; virtual void react(const database::event::UpdateProgress&){};
virtual void react(const database::event::UpdateFinished&){}; virtual void react(const database::event::UpdateFinished&){};
@ -129,6 +132,7 @@ class Lua : public UiState {
void react(const audio::PlaybackStarted&) override; void react(const audio::PlaybackStarted&) override;
void react(const audio::PlaybackUpdate&) override; void react(const audio::PlaybackUpdate&) override;
void react(const audio::PlaybackFinished&) override; void react(const audio::PlaybackFinished&) override;
void react(const audio::VolumeChanged&) override;
void react(const internal::BackPressed&) override; void react(const internal::BackPressed&) override;
using UiState::react; using UiState::react;
@ -136,6 +140,10 @@ class Lua : public UiState {
private: private:
auto PushLuaScreen(lua_State*) -> int; auto PushLuaScreen(lua_State*) -> int;
auto PopLuaScreen(lua_State*) -> int; auto PopLuaScreen(lua_State*) -> int;
auto ShowAlert(lua_State*) -> int;
auto HideAlert(lua_State*) -> int;
auto SetPlaying(const lua::LuaValue&) -> bool; auto SetPlaying(const lua::LuaValue&) -> bool;
auto SetRandom(const lua::LuaValue&) -> bool; auto SetRandom(const lua::LuaValue&) -> bool;
auto SetRepeat(const lua::LuaValue&) -> bool; auto SetRepeat(const lua::LuaValue&) -> bool;
@ -154,6 +162,9 @@ class Lua : public UiState {
std::shared_ptr<lua::Property> queue_size_; std::shared_ptr<lua::Property> queue_size_;
std::shared_ptr<lua::Property> queue_repeat_; std::shared_ptr<lua::Property> queue_repeat_;
std::shared_ptr<lua::Property> queue_random_; std::shared_ptr<lua::Property> queue_random_;
std::shared_ptr<lua::Property> volume_current_pct_;
std::shared_ptr<lua::Property> volume_current_db_;
}; };
class Browse : public UiState { class Browse : public UiState {

@ -10,6 +10,7 @@
#include "core/lv_obj_pos.h" #include "core/lv_obj_pos.h"
#include "core/lv_obj_tree.h" #include "core/lv_obj_tree.h"
#include "hal/lv_hal_disp.h"
#include "misc/lv_area.h" #include "misc/lv_area.h"
#include "misc/lv_color.h" #include "misc/lv_color.h"
#include "model_top_bar.hpp" #include "model_top_bar.hpp"
@ -21,14 +22,17 @@ Screen::Screen()
: root_(lv_obj_create(NULL)), : root_(lv_obj_create(NULL)),
content_(lv_obj_create(root_)), content_(lv_obj_create(root_)),
modal_content_(lv_obj_create(root_)), modal_content_(lv_obj_create(root_)),
alert_(lv_obj_create(root_)),
group_(lv_group_create()), group_(lv_group_create()),
modal_group_(nullptr) { modal_group_(nullptr) {
lv_obj_set_size(root_, lv_pct(100), lv_pct(100)); lv_obj_set_size(root_, lv_pct(100), lv_pct(100));
lv_obj_set_size(content_, lv_pct(100), lv_pct(100)); lv_obj_set_size(content_, lv_pct(100), lv_pct(100));
lv_obj_set_size(modal_content_, lv_pct(100), lv_pct(100)); lv_obj_set_size(modal_content_, lv_pct(100), lv_pct(100));
lv_obj_set_size(alert_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_center(root_); lv_obj_center(root_);
lv_obj_center(content_); lv_obj_center(content_);
lv_obj_center(modal_content_); lv_obj_center(modal_content_);
lv_obj_center(alert_);
lv_obj_set_style_bg_opa(modal_content_, LV_OPA_TRANSP, 0); lv_obj_set_style_bg_opa(modal_content_, LV_OPA_TRANSP, 0);
lv_obj_set_style_bg_color(modal_content_, lv_color_black(), 0); lv_obj_set_style_bg_color(modal_content_, lv_color_black(), 0);

@ -9,6 +9,8 @@
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
#include "lua.h" #include "lua.h"
#include "lua.hpp" #include "lua.hpp"
@ -44,6 +46,7 @@
#include "spiffs.hpp" #include "spiffs.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "system_events.hpp" #include "system_events.hpp"
#include "tinyfsm.hpp"
#include "touchwheel.hpp" #include "touchwheel.hpp"
#include "track_queue.hpp" #include "track_queue.hpp"
#include "ui_events.hpp" #include "ui_events.hpp"
@ -70,6 +73,13 @@ models::TopBar UiState::sTopBarModel{{},
UiState::sPlaybackModel.is_playing, UiState::sPlaybackModel.is_playing,
UiState::sPlaybackModel.current_track}; UiState::sPlaybackModel.current_track};
static TimerHandle_t sAlertTimer;
static lv_obj_t* sAlertContainer;
static void alert_timer_callback(TimerHandle_t timer) {
events::Ui().Dispatch(internal::DismissAlerts{});
}
auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool { auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
// Init LVGL first, since the display driver registers itself with LVGL. // Init LVGL first, since the display driver registers itself with LVGL.
lv_init(); lv_init();
@ -89,6 +99,7 @@ void UiState::PushScreen(std::shared_ptr<Screen> screen) {
sScreens.push(sCurrentScreen); sScreens.push(sCurrentScreen);
} }
sCurrentScreen = screen; sCurrentScreen = screen;
lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
} }
int UiState::PopScreen() { int UiState::PopScreen() {
@ -96,6 +107,8 @@ int UiState::PopScreen() {
return 0; return 0;
} }
sCurrentScreen = sScreens.top(); sCurrentScreen = sScreens.top();
lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
sScreens.pop(); sScreens.pop();
return sScreens.size(); return sScreens.size();
} }
@ -127,6 +140,10 @@ void UiState::react(const internal::ControlSchemeChanged&) {
sInput->mode(sServices->nvs().PrimaryInput()); sInput->mode(sServices->nvs().PrimaryInput());
} }
void UiState::react(const internal::DismissAlerts&) {
lv_obj_clean(sAlertContainer);
}
namespace states { namespace states {
void Splash::exit() { void Splash::exit() {
@ -164,6 +181,10 @@ void Splash::react(const system_fsm::StorageMounted&) {
void Lua::entry() { void Lua::entry() {
if (!sLua) { if (!sLua) {
sAlertTimer = xTimerCreate("ui_alerts", pdMS_TO_TICKS(1000), false, NULL,
alert_timer_callback);
sAlertContainer = lv_obj_create(sCurrentScreen->alert());
auto bat = auto bat =
sServices->battery().State().value_or(battery::Battery::BatteryState{}); sServices->battery().State().value_or(battery::Battery::BatteryState{});
battery_pct_ = battery_pct_ =
@ -184,6 +205,9 @@ void Lua::entry() {
playback_track_ = std::make_shared<lua::Property>(); playback_track_ = std::make_shared<lua::Property>();
playback_position_ = std::make_shared<lua::Property>(); playback_position_ = std::make_shared<lua::Property>();
volume_current_pct_ = std::make_shared<lua::Property>(0);
volume_current_db_ = std::make_shared<lua::Property>(0);
sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content())); sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content()));
sLua->bridge().AddPropertyModule("power", sLua->bridge().AddPropertyModule("power",
{ {
@ -208,12 +232,23 @@ void Lua::entry() {
{"replay", queue_repeat_}, {"replay", queue_repeat_},
{"random", queue_random_}, {"random", queue_random_},
}); });
sLua->bridge().AddPropertyModule("volume",
{
{"current_pct", volume_current_pct_},
{"current_db", volume_current_db_},
});
sLua->bridge().AddPropertyModule( sLua->bridge().AddPropertyModule(
"backstack", "backstack",
{ {
{"push", [&](lua_State* s) { return PushLuaScreen(s); }}, {"push", [&](lua_State* s) { return PushLuaScreen(s); }},
{"pop", [&](lua_State* s) { return PopLuaScreen(s); }}, {"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
}); });
sLua->bridge().AddPropertyModule(
"alerts", {
{"show", [&](lua_State* s) { return ShowAlert(s); }},
{"hide", [&](lua_State* s) { return HideAlert(s); }},
});
sCurrentScreen.reset(); sCurrentScreen.reset();
sLua->RunScript("/lua/main.lua"); sLua->RunScript("/lua/main.lua");
@ -265,6 +300,38 @@ auto Lua::SetPlaying(const lua::LuaValue& val) -> bool {
return true; return true;
} }
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 { auto Lua::SetRandom(const lua::LuaValue& val) -> bool {
if (!std::holds_alternative<bool>(val)) { if (!std::holds_alternative<bool>(val)) {
return false; return false;
@ -327,6 +394,11 @@ void Lua::react(const audio::PlaybackFinished&) {
playback_playing_->Update(false); playback_playing_->Update(false);
} }
void Lua::react(const audio::VolumeChanged& ev) {
volume_current_pct_->Update(static_cast<int>(ev.percent));
volume_current_db_->Update(static_cast<int>(ev.db));
}
void Lua::react(const internal::BackPressed& ev) { void Lua::react(const internal::BackPressed& ev) {
PopLuaScreen(sLua->state()); PopLuaScreen(sLua->state());
} }

Loading…
Cancel
Save