summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app_console/app_console.cpp8
-rw-r--r--src/audio/audio_fsm.cpp28
-rw-r--r--src/audio/track_queue.cpp57
-rw-r--r--src/database/database.cpp7
-rw-r--r--src/database/file_gatherer.cpp10
-rw-r--r--src/database/include/file_gatherer.hpp4
-rw-r--r--src/database/include/tag_parser.hpp6
-rw-r--r--src/database/tag_parser.cpp8
-rw-r--r--src/lua/CMakeLists.txt2
-rw-r--r--src/lua/bridge.cpp74
-rw-r--r--src/lua/include/bridge.hpp27
-rw-r--r--src/lua/include/lua_registry.hpp51
-rw-r--r--src/lua/include/lua_thread.hpp12
-rw-r--r--src/lua/include/property.hpp4
-rw-r--r--src/lua/lua_queue.cpp3
-rw-r--r--src/lua/lua_thread.cpp51
-rw-r--r--src/lua/property.cpp78
-rw-r--r--src/lua/registry.cpp73
-rw-r--r--src/ui/include/ui_fsm.hpp2
-rw-r--r--src/ui/ui_fsm.cpp144
20 files changed, 422 insertions, 227 deletions
diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp
index 33f41306..94a48955 100644
--- a/src/app_console/app_console.cpp
+++ b/src/app_console/app_console.cpp
@@ -40,7 +40,7 @@
#include "freertos/projdefs.h"
#include "haptics.hpp"
#include "index.hpp"
-#include "lua_thread.hpp"
+#include "lua_registry.hpp"
#include "memory_resource.hpp"
#include "samd.hpp"
#include "service_locator.hpp"
@@ -628,8 +628,7 @@ static const char kReplMain[] =
"repl:run()\n";
int CmdLua(int argc, char** argv) {
- std::unique_ptr<lua::LuaThread> context{
- lua::LuaThread::Start(*AppConsole::sServices)};
+ auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
if (!context) {
return 1;
}
@@ -652,8 +651,7 @@ int CmdLua(int argc, char** argv) {
}
int CmdLuaRun(int argc, char** argv) {
- std::unique_ptr<lua::LuaThread> context{
- lua::LuaThread::Start(*AppConsole::sServices)};
+ auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
if (!context) {
return 1;
}
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index bb7d33dc..b5b6e04b 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -13,7 +13,9 @@
#include "audio_sink.hpp"
#include "bluetooth_types.hpp"
+#include "esp_heap_caps.h"
#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
@@ -192,20 +194,28 @@ void AudioState::react(const TogglePlayPause& ev) {
namespace states {
+// Two seconds of samples for two channels, at a representative sample rate.
+constexpr size_t kDrainBufferSize = sizeof(sample::Sample) * 48000 * 4;
+static StreamBufferHandle_t sDrainBuffer;
+
void Uninitialised::react(const system_fsm::BootComplete& ev) {
sServices = ev.services;
- constexpr size_t kDrainBufferSize =
- drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2 * 8;
ESP_LOGI(kTag, "allocating drain buffer, size %u KiB",
kDrainBufferSize / 1024);
- StreamBufferHandle_t stream = xStreamBufferCreateWithCaps(
- kDrainBufferSize, sizeof(sample::Sample), MALLOC_CAP_DMA);
+
+ auto meta = reinterpret_cast<StaticStreamBuffer_t*>(
+ heap_caps_malloc(sizeof(StaticStreamBuffer_t), MALLOC_CAP_DMA));
+ auto storage = reinterpret_cast<uint8_t*>(
+ heap_caps_malloc(kDrainBufferSize, MALLOC_CAP_SPIRAM));
+
+ sDrainBuffer = xStreamBufferCreateStatic(
+ kDrainBufferSize, sizeof(sample::Sample), storage, meta);
sFileSource.reset(
new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker()));
- sI2SOutput.reset(new I2SAudioOutput(stream, sServices->gpios()));
- sBtOutput.reset(new BluetoothAudioOutput(stream, sServices->bluetooth(),
+ sI2SOutput.reset(new I2SAudioOutput(sDrainBuffer, sServices->gpios()));
+ sBtOutput.reset(new BluetoothAudioOutput(sDrainBuffer, sServices->bluetooth(),
sServices->bg_worker()));
auto& nvs = sServices->nvs();
@@ -366,6 +376,12 @@ void Playback::react(const internal::InputFileFinished& ev) {
ESP_LOGI(kTag, "finished playing file");
sServices->track_queue().finish();
if (!sServices->track_queue().current()) {
+ for (int i = 0; i < 20; i++) {
+ if (xStreamBufferIsEmpty(sDrainBuffer)) {
+ break;
+ }
+ vTaskDelay(pdMS_TO_TICKS(200));
+ }
transit<Standby>();
}
}
diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp
index 534da10c..b75230fc 100644
--- a/src/audio/track_queue.cpp
+++ b/src/audio/track_queue.cpp
@@ -200,39 +200,52 @@ auto TrackQueue::append(Item i) -> void {
}
auto TrackQueue::next() -> void {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (shuffle_) {
- shuffle_->next();
- pos_ = shuffle_->current();
- } else {
- if (pos_ + 1 >= tracks_.size()) {
- if (replay_) {
- pos_ = 0;
- }
+ bool changed = true;
+
+ {
+ const std::unique_lock<std::shared_mutex> lock(mutex_);
+ if (shuffle_) {
+ shuffle_->next();
+ pos_ = shuffle_->current();
} else {
- pos_++;
+ if (pos_ + 1 >= tracks_.size()) {
+ if (replay_) {
+ pos_ = 0;
+ } else {
+ pos_ = tracks_.size();
+ changed = false;
+ }
+ } else {
+ pos_++;
+ }
}
}
- notifyChanged(true);
+ notifyChanged(changed);
}
auto TrackQueue::previous() -> void {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (shuffle_) {
- shuffle_->prev();
- pos_ = shuffle_->current();
- } else {
- if (pos_ == 0) {
- if (repeat_) {
- pos_ = tracks_.size() - 1;
- }
+ bool changed = true;
+
+ {
+ const std::unique_lock<std::shared_mutex> lock(mutex_);
+ if (shuffle_) {
+ shuffle_->prev();
+ pos_ = shuffle_->current();
} else {
- pos_--;
+ if (pos_ == 0) {
+ if (repeat_) {
+ pos_ = tracks_.size() - 1;
+ } else {
+ changed = false;
+ }
+ } else {
+ pos_--;
+ }
}
}
- notifyChanged(true);
+ notifyChanged(changed);
}
auto TrackQueue::finish() -> void {
diff --git a/src/database/database.cpp b/src/database/database.cpp
index b596063c..ec11455b 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -383,8 +383,7 @@ auto Database::updateIndexes() -> void {
ESP_LOGI(kTag, "scanning for new tracks");
uint64_t num_processed = 0;
std::pair<uint16_t, uint16_t> newest_track = last_update;
- file_gatherer_.FindFiles("", [&](const std::string& path,
- const FILINFO& info) {
+ file_gatherer_.FindFiles("", [&](std::string_view path, const FILINFO& info) {
num_processed++;
events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kScanningForNewTracks,
@@ -456,9 +455,7 @@ auto Database::updateIndexes() -> void {
dbCreateIndexesForTrack(*t);
} else if (existing_data->filepath !=
std::pmr::string{path.data(), path.size()}) {
- ESP_LOGW(kTag, "tag hash collision for %s and %s",
- existing_data->filepath.c_str(), path.c_str());
- ESP_LOGI(kTag, "hash components: %s, %s, %s",
+ ESP_LOGW(kTag, "hash collision: %s, %s, %s",
tags->title().value_or("no title").c_str(),
tags->artist().value_or("no artist").c_str(),
tags->album().value_or("no album").c_str());
diff --git a/src/database/file_gatherer.cpp b/src/database/file_gatherer.cpp
index dde363bd..b7b7271e 100644
--- a/src/database/file_gatherer.cpp
+++ b/src/database/file_gatherer.cpp
@@ -22,12 +22,12 @@ static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR");
auto FileGathererImpl::FindFiles(
const std::string& root,
- std::function<void(const std::string&, const FILINFO&)> cb) -> void {
- std::deque<std::string> to_explore;
- to_explore.push_back(root);
+ std::function<void(std::string_view, const FILINFO&)> cb) -> void {
+ std::pmr::deque<std::pmr::string> to_explore{&memory::kSpiRamResource};
+ to_explore.push_back({root.data(), root.size()});
while (!to_explore.empty()) {
- std::string next_path_str = to_explore.front();
+ auto next_path_str = to_explore.front();
to_explore.pop_front();
const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str());
@@ -56,7 +56,7 @@ auto FileGathererImpl::FindFiles(
// System or hidden file. Ignore it and move on.
continue;
} else {
- std::string full_path;
+ std::pmr::string full_path{&memory::kSpiRamResource};
full_path += next_path_str;
full_path += "/";
full_path += info.fname;
diff --git a/src/database/include/file_gatherer.hpp b/src/database/include/file_gatherer.hpp
index 66127bb7..685bdb2c 100644
--- a/src/database/include/file_gatherer.hpp
+++ b/src/database/include/file_gatherer.hpp
@@ -21,7 +21,7 @@ class IFileGatherer {
virtual auto FindFiles(
const std::string& root,
- std::function<void(const std::string&, const FILINFO&)> cb)
+ std::function<void(std::string_view, const FILINFO&)> cb)
-> void = 0;
};
@@ -29,7 +29,7 @@ class FileGathererImpl : public IFileGatherer {
public:
virtual auto FindFiles(
const std::string& root,
- std::function<void(const std::string&, const FILINFO&)> cb)
+ std::function<void(std::string_view, const FILINFO&)> cb)
-> void override;
};
diff --git a/src/database/include/tag_parser.hpp b/src/database/include/tag_parser.hpp
index f196c479..966258b5 100644
--- a/src/database/include/tag_parser.hpp
+++ b/src/database/include/tag_parser.hpp
@@ -16,18 +16,18 @@ namespace database {
class ITagParser {
public:
virtual ~ITagParser() {}
- virtual auto ReadAndParseTags(const std::string& path)
+ virtual auto ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> = 0;
};
class TagParserImpl : public ITagParser {
public:
TagParserImpl();
- auto ReadAndParseTags(const std::string& path)
+ auto ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> override;
private:
- auto parseNew(const std::string& path) -> std::shared_ptr<TrackTags>;
+ auto parseNew(std::string_view path) -> std::shared_ptr<TrackTags>;
/*
* Cache of tags that have already been extracted from files. Ideally this
diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp
index c247bb7d..cbcbdcb5 100644
--- a/src/database/tag_parser.cpp
+++ b/src/database/tag_parser.cpp
@@ -108,7 +108,7 @@ static const std::size_t kBufSize = 1024;
TagParserImpl::TagParserImpl() {}
-auto TagParserImpl::ReadAndParseTags(const std::string& path)
+auto TagParserImpl::ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> {
{
std::lock_guard<std::mutex> lock{cache_mutex_};
@@ -130,7 +130,7 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path)
if (!tags->track()) {
auto slash_pos = path.find_last_of("/");
if (slash_pos != std::string::npos && path.size() - slash_pos > 1) {
- std::string trunc = path.substr(slash_pos + 1);
+ auto trunc = path.substr(slash_pos + 1);
tags->track({trunc.data(), trunc.size()});
}
}
@@ -143,8 +143,8 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path)
return tags;
}
-auto TagParserImpl::parseNew(const std::string& path)
- -> std::shared_ptr<TrackTags> {
+auto TagParserImpl::parseNew(std::string_view p) -> std::shared_ptr<TrackTags> {
+ std::string path{p};
libtags::Aux aux;
auto out = TrackTags::create();
aux.tags = out.get();
diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt
index d89b22e8..ff0831c9 100644
--- a/src/lua/CMakeLists.txt
+++ b/src/lua/CMakeLists.txt
@@ -4,7 +4,7 @@
idf_component_register(
SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp"
- "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp"
+ "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp"
INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database"
"esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term"
diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp
index a680a521..a26f74bb 100644
--- a/src/lua/bridge.cpp
+++ b/src/lua/bridge.cpp
@@ -22,6 +22,9 @@
#include "lua_version.hpp"
#include "lvgl.h"
+#include "font/lv_font_loader.h"
+#include "luavgl.h"
+
#include "event_queue.hpp"
#include "property.hpp"
#include "service_locator.hpp"
@@ -32,34 +35,62 @@ int luaopen_linenoise(lua_State* L);
int luaopen_term_core(lua_State* L);
}
+LV_FONT_DECLARE(font_fusion_12);
+LV_FONT_DECLARE(font_fusion_10);
+
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_bridge";
static constexpr char kBridgeKey[] = "bridge";
+static auto make_font_cb(const char* name, int size, int weight)
+ -> const lv_font_t* {
+ if (std::string{"fusion"} == name) {
+ if (size == 12) {
+ return &font_fusion_12;
+ }
+ if (size == 10) {
+ return &font_fusion_10;
+ }
+ }
+ return NULL;
+}
+
+static auto delete_font_cb(lv_font_t* font) -> void {}
+
auto Bridge::Get(lua_State* state) -> Bridge* {
lua_pushstring(state, kBridgeKey);
lua_gettable(state, LUA_REGISTRYINDEX);
return reinterpret_cast<Bridge*>(lua_touserdata(state, -1));
}
-Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s)
- : services_(services), state_(s), bindings_(s) {
- lua_pushstring(&s, kBridgeKey);
- lua_pushlightuserdata(&s, this);
- lua_settable(&s, LUA_REGISTRYINDEX);
+Bridge::Bridge(system_fsm::ServiceLocator& services) : services_(services) {}
+
+auto Bridge::installBaseModules(lua_State* L) -> void {
+ lua_pushstring(L, kBridgeKey);
+ lua_pushlightuserdata(L, this);
+ lua_settable(L, LUA_REGISTRYINDEX);
+
+ bindings_.install(L);
- luaL_requiref(&s, "linenoise", luaopen_linenoise, true);
- lua_pop(&s, 1);
+ luaL_requiref(L, "linenoise", luaopen_linenoise, true);
+ lua_pop(L, 1);
- luaL_requiref(&s, "term.core", luaopen_term_core, true);
- lua_pop(&s, 1);
+ luaL_requiref(L, "term.core", luaopen_term_core, true);
+ lua_pop(L, 1);
+
+ RegisterControlsModule(L);
+ RegisterDatabaseModule(L);
+ RegisterQueueModule(L);
+ RegisterVersionModule(L);
+}
- RegisterControlsModule(&s);
- RegisterDatabaseModule(&s);
- RegisterQueueModule(&s);
- RegisterVersionModule(&s);
+auto Bridge::installLvgl(lua_State* L) -> void {
+ luavgl_set_pcall(L, CallProtected);
+ luavgl_set_font_extension(L, make_font_cb, delete_font_cb);
+ luaL_requiref(L, "lvgl", luaopen_lvgl, true);
+ lua_pop(L, 1);
}
static auto new_property_module(lua_State* state) -> int {
@@ -76,32 +107,33 @@ static auto new_property_module(lua_State* state) -> int {
template <class... Ts>
inline constexpr bool always_false_v = false;
-auto Bridge::AddPropertyModule(
+auto Bridge::installPropertyModule(
+ lua_State* L,
const std::string& name,
- std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
+ std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>&
props) -> void {
// Create the module (or retrieve it if one with this name already exists)
- luaL_requiref(&state_, name.c_str(), new_property_module, true);
+ luaL_requiref(L, name.c_str(), new_property_module, true);
for (const auto& prop : props) {
- lua_pushstring(&state_, prop.first.c_str());
+ lua_pushstring(L, prop.first.c_str());
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, LuaFunction>) {
- bindings_.Register(&state_, arg);
+ bindings_.Register(L, arg);
} else if constexpr (std::is_same_v<T, Property*>) {
- bindings_.Register(&state_, arg);
+ bindings_.Register(L, arg);
} else {
static_assert(always_false_v<T>, "missing case");
}
},
prop.second);
- lua_settable(&state_, -3); // metatable.propname = property
+ lua_settable(L, -3); // metatable.propname = property
}
- lua_pop(&state_, 1); // pop the module off the stack
+ lua_pop(L, 1); // pop the module off the stack
}
} // namespace lua
diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp
index 62fbc340..64f14e0e 100644
--- a/src/lua/include/bridge.hpp
+++ b/src/lua/include/bridge.hpp
@@ -16,25 +16,38 @@
namespace lua {
+/*
+ * Responsible for adding C/C++ module bindings to Lua threads. This class
+ * keeps no thread-specific internal state, and instead uses the LUA_REGISTRY
+ * table of its host threads to store data.
+ */
class Bridge {
public:
+ /*
+ * Utility for retrieving the Bridge from a Lua thread in which the Bridge's
+ * bindings have been installed. Use by Lua's C callbacks to access the rest
+ * of the system.
+ */
static auto Get(lua_State* state) -> Bridge*;
- Bridge(system_fsm::ServiceLocator&, lua_State& s);
+ Bridge(system_fsm::ServiceLocator& s);
- auto AddPropertyModule(
+ system_fsm::ServiceLocator& services() { return services_; }
+
+ auto installBaseModules(lua_State* L) -> void;
+ auto installLvgl(lua_State* L) -> void;
+ auto installPropertyModule(
+ lua_State* L,
const std::string&,
std::vector<
- std::pair<std::string,
- std::variant<LuaFunction, Property*>>>)
+ std::pair<std::string, std::variant<LuaFunction, Property*>>>&)
-> void;
- system_fsm::ServiceLocator& services() { return services_; }
- PropertyBindings& bindings() { return bindings_; }
+ Bridge(const Bridge&) = delete;
+ Bridge& operator=(const Bridge&) = delete;
private:
system_fsm::ServiceLocator& services_;
- lua_State& state_;
PropertyBindings bindings_;
};
diff --git a/src/lua/include/lua_registry.hpp b/src/lua/include/lua_registry.hpp
new file mode 100644
index 00000000..abc5063e
--- /dev/null
+++ b/src/lua/include/lua_registry.hpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "lua.hpp"
+
+#include "bridge.hpp"
+#include "lua_thread.hpp"
+#include "service_locator.hpp"
+
+namespace lua {
+
+class Registry {
+ public:
+ static auto instance(system_fsm::ServiceLocator&) -> Registry&;
+
+ auto uiThread() -> std::shared_ptr<LuaThread>;
+ auto newThread() -> std::shared_ptr<LuaThread>;
+
+ auto AddPropertyModule(
+ const std::string&,
+ std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>)
+ -> void;
+
+ Registry(const Registry&) = delete;
+ Registry& operator=(const Registry&) = delete;
+
+ private:
+ Registry(system_fsm::ServiceLocator&);
+
+ system_fsm::ServiceLocator& services_;
+ std::unique_ptr<Bridge> bridge_;
+
+ std::shared_ptr<LuaThread> ui_thread_;
+ std::list<std::weak_ptr<LuaThread>> threads_;
+
+ std::vector<
+ std::pair<std::string,
+ std::vector<std::pair<std::string,
+ std::variant<LuaFunction, Property*>>>>>
+ modules_;
+};
+
+} // namespace lua
diff --git a/src/lua/include/lua_thread.hpp b/src/lua/include/lua_thread.hpp
index d10dba3a..384de61d 100644
--- a/src/lua/include/lua_thread.hpp
+++ b/src/lua/include/lua_thread.hpp
@@ -10,9 +10,7 @@
#include <string>
#include "lua.hpp"
-#include "lvgl.h"
-#include "bridge.hpp"
#include "service_locator.hpp"
namespace lua {
@@ -23,8 +21,7 @@ auto CallProtected(lua_State*, int nargs, int nresults) -> int;
class LuaThread {
public:
- static auto Start(system_fsm::ServiceLocator&, lv_obj_t* lvgl_root = nullptr)
- -> LuaThread*;
+ static auto Start(system_fsm::ServiceLocator&) -> LuaThread*;
~LuaThread();
auto RunScript(const std::string& path) -> bool;
@@ -32,14 +29,15 @@ class LuaThread {
auto DumpStack() -> void;
- auto bridge() -> Bridge& { return *bridge_; }
auto state() -> lua_State* { return state_; }
+ LuaThread(const LuaThread&) = delete;
+ LuaThread& operator=(const LuaThread&) = delete;
+
private:
- LuaThread(std::unique_ptr<Allocator>&, std::unique_ptr<Bridge>&, lua_State*);
+ LuaThread(std::unique_ptr<Allocator>&, lua_State*);
std::unique_ptr<Allocator> alloc_;
- std::unique_ptr<Bridge> bridge_;
lua_State* state_;
};
diff --git a/src/lua/include/property.hpp b/src/lua/include/property.hpp
index 03229bc1..7d160fba 100644
--- a/src/lua/include/property.hpp
+++ b/src/lua/include/property.hpp
@@ -53,7 +53,9 @@ class Property {
class PropertyBindings {
public:
- PropertyBindings(lua_State&);
+ PropertyBindings();
+
+ auto install(lua_State*) -> void;
auto Register(lua_State*, Property*) -> void;
auto Register(lua_State*, LuaFunction) -> void;
diff --git a/src/lua/lua_queue.cpp b/src/lua/lua_queue.cpp
index 69d3b03d..dfb820c2 100644
--- a/src/lua/lua_queue.cpp
+++ b/src/lua/lua_queue.cpp
@@ -16,6 +16,7 @@
#include "lua.h"
#include "lvgl.h"
+#include "bridge.hpp"
#include "database.hpp"
#include "event_queue.hpp"
#include "index.hpp"
@@ -70,4 +71,4 @@ auto RegisterQueueModule(lua_State* s) -> void {
lua_pop(s, 1);
}
-} // namespace lua \ No newline at end of file
+} // namespace lua
diff --git a/src/lua/lua_thread.cpp b/src/lua/lua_thread.cpp
index b94b70ab..dd72e41d 100644
--- a/src/lua/lua_thread.cpp
+++ b/src/lua/lua_thread.cpp
@@ -9,22 +9,16 @@
#include <iostream>
#include <memory>
-#include "lauxlib.h"
-#include "lua.h"
-#include "lua.hpp"
-
-#include "font/lv_font_loader.h"
-#include "luavgl.h"
-
#include "esp_heap_caps.h"
#include "esp_log.h"
+#include "lua.hpp"
+
+#include "bridge.hpp"
#include "event_queue.hpp"
+#include "memory_resource.hpp"
#include "service_locator.hpp"
#include "ui_events.hpp"
-LV_FONT_DECLARE(font_fusion_12);
-LV_FONT_DECLARE(font_fusion_10);
-
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua";
@@ -59,23 +53,7 @@ static int lua_panic(lua_State* L) {
return 0;
}
-static auto make_font_cb(const char* name, int size, int weight)
- -> const lv_font_t* {
- if (std::string{"fusion"} == name) {
- if (size == 12) {
- return &font_fusion_12;
- }
- if (size == 10) {
- return &font_fusion_10;
- }
- }
- return NULL;
-}
-
-static auto delete_font_cb(lv_font_t* font) -> void {}
-
-auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
- -> LuaThread* {
+auto LuaThread::Start(system_fsm::ServiceLocator& services) -> LuaThread* {
auto alloc = std::make_unique<Allocator>();
lua_State* state = lua_newstate(lua_alloc, alloc.get());
if (!state) {
@@ -85,24 +63,11 @@ auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
luaL_openlibs(state);
lua_atpanic(state, lua_panic);
- auto bridge = std::make_unique<Bridge>(services, *state);
-
- // FIXME: luavgl init should probably be a part of the bridge.
- if (lvgl_root) {
- luavgl_set_pcall(state, CallProtected);
- luavgl_set_font_extension(state, make_font_cb, delete_font_cb);
- luavgl_set_root(state, lvgl_root);
- luaL_requiref(state, "lvgl", luaopen_lvgl, true);
- lua_pop(state, 1);
- }
-
- return new LuaThread(alloc, bridge, state);
+ return new LuaThread(alloc, state);
}
-LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc,
- std::unique_ptr<Bridge>& bridge,
- lua_State* state)
- : alloc_(std::move(alloc)), bridge_(std::move(bridge)), state_(state) {}
+LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc, lua_State* state)
+ : alloc_(std::move(alloc)), state_(state) {}
LuaThread::~LuaThread() {
lua_close(state_);
diff --git a/src/lua/property.cpp b/src/lua/property.cpp
index 5357ccc5..f721f9ce 100644
--- a/src/lua/property.cpp
+++ b/src/lua/property.cpp
@@ -10,10 +10,12 @@
#include <cmath>
#include <memory>
#include <memory_resource>
+#include <sstream>
#include <string>
#include <variant>
#include "bluetooth_types.hpp"
+#include "lauxlib.h"
#include "lua.h"
#include "lua.hpp"
#include "lua_thread.hpp"
@@ -76,10 +78,30 @@ static auto property_bind(lua_State* state) -> int {
return 1;
}
-static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get},
- {"set", property_set},
- {"bind", property_bind},
- {NULL, NULL}};
+static auto property_tostring(lua_State* state) -> int {
+ Property* p = check_property(state);
+ p->PushValue(*state);
+
+ std::stringstream str{};
+ str << "property { " << luaL_tolstring(state, -1, NULL);
+ if (!p->IsTwoWay()) {
+ str << ", read-only";
+ }
+ str << " }";
+
+ lua_settop(state, 0);
+
+ std::string res = str.str();
+ lua_pushlstring(state, res.data(), res.size());
+ return 1;
+}
+
+static const struct luaL_Reg kPropertyBindingFuncs[] = {
+ {"get", property_get},
+ {"set", property_set},
+ {"bind", property_bind},
+ {"__tostring", property_tostring},
+ {NULL, NULL}};
static auto generic_function_cb(lua_State* state) -> int {
lua_pushstring(state, kBinderKey);
@@ -98,45 +120,47 @@ static auto generic_function_cb(lua_State* state) -> int {
return std::invoke(fn, state);
}
-PropertyBindings::PropertyBindings(lua_State& s) {
- lua_pushstring(&s, kBinderKey);
- lua_pushlightuserdata(&s, this);
- lua_settable(&s, LUA_REGISTRYINDEX);
+PropertyBindings::PropertyBindings() : functions_(&memory::kSpiRamResource) {}
+
+auto PropertyBindings::install(lua_State* L) -> void {
+ lua_pushstring(L, kBinderKey);
+ lua_pushlightuserdata(L, this);
+ lua_settable(L, LUA_REGISTRYINDEX);
// Create the metatable responsible for the Property API.
- luaL_newmetatable(&s, kPropertyMetatable);
+ luaL_newmetatable(L, kPropertyMetatable);
- lua_pushliteral(&s, "__index");
- lua_pushvalue(&s, -2);
- lua_settable(&s, -3); // metatable.__index = metatable
+ lua_pushliteral(L, "__index");
+ lua_pushvalue(L, -2);
+ lua_settable(L, -3); // metatable.__index = metatable
// Add our binding funcs (get, set, bind) to the metatable.
- luaL_setfuncs(&s, kPropertyBindingFuncs, 0);
+ luaL_setfuncs(L, kPropertyBindingFuncs, 0);
// We've finished setting up the metatable, so pop it.
- lua_pop(&s, 1);
+ lua_pop(L, 1);
// Create a weak table in the registry to hold live bindings.
- lua_pushstring(&s, kBindingsTable);
- lua_newtable(&s); // bindings = {}
+ lua_pushstring(L, kBindingsTable);
+ lua_newtable(L); // bindings = {}
// Metatable for the weak table. Values are weak.
- lua_newtable(&s); // meta = {}
- lua_pushliteral(&s, "__mode");
- lua_pushliteral(&s, "v");
- lua_settable(&s, -3); // meta.__mode='v'
- lua_setmetatable(&s, -2); // setmetatable(bindings, meta)
+ lua_newtable(L); // meta = {}
+ lua_pushliteral(L, "__mode");
+ lua_pushliteral(L, "v");
+ lua_settable(L, -3); // meta.__mode='v'
+ lua_setmetatable(L, -2); // setmetatable(bindings, meta)
- lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
+ lua_settable(L, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
// Create the metatable for C++ functions.
- luaL_newmetatable(&s, kFunctionMetatable);
+ luaL_newmetatable(L, kFunctionMetatable);
- lua_pushliteral(&s, "__call");
- lua_pushcfunction(&s, generic_function_cb);
- lua_settable(&s, -3); // metatable.__call = metatable
+ lua_pushliteral(L, "__call");
+ lua_pushcfunction(L, generic_function_cb);
+ lua_settable(L, -3); // metatable.__call = metatable
- lua_pop(&s, 1); // Clean up the function metatable
+ lua_pop(L, 1); // Clean up the function metatable
}
auto PropertyBindings::Register(lua_State* s, Property* prop) -> void {
diff --git a/src/lua/registry.cpp b/src/lua/registry.cpp
new file mode 100644
index 00000000..a6487858
--- /dev/null
+++ b/src/lua/registry.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "lua_registry.hpp"
+
+#include <iostream>
+#include <memory>
+
+#include "esp_heap_caps.h"
+#include "esp_log.h"
+#include "lua.hpp"
+
+#include "bridge.hpp"
+#include "event_queue.hpp"
+#include "memory_resource.hpp"
+#include "service_locator.hpp"
+#include "ui_events.hpp"
+
+namespace lua {
+
+[[maybe_unused]] static constexpr char kTag[] = "lua";
+
+auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& {
+ static Registry sRegistry{s};
+ return sRegistry;
+}
+
+Registry::Registry(system_fsm::ServiceLocator& services)
+ : services_(services), bridge_(new Bridge(services)) {}
+
+auto Registry::uiThread() -> std::shared_ptr<LuaThread> {
+ if (!ui_thread_) {
+ ui_thread_ = newThread();
+ bridge_->installLvgl(ui_thread_->state());
+ }
+ return ui_thread_;
+}
+
+auto Registry::newThread() -> std::shared_ptr<LuaThread> {
+ std::shared_ptr<LuaThread> thread{LuaThread::Start(services_)};
+ bridge_->installBaseModules(thread->state());
+ for (auto& module : modules_) {
+ bridge_->installPropertyModule(thread->state(), module.first,
+ module.second);
+ }
+ threads_.push_back(thread);
+ return thread;
+}
+
+auto Registry::AddPropertyModule(
+ const std::string& name,
+ std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
+ properties) -> void {
+ modules_.push_back(std::make_pair(name, properties));
+
+ // Any live threads will need to be updated to include the new module.
+ auto it = threads_.begin();
+ while (it != threads_.end()) {
+ auto thread = it->lock();
+ if (!thread) {
+ // Thread has been destroyed; stop tracking it.
+ it = threads_.erase(it);
+ } else {
+ bridge_->installPropertyModule(thread->state(), name, properties);
+ it++;
+ }
+ }
+}
+
+} // namespace lua
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index ffaff0bb..6cf2ba4c 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -163,6 +163,8 @@ class Lua : public UiState {
auto ShowAlert(lua_State*) -> int;
auto HideAlert(lua_State*) -> int;
+ auto Ticks(lua_State*) -> int;
+
auto SetPlaying(const lua::LuaValue&) -> bool;
auto SetRandom(const lua::LuaValue&) -> bool;
auto SetRepeat(const lua::LuaValue&) -> bool;
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 3e85c36e..d98e435d 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -24,6 +24,7 @@
#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"
@@ -36,6 +37,7 @@
#include "encoder_input.hpp"
#include "event_queue.hpp"
#include "gpios.hpp"
+#include "lua_registry.hpp"
#include "lvgl_task.hpp"
#include "nvs.hpp"
#include "property.hpp"
@@ -142,29 +144,29 @@ lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) {
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;
-}};
+ 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;
-}};
+ 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;
-}};
+ 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) {
@@ -456,27 +458,26 @@ void Lua::entry() {
alert_timer_callback);
sAlertContainer = lv_obj_create(sCurrentScreen->alert());
- sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content()));
- sLua->bridge().AddPropertyModule("power",
- {
- {"battery_pct", &sBatteryPct},
- {"battery_millivolts", &sBatteryMv},
- {"plugged_in", &sBatteryCharging},
- });
- sLua->bridge().AddPropertyModule(
- "bluetooth", {
- {"enabled", &sBluetoothEnabled},
- {"connected", &sBluetoothConnected},
- {"paired_device", &sBluetoothPairedDevice},
- {"devices", &sBluetoothDevices},
- });
- sLua->bridge().AddPropertyModule("playback",
- {
- {"playing", &sPlaybackPlaying},
- {"track", &sPlaybackTrack},
- {"position", &sPlaybackPosition},
- });
- sLua->bridge().AddPropertyModule(
+ 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); }},
@@ -487,40 +488,44 @@ void Lua::entry() {
{"repeat_track", &sQueueRepeat},
{"random", &sQueueRandom},
});
- sLua->bridge().AddPropertyModule("volume",
- {
- {"current_pct", &sVolumeCurrentPct},
- {"current_db", &sVolumeCurrentDb},
- {"left_bias", &sVolumeLeftBias},
- {"limit_db", &sVolumeLimit},
- });
-
- sLua->bridge().AddPropertyModule("display",
- {
- {"brightness", &sDisplayBrightness},
- });
-
- sLua->bridge().AddPropertyModule("controls",
- {
- {"scheme", &sControlsScheme},
- {"scroll_sensitivity", &sScrollSensitivity},
- });
-
- sLua->bridge().AddPropertyModule(
+ registry.AddPropertyModule("volume",
+ {
+ {"current_pct", &sVolumeCurrentPct},
+ {"current_db", &sVolumeCurrentDb},
+ {"left_bias", &sVolumeLeftBias},
+ {"limit_db", &sVolumeLimit},
+ });
+
+ registry.AddPropertyModule("display",
+ {
+ {"brightness", &sDisplayBrightness},
+ });
+
+ registry.AddPropertyModule("controls",
+ {
+ {"scheme", &sControlsScheme},
+ {"scroll_sensitivity", &sScrollSensitivity},
+ });
+
+ registry.AddPropertyModule(
"backstack",
{
{"push", [&](lua_State* s) { return PushLuaScreen(s); }},
{"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
});
- sLua->bridge().AddPropertyModule(
+ registry.AddPropertyModule(
"alerts", {
{"show", [&](lua_State* s) { return ShowAlert(s); }},
{"hide", [&](lua_State* s) { return HideAlert(s); }},
});
- sLua->bridge().AddPropertyModule("database",
- {
- {"updating", &sDatabaseUpdating},
- });
+
+ registry.AddPropertyModule(
+ "time", {
+ {"ticks", [&](lua_State* s) { return Ticks(s); }},
+ });
+ registry.AddPropertyModule("database", {
+ {"updating", &sDatabaseUpdating},
+ });
auto bt = sServices->bluetooth();
sBluetoothEnabled.Update(bt.IsEnabled());
@@ -579,6 +584,11 @@ auto Lua::PopLuaScreen(lua_State* s) -> int {
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;