summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-08-30 16:48:10 +1000
committerjacqueline <me@jacqueline.id.au>2023-08-30 16:48:10 +1000
commit320fdeb9d8355d3c361d5c6d60de8afc64501af9 (patch)
treef0d5a2ab82199c78ad6768c6b18ba1239a0b7ee4
parent4247c9fe7d25c921fbfc73fc50e849c8780e7ad6 (diff)
downloadtangara-fw-320fdeb9d8355d3c361d5c6d60de8afc64501af9.tar.gz
Use a service locator instead of passing around subsets of drivers between FSMs
-rw-r--r--src/app_console/app_console.cpp37
-rw-r--r--src/app_console/include/app_console.hpp6
-rw-r--r--src/audio/audio_fsm.cpp71
-rw-r--r--src/audio/fatfs_audio_input.cpp5
-rw-r--r--src/audio/i2s_audio_output.cpp6
-rw-r--r--src/audio/include/audio_fsm.hpp14
-rw-r--r--src/audio/include/fatfs_audio_input.hpp4
-rw-r--r--src/audio/include/i2s_audio_output.hpp7
-rw-r--r--src/battery/battery.cpp6
-rw-r--r--src/battery/include/battery.hpp6
-rw-r--r--src/database/database.cpp25
-rw-r--r--src/database/include/database.hpp10
-rw-r--r--src/drivers/display.cpp4
-rw-r--r--src/drivers/i2s_dac.cpp16
-rw-r--r--src/drivers/include/display.hpp6
-rw-r--r--src/drivers/include/i2s_dac.hpp6
-rw-r--r--src/drivers/include/relative_wheel.hpp8
-rw-r--r--src/drivers/include/storage.hpp6
-rw-r--r--src/drivers/relative_wheel.cpp6
-rw-r--r--src/drivers/storage.cpp14
-rw-r--r--src/system_fsm/CMakeLists.txt2
-rw-r--r--src/system_fsm/booting.cpp57
-rw-r--r--src/system_fsm/idle.cpp26
-rw-r--r--src/system_fsm/include/service_locator.hpp113
-rw-r--r--src/system_fsm/include/system_events.hpp9
-rw-r--r--src/system_fsm/include/system_fsm.hpp19
-rw-r--r--src/system_fsm/running.cpp15
-rw-r--r--src/system_fsm/service_locator.cpp21
-rw-r--r--src/system_fsm/system_fsm.cpp53
-rw-r--r--src/ui/include/lvgl_task.hpp27
-rw-r--r--src/ui/include/screen_playing.hpp4
-rw-r--r--src/ui/include/screen_settings.hpp6
-rw-r--r--src/ui/include/ui_fsm.hpp25
-rw-r--r--src/ui/include/wheel_encoder.hpp4
-rw-r--r--src/ui/lvgl_task.cpp79
-rw-r--r--src/ui/screen_playing.cpp6
-rw-r--r--src/ui/screen_settings.cpp8
-rw-r--r--src/ui/ui_fsm.cpp101
-rw-r--r--src/ui/wheel_encoder.cpp15
39 files changed, 471 insertions, 382 deletions
diff --git a/src/app_console/app_console.cpp b/src/app_console/app_console.cpp
index 9aff20dc..2d287e5d 100644
--- a/src/app_console/app_console.cpp
+++ b/src/app_console/app_console.cpp
@@ -31,18 +31,16 @@
#include "freertos/FreeRTOSConfig_arch.h"
#include "freertos/projdefs.h"
#include "index.hpp"
+#include "service_locator.hpp"
#include "track.hpp"
namespace console {
-std::weak_ptr<database::Database> AppConsole::sDatabase;
-audio::TrackQueue* AppConsole::sTrackQueue;
-drivers::Bluetooth* AppConsole::sBluetooth;
-drivers::Samd* AppConsole::sSamd;
+std::shared_ptr<system_fsm::ServiceLocator> AppConsole::sServices;
int CmdListDir(int argc, char** argv) {
- auto lock = AppConsole::sDatabase.lock();
- if (lock == nullptr) {
+ auto db = AppConsole::sServices->database().lock();
+ if (!db) {
std::cout << "storage is not available" << std::endl;
return 1;
}
@@ -117,7 +115,7 @@ int CmdPlayFile(int argc, char** argv) {
if (is_id) {
database::TrackId id = std::atoi(argv[1]);
- AppConsole::sTrackQueue->AddLast(id);
+ AppConsole::sServices->track_queue().AddLast(id);
} else {
std::ostringstream path;
path << '/' << argv[1];
@@ -147,7 +145,7 @@ int CmdDbInit(int argc, char** argv) {
return 1;
}
- auto db = AppConsole::sDatabase.lock();
+ auto db = AppConsole::sServices->database().lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
@@ -174,7 +172,7 @@ int CmdDbTracks(int argc, char** argv) {
return 1;
}
- auto db = AppConsole::sDatabase.lock();
+ auto db = AppConsole::sServices->database().lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
@@ -211,7 +209,7 @@ int CmdDbIndex(int argc, char** argv) {
vTaskDelay(1);
static const std::string usage = "usage: db_index [id] [choices ...]";
- auto db = AppConsole::sDatabase.lock();
+ auto db = AppConsole::sServices->database().lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
@@ -252,9 +250,9 @@ int CmdDbIndex(int argc, char** argv) {
return -1;
}
if (res->values().at(choice).track()) {
- AppConsole::sTrackQueue->IncludeLast(
- std::make_shared<playlist::IndexRecordSource>(AppConsole::sDatabase,
- res, 0, res, choice));
+ AppConsole::sServices->track_queue().IncludeLast(
+ std::make_shared<playlist::IndexRecordSource>(
+ AppConsole::sServices->database(), res, 0, res, choice));
}
auto cont = res->values().at(choice).Expand(20);
if (!cont) {
@@ -296,7 +294,7 @@ int CmdDbDump(int argc, char** argv) {
return 1;
}
- auto db = AppConsole::sDatabase.lock();
+ auto db = AppConsole::sServices->database().lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
@@ -448,14 +446,15 @@ int CmdBtList(int argc, char** argv) {
return 1;
}
- auto devices = AppConsole::sBluetooth->KnownDevices();
+ auto devices = AppConsole::sServices->bluetooth().KnownDevices();
if (argc == 2) {
int index = std::atoi(argv[1]);
if (index < 0 || index >= devices.size()) {
std::cout << "index out of range" << std::endl;
return -1;
}
- AppConsole::sBluetooth->SetPreferredDevice(devices[index].address);
+ AppConsole::sServices->bluetooth().SetPreferredDevice(
+ devices[index].address);
} else {
std::cout << "mac\t\trssi\tname" << std::endl;
for (const auto& device : devices) {
@@ -493,9 +492,9 @@ int CmdSamd(int argc, char** argv) {
if (cmd == "flash") {
std::cout << "resetting samd..." << std::endl;
vTaskDelay(pdMS_TO_TICKS(5));
- AppConsole::sSamd->ResetToFlashSamd();
+ AppConsole::sServices->samd().ResetToFlashSamd();
} else if (cmd == "charge") {
- auto res = AppConsole::sSamd->GetChargeStatus();
+ auto res = AppConsole::sServices->samd().GetChargeStatus();
if (res) {
switch (res.value()) {
case drivers::Samd::ChargeStatus::kNoBattery:
@@ -523,7 +522,7 @@ int CmdSamd(int argc, char** argv) {
} else if (cmd == "off") {
std::cout << "bye !!!" << std::endl;
vTaskDelay(pdMS_TO_TICKS(5));
- AppConsole::sSamd->PowerDown();
+ AppConsole::sServices->samd().PowerDown();
} else {
std::cout << usage << std::endl;
return 1;
diff --git a/src/app_console/include/app_console.hpp b/src/app_console/include/app_console.hpp
index 6c23552e..5981cc04 100644
--- a/src/app_console/include/app_console.hpp
+++ b/src/app_console/include/app_console.hpp
@@ -12,16 +12,14 @@
#include "console.hpp"
#include "database.hpp"
#include "samd.hpp"
+#include "service_locator.hpp"
#include "track_queue.hpp"
namespace console {
class AppConsole : public Console {
public:
- static std::weak_ptr<database::Database> sDatabase;
- static audio::TrackQueue* sTrackQueue;
- static drivers::Bluetooth* sBluetooth;
- static drivers::Samd* sSamd;
+ static std::shared_ptr<system_fsm::ServiceLocator> sServices;
protected:
virtual auto RegisterExtraComponents() -> void;
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index 6cca8211..9121cb5a 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -25,6 +25,7 @@
#include "future_fetcher.hpp"
#include "i2s_audio_output.hpp"
#include "i2s_dac.hpp"
+#include "service_locator.hpp"
#include "system_events.hpp"
#include "track.hpp"
#include "track_queue.hpp"
@@ -33,49 +34,15 @@ namespace audio {
static const char kTag[] = "audio_fsm";
-drivers::IGpios* AudioState::sIGpios;
-std::shared_ptr<drivers::I2SDac> AudioState::sDac;
-std::weak_ptr<database::Database> AudioState::sDatabase;
+std::shared_ptr<system_fsm::ServiceLocator> AudioState::sServices;
std::shared_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<Decoder> AudioState::sDecoder;
std::shared_ptr<SampleConverter> AudioState::sSampleConverter;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
-TrackQueue* AudioState::sTrackQueue;
std::optional<database::TrackId> AudioState::sCurrentTrack;
-auto AudioState::Init(drivers::IGpios* gpio_expander,
- std::weak_ptr<database::Database> database,
- std::shared_ptr<database::ITagParser> tag_parser,
- drivers::Bluetooth* bluetooth,
- TrackQueue* queue) -> bool {
- sIGpios = gpio_expander;
- sTrackQueue = queue;
-
- auto dac = drivers::I2SDac::create(gpio_expander);
- if (!dac) {
- return false;
- }
- sDac.reset(dac.value());
- sDatabase = database;
-
- sFileSource.reset(new FatfsAudioInput(tag_parser));
- sOutput.reset(new I2SAudioOutput(sIGpios, sDac));
- // sOutput.reset(new BluetoothAudioOutput(bluetooth));
-
- sSampleConverter.reset(new SampleConverter());
- sSampleConverter->SetOutput(sOutput);
-
- Decoder::Start(sFileSource, sSampleConverter);
-
- return true;
-}
-
-void AudioState::react(const system_fsm::StorageMounted& ev) {
- sDatabase = ev.db;
-}
-
void AudioState::react(const system_fsm::KeyUpChanged& ev) {
if (ev.falling && sOutput->AdjustVolumeUp()) {
ESP_LOGI(kTag, "volume up!");
@@ -100,7 +67,26 @@ void AudioState::react(const system_fsm::HasPhonesChanged& ev) {
namespace states {
-void Uninitialised::react(const system_fsm::BootComplete&) {
+void Uninitialised::react(const system_fsm::BootComplete& ev) {
+ sServices = ev.services;
+
+ auto dac = drivers::I2SDac::create(sServices->gpios());
+ if (!dac) {
+ events::System().Dispatch(system_fsm::FatalError{});
+ events::Ui().Dispatch(system_fsm::FatalError{});
+ return;
+ }
+
+ sFileSource.reset(new FatfsAudioInput(sServices->tag_parser()));
+ sOutput.reset(new I2SAudioOutput(sServices->gpios(),
+ std::unique_ptr<drivers::I2SDac>{*dac}));
+ // sOutput.reset(new BluetoothAudioOutput(bluetooth));
+
+ sSampleConverter.reset(new SampleConverter());
+ sSampleConverter->SetOutput(sOutput);
+
+ Decoder::Start(sFileSource, sSampleConverter);
+
transit<Standby>();
}
@@ -117,19 +103,18 @@ void Standby::react(const internal::InputFileOpened& ev) {
}
void Standby::react(const QueueUpdate& ev) {
- auto current_track = sTrackQueue->GetCurrent();
+ auto current_track = sServices->track_queue().GetCurrent();
if (!current_track || (sCurrentTrack && *sCurrentTrack == *current_track)) {
return;
}
sCurrentTrack = current_track;
- auto db = sDatabase.lock();
+ auto db = sServices->database().lock();
if (!db) {
ESP_LOGW(kTag, "database not open; ignoring play request");
return;
}
-
sFileSource->SetPath(db->GetTrackPath(*current_track));
}
@@ -158,7 +143,7 @@ void Playback::react(const QueueUpdate& ev) {
if (!ev.current_changed) {
return;
}
- auto current_track = sTrackQueue->GetCurrent();
+ auto current_track = sServices->track_queue().GetCurrent();
if (!current_track) {
sFileSource->SetPath();
sCurrentTrack.reset();
@@ -168,7 +153,7 @@ void Playback::react(const QueueUpdate& ev) {
sCurrentTrack = current_track;
- auto db = sDatabase.lock();
+ auto db = sServices->database().lock();
if (!db) {
return;
}
@@ -191,8 +176,8 @@ void Playback::react(const internal::InputFileClosed& ev) {}
void Playback::react(const internal::InputFileFinished& ev) {
ESP_LOGI(kTag, "finished playing file");
- sTrackQueue->Next();
- if (!sTrackQueue->GetCurrent()) {
+ sServices->track_queue().Next();
+ if (!sServices->track_queue().GetCurrent()) {
transit<Standby>();
}
}
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index 44653624..6b032632 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -40,8 +40,7 @@ static const char* kTag = "SRC";
namespace audio {
-FatfsAudioInput::FatfsAudioInput(
- std::shared_ptr<database::ITagParser> tag_parser)
+FatfsAudioInput::FatfsAudioInput(database::ITagParser& tag_parser)
: IAudioSource(),
tag_parser_(tag_parser),
new_stream_mutex_(),
@@ -119,7 +118,7 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
ESP_LOGI(kTag, "opening file %s", path.c_str());
database::TrackTags tags;
- if (!tag_parser_->ReadAndParseTags(path, &tags)) {
+ if (!tag_parser_.ReadAndParseTags(path, &tags)) {
ESP_LOGE(kTag, "failed to read tags");
return false;
}
diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp
index 359cd4c6..927f6541 100644
--- a/src/audio/i2s_audio_output.cpp
+++ b/src/audio/i2s_audio_output.cpp
@@ -41,11 +41,11 @@ static constexpr uint16_t kDefaultVolume = 0x100;
static constexpr size_t kDrainBufferSize = 8 * 1024;
-I2SAudioOutput::I2SAudioOutput(drivers::IGpios* expander,
- std::weak_ptr<drivers::I2SDac> dac)
+I2SAudioOutput::I2SAudioOutput(drivers::IGpios& expander,
+ std::unique_ptr<drivers::I2SDac> dac)
: IAudioOutput(kDrainBufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT),
expander_(expander),
- dac_(dac.lock()),
+ dac_(std::move(dac)),
current_config_(),
left_difference_(0),
current_volume_(kDefaultVolume),
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index cfa65551..1376feae 100644
--- a/src/audio/include/audio_fsm.hpp
+++ b/src/audio/include/audio_fsm.hpp
@@ -11,6 +11,7 @@
#include <vector>
#include "audio_sink.hpp"
+#include "service_locator.hpp"
#include "tinyfsm.hpp"
#include "audio_decoder.hpp"
@@ -32,12 +33,6 @@ namespace audio {
class AudioState : public tinyfsm::Fsm<AudioState> {
public:
- static auto Init(drivers::IGpios* gpio_expander,
- std::weak_ptr<database::Database>,
- std::shared_ptr<database::ITagParser>,
- drivers::Bluetooth* bluetooth,
- TrackQueue* queue) -> bool;
-
virtual ~AudioState() {}
virtual void entry() {}
@@ -46,8 +41,6 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
/* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {}
- void react(const system_fsm::StorageMounted&);
-
void react(const system_fsm::KeyUpChanged&);
void react(const system_fsm::KeyDownChanged&);
void react(const system_fsm::HasPhonesChanged&);
@@ -65,16 +58,13 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
virtual void react(const internal::AudioPipelineIdle&) {}
protected:
- static drivers::IGpios* sIGpios;
- static std::shared_ptr<drivers::I2SDac> sDac;
- static std::weak_ptr<database::Database> sDatabase;
+ static std::shared_ptr<system_fsm::ServiceLocator> sServices;
static std::shared_ptr<FatfsAudioInput> sFileSource;
static std::unique_ptr<Decoder> sDecoder;
static std::shared_ptr<SampleConverter> sSampleConverter;
static std::shared_ptr<IAudioOutput> sOutput;
- static TrackQueue* sTrackQueue;
static std::optional<database::TrackId> sCurrentTrack;
};
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index da44d2ec..b67d29dc 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -30,7 +30,7 @@ namespace audio {
*/
class FatfsAudioInput : public IAudioSource {
public:
- explicit FatfsAudioInput(std::shared_ptr<database::ITagParser> tag_parser);
+ explicit FatfsAudioInput(database::ITagParser& tag_parser);
~FatfsAudioInput();
/*
@@ -53,7 +53,7 @@ class FatfsAudioInput : public IAudioSource {
auto ContainerToStreamType(database::Container)
-> std::optional<codecs::StreamType>;
- std::shared_ptr<database::ITagParser> tag_parser_;
+ database::ITagParser& tag_parser_;
std::mutex new_stream_mutex_;
std::shared_ptr<codecs::IStream> new_stream_;
diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp
index 0068916e..fa09deef 100644
--- a/src/audio/include/i2s_audio_output.hpp
+++ b/src/audio/include/i2s_audio_output.hpp
@@ -19,7 +19,8 @@ namespace audio {
class I2SAudioOutput : public IAudioOutput {
public:
- I2SAudioOutput(drivers::IGpios* expander, std::weak_ptr<drivers::I2SDac> dac);
+ I2SAudioOutput(drivers::IGpios& expander,
+ std::unique_ptr<drivers::I2SDac> dac);
~I2SAudioOutput();
auto SetInUse(bool) -> void override;
@@ -37,8 +38,8 @@ class I2SAudioOutput : public IAudioOutput {
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;
private:
- drivers::IGpios* expander_;
- std::shared_ptr<drivers::I2SDac> dac_;
+ drivers::IGpios& expander_;
+ std::unique_ptr<drivers::I2SDac> dac_;
std::optional<Format> current_config_;
int_fast8_t left_difference_;
diff --git a/src/battery/battery.cpp b/src/battery/battery.cpp
index d73f4f29..9ac6ebf1 100644
--- a/src/battery/battery.cpp
+++ b/src/battery/battery.cpp
@@ -40,8 +40,8 @@ void check_voltage_cb(TimerHandle_t timer) {
instance->Update();
}
-Battery::Battery(drivers::Samd* samd, drivers::AdcBattery* adc)
- : samd_(samd), adc_(adc) {
+Battery::Battery(drivers::Samd& samd, std::unique_ptr<drivers::AdcBattery> adc)
+ : samd_(samd), adc_(std::move(adc)) {
timer_ = xTimerCreate("BATTERY", kBatteryCheckPeriod, true, this,
check_voltage_cb);
xTimerStart(timer_, portMAX_DELAY);
@@ -56,7 +56,7 @@ Battery::~Battery() {
auto Battery::Update() -> void {
std::lock_guard<std::mutex> lock{state_mutex_};
- auto charge_state = samd_->GetChargeStatus();
+ auto charge_state = samd_.GetChargeStatus();
if (!charge_state || *charge_state == ChargeStatus::kNoBattery) {
if (state_) {
EmitEvent();
diff --git a/src/battery/include/battery.hpp b/src/battery/include/battery.hpp
index dcb9b4ea..63a8a47b 100644
--- a/src/battery/include/battery.hpp
+++ b/src/battery/include/battery.hpp
@@ -18,7 +18,7 @@ namespace battery {
class Battery {
public:
- Battery(drivers::Samd* samd, drivers::AdcBattery* adc);
+ Battery(drivers::Samd& samd, std::unique_ptr<drivers::AdcBattery> adc);
~Battery();
auto Update() -> void;
@@ -33,8 +33,8 @@ class Battery {
private:
auto EmitEvent() -> void;
- drivers::Samd* samd_;
- drivers::AdcBattery* adc_;
+ drivers::Samd& samd_;
+ std::unique_ptr<drivers::AdcBattery> adc_;
TimerHandle_t timer_;
std::mutex state_mutex_;
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 2a5b3236..76c2dda1 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -46,9 +46,6 @@ static const char kTrackIdKey[] = "next_track_id";
static std::atomic<bool> sIsDbOpen(false);
-static FileGathererImpl sFileGatherer;
-static TagParserImpl sTagParser;
-
template <typename Parser>
auto IterateAndParse(leveldb::Iterator* it, std::size_t limit, Parser p)
-> void {
@@ -62,11 +59,7 @@ auto IterateAndParse(leveldb::Iterator* it, std::size_t limit, Parser p)
}
}
-auto Database::Open() -> cpp::result<Database*, DatabaseError> {
- return Open(&sFileGatherer, &sTagParser);
-}
-
-auto Database::Open(IFileGatherer* gatherer, ITagParser* parser)
+auto Database::Open(IFileGatherer& gatherer, ITagParser& parser)
-> cpp::result<Database*, DatabaseError> {
// TODO(jacqueline): Why isn't compare_and_exchange_* available?
if (sIsDbOpen.exchange(true)) {
@@ -79,7 +72,7 @@ auto Database::Open(IFileGatherer* gatherer, ITagParser* parser)
tasks::Worker::Start<tasks::Type::kDatabase>());
return worker
->Dispatch<cpp::result<Database*, DatabaseError>>(
- [=]() -> cpp::result<Database*, DatabaseError> {
+ [&]() -> cpp::result<Database*, DatabaseError> {
leveldb::DB* db;
leveldb::Cache* cache = leveldb::NewLRUCache(24 * 1024);
leveldb::Options options;
@@ -112,8 +105,8 @@ auto Database::Destroy() -> void {
Database::Database(leveldb::DB* db,
leveldb::Cache* cache,
- IFileGatherer* file_gatherer,
- ITagParser* tag_parser,
+ IFileGatherer& file_gatherer,
+ ITagParser& tag_parser,
std::shared_ptr<tasks::Worker> worker)
: db_(db),
cache_(cache),
@@ -178,7 +171,7 @@ auto Database::Update() -> std::future<void> {
}
TrackTags tags{};
- if (!tag_parser_->ReadAndParseTags(track->filepath(), &tags) ||
+ if (!tag_parser_.ReadAndParseTags(track->filepath(), &tags) ||
tags.encoding() == Container::kUnsupported) {
// We couldn't read the tags for this track. Either they were
// malformed, or perhaps the file is missing. Either way, tombstone
@@ -215,9 +208,9 @@ auto Database::Update() -> std::future<void> {
events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kScanningForNewTracks,
});
- file_gatherer_->FindFiles("", [&](const std::string& path) {
+ file_gatherer_.FindFiles("", [&](const std::string& path) {
TrackTags tags;
- if (!tag_parser_->ReadAndParseTags(path, &tags) ||
+ if (!tag_parser_.ReadAndParseTags(path, &tags) ||
tags.encoding() == Container::kUnsupported) {
// No parseable tags; skip this fiile.
return;
@@ -287,7 +280,7 @@ auto Database::GetTrack(TrackId id) -> std::future<std::optional<Track>> {
return {};
}
TrackTags tags;
- if (!tag_parser_->ReadAndParseTags(data->filepath(), &tags)) {
+ if (!tag_parser_.ReadAndParseTags(data->filepath(), &tags)) {
return {};
}
return Track(*data, tags);
@@ -628,7 +621,7 @@ auto Database::ParseRecord<Track>(const leveldb::Slice& key,
return {};
}
TrackTags tags;
- if (!tag_parser_->ReadAndParseTags(data->filepath(), &tags)) {
+ if (!tag_parser_.ReadAndParseTags(data->filepath(), &tags)) {
return {};
}
return Track(*data, tags);
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp
index dd6bd7cc..559405cb 100644
--- a/src/database/include/database.hpp
+++ b/src/database/include/database.hpp
@@ -91,7 +91,7 @@ class Database {
ALREADY_OPEN,
FAILED_TO_OPEN,
};
- static auto Open(IFileGatherer* file_gatherer, ITagParser* tag_parser)
+ static auto Open(IFileGatherer& file_gatherer, ITagParser& tag_parser)
-> cpp::result<Database*, DatabaseError>;
static auto Open() -> cpp::result<Database*, DatabaseError>;
@@ -133,13 +133,13 @@ class Database {
std::shared_ptr<tasks::Worker> worker_task_;
// Not owned.
- IFileGatherer* file_gatherer_;
- ITagParser* tag_parser_;
+ IFileGatherer& file_gatherer_;
+ ITagParser& tag_parser_;
Database(leveldb::DB* db,
leveldb::Cache* cache,
- IFileGatherer* file_gatherer,
- ITagParser* tag_parser,
+ IFileGatherer& file_gatherer,
+ ITagParser& tag_parser,
std::shared_ptr<tasks::Worker> worker);
auto dbMintNewTrackId() -> TrackId;
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index 2d480aa6..e04de477 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -85,7 +85,7 @@ extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv,
instance->OnLvglFlush(disp_drv, area, color_map);
}
-auto Display::Create(IGpios* expander,
+auto Display::Create(IGpios& expander,
const displays::InitialisationData& init_data)
-> Display* {
ESP_LOGI(kTag, "Init I/O pins");
@@ -182,7 +182,7 @@ auto Display::Create(IGpios* expander,
return display.release();
}
-Display::Display(IGpios* gpio, spi_device_handle_t handle)
+Display::Display(IGpios& gpio, spi_device_handle_t handle)
: gpio_(gpio),
handle_(handle),
worker_task_(tasks::Worker::Start<tasks::Type::kUiFlush>()),
diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp
index af2125a6..bedf7ebf 100644
--- a/src/drivers/i2s_dac.cpp
+++ b/src/drivers/i2s_dac.cpp
@@ -39,7 +39,7 @@ namespace drivers {
static const char* kTag = "i2s_dac";
static const i2s_port_t kI2SPort = I2S_NUM_0;
-auto I2SDac::create(IGpios* expander) -> std::optional<I2SDac*> {
+auto I2SDac::create(IGpios& expander) -> std::optional<I2SDac*> {
i2s_chan_handle_t i2s_handle;
i2s_chan_config_t channel_config =
I2S_CHANNEL_DEFAULT_CONFIG(kI2SPort, I2S_ROLE_MASTER);
@@ -77,7 +77,7 @@ auto I2SDac::create(IGpios* expander) -> std::optional<I2SDac*> {
return dac.release();
}
-I2SDac::I2SDac(IGpios* gpio, i2s_chan_handle_t i2s_handle)
+I2SDac::I2SDac(IGpios& gpio, i2s_chan_handle_t i2s_handle)
: gpio_(gpio),
i2s_handle_(i2s_handle),
i2s_active_(false),
@@ -87,7 +87,7 @@ I2SDac::I2SDac(IGpios* gpio, i2s_chan_handle_t i2s_handle)
clock_config_.clk_src = I2S_CLK_SRC_APLL;
// Keep the 5V circuity off until it's needed.
- gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierEnable, false);
// Reset all registers back to their default values.
wm8523::WriteRegister(wm8523::Register::kReset, 1);
@@ -103,9 +103,9 @@ I2SDac::~I2SDac() {
auto I2SDac::Start() -> void {
std::lock_guard<std::mutex> lock(configure_mutex_);
- gpio_->WriteSync(IGpios::Pin::kAmplifierUnmute, false);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierUnmute, false);
// Ramp up the amplifier power supply.
- gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, true);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierEnable, true);
// Wait for voltage to stabilise
vTaskDelay(pdMS_TO_TICKS(5));
@@ -124,7 +124,7 @@ auto I2SDac::Start() -> void {
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b11);
vTaskDelay(pdMS_TO_TICKS(5));
- gpio_->WriteSync(IGpios::Pin::kAmplifierUnmute, true);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierUnmute, true);
}
auto I2SDac::Stop() -> void {
@@ -134,7 +134,7 @@ auto I2SDac::Stop() -> void {
wm8523::WriteRegister(wm8523::Register::kPsCtrl, 0b10);
vTaskDelay(pdMS_TO_TICKS(5));
// Silence the output.
- gpio_->WriteSync(IGpios::Pin::kAmplifierUnmute, false);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierUnmute, false);
vTaskDelay(pdMS_TO_TICKS(5));
@@ -143,7 +143,7 @@ auto I2SDac::Stop() -> void {
i2s_channel_disable(i2s_handle_);
i2s_active_ = false;
- gpio_->WriteSync(IGpios::Pin::kAmplifierEnable, false);
+ gpio_.WriteSync(IGpios::Pin::kAmplifierEnable, false);
}
auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp
index 77165c99..766fc4ea 100644
--- a/src/drivers/include/display.hpp
+++ b/src/drivers/include/display.hpp
@@ -30,10 +30,10 @@ class Display {
* over SPI. This never fails, since unfortunately these display don't give
* us back any kind of signal to tell us we're actually using them correctly.
*/
- static auto Create(IGpios* expander,
+ static auto Create(IGpios& expander,
const displays::InitialisationData& init_data) -> Display*;
- Display(IGpios* gpio, spi_device_handle_t handle);
+ Display(IGpios& gpio, spi_device_handle_t handle);
~Display();
auto SetDisplayOn(bool) -> void;
@@ -49,7 +49,7 @@ class Display {
Display& operator=(const Display&) = delete;
private:
- IGpios* gpio_;
+ IGpios& gpio_;
spi_device_handle_t handle_;
std::unique_ptr<tasks::Worker> worker_task_;
diff --git a/src/drivers/include/i2s_dac.hpp b/src/drivers/include/i2s_dac.hpp
index c7faed2f..6bc5b6a4 100644
--- a/src/drivers/include/i2s_dac.hpp
+++ b/src/drivers/include/i2s_dac.hpp
@@ -33,9 +33,9 @@ namespace drivers {
*/
class I2SDac {
public:
- static auto create(IGpios* expander) -> std::optional<I2SDac*>;
+ static auto create(IGpios& expander) -> std::optional<I2SDac*>;
- I2SDac(IGpios* gpio, i2s_chan_handle_t i2s_handle);
+ I2SDac(IGpios& gpio, i2s_chan_handle_t i2s_handle);
~I2SDac();
auto Start() -> void;
@@ -69,7 +69,7 @@ class I2SDac {
I2SDac& operator=(const I2SDac&) = delete;
private:
- IGpios* gpio_;
+ IGpios& gpio_;
i2s_chan_handle_t i2s_handle_;
bool i2s_active_;
StreamBufferHandle_t buffer_;
diff --git a/src/drivers/include/relative_wheel.hpp b/src/drivers/include/relative_wheel.hpp
index 5e801aba..b5532a4c 100644
--- a/src/drivers/include/relative_wheel.hpp
+++ b/src/drivers/include/relative_wheel.hpp
@@ -20,11 +20,7 @@ namespace drivers {
class RelativeWheel {
public:
- static auto Create(TouchWheel* touch) -> RelativeWheel* {
- return new RelativeWheel(touch);
- }
-
- explicit RelativeWheel(TouchWheel* touch);
+ explicit RelativeWheel(TouchWheel& touch);
auto Update() -> void;
auto SetEnabled(bool) -> void;
@@ -37,7 +33,7 @@ class RelativeWheel {
RelativeWheel& operator=(const RelativeWheel&) = delete;
private:
- TouchWheel* touch_;
+ TouchWheel& touch_;
bool is_enabled_;
diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp
index 65be75f1..0b0cb494 100644
--- a/src/drivers/include/storage.hpp
+++ b/src/drivers/include/storage.hpp
@@ -31,9 +31,9 @@ class SdStorage {
FAILED_TO_MOUNT,
};
- static auto Create(IGpios* gpio) -> cpp::result<SdStorage*, Error>;
+ static auto Create(IGpios& gpio) -> cpp::result<SdStorage*, Error>;
- SdStorage(IGpios* gpio,
+ SdStorage(IGpios& gpio,
sdspi_dev_handle_t handle_,
std::unique_ptr<sdmmc_host_t> host_,
std::unique_ptr<sdmmc_card_t> card_,
@@ -50,7 +50,7 @@ class SdStorage {
SdStorage& operator=(const SdStorage&) = delete;
private:
- IGpios* gpio_;
+ IGpios& gpio_;
// SPI and SD driver info
sdspi_dev_handle_t handle_;
diff --git a/src/drivers/relative_wheel.cpp b/src/drivers/relative_wheel.cpp
index 75b62ae7..c014ab5e 100644
--- a/src/drivers/relative_wheel.cpp
+++ b/src/drivers/relative_wheel.cpp
@@ -13,7 +13,7 @@
namespace drivers {
-RelativeWheel::RelativeWheel(TouchWheel* touch)
+RelativeWheel::RelativeWheel(TouchWheel& touch)
: touch_(touch),
is_enabled_(true),
is_clicking_(false),
@@ -23,8 +23,8 @@ RelativeWheel::RelativeWheel(TouchWheel* touch)
last_angle_(0) {}
auto RelativeWheel::Update() -> void {
- touch_->Update();
- TouchWheelData d = touch_->GetTouchWheelData();
+ touch_.Update();
+ TouchWheelData d = touch_.GetTouchWheelData();
is_clicking_ = d.is_button_touched;
diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp
index f253a79a..6acb6870 100644
--- a/src/drivers/storage.cpp
+++ b/src/drivers/storage.cpp
@@ -32,10 +32,10 @@ namespace drivers {
const char* kStoragePath = "/sdcard";
-auto SdStorage::Create(IGpios* gpio) -> cpp::result<SdStorage*, Error> {
- gpio->WriteSync(IGpios::Pin::kSdPowerEnable, 1);
- gpio->WriteSync(IGpios::Pin::kSdMuxSwitch, IGpios::SD_MUX_ESP);
- gpio->WriteSync(IGpios::Pin::kSdMuxDisable, 0);
+auto SdStorage::Create(IGpios& gpio) -> cpp::result<SdStorage*, Error> {
+ gpio.WriteSync(IGpios::Pin::kSdPowerEnable, 1);
+ gpio.WriteSync(IGpios::Pin::kSdMuxSwitch, IGpios::SD_MUX_ESP);
+ gpio.WriteSync(IGpios::Pin::kSdMuxDisable, 0);
sdspi_dev_handle_t handle;
FATFS* fs = nullptr;
@@ -95,7 +95,7 @@ auto SdStorage::Create(IGpios* gpio) -> cpp::result<SdStorage*, Error> {
return new SdStorage(gpio, handle, std::move(host), std::move(card), fs);
}
-SdStorage::SdStorage(IGpios* gpio,
+SdStorage::SdStorage(IGpios& gpio,
sdspi_dev_handle_t handle,
std::unique_ptr<sdmmc_host_t> host,
std::unique_ptr<sdmmc_card_t> card,
@@ -117,8 +117,8 @@ SdStorage::~SdStorage() {
sdspi_host_remove_device(this->handle_);
sdspi_host_deinit();
- gpio_->WriteSync(IGpios::Pin::kSdPowerEnable, 1);
- gpio_->WriteSync(IGpios::Pin::kSdMuxDisable, 1);
+ gpio_.WriteSync(IGpios::Pin::kSdPowerEnable, 1);
+ gpio_.WriteSync(IGpios::Pin::kSdMuxDisable, 1);
}
auto SdStorage::GetFs() -> FATFS* {
diff --git a/src/system_fsm/CMakeLists.txt b/src/system_fsm/CMakeLists.txt
index fced4093..449e14cc 100644
--- a/src/system_fsm/CMakeLists.txt
+++ b/src/system_fsm/CMakeLists.txt
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" "idle.cpp"
+ SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" "idle.cpp" "service_locator.cpp"
INCLUDE_DIRS "include"
REQUIRES "tinyfsm" "drivers" "database" "ui" "result" "events" "audio" "app_console" "battery")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp
index a988c622..006ed395 100644
--- a/src/system_fsm/booting.cpp
+++ b/src/system_fsm/booting.cpp
@@ -6,8 +6,10 @@
#include <stdint.h>
+#include "adc.hpp"
#include "assert.h"
#include "audio_fsm.hpp"
+#include "battery.hpp"
#include "bluetooth.hpp"
#include "core/lv_obj.h"
#include "display_init.hpp"
@@ -23,6 +25,7 @@
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "samd.hpp"
+#include "service_locator.hpp"
#include "spi.hpp"
#include "system_events.hpp"
#include "system_fsm.hpp"
@@ -40,65 +43,55 @@ static const char kTag[] = "BOOT";
auto Booting::entry() -> void {
ESP_LOGI(kTag, "beginning tangara boot");
- ESP_LOGI(kTag, "installing early drivers");
+ sServices.reset(new ServiceLocator());
+ ESP_LOGI(kTag, "installing early drivers");
// 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());
- sGpios.reset(drivers::Gpios::Create());
-
- sSamd.reset(drivers::Samd::Create());
- sAdc.reset(drivers::AdcBattery::Create());
- sNvs.reset(drivers::NvsStorage::OpenSync());
- assert(sSamd.get() && sAdc.get() && sNvs.get());
+ sServices->gpios(std::unique_ptr<drivers::Gpios>(drivers::Gpios::Create()));
- sBattery.reset(new battery::Battery(sSamd.get(), sAdc.get()));
-
- // Start bringing up LVGL now, since we have all of its prerequisites.
- sTrackQueue.reset(new audio::TrackQueue());
ESP_LOGI(kTag, "starting ui");
- if (!ui::UiState::Init(sGpios.get(), sNvs, sTrackQueue.get(), sBattery)) {
+ if (!ui::UiState::InitBootSplash(sServices->gpios())) {
events::System().Dispatch(FatalError{});
return;
}
- // Install everything else that is certain to be needed.
ESP_LOGI(kTag, "installing remaining drivers");
- sTagParser.reset(new database::TagParserImpl());
+ 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()});
+
+ auto adc = drivers::AdcBattery::Create();
+ sServices->battery(std::make_unique<battery::Battery>(
+ sServices->samd(), std::unique_ptr<drivers::AdcBattery>(adc)));
+
+ sServices->track_queue(std::make_unique<audio::TrackQueue>());
+ sServices->tag_parser(std::make_unique<database::TagParserImpl>());
// ESP_LOGI(kTag, "starting bluetooth");
// sBluetooth.reset(new drivers::Bluetooth(sNvs.get()));
// sBluetooth->Enable();
- // At this point we've done all of the essential boot tasks. Start remaining
- // state machines and inform them that the system is ready.
-
- ESP_LOGI(kTag, "starting audio");
- if (!audio::AudioState::Init(sGpios.get(), sDatabase, sTagParser,
- sBluetooth.get(), sTrackQueue.get())) {
- events::System().Dispatch(FatalError{});
- events::Ui().Dispatch(FatalError{});
- return;
- }
-
- events::System().Dispatch(BootComplete{});
- events::Audio().Dispatch(BootComplete{});
- events::Ui().Dispatch(BootComplete{});
+ BootComplete ev{.services = sServices};
+ events::Audio().Dispatch(ev);
+ events::Ui().Dispatch(ev);
+ events::System().Dispatch(ev);
}
auto Booting::exit() -> void {
// TODO(jacqueline): Gate this on something. Debug flag? Flashing mode?
sAppConsole = new console::AppConsole();
- sAppConsole->sTrackQueue = sTrackQueue.get();
- sAppConsole->sBluetooth = sBluetooth.get();
- sAppConsole->sSamd = sSamd.get();
+ sAppConsole->sServices = sServices;
sAppConsole->Launch();
}
auto Booting::react(const BootComplete& ev) -> void {
ESP_LOGI(kTag, "bootup completely successfully");
- if (sGpios->Get(drivers::Gpios::Pin::kKeyLock)) {
+ if (sServices->gpios().Get(drivers::Gpios::Pin::kKeyLock)) {
transit<Running>();
} else {
transit<Idle>();
diff --git a/src/system_fsm/idle.cpp b/src/system_fsm/idle.cpp
index 7cc1fa39..bd327134 100644
--- a/src/system_fsm/idle.cpp
+++ b/src/system_fsm/idle.cpp
@@ -64,30 +64,32 @@ void Idle::react(const internal::IdleTimeout& ev) {
// FIXME: It would be neater to just free a bunch of our pointers, deinit the
// other state machines, etc.
- if (sTouch) {
- sTouch->PowerDown();
+ auto touchwheel = sServices->touchwheel();
+ if (touchwheel) {
+ touchwheel.value()->PowerDown();
}
+ auto& gpios = sServices->gpios();
// Pull down to turn things off
- sGpios->WriteBuffered(drivers::IGpios::Pin::kAmplifierEnable, false);
- sGpios->WriteBuffered(drivers::IGpios::Pin::kSdPowerEnable, false);
- sGpios->WriteBuffered(drivers::IGpios::Pin::kDisplayEnable, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kAmplifierEnable, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kSdPowerEnable, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kDisplayEnable, false);
// Leave up to match the external pullups
- sGpios->WriteBuffered(drivers::IGpios::Pin::kSdMuxSwitch, true);
- sGpios->WriteBuffered(drivers::IGpios::Pin::kSdMuxDisable, true);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kSdMuxSwitch, true);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kSdMuxDisable, true);
// Pull down to prevent sourcing current uselessly from input pins.
- sGpios->WriteBuffered(drivers::IGpios::Pin::kSdCardDetect, false);
- sGpios->WriteBuffered(drivers::IGpios::Pin::kKeyUp, false);
- sGpios->WriteBuffered(drivers::IGpios::Pin::kKeyDown, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kSdCardDetect, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kKeyUp, false);
+ gpios.WriteBuffered(drivers::IGpios::Pin::kKeyDown, false);
- sGpios->Flush();
+ gpios.Flush();
// Retry shutting down in case of a transient failure with the SAMD. e.g. i2c
// timeouts. This guards against a buggy SAMD firmware preventing idle.
for (;;) {
- sSamd->PowerDown();
+ sServices->samd().PowerDown();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
diff --git a/src/system_fsm/include/service_locator.hpp b/src/system_fsm/include/service_locator.hpp
new file mode 100644
index 00000000..00285ed5
--- /dev/null
+++ b/src/system_fsm/include/service_locator.hpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "battery.hpp"
+#include "bluetooth.hpp"
+#include "database.hpp"
+#include "gpios.hpp"
+#include "nvs.hpp"
+#include "samd.hpp"
+#include "tag_parser.hpp"
+#include "touchwheel.hpp"
+#include "track_queue.hpp"
+
+namespace system_fsm {
+
+class ServiceLocator {
+ public:
+ static auto instance() -> ServiceLocator&;
+
+ auto gpios() -> drivers::Gpios& {
+ assert(gpios_ != nullptr);
+ return *gpios_;
+ }
+
+ auto gpios(std::unique_ptr<drivers::Gpios> i) { gpios_ = std::move(i); }
+
+ auto samd() -> drivers::Samd& {
+ assert(samd_ != nullptr);
+ return *samd_;
+ }
+
+ auto samd(std::unique_ptr<drivers::Samd> i) { samd_ = std::move(i); }
+
+ auto nvs() -> drivers::NvsStorage& {
+ assert(nvs_ != nullptr);
+ return *nvs_;
+ }
+
+ auto nvs(std::unique_ptr<drivers::NvsStorage> i) { nvs_ = std::move(i); }
+
+ auto bluetooth() -> drivers::Bluetooth& {
+ assert(bluetooth_ != nullptr);
+ return *bluetooth_;
+ }
+
+ auto bluetooth(std::unique_ptr<drivers::Bluetooth> i) {
+ bluetooth_ = std::move(i);
+ }
+
+ auto battery() -> battery::Battery& {
+ assert(battery_ != nullptr);
+ return *battery_;
+ }
+
+ auto battery(std::unique_ptr<battery::Battery> i) { battery_ = std::move(i); }
+
+ auto touchwheel() -> std::optional<drivers::TouchWheel*> {
+ if (!touchwheel_) {
+ return {};
+ }
+ return touchwheel_.get();
+ }
+
+ auto touchwheel(std::unique_ptr<drivers::TouchWheel> i) {
+ touchwheel_ = std::move(i);
+ }
+
+ auto database() -> std::weak_ptr<database::Database> { return database_; }
+
+ auto database(std::unique_ptr<database::Database> i) {
+ database_ = std::move(i);
+ }
+
+ auto tag_parser() -> database::ITagParser& {
+ assert(tag_parser_ != nullptr);
+ return *tag_parser_;
+ }
+
+ auto tag_parser(std::unique_ptr<database::ITagParser> i) {
+ tag_parser_ = std::move(i);
+ }
+
+ auto track_queue() -> audio::TrackQueue& {
+ assert(queue_ != nullptr);
+ return *queue_;
+ }
+
+ auto track_queue(std::unique_ptr<audio::TrackQueue> i) {
+ queue_ = std::move(i);
+ }
+
+ private:
+ std::unique_ptr<drivers::Gpios> gpios_;
+ std::unique_ptr<drivers::Samd> samd_;
+ std::unique_ptr<drivers::NvsStorage> nvs_;
+ std::unique_ptr<drivers::TouchWheel> touchwheel_;
+ std::unique_ptr<drivers::Bluetooth> bluetooth_;
+
+ std::unique_ptr<audio::TrackQueue> queue_;
+ std::unique_ptr<battery::Battery> battery_;
+
+ std::shared_ptr<database::Database> database_;
+ std::unique_ptr<database::ITagParser> tag_parser_;
+};
+
+} // namespace system_fsm
diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp
index 64cbd393..e22fe2ae 100644
--- a/src/system_fsm/include/system_events.hpp
+++ b/src/system_fsm/include/system_events.hpp
@@ -9,6 +9,7 @@
#include <memory>
#include "database.hpp"
+#include "service_locator.hpp"
#include "tinyfsm.hpp"
namespace system_fsm {
@@ -19,7 +20,9 @@ struct DisplayReady : tinyfsm::Event {};
* Sent by SysState when the system has finished with its boot and self-test,
* and is now ready to run normally.
*/
-struct BootComplete : tinyfsm::Event {};
+struct BootComplete : tinyfsm::Event {
+ std::shared_ptr<ServiceLocator> services;
+};
/*
* May be sent by any component to indicate that the system has experienced an
@@ -33,9 +36,7 @@ struct OnIdle : tinyfsm::Event {};
/*
* Sent by SysState when the system storage has been successfully mounted.
*/
-struct StorageMounted : tinyfsm::Event {
- std::weak_ptr<database::Database> db;
-};
+struct StorageMounted : tinyfsm::Event {};
struct StorageError : tinyfsm::Event {};
diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp
index 371e5527..28448e5a 100644
--- a/src/system_fsm/include/system_fsm.hpp
+++ b/src/system_fsm/include/system_fsm.hpp
@@ -18,6 +18,7 @@
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "samd.hpp"
+#include "service_locator.hpp"
#include "storage.hpp"
#include "tag_parser.hpp"
#include "tinyfsm.hpp"
@@ -60,22 +61,8 @@ class SystemState : public tinyfsm::Fsm<SystemState> {
protected:
auto IdleCondition() -> bool;
- static std::shared_ptr<drivers::Gpios> sGpios;
- static std::shared_ptr<drivers::Samd> sSamd;
- static std::shared_ptr<drivers::NvsStorage> sNvs;
-
- static std::shared_ptr<drivers::TouchWheel> sTouch;
- static std::shared_ptr<drivers::RelativeWheel> sRelativeTouch;
- static std::shared_ptr<drivers::AdcBattery> sAdc;
- static std::shared_ptr<battery::Battery> sBattery;
- static std::shared_ptr<drivers::SdStorage> sStorage;
- static std::shared_ptr<drivers::Display> sDisplay;
- static std::shared_ptr<drivers::Bluetooth> sBluetooth;
-
- static std::shared_ptr<database::Database> sDatabase;
- static std::shared_ptr<database::TagParserImpl> sTagParser;
-
- static std::shared_ptr<audio::TrackQueue> sTrackQueue;
+ static std::shared_ptr<ServiceLocator> sServices;
+ static std::unique_ptr<drivers::SdStorage> sStorage;
static console::AppConsole* sAppConsole;
};
diff --git a/src/system_fsm/running.cpp b/src/system_fsm/running.cpp
index 3fc5493f..e42429e7 100644
--- a/src/system_fsm/running.cpp
+++ b/src/system_fsm/running.cpp
@@ -6,6 +6,7 @@
#include "app_console.hpp"
#include "audio_events.hpp"
+#include "database.hpp"
#include "file_gatherer.hpp"
#include "freertos/projdefs.h"
#include "result.hpp"
@@ -31,7 +32,7 @@ static database::IFileGatherer* sFileGatherer;
void Running::entry() {
ESP_LOGI(kTag, "mounting sd card");
vTaskDelay(pdMS_TO_TICKS(250));
- auto storage_res = drivers::SdStorage::Create(sGpios.get());
+ auto storage_res = drivers::SdStorage::Create(sServices->gpios());
if (storage_res.has_error()) {
ESP_LOGW(kTag, "failed to mount!");
@@ -41,11 +42,11 @@ void Running::entry() {
return;
}
sStorage.reset(storage_res.value());
- vTaskDelay(pdMS_TO_TICKS(250));
ESP_LOGI(kTag, "opening database");
sFileGatherer = new database::FileGathererImpl();
- auto database_res = database::Database::Open(sFileGatherer, sTagParser.get());
+ auto database_res =
+ database::Database::Open(*sFileGatherer, sServices->tag_parser());
if (database_res.has_error()) {
ESP_LOGW(kTag, "failed to open!");
events::System().Dispatch(StorageError{});
@@ -53,18 +54,18 @@ void Running::entry() {
events::Ui().Dispatch(StorageError{});
return;
}
- sDatabase.reset(database_res.value());
- console::AppConsole::sDatabase = sDatabase;
+ sServices->database(
+ std::unique_ptr<database::Database>{database_res.value()});
ESP_LOGI(kTag, "storage loaded okay");
- StorageMounted ev{.db = sDatabase};
+ StorageMounted ev{};
events::System().Dispatch(ev);
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
void Running::exit() {
- sDatabase.reset();
+ sServices->database({});
sStorage.reset();
}
diff --git a/src/system_fsm/service_locator.cpp b/src/system_fsm/service_locator.cpp
new file mode 100644
index 00000000..1d4d8a65
--- /dev/null
+++ b/src/system_fsm/service_locator.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "service_locator.hpp"
+
+#include <memory>
+
+#include "nvs.hpp"
+#include "touchwheel.hpp"
+
+namespace system_fsm {
+
+auto ServiceLocator::instance() -> ServiceLocator& {
+ static ServiceLocator sInstance{};
+ return sInstance;
+}
+
+} // namespace system_fsm
diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp
index d21e8bcb..e048cec7 100644
--- a/src/system_fsm/system_fsm.cpp
+++ b/src/system_fsm/system_fsm.cpp
@@ -9,6 +9,7 @@
#include "event_queue.hpp"
#include "gpios.hpp"
#include "relative_wheel.hpp"
+#include "service_locator.hpp"
#include "system_events.hpp"
#include "tag_parser.hpp"
#include "track_queue.hpp"
@@ -17,22 +18,8 @@ static const char kTag[] = "system";
namespace system_fsm {
-std::shared_ptr<drivers::Gpios> SystemState::sGpios;
-std::shared_ptr<drivers::Samd> SystemState::sSamd;
-std::shared_ptr<drivers::NvsStorage> SystemState::sNvs;
-
-std::shared_ptr<drivers::TouchWheel> SystemState::sTouch;
-std::shared_ptr<drivers::RelativeWheel> SystemState::sRelativeTouch;
-std::shared_ptr<drivers::AdcBattery> SystemState::sAdc;
-std::shared_ptr<battery::Battery> SystemState::sBattery;
-std::shared_ptr<drivers::SdStorage> SystemState::sStorage;
-std::shared_ptr<drivers::Display> SystemState::sDisplay;
-std::shared_ptr<drivers::Bluetooth> SystemState::sBluetooth;
-
-std::shared_ptr<database::Database> SystemState::sDatabase;
-std::shared_ptr<database::TagParserImpl> SystemState::sTagParser;
-
-std::shared_ptr<audio::TrackQueue> SystemState::sTrackQueue;
+std::shared_ptr<ServiceLocator> SystemState::sServices;
+std::unique_ptr<drivers::SdStorage> SystemState::sStorage;
console::AppConsole* SystemState::sAppConsole;
@@ -43,17 +30,18 @@ void SystemState::react(const FatalError& err) {
}
void SystemState::react(const internal::GpioInterrupt&) {
- bool prev_key_up = sGpios->Get(drivers::Gpios::Pin::kKeyUp);
- bool prev_key_down = sGpios->Get(drivers::Gpios::Pin::kKeyDown);
- bool prev_key_lock = sGpios->Get(drivers::Gpios::Pin::kKeyLock);
- bool prev_has_headphones = !sGpios->Get(drivers::Gpios::Pin::kPhoneDetect);
+ auto& gpios = sServices->gpios();
+ bool prev_key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp);
+ bool prev_key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown);
+ bool prev_key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
+ bool prev_has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect);
- sGpios->Read();
+ gpios.Read();
- bool key_up = sGpios->Get(drivers::Gpios::Pin::kKeyUp);
- bool key_down = sGpios->Get(drivers::Gpios::Pin::kKeyDown);
- bool key_lock = sGpios->Get(drivers::Gpios::Pin::kKeyLock);
- bool has_headphones = !sGpios->Get(drivers::Gpios::Pin::kPhoneDetect);
+ bool key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp);
+ bool key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown);
+ bool key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock);
+ bool has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect);
if (key_up != prev_key_up) {
KeyUpChanged ev{.falling = prev_key_up};
@@ -77,14 +65,15 @@ void SystemState::react(const internal::GpioInterrupt&) {
}
void SystemState::react(const internal::SamdInterrupt&) {
- auto prev_charge_status = sSamd->GetChargeStatus();
- auto prev_usb_status = sSamd->GetUsbStatus();
+ auto& samd = sServices->samd();
+ auto prev_charge_status = samd.GetChargeStatus();
+ auto prev_usb_status = samd.GetUsbStatus();
- sSamd->UpdateChargeStatus();
- sSamd->UpdateUsbStatus();
+ samd.UpdateChargeStatus();
+ samd.UpdateUsbStatus();
- auto charge_status = sSamd->GetChargeStatus();
- auto usb_status = sSamd->GetUsbStatus();
+ auto charge_status = samd.GetChargeStatus();
+ auto usb_status = samd.GetUsbStatus();
if (charge_status != prev_charge_status) {
ChargingStatusChanged ev{};
@@ -97,7 +86,7 @@ void SystemState::react(const internal::SamdInterrupt&) {
}
auto SystemState::IdleCondition() -> bool {
- return !sGpios->Get(drivers::IGpios::Pin::kKeyLock) &&
+ return !sServices->gpios().Get(drivers::IGpios::Pin::kKeyLock) &&
audio::AudioState::is_in_state<audio::states::Standby>();
}
diff --git a/src/ui/include/lvgl_task.hpp b/src/ui/include/lvgl_task.hpp
index 7e60c4b4..6b7e446e 100644
--- a/src/ui/include/lvgl_task.hpp
+++ b/src/ui/include/lvgl_task.hpp
@@ -12,15 +12,38 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
+#include "freertos/timers.h"
#include "display.hpp"
#include "relative_wheel.hpp"
+#include "screen.hpp"
#include "themes.hpp"
#include "touchwheel.hpp"
+#include "wheel_encoder.hpp"
namespace ui {
-auto StartLvgl(std::weak_ptr<drivers::RelativeWheel> touch_wheel,
- std::weak_ptr<drivers::Display> display) -> void;
+class UiTask {
+ public:
+ static auto Start() -> UiTask*;
+
+ ~UiTask();
+
+ // FIXME: Once we have more input devices, this function should accept a more
+ // generic interface.
+ auto SetInputDevice(std::shared_ptr<TouchWheelEncoder> dev) -> void;
+
+ private:
+ UiTask();
+
+ auto Main() -> void;
+
+ std::shared_ptr<TouchWheelEncoder> input_device_;
+ std::shared_ptr<Screen> current_screen_;
+
+ std::atomic<bool> quit_;
+ SemaphoreHandle_t frame_semaphore_;
+ TimerHandle_t frame_timer_;
+};
} // namespace ui
diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp
index c684ddff..f2998c88 100644
--- a/src/ui/include/screen_playing.hpp
+++ b/src/ui/include/screen_playing.hpp
@@ -29,7 +29,7 @@ namespace screens {
class Playing : public Screen {
public:
explicit Playing(std::weak_ptr<database::Database> db,
- audio::TrackQueue* queue);
+ audio::TrackQueue& queue);
~Playing();
auto Tick() -> void override;
@@ -51,7 +51,7 @@ class Playing : public Screen {
auto ApplyNextUp(const std::vector<database::Track>& tracks) -> void;
std::weak_ptr<database::Database> db_;
- audio::TrackQueue* queue_;
+ audio::TrackQueue& queue_;
std::optional<database::Track> track_;
std::vector<database::Track> next_tracks_;
diff --git a/src/ui/include/screen_settings.hpp b/src/ui/include/screen_settings.hpp
index 61375fa9..0ec96d26 100644
--- a/src/ui/include/screen_settings.hpp
+++ b/src/ui/include/screen_settings.hpp
@@ -37,14 +37,14 @@ class Headphones : public MenuScreen {
class Appearance : public MenuScreen {
public:
- Appearance(drivers::NvsStorage* nvs, drivers::Display* display);
+ Appearance(drivers::NvsStorage& nvs, drivers::Display& display);
auto ChangeBrightness(uint_fast8_t) -> void;
auto CommitBrightness() -> void;
private:
- drivers::NvsStorage* nvs_;
- drivers::Display* display_;
+ drivers::NvsStorage& nvs_;
+ drivers::Display& display_;
lv_obj_t* current_brightness_label_;
uint_fast8_t current_brightness_;
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 1fa6bf26..12fe5c69 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -11,9 +11,12 @@
#include "audio_events.hpp"
#include "battery.hpp"
+#include "gpios.hpp"
+#include "lvgl_task.hpp"
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "screen_playing.hpp"
+#include "service_locator.hpp"
#include "tinyfsm.hpp"
#include "display.hpp"
@@ -24,15 +27,13 @@
#include "touchwheel.hpp"
#include "track_queue.hpp"
#include "ui_events.hpp"
+#include "wheel_encoder.hpp"
namespace ui {
class UiState : public tinyfsm::Fsm<UiState> {
public:
- static auto Init(drivers::IGpios*,
- std::shared_ptr<drivers::NvsStorage>,
- audio::TrackQueue*,
- std::shared_ptr<battery::Battery>) -> bool;
+ static auto InitBootSplash(drivers::IGpios&) -> bool;
virtual ~UiState() {}
@@ -46,7 +47,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
/* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {}
- void react(const system_fsm::BatteryStateChanged&);
+ virtual void react(const system_fsm::BatteryStateChanged&);
virtual void react(const audio::PlaybackStarted&) {}
virtual void react(const audio::PlaybackUpdate&) {}
@@ -76,15 +77,10 @@ class UiState : public tinyfsm::Fsm<UiState> {
void PopScreen();
void UpdateTopBar();
- static drivers::IGpios* sIGpios;
- static audio::TrackQueue* sQueue;
-
- static std::shared_ptr<drivers::TouchWheel> sTouchWheel;
- static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel;
- static std::shared_ptr<drivers::Display> sDisplay;
- static std::shared_ptr<battery::Battery> sBattery;
- static std::shared_ptr<drivers::NvsStorage> sNvs;
- static std::weak_ptr<database::Database> sDb;
+ static std::unique_ptr<UiTask> sTask;
+ static std::shared_ptr<system_fsm::ServiceLocator> sServices;
+ static std::unique_ptr<drivers::Display> sDisplay;
+ static std::shared_ptr<TouchWheelEncoder> sEncoder;
static std::stack<std::shared_ptr<Screen>> sScreens;
static std::shared_ptr<Screen> sCurrentScreen;
@@ -97,6 +93,7 @@ class Splash : public UiState {
public:
void exit() override;
void react(const system_fsm::BootComplete&) override;
+ void react(const system_fsm::BatteryStateChanged&) override{};
using UiState::react;
};
diff --git a/src/ui/include/wheel_encoder.hpp b/src/ui/include/wheel_encoder.hpp
index c49e5929..fcac5edd 100644
--- a/src/ui/include/wheel_encoder.hpp
+++ b/src/ui/include/wheel_encoder.hpp
@@ -17,7 +17,7 @@ namespace ui {
class TouchWheelEncoder {
public:
- explicit TouchWheelEncoder(std::weak_ptr<drivers::RelativeWheel> wheel);
+ explicit TouchWheelEncoder(std::unique_ptr<drivers::RelativeWheel> wheel);
auto Read(lv_indev_data_t* data) -> void;
auto registration() -> lv_indev_t* { return registration_; }
@@ -27,7 +27,7 @@ class TouchWheelEncoder {
lv_indev_t* registration_;
lv_key_t last_key_;
- std::weak_ptr<drivers::RelativeWheel> wheel_;
+ std::unique_ptr<drivers::RelativeWheel> wheel_;
};
} // namespace ui
diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp
index 9952d0bb..a0f14f7d 100644
--- a/src/ui/lvgl_task.cpp
+++ b/src/ui/lvgl_task.cpp
@@ -48,67 +48,74 @@
namespace ui {
-static const char* kTag = "lv_task";
+static const char* kTag = "ui_task";
static const TickType_t kMaxFrameRate = pdMS_TO_TICKS(100);
-static int sTimerId;
-static SemaphoreHandle_t sFrameSemaphore;
-
-auto next_frame(TimerHandle_t) {
- xSemaphoreGive(sFrameSemaphore);
+static auto next_frame(TimerHandle_t t) {
+ SemaphoreHandle_t sem =
+ reinterpret_cast<SemaphoreHandle_t>(pvTimerGetTimerID(t));
+ xSemaphoreGive(sem);
}
-void LvglMain(std::weak_ptr<drivers::RelativeWheel> weak_touch_wheel,
- std::weak_ptr<drivers::Display> weak_display) {
- ESP_LOGI(kTag, "init lvgl");
- lv_init();
-
- sFrameSemaphore = xSemaphoreCreateBinary();
- auto timer =
- xTimerCreate("lvgl_frame", kMaxFrameRate, pdTRUE, &sTimerId, next_frame);
- xTimerStart(timer, portMAX_DELAY);
-
- lv_theme_t* base_theme = lv_theme_basic_init(NULL);
- lv_disp_set_theme(NULL, base_theme);
- themes::Theme::instance()->Apply();
+UiTask::UiTask()
+ : quit_(false),
+ frame_semaphore_(xSemaphoreCreateBinary()),
+ frame_timer_(xTimerCreate("ui_frame",
+ kMaxFrameRate,
+ pdTRUE,
+ frame_semaphore_,
+ next_frame)) {
+ xTimerStart(frame_timer_, portMAX_DELAY);
+}
- TouchWheelEncoder encoder(weak_touch_wheel);
+UiTask::~UiTask() {
+ assert(false);
+}
- std::shared_ptr<Screen> current_screen;
+auto UiTask::Main() -> void {
+ ESP_LOGI(kTag, "start ui task");
lv_group_t* current_group = nullptr;
auto* events = events::queues::Ui();
- while (1) {
+ while (true) {
while (events->Service(0)) {
}
std::shared_ptr<Screen> screen = UiState::current_screen();
- if (screen != current_screen && screen != nullptr) {
- // TODO(jacqueline): animate this sometimes
+ if (screen != current_screen_ && screen != nullptr) {
lv_scr_load(screen->root());
- lv_indev_set_group(encoder.registration(), screen->group());
- current_screen = screen;
+ if (input_device_) {
+ lv_indev_set_group(input_device_->registration(), screen->group());
+ }
+ current_screen_ = screen;
}
- if (current_screen->group() != current_group) {
- current_group = current_screen->group();
- lv_indev_set_group(encoder.registration(), current_group);
+ if (input_device_ && current_screen_->group() != current_group) {
+ current_group = current_screen_->group();
+ lv_indev_set_group(input_device_->registration(), current_group);
}
- if (current_screen) {
- current_screen->Tick();
+ if (current_screen_) {
+ current_screen_->Tick();
}
lv_task_handler();
// Wait for the signal to loop again.
- xSemaphoreTake(sFrameSemaphore, portMAX_DELAY);
+ xSemaphoreTake(frame_semaphore_, portMAX_DELAY);
+ }
+}
+
+auto UiTask::SetInputDevice(std::shared_ptr<TouchWheelEncoder> dev) -> void {
+ input_device_ = std::move(dev);
+ if (current_screen_ && input_device_) {
+ lv_indev_set_group(input_device_->registration(), current_screen_->group());
}
}
-auto StartLvgl(std::weak_ptr<drivers::RelativeWheel> touch_wheel,
- std::weak_ptr<drivers::Display> display) -> void {
- tasks::StartPersistent<tasks::Type::kUi>(
- 0, [=]() { LvglMain(touch_wheel, display); });
+auto UiTask::Start() -> UiTask* {
+ UiTask* ret = new UiTask();
+ tasks::StartPersistent<tasks::Type::kUi>(0, [=]() { ret->Main(); });
+ return ret;
}
} // namespace ui
diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp
index 7538d093..2eb4e09e 100644
--- a/src/ui/screen_playing.cpp
+++ b/src/ui/screen_playing.cpp
@@ -104,7 +104,7 @@ auto Playing::next_up_label(lv_obj_t* parent, const std::string& text)
return button;
}
-Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue)
+Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue)
: db_(db),
queue_(queue),
track_(),
@@ -204,7 +204,7 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue)
Playing::~Playing() {}
auto Playing::OnTrackUpdate() -> void {
- auto current = queue_->GetCurrent();
+ auto current = queue_.GetCurrent();
if (!current) {
return;
}
@@ -230,7 +230,7 @@ auto Playing::OnPlaybackUpdate(uint32_t pos_seconds, uint32_t new_duration)
auto Playing::OnQueueUpdate() -> void {
OnTrackUpdate();
- auto current = queue_->GetUpcoming(kMaxUpcoming);
+ auto current = queue_.GetUpcoming(kMaxUpcoming);
auto db = db_.lock();
if (!db) {
return;
diff --git a/src/ui/screen_settings.cpp b/src/ui/screen_settings.cpp
index 6bafb9a8..01f40cb5 100644
--- a/src/ui/screen_settings.cpp
+++ b/src/ui/screen_settings.cpp
@@ -161,7 +161,7 @@ static auto brightness_str(uint_fast8_t percent) -> std::string {
return std::to_string(percent) + "%";
}
-Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display)
+Appearance::Appearance(drivers::NvsStorage& nvs, drivers::Display& display)
: MenuScreen("Appearance"), nvs_(nvs), display_(display) {
lv_obj_t* toggle_container = settings_container(content_);
lv_obj_t* toggle_label = lv_label_create(toggle_container);
@@ -170,7 +170,7 @@ Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display)
lv_obj_t* toggle = lv_switch_create(toggle_container);
lv_group_add_obj(group_, toggle);
- uint_fast8_t initial_brightness = nvs_->ScreenBrightness().get();
+ uint_fast8_t initial_brightness = nvs_.ScreenBrightness().get();
lv_obj_t* brightness_label = lv_label_create(content_);
lv_label_set_text(brightness_label, "Brightness");
@@ -192,13 +192,13 @@ Appearance::Appearance(drivers::NvsStorage* nvs, drivers::Display* display)
auto Appearance::ChangeBrightness(uint_fast8_t new_level) -> void {
current_brightness_ = new_level;
- display_->SetBrightness(new_level);
+ display_.SetBrightness(new_level);
lv_label_set_text(current_brightness_label_,
brightness_str(new_level).c_str());
}
auto Appearance::CommitBrightness() -> void {
- nvs_->ScreenBrightness(current_brightness_);
+ nvs_.ScreenBrightness(current_brightness_);
}
InputMethod::InputMethod() : MenuScreen("Input Method") {
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 1febd1c7..0054db23 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -32,6 +32,7 @@
#include "touchwheel.hpp"
#include "track_queue.hpp"
#include "ui_events.hpp"
+#include "wheel_encoder.hpp"
#include "widget_top_bar.hpp"
namespace ui {
@@ -40,51 +41,25 @@ static constexpr char kTag[] = "ui_fsm";
static const std::size_t kRecordsPerPage = 15;
-drivers::IGpios* UiState::sIGpios;
-audio::TrackQueue* UiState::sQueue;
-
-std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel;
-std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel;
-std::shared_ptr<drivers::Display> UiState::sDisplay;
-std::shared_ptr<battery::Battery> UiState::sBattery;
-std::shared_ptr<drivers::NvsStorage> UiState::sNvs;
-std::weak_ptr<database::Database> UiState::sDb;
+std::unique_ptr<UiTask> UiState::sTask;
+std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices;
+std::unique_ptr<drivers::Display> UiState::sDisplay;
+std::shared_ptr<TouchWheelEncoder> UiState::sEncoder;
std::stack<std::shared_ptr<Screen>> UiState::sScreens;
std::shared_ptr<Screen> UiState::sCurrentScreen;
std::shared_ptr<Modal> UiState::sCurrentModal;
-auto UiState::Init(drivers::IGpios* gpio_expander,
- std::shared_ptr<drivers::NvsStorage> nvs,
- audio::TrackQueue* queue,
- std::shared_ptr<battery::Battery> battery) -> bool {
- sIGpios = gpio_expander;
- sNvs = nvs;
- sQueue = queue;
- sBattery = battery;
-
+auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
+ // Init LVGL first, since the display driver registers itself with LVGL.
lv_init();
- sDisplay.reset(
- drivers::Display::Create(gpio_expander, drivers::displays::kST7735R));
+ sDisplay.reset(drivers::Display::Create(gpios, drivers::displays::kST7735R));
if (sDisplay == nullptr) {
return false;
}
- sDisplay->SetBrightness(nvs->ScreenBrightness().get());
-
- sTouchWheel.reset(drivers::TouchWheel::Create());
- if (sTouchWheel != nullptr) {
- sRelativeWheel.reset(new drivers::RelativeWheel(sTouchWheel.get()));
- }
sCurrentScreen.reset(new screens::Splash());
-
- // Start the UI task even if init ultimately failed, so that we can show some
- // kind of error screen to the user.
- StartLvgl(sRelativeWheel, sDisplay);
-
- if (sTouchWheel == nullptr) {
- return false;
- }
+ sTask.reset(UiTask::Start());
return true;
}
@@ -107,7 +82,8 @@ void UiState::PopScreen() {
void UiState::react(const system_fsm::KeyLockChanged& ev) {
sDisplay->SetDisplayOn(ev.falling);
- sRelativeWheel->SetEnabled(ev.falling);
+ sTask->SetInputDevice(ev.falling ? sEncoder
+ : std::shared_ptr<TouchWheelEncoder>());
}
void UiState::react(const system_fsm::BatteryStateChanged&) {
@@ -115,8 +91,8 @@ void UiState::react(const system_fsm::BatteryStateChanged&) {
}
void UiState::UpdateTopBar() {
- auto battery_state = sBattery->State();
- bool has_queue = sQueue->GetCurrent().has_value();
+ auto battery_state = sServices->battery().State();
+ bool has_queue = sServices->track_queue().GetCurrent().has_value();
bool is_playing = audio::AudioState::is_in_state<audio::states::Playback>();
widgets::TopBar::State state{
@@ -140,19 +116,40 @@ namespace states {
void Splash::exit() {
if (sDisplay != nullptr) {
- sDisplay->SetDisplayOn(sIGpios->Get(drivers::IGpios::Pin::kKeyLock));
+ sDisplay->SetDisplayOn(
+ sServices->gpios().Get(drivers::IGpios::Pin::kKeyLock));
}
}
void Splash::react(const system_fsm::BootComplete& ev) {
+ sServices = ev.services;
+
+ // The system has finished booting! We now need to prepare to show real UI.
+ // This basically just involves reading and applying the user's preferences.
+
+ lv_theme_t* base_theme = lv_theme_basic_init(NULL);
+ lv_disp_set_theme(NULL, base_theme);
+ themes::Theme::instance()->Apply();
+
+ sDisplay->SetBrightness(sServices->nvs().ScreenBrightness().get());
+
+ auto touchwheel = sServices->touchwheel();
+ if (touchwheel) {
+ auto relative_wheel =
+ std::make_unique<drivers::RelativeWheel>(**touchwheel);
+ sEncoder = std::make_shared<TouchWheelEncoder>(std::move(relative_wheel));
+ sTask->SetInputDevice(sEncoder);
+ } else {
+ ESP_LOGE(kTag, "no input devices initialised!");
+ }
+
transit<Browse>();
}
void Browse::entry() {}
void Browse::react(const system_fsm::StorageMounted& ev) {
- sDb = ev.db;
- auto db = ev.db.lock();
+ auto db = sServices->database().lock();
if (!db) {
// TODO(jacqueline): Hmm.
return;
@@ -177,7 +174,7 @@ void Browse::react(const internal::ShowSettingsPage& ev) {
screen.reset(new screens::Headphones());
break;
case internal::ShowSettingsPage::Page::kAppearance:
- screen.reset(new screens::Appearance(sNvs.get(), sDisplay.get()));
+ screen.reset(new screens::Appearance(sServices->nvs(), *sDisplay));
break;
case internal::ShowSettingsPage::Page::kInput:
screen.reset(new screens::InputMethod());
@@ -198,7 +195,7 @@ void Browse::react(const internal::ShowSettingsPage& ev) {
}
void Browse::react(const internal::RecordSelected& ev) {
- auto db = sDb.lock();
+ auto db = sServices->database().lock();
if (!db) {
return;
}
@@ -206,9 +203,10 @@ void Browse::react(const internal::RecordSelected& ev) {
auto record = ev.page->values().at(ev.record);
if (record.track()) {
ESP_LOGI(kTag, "selected track '%s'", record.text()->c_str());
- sQueue->Clear();
- sQueue->IncludeLast(std::make_shared<playlist::IndexRecordSource>(
- sDb, ev.initial_page, 0, ev.page, ev.record));
+ auto& queue = sServices->track_queue();
+ queue.Clear();
+ queue.IncludeLast(std::make_shared<playlist::IndexRecordSource>(
+ sServices->database(), ev.initial_page, 0, ev.page, ev.record));
transit<Playing>();
} else {
ESP_LOGI(kTag, "selected record '%s'", record.text()->c_str());
@@ -218,21 +216,21 @@ void Browse::react(const internal::RecordSelected& ev) {
}
auto query = db->GetPage(&cont.value());
std::string title = record.text().value_or("TODO");
- PushScreen(
- std::make_shared<screens::TrackBrowser>(sDb, title, std::move(query)));
+ PushScreen(std::make_shared<screens::TrackBrowser>(
+ sServices->database(), title, std::move(query)));
}
}
void Browse::react(const internal::IndexSelected& ev) {
- auto db = sDb.lock();
+ auto db = sServices->database().lock();
if (!db) {
return;
}
ESP_LOGI(kTag, "selected index %s", ev.index.name.c_str());
auto query = db->GetTracksByIndex(ev.index, kRecordsPerPage);
- PushScreen(std::make_shared<screens::TrackBrowser>(sDb, ev.index.name,
- std::move(query)));
+ PushScreen(std::make_shared<screens::TrackBrowser>(
+ sServices->database(), ev.index.name, std::move(query)));
}
void Browse::react(const internal::BackPressed& ev) {
@@ -242,7 +240,8 @@ void Browse::react(const internal::BackPressed& ev) {
static std::shared_ptr<screens::Playing> sPlayingScreen;
void Playing::entry() {
- sPlayingScreen.reset(new screens::Playing(sDb, sQueue));
+ sPlayingScreen.reset(
+ new screens::Playing(sServices->database(), sServices->track_queue()));
PushScreen(sPlayingScreen);
}
diff --git a/src/ui/wheel_encoder.cpp b/src/ui/wheel_encoder.cpp
index a0e12b7f..2f2e7f68 100644
--- a/src/ui/wheel_encoder.cpp
+++ b/src/ui/wheel_encoder.cpp
@@ -22,8 +22,8 @@ void encoder_feedback(lv_indev_drv_t* drv, uint8_t event_code) {
}
TouchWheelEncoder::TouchWheelEncoder(
- std::weak_ptr<drivers::RelativeWheel> wheel)
- : last_key_(0), wheel_(wheel) {
+ std::unique_ptr<drivers::RelativeWheel> wheel)
+ : last_key_(0), wheel_(std::move(wheel)) {
lv_indev_drv_init(&driver_);
driver_.type = LV_INDEV_TYPE_ENCODER;
driver_.read_cb = encoder_read;
@@ -34,16 +34,11 @@ TouchWheelEncoder::TouchWheelEncoder(
}
auto TouchWheelEncoder::Read(lv_indev_data_t* data) -> void {
- auto lock = wheel_.lock();
- if (lock == nullptr) {
- return;
- }
+ wheel_->Update();
- lock->Update();
-
- data->enc_diff = lock->ticks();
+ data->enc_diff = wheel_->ticks();
data->state =
- lock->is_clicking() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
+ wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
}
} // namespace ui