summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorailurux <ailuruxx@gmail.com>2024-03-20 13:42:03 +1100
committerailurux <ailuruxx@gmail.com>2024-03-20 13:42:03 +1100
commit51dfb5b3e30caf823c2355ff957c01864f35f9f6 (patch)
tree1f0e41397259c6e206aba136ad5070b9de30e1b1 /src
parent170c23b832eed6dad2b118e50164464cc93e5c4c (diff)
parenta05d93a1e26181237a76da5ce398c6b08497d591 (diff)
downloadtangara-fw-51dfb5b3e30caf823c2355ff957c01864f35f9f6.tar.gz
Merge branch 'main' into themes
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_fsm.cpp43
-rw-r--r--src/audio/include/audio_events.hpp1
-rw-r--r--src/audio/track_queue.cpp2
-rw-r--r--src/battery/battery.cpp5
-rw-r--r--src/database/database.cpp8
-rw-r--r--src/drivers/display.cpp15
-rw-r--r--src/drivers/display_init.cpp2
-rw-r--r--src/drivers/gpios.cpp18
-rw-r--r--src/drivers/include/display_init.hpp3
-rw-r--r--src/drivers/include/gpios.hpp9
-rw-r--r--src/drivers/include/nvs.hpp8
-rw-r--r--src/drivers/nvs.cpp21
-rw-r--r--src/drivers/samd.cpp31
-rw-r--r--src/lua/CMakeLists.txt1
-rw-r--r--src/lua/bridge.cpp2
-rw-r--r--src/lua/include/lua_screen.hpp15
-rw-r--r--src/lua/lua_screen.cpp75
-rw-r--r--src/system_fsm/booting.cpp17
-rw-r--r--src/system_fsm/include/system_events.hpp1
-rw-r--r--src/system_fsm/system_fsm.cpp6
-rw-r--r--src/ui/include/screen.hpp3
-rw-r--r--src/ui/include/screen_lua.hpp3
-rw-r--r--src/ui/include/ui_fsm.hpp3
-rw-r--r--src/ui/screen.cpp3
-rw-r--r--src/ui/screen_lua.cpp36
-rw-r--r--src/ui/ui_fsm.cpp78
26 files changed, 330 insertions, 79 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index d4272c3d..05c7c216 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -13,6 +13,8 @@
#include "audio_sink.hpp"
#include "bluetooth_types.hpp"
+#include "cppbor.h"
+#include "cppbor_parse.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
@@ -58,6 +60,8 @@ StreamBufferHandle_t AudioState::sDrainBuffer;
std::optional<database::TrackId> AudioState::sCurrentTrack;
bool AudioState::sIsPlaybackAllowed;
+static std::optional<std::pair<std::string, uint32_t>> sLastTrackUpdate;
+
void AudioState::react(const system_fsm::BluetoothEvent& ev) {
if (ev.event != drivers::bluetooth::Event::kConnectionStateChanged) {
return;
@@ -310,11 +314,15 @@ void Standby::react(const QueueUpdate& ev) {
if (!current_track || (sCurrentTrack && (*sCurrentTrack == *current_track))) {
return;
}
+ if (ev.reason == QueueUpdate::Reason::kDeserialised && sLastTrackUpdate) {
+ return;
+ }
clearDrainBuffer();
playTrack(*current_track);
}
static const char kQueueKey[] = "audio:queue";
+static const char kCurrentFileKey[] = "audio:current";
void Standby::react(const system_fsm::KeyLockChanged& ev) {
if (!ev.locking) {
@@ -332,6 +340,14 @@ void Standby::react(const system_fsm::KeyLockChanged& ev) {
return;
}
db->put(kQueueKey, queue.serialise());
+
+ if (sLastTrackUpdate) {
+ cppbor::Array current_track{
+ cppbor::Tstr{sLastTrackUpdate->first},
+ cppbor::Uint{sLastTrackUpdate->second},
+ };
+ db->put(kCurrentFileKey, current_track.toString());
+ }
});
}
@@ -341,13 +357,32 @@ void Standby::react(const system_fsm::StorageMounted& ev) {
if (!db) {
return;
}
- auto res = db->get(kQueueKey);
- if (res) {
+
+ // Restore the currently playing file before restoring the queue. This way,
+ // we can fall back to restarting the queue's current track if there's any
+ // issue restoring the current file.
+ auto current = db->get(kCurrentFileKey);
+ if (current) {
+ // Again, ensure we don't boot-loop by trying to play a track that causes
+ // a crash over and over again.
+ db->put(kCurrentFileKey, "");
+ auto [parsed, unused, err] = cppbor::parse(
+ reinterpret_cast<uint8_t*>(current->data()), current->size());
+ if (parsed->type() == cppbor::ARRAY) {
+ std::string filename = parsed->asArray()->get(0)->asTstr()->value();
+ uint32_t pos = parsed->asArray()->get(1)->asUint()->value();
+ sLastTrackUpdate = std::make_pair(filename, pos);
+ sFileSource->SetPath(filename, pos);
+ }
+ }
+
+ auto queue = db->get(kQueueKey);
+ if (queue) {
// Don't restore the same queue again. This ideally should do nothing,
// but guards against bad edge cases where restoring the queue ends up
// causing a crash.
db->put(kQueueKey, "");
- sServices->track_queue().deserialise(*res);
+ sServices->track_queue().deserialise(*queue);
}
});
}
@@ -399,6 +434,7 @@ void Playback::react(const QueueUpdate& ev) {
void Playback::react(const PlaybackUpdate& ev) {
ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed,
ev.track->duration);
+ sLastTrackUpdate = std::make_pair(ev.track->filepath, ev.seconds_elapsed);
}
void Playback::react(const internal::InputFileOpened& ev) {}
@@ -407,6 +443,7 @@ void Playback::react(const internal::InputFileClosed& ev) {}
void Playback::react(const internal::InputFileFinished& ev) {
ESP_LOGI(kTag, "finished playing file");
+ sLastTrackUpdate.reset();
sServices->track_queue().finish();
if (!sServices->track_queue().current()) {
for (int i = 0; i < 20; i++) {
diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp
index d55e4e0d..a8533646 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -44,6 +44,7 @@ struct QueueUpdate : tinyfsm::Event {
kExplicitUpdate,
kRepeatingLastTrack,
kTrackFinished,
+ kDeserialised,
};
Reason reason;
};
diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp
index ccadd3a6..a3f4c815 100644
--- a/src/audio/track_queue.cpp
+++ b/src/audio/track_queue.cpp
@@ -486,7 +486,7 @@ auto TrackQueue::deserialise(const std::string& s) -> void {
QueueParseClient client{*this};
const uint8_t* data = reinterpret_cast<const uint8_t*>(s.data());
cppbor::parse(data, data + s.size(), &client);
- notifyChanged(true, Reason::kExplicitUpdate);
+ notifyChanged(true, Reason::kDeserialised);
}
} // namespace audio
diff --git a/src/battery/battery.cpp b/src/battery/battery.cpp
index 95f2d17b..debef9e6 100644
--- a/src/battery/battery.cpp
+++ b/src/battery/battery.cpp
@@ -73,7 +73,10 @@ auto Battery::Update() -> void {
} else {
is_charging = *charge_state == ChargeStatus::kChargingRegular ||
*charge_state == ChargeStatus::kChargingFast ||
- *charge_state == ChargeStatus::kFullCharge;
+ *charge_state == ChargeStatus::kFullCharge ||
+ // Treat 'no battery' as charging because, for UI purposes,
+ // we're *kind of* at full charge if u think about it.
+ *charge_state == ChargeStatus::kNoBattery;
}
if (state_ && state_->is_charging == is_charging &&
diff --git a/src/database/database.cpp b/src/database/database.cpp
index ec11455b..ca92cf6b 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -229,13 +229,17 @@ auto Database::sizeOnDiskBytes() -> size_t {
}
auto Database::put(const std::string& key, const std::string& val) -> void {
- db_->Put(leveldb::WriteOptions{}, kKeyCustom + key, val);
+ if (val.empty()) {
+ db_->Delete(leveldb::WriteOptions{}, kKeyCustom + key);
+ } else {
+ db_->Put(leveldb::WriteOptions{}, kKeyCustom + key, val);
+ }
}
auto Database::get(const std::string& key) -> std::optional<std::string> {
std::string val;
auto res = db_->Get(leveldb::ReadOptions{}, kKeyCustom + key, &val);
- if (!res.ok()) {
+ if (!res.ok() || val.empty()) {
return {};
}
return val;
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index cb3ee3a0..c16fc148 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -39,9 +39,6 @@
[[maybe_unused]] static const char* kTag = "DISPLAY";
-// TODO(jacqueline): Encode width and height variations in the init data.
-static const uint8_t kDisplayHeight = 128 + 2;
-static const uint8_t kDisplayWidth = 160 + 1;
static const uint8_t kTransactionQueueSize = 2;
static const gpio_num_t kDisplayDr = GPIO_NUM_33;
@@ -51,9 +48,11 @@ static const gpio_num_t kDisplayCs = GPIO_NUM_22;
/*
* The size of each of our two display buffers. This is fundamentally a balance
* between performance and memory usage. LVGL docs recommend a buffer 1/10th the
- * size of the screen is the best tradeoff
+ * size of the screen is the best tradeoff.
+ 8
+ * The 160x128 is the nominal size of our standard faceplate's display.
*/
-static const int kDisplayBufferSize = kDisplayWidth * kDisplayHeight / 10;
+static const int kDisplayBufferSize = 160 * 128 / 10;
DMA_ATTR static lv_color_t kDisplayBuffer[kDisplayBufferSize];
namespace drivers {
@@ -154,10 +153,8 @@ auto Display::Create(IGpios& expander,
lv_disp_drv_init(&display->driver_);
display->driver_.draw_buf = &display->buffers_;
- display->driver_.hor_res = kDisplayWidth;
- display->driver_.ver_res = kDisplayHeight;
- // display->driver_.sw_rotate = 1;
- // display->driver_.rotated = LV_DISP_ROT_270;
+ display->driver_.hor_res = init_data.width;
+ display->driver_.ver_res = init_data.height;
display->driver_.sw_rotate = 0;
display->driver_.rotated = LV_DISP_ROT_NONE;
display->driver_.antialiasing = 0;
diff --git a/src/drivers/display_init.cpp b/src/drivers/display_init.cpp
index 833ea6a4..a69826fa 100644
--- a/src/drivers/display_init.cpp
+++ b/src/drivers/display_init.cpp
@@ -101,6 +101,8 @@ static const uint8_t kST7735RCommonFooter[]{
// clang-format on
const InitialisationData kST7735R = {
+ .width = 160,
+ .height = 128,
.num_sequences = 3,
.sequences = {kST7735RCommonHeader, kST7735RCommonGreen,
kST7735RCommonFooter}};
diff --git a/src/drivers/gpios.cpp b/src/drivers/gpios.cpp
index 5c255204..aab932a7 100644
--- a/src/drivers/gpios.cpp
+++ b/src/drivers/gpios.cpp
@@ -63,8 +63,8 @@ constexpr std::pair<uint8_t, uint8_t> unpack(uint16_t ba) {
static constexpr gpio_num_t kIntPin = GPIO_NUM_34;
-auto Gpios::Create() -> Gpios* {
- Gpios* instance = new Gpios();
+auto Gpios::Create(bool invert_lock) -> Gpios* {
+ Gpios* instance = new Gpios(invert_lock);
// Read and write initial values on initialisation so that we do not have a
// strange partially-initialised state.
if (!instance->Flush() || !instance->Read()) {
@@ -73,7 +73,10 @@ auto Gpios::Create() -> Gpios* {
return instance;
}
-Gpios::Gpios() : ports_(pack(kPortADefault, kPortBDefault)), inputs_(0) {
+Gpios::Gpios(bool invert_lock)
+ : ports_(pack(kPortADefault, kPortBDefault)),
+ inputs_(0),
+ invert_lock_switch_(invert_lock) {
gpio_set_direction(kIntPin, GPIO_MODE_INPUT);
}
@@ -108,6 +111,15 @@ auto Gpios::Get(Pin pin) const -> bool {
return (inputs_ & (1 << static_cast<int>(pin))) > 0;
}
+auto Gpios::IsLocked() const -> bool {
+ bool pin = Get(Pin::kKeyLock);
+ if (invert_lock_switch_) {
+ return pin;
+ } else {
+ return !pin;
+ }
+}
+
auto Gpios::Read() -> bool {
uint8_t input_a, input_b;
diff --git a/src/drivers/include/display_init.hpp b/src/drivers/include/display_init.hpp
index f6c28b54..9bf5b3f5 100644
--- a/src/drivers/include/display_init.hpp
+++ b/src/drivers/include/display_init.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <stdint.h>
#include <cstdint>
namespace drivers {
@@ -14,6 +15,8 @@ namespace displays {
extern const uint8_t kDelayBit;
struct InitialisationData {
+ uint16_t width;
+ uint16_t height;
uint8_t num_sequences;
const uint8_t* sequences[4];
};
diff --git a/src/drivers/include/gpios.hpp b/src/drivers/include/gpios.hpp
index 55486be7..e27a3ade 100644
--- a/src/drivers/include/gpios.hpp
+++ b/src/drivers/include/gpios.hpp
@@ -79,12 +79,12 @@ class IGpios {
*/
virtual auto Get(Pin) const -> bool = 0;
- virtual auto IsLocked() const -> bool { return Get(Pin::kKeyLock); }
+ virtual auto IsLocked() const -> bool = 0;
};
class Gpios : public IGpios {
public:
- static auto Create() -> Gpios*;
+ static auto Create(bool invert_lock_switch) -> Gpios*;
~Gpios();
/*
@@ -106,6 +106,8 @@ class Gpios : public IGpios {
auto Get(Pin) const -> bool override;
+ auto IsLocked() const -> bool override;
+
/**
* Reads from the GPIO expander, populating `inputs` with the most recent
* values.
@@ -118,10 +120,11 @@ class Gpios : public IGpios {
Gpios& operator=(const Gpios&) = delete;
private:
- Gpios();
+ Gpios(bool invert_lock);
std::atomic<uint16_t> ports_;
std::atomic<uint16_t> inputs_;
+ const bool invert_lock_switch_;
};
} // namespace drivers
diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp
index 5bd825e5..f288f8e2 100644
--- a/src/drivers/include/nvs.hpp
+++ b/src/drivers/include/nvs.hpp
@@ -71,6 +71,11 @@ class NvsStorage {
auto LockPolarity() -> bool;
auto LockPolarity(bool) -> void;
+ auto DisplaySize()
+ -> std::pair<std::optional<uint16_t>, std::optional<uint16_t>>;
+ auto DisplaySize(std::pair<std::optional<uint16_t>, std::optional<uint16_t>>)
+ -> void;
+
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> void;
@@ -120,6 +125,9 @@ class NvsStorage {
nvs_handle_t handle_;
Setting<uint8_t> lock_polarity_;
+ Setting<uint16_t> display_cols_;
+ Setting<uint16_t> display_rows_;
+
Setting<uint8_t> brightness_;
Setting<uint8_t> sensitivity_;
Setting<uint16_t> amp_max_vol_;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 875059be..28cb542c 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -37,6 +37,8 @@ static constexpr char kKeyAmpLeftBias[] = "hp_bias";
static constexpr char kKeyPrimaryInput[] = "in_pri";
static constexpr char kKeyScrollSensitivity[] = "scroll";
static constexpr char kKeyLockPolarity[] = "lockpol";
+static constexpr char kKeyDisplayCols[] = "dispcols";
+static constexpr char kKeyDisplayRows[] = "disprows";
static auto nvs_get_string(nvs_handle_t nvs, const char* key)
-> std::optional<std::string> {
@@ -161,6 +163,8 @@ auto NvsStorage::OpenSync() -> NvsStorage* {
NvsStorage::NvsStorage(nvs_handle_t handle)
: handle_(handle),
lock_polarity_(kKeyLockPolarity),
+ display_cols_(kKeyDisplayCols),
+ display_rows_(kKeyDisplayRows),
brightness_(kKeyBrightness),
sensitivity_(kKeyScrollSensitivity),
amp_max_vol_(kKeyAmpMaxVolume),
@@ -180,6 +184,8 @@ NvsStorage::~NvsStorage() {
auto NvsStorage::Read() -> void {
std::lock_guard<std::mutex> lock{mutex_};
lock_polarity_.read(handle_);
+ display_cols_.read(handle_);
+ display_rows_.read(handle_);
brightness_.read(handle_);
sensitivity_.read(handle_);
amp_max_vol_.read(handle_);
@@ -194,6 +200,8 @@ auto NvsStorage::Read() -> void {
auto NvsStorage::Write() -> bool {
std::lock_guard<std::mutex> lock{mutex_};
lock_polarity_.write(handle_);
+ display_cols_.write(handle_);
+ display_rows_.write(handle_);
brightness_.write(handle_);
sensitivity_.write(handle_);
amp_max_vol_.write(handle_);
@@ -231,6 +239,19 @@ auto NvsStorage::LockPolarity(bool p) -> void {
lock_polarity_.set(p);
}
+auto NvsStorage::DisplaySize()
+ -> std::pair<std::optional<uint16_t>, std::optional<uint16_t>> {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return std::make_pair(display_cols_.get(), display_rows_.get());
+}
+
+auto NvsStorage::DisplaySize(
+ std::pair<std::optional<uint16_t>, std::optional<uint16_t>> size) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ display_cols_.set(std::move(size.first));
+ display_rows_.set(std::move(size.second));
+}
+
auto NvsStorage::PreferredBluetoothDevice()
-> std::optional<bluetooth::MacAndName> {
std::lock_guard<std::mutex> lock{mutex_};
diff --git a/src/drivers/samd.cpp b/src/drivers/samd.cpp
index f12a18de..b631b4fb 100644
--- a/src/drivers/samd.cpp
+++ b/src/drivers/samd.cpp
@@ -77,29 +77,16 @@ auto Samd::UpdateChargeStatus() -> void {
return;
}
+ // FIXME: Ideally we should be using the three 'charge status' bits to work
+ // out whether we're actually charging, or if we've got a full charge,
+ // critically low charge, etc.
uint8_t usb_state = raw_res & 0b11;
- uint8_t charge_state = (raw_res >> 2) & 0b111;
- switch (charge_state) {
- case 0b000:
- case 0b011:
- charge_status_ = ChargeStatus::kNoBattery;
- break;
- case 0b001:
- charge_status_ = usb_state == 1 ? ChargeStatus::kChargingRegular
- : ChargeStatus::kChargingFast;
- break;
- case 0b010:
- charge_status_ = ChargeStatus::kFullCharge;
- break;
- case 0b100:
- charge_status_ = ChargeStatus::kBatteryCritical;
- break;
- case 0b101:
- charge_status_ = ChargeStatus::kDischarging;
- break;
- default:
- charge_status_ = {};
- break;
+ if (usb_state == 0) {
+ charge_status_ = ChargeStatus::kDischarging;
+ } else if (usb_state == 1) {
+ charge_status_ = ChargeStatus::kChargingRegular;
+ } else {
+ charge_status_ = ChargeStatus::kChargingFast;
}
}
diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt
index 72e48aa0..0240a50c 100644
--- a/src/lua/CMakeLists.txt
+++ b/src/lua/CMakeLists.txt
@@ -5,6 +5,7 @@
idf_component_register(
SRCS "lua_theme.cpp" "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp"
"lua_queue.cpp" "lua_version.cpp" "lua_theme.cpp" "lua_controls.cpp" "registry.cpp"
+ "lua_screen.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 44be06f8..cfa9d5f7 100644
--- a/src/lua/bridge.cpp
+++ b/src/lua/bridge.cpp
@@ -19,6 +19,7 @@
#include "lua_controls.hpp"
#include "lua_database.hpp"
#include "lua_queue.hpp"
+#include "lua_screen.hpp"
#include "lua_version.hpp"
#include "lua_theme.hpp"
#include "lvgl.h"
@@ -86,6 +87,7 @@ auto Bridge::installBaseModules(lua_State* L) -> void {
RegisterQueueModule(L);
RegisterVersionModule(L);
RegisterThemeModule(L);
+ RegisterScreenModule(L);
}
auto Bridge::installLvgl(lua_State* L) -> void {
diff --git a/src/lua/include/lua_screen.hpp b/src/lua/include/lua_screen.hpp
new file mode 100644
index 00000000..1c3bed1a
--- /dev/null
+++ b/src/lua/include/lua_screen.hpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include "lua.hpp"
+
+namespace lua {
+
+auto RegisterScreenModule(lua_State*) -> void;
+
+} // namespace lua
diff --git a/src/lua/lua_screen.cpp b/src/lua/lua_screen.cpp
new file mode 100644
index 00000000..27843bc7
--- /dev/null
+++ b/src/lua/lua_screen.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "lua_screen.hpp"
+
+#include <memory>
+#include <string>
+
+#include "lua.hpp"
+
+#include "esp_log.h"
+#include "lauxlib.h"
+#include "lua.h"
+#include "lvgl.h"
+
+#include "bridge.hpp"
+#include "database.hpp"
+#include "event_queue.hpp"
+#include "index.hpp"
+#include "property.hpp"
+#include "service_locator.hpp"
+#include "track.hpp"
+#include "track_queue.hpp"
+#include "ui_events.hpp"
+
+namespace lua {
+
+static auto screen_new(lua_State* L) -> int {
+ // o = o or {}
+ if (lua_gettop(L) != 2) {
+ lua_settop(L, 1);
+ lua_newtable(L);
+ }
+ // Swap o and self on the stack.
+ lua_insert(L, 1);
+
+ lua_pushliteral(L, "__index");
+ lua_pushvalue(L, 1);
+ lua_settable(L, 1); // self.__index = self
+
+ lua_setmetatable(L, 1); // setmetatable(o, self)
+
+ return 1; // return o
+}
+
+static auto screen_noop(lua_State* state) -> int {
+ return 0;
+}
+
+static const struct luaL_Reg kScreenFuncs[] = {{"new", screen_new},
+ {"createUi", screen_noop},
+ {"onShown", screen_noop},
+ {"onHidden", screen_noop},
+ {NULL, NULL}};
+
+static auto lua_screen(lua_State* state) -> int {
+ luaL_newlib(state, kScreenFuncs);
+
+ lua_pushliteral(state, "__index");
+ lua_pushvalue(state, -2);
+ lua_rawset(state, -3);
+
+ return 1;
+}
+
+auto RegisterScreenModule(lua_State* s) -> void {
+ luaL_requiref(s, "screen", lua_screen, true);
+
+ lua_pop(s, 1);
+}
+
+} // namespace lua
diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp
index 41f46df2..eb931192 100644
--- a/src/system_fsm/booting.cpp
+++ b/src/system_fsm/booting.cpp
@@ -57,13 +57,22 @@ auto Booting::entry() -> void {
sServices.reset(new ServiceLocator());
ESP_LOGI(kTag, "installing early drivers");
+ // NVS is needed first because it contains information about what specific
+ // hardware configuration we're running on.
+ sServices->nvs(
+ std::unique_ptr<drivers::NvsStorage>(drivers::NvsStorage::OpenSync()));
+
+ // HACK: fix up the switch polarity on newer dev units
+ sServices->nvs().LockPolarity(false);
+
// I2C and SPI are both always needed. We can't even power down or show an
// error without these.
ESP_ERROR_CHECK(drivers::init_spi());
- sServices->gpios(std::unique_ptr<drivers::Gpios>(drivers::Gpios::Create()));
+ sServices->gpios(std::unique_ptr<drivers::Gpios>(
+ drivers::Gpios::Create(sServices->nvs().LockPolarity())));
ESP_LOGI(kTag, "starting ui");
- if (!ui::UiState::InitBootSplash(sServices->gpios())) {
+ if (!ui::UiState::InitBootSplash(sServices->gpios(), sServices->nvs())) {
events::System().Dispatch(FatalError{});
return;
}
@@ -74,8 +83,6 @@ auto Booting::entry() -> void {
ESP_LOGI(kTag, "installing remaining drivers");
drivers::spiffs_mount();
sServices->samd(std::unique_ptr<drivers::Samd>(drivers::Samd::Create()));
- sServices->nvs(
- std::unique_ptr<drivers::NvsStorage>(drivers::NvsStorage::OpenSync()));
sServices->touchwheel(
std::unique_ptr<drivers::TouchWheel>{drivers::TouchWheel::Create()});
sServices->haptics(std::make_unique<drivers::Haptics>());
@@ -100,8 +107,6 @@ auto Booting::entry() -> void {
sServices->bluetooth().Enable();
}
- sServices->nvs().LockPolarity(true);
-
BootComplete ev{.services = sServices};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp
index 32394958..1be03f82 100644
--- a/src/system_fsm/include/system_events.hpp
+++ b/src/system_fsm/include/system_events.hpp
@@ -57,7 +57,6 @@ struct SamdUsbMscChanged : tinyfsm::Event {
bool en;
};
-struct ChargingStatusChanged : tinyfsm::Event {};
struct BatteryStateChanged : tinyfsm::Event {
battery::Battery::BatteryState new_state;
};
diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp
index 977f4a6d..5a1ccf8c 100644
--- a/src/system_fsm/system_fsm.cpp
+++ b/src/system_fsm/system_fsm.cpp
@@ -84,10 +84,8 @@ void SystemState::react(const internal::SamdInterrupt&) {
auto charge_status = samd.GetChargeStatus();
auto usb_status = samd.GetUsbStatus();
- if (charge_status != prev_charge_status) {
- ChargingStatusChanged ev{};
- events::System().Dispatch(ev);
- events::Ui().Dispatch(ev);
+ if (charge_status != prev_charge_status && sServices) {
+ sServices->battery().Update();
}
if (usb_status != prev_usb_status) {
ESP_LOGI(kTag, "usb status changed");
diff --git a/src/ui/include/screen.hpp b/src/ui/include/screen.hpp
index 60939660..4241c712 100644
--- a/src/ui/include/screen.hpp
+++ b/src/ui/include/screen.hpp
@@ -27,6 +27,9 @@ class Screen {
Screen();
virtual ~Screen();
+ virtual auto onShown() -> void {}
+ virtual auto onHidden() -> void {}
+
auto root() -> lv_obj_t* { return root_; }
auto content() -> lv_obj_t* { return content_; }
auto alert() -> lv_obj_t* { return alert_; }
diff --git a/src/ui/include/screen_lua.hpp b/src/ui/include/screen_lua.hpp
index ee9f6813..0ed3a508 100644
--- a/src/ui/include/screen_lua.hpp
+++ b/src/ui/include/screen_lua.hpp
@@ -18,6 +18,9 @@ class Lua : public Screen {
Lua();
~Lua();
+ auto onShown() -> void override;
+ auto onHidden() -> void override;
+
auto SetObjRef(lua_State*) -> void;
private:
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 6cf2ba4c..579cc2bb 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -36,7 +36,7 @@ namespace ui {
class UiState : public tinyfsm::Fsm<UiState> {
public:
- static auto InitBootSplash(drivers::IGpios&) -> bool;
+ static auto InitBootSplash(drivers::IGpios&, drivers::NvsStorage&) -> bool;
virtual ~UiState() {}
@@ -129,6 +129,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
static lua::Property sControlsScheme;
static lua::Property sScrollSensitivity;
+ static lua::Property sLockSwitch;
static lua::Property sDatabaseUpdating;
};
diff --git a/src/ui/screen.cpp b/src/ui/screen.cpp
index bacce3f9..a39aaf7e 100644
--- a/src/ui/screen.cpp
+++ b/src/ui/screen.cpp
@@ -35,6 +35,9 @@ Screen::Screen()
lv_obj_set_style_bg_opa(modal_content_, LV_OPA_TRANSP, 0);
lv_obj_set_style_bg_opa(alert_, LV_OPA_TRANSP, 0);
+ lv_obj_set_scrollbar_mode(root_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
+
// Disable wrapping by default, since it's confusing and generally makes it
// harder to navigate quickly.
lv_group_set_wrap(group_, false);
diff --git a/src/ui/screen_lua.cpp b/src/ui/screen_lua.cpp
index b3554241..1ad4a8e8 100644
--- a/src/ui/screen_lua.cpp
+++ b/src/ui/screen_lua.cpp
@@ -7,9 +7,11 @@
#include "screen_lua.hpp"
#include "core/lv_obj_tree.h"
+#include "lua.h"
#include "lua.hpp"
#include "themes.hpp"
+#include "lua_thread.hpp"
#include "luavgl.h"
namespace ui {
@@ -25,6 +27,40 @@ Lua::~Lua() {
}
}
+auto Lua::onShown() -> void {
+ if (!s_ || !obj_ref_) {
+ return;
+ }
+ lua_rawgeti(s_, LUA_REGISTRYINDEX, *obj_ref_);
+ lua_pushliteral(s_, "onShown");
+
+ if (lua_gettable(s_, -2) == LUA_TFUNCTION) {
+ lua_pushvalue(s_, -2);
+ lua::CallProtected(s_, 1, 0);
+ } else {
+ lua_pop(s_, 1);
+ }
+
+ lua_pop(s_, 1);
+}
+
+auto Lua::onHidden() -> void {
+ if (!s_ || !obj_ref_) {
+ return;
+ }
+ lua_rawgeti(s_, LUA_REGISTRYINDEX, *obj_ref_);
+ lua_pushliteral(s_, "onHidden");
+
+ if (lua_gettable(s_, -2) == LUA_TFUNCTION) {
+ lua_pushvalue(s_, -2);
+ lua::CallProtected(s_, 1, 0);
+ } else {
+ lua_pop(s_, 1);
+ }
+
+ lua_pop(s_, 1);
+}
+
auto Lua::SetObjRef(lua_State* s) -> void {
assert(s_ == nullptr);
s_ = s;
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 25ae9817..5582dabe 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -12,6 +12,7 @@
#include "bluetooth_types.hpp"
#include "db_events.hpp"
+#include "display_init.hpp"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
#include "lua.h"
@@ -125,21 +126,24 @@ lua::Property UiState::sPlaybackPlaying{
}};
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::Track>(track)) {
+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;
}
- events::Audio().Dispatch(audio::SeekFile{.offset = (uint32_t)new_val, .filename = std::get<audio::Track>(track).filepath});
- }
- return true;
-}};
+ int new_val = std::get<int>(val);
+ if (current_val != new_val) {
+ auto track = sPlaybackTrack.Get();
+ if (!std::holds_alternative<audio::Track>(track)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SeekFile{
+ .offset = (uint32_t)new_val,
+ .filename = std::get<audio::Track>(track).filepath});
+ }
+ return true;
+ }};
lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{0};
@@ -277,12 +281,25 @@ lua::Property UiState::sScrollSensitivity{
return true;
}};
+lua::Property UiState::sLockSwitch{false};
+
lua::Property UiState::sDatabaseUpdating{false};
-auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
+auto UiState::InitBootSplash(drivers::IGpios& gpios, drivers::NvsStorage& nvs)
+ -> bool {
// Init LVGL first, since the display driver registers itself with LVGL.
lv_init();
- sDisplay.reset(drivers::Display::Create(gpios, drivers::displays::kST7735R));
+
+ drivers::displays::InitialisationData init_data = drivers::displays::kST7735R;
+
+ // HACK: correct the display size for our prototypes.
+ // nvs.DisplaySize({161, 130});
+
+ auto actual_size = 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(gpios, init_data));
if (sDisplay == nullptr) {
return false;
}
@@ -294,27 +311,36 @@ auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
}
void UiState::PushScreen(std::shared_ptr<Screen> screen) {
+ lv_obj_set_parent(sAlertContainer, screen->alert());
+
if (sCurrentScreen) {
+ sCurrentScreen->onHidden();
sScreens.push(sCurrentScreen);
}
sCurrentScreen = screen;
- lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
+ sCurrentScreen->onShown();
}
int UiState::PopScreen() {
if (sScreens.empty()) {
return 0;
}
- sCurrentScreen = sScreens.top();
- lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
+ 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.Update(ev.locking);
}
void UiState::react(const internal::ControlSchemeChanged&) {
@@ -506,6 +532,7 @@ void Lua::entry() {
{
{"scheme", &sControlsScheme},
{"scroll_sensitivity", &sScrollSensitivity},
+ {"lock_switch", &sLockSwitch},
});
registry.AddPropertyModule(
@@ -540,7 +567,7 @@ void Lua::entry() {
auto Lua::PushLuaScreen(lua_State* s) -> int {
// Ensure the arg looks right before continuing.
- luaL_checktype(s, 1, LUA_TFUNCTION);
+ 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
@@ -555,10 +582,15 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
lv_group_set_default(new_screen->group());
// Call the constructor for this screen.
- lua_settop(s, 1); // Make sure the function is actually at top of stack
- lua::CallProtected(s, 0, 1);
+ // 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 the table the constructor returned.
+ // 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++
@@ -586,7 +618,7 @@ auto Lua::PopLuaScreen(lua_State* s) -> int {
}
auto Lua::Ticks(lua_State* s) -> int {
- lua_pushinteger(s, esp_timer_get_time()/1000);
+ lua_pushinteger(s, esp_timer_get_time() / 1000);
return 1;
}