Use a service locator instead of passing around subsets of drivers between FSMs

custom
jacqueline 2 years ago
parent 4247c9fe7d
commit 320fdeb9d8
  1. 37
      src/app_console/app_console.cpp
  2. 6
      src/app_console/include/app_console.hpp
  3. 71
      src/audio/audio_fsm.cpp
  4. 5
      src/audio/fatfs_audio_input.cpp
  5. 6
      src/audio/i2s_audio_output.cpp
  6. 14
      src/audio/include/audio_fsm.hpp
  7. 4
      src/audio/include/fatfs_audio_input.hpp
  8. 7
      src/audio/include/i2s_audio_output.hpp
  9. 6
      src/battery/battery.cpp
  10. 6
      src/battery/include/battery.hpp
  11. 25
      src/database/database.cpp
  12. 10
      src/database/include/database.hpp
  13. 4
      src/drivers/display.cpp
  14. 16
      src/drivers/i2s_dac.cpp
  15. 6
      src/drivers/include/display.hpp
  16. 6
      src/drivers/include/i2s_dac.hpp
  17. 8
      src/drivers/include/relative_wheel.hpp
  18. 6
      src/drivers/include/storage.hpp
  19. 6
      src/drivers/relative_wheel.cpp
  20. 14
      src/drivers/storage.cpp
  21. 2
      src/system_fsm/CMakeLists.txt
  22. 57
      src/system_fsm/booting.cpp
  23. 26
      src/system_fsm/idle.cpp
  24. 113
      src/system_fsm/include/service_locator.hpp
  25. 9
      src/system_fsm/include/system_events.hpp
  26. 19
      src/system_fsm/include/system_fsm.hpp
  27. 15
      src/system_fsm/running.cpp
  28. 21
      src/system_fsm/service_locator.cpp
  29. 53
      src/system_fsm/system_fsm.cpp
  30. 27
      src/ui/include/lvgl_task.hpp
  31. 4
      src/ui/include/screen_playing.hpp
  32. 6
      src/ui/include/screen_settings.hpp
  33. 25
      src/ui/include/ui_fsm.hpp
  34. 4
      src/ui/include/wheel_encoder.hpp
  35. 79
      src/ui/lvgl_task.cpp
  36. 6
      src/ui/screen_playing.cpp
  37. 8
      src/ui/screen_settings.cpp
  38. 101
      src/ui/ui_fsm.cpp
  39. 15
      src/ui/wheel_encoder.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;

@ -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;

@ -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})

@ -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>();

@ -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,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

@ -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

@ -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") {

@ -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

Loading…
Cancel
Save