Use a service locator instead of passing around subsets of drivers between FSMs
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+28
-43
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>()),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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* {
|
||||
|
||||
@@ -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})
|
||||
|
||||
+25
-32
@@ -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());
|
||||
sServices->gpios(std::unique_ptr<drivers::Gpios>(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());
|
||||
|
||||
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>();
|
||||
|
||||
+14
-12
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 {};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
+11
-14
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+43
-36
@@ -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();
|
||||
UiTask::UiTask()
|
||||
: quit_(false),
|
||||
frame_semaphore_(xSemaphoreCreateBinary()),
|
||||
frame_timer_(xTimerCreate("ui_frame",
|
||||
kMaxFrameRate,
|
||||
pdTRUE,
|
||||
frame_semaphore_,
|
||||
next_frame)) {
|
||||
xTimerStart(frame_timer_, portMAX_DELAY);
|
||||
}
|
||||
|
||||
sFrameSemaphore = xSemaphoreCreateBinary();
|
||||
auto timer =
|
||||
xTimerCreate("lvgl_frame", kMaxFrameRate, pdTRUE, &sTimerId, next_frame);
|
||||
xTimerStart(timer, portMAX_DELAY);
|
||||
UiTask::~UiTask() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
lv_theme_t* base_theme = lv_theme_basic_init(NULL);
|
||||
lv_disp_set_theme(NULL, base_theme);
|
||||
themes::Theme::instance()->Apply();
|
||||
|
||||
TouchWheelEncoder encoder(weak_touch_wheel);
|
||||
|
||||
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 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::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 UiTask::Start() -> UiTask* {
|
||||
UiTask* ret = new UiTask();
|
||||
tasks::StartPersistent<tasks::Type::kUi>(0, [=]() { ret->Main(); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
+50
-51
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user