Merge branch 'main' of git.sr.ht:~jacqueline/tangara-fw

custom
ailurux 2 years ago
commit db601935c6
  1. 14
      src/audio/audio_fsm.cpp
  2. 4
      src/audio/include/audio_events.hpp
  3. 3
      src/audio/include/audio_fsm.hpp
  4. 10
      src/battery/CMakeLists.txt
  5. 101
      src/battery/battery.cpp
  6. 44
      src/battery/include/battery.hpp
  7. 2
      src/drivers/CMakeLists.txt
  8. 38
      src/drivers/adc.cpp
  9. 16
      src/drivers/include/adc.hpp
  10. 2
      src/system_fsm/CMakeLists.txt
  11. 24
      src/system_fsm/booting.cpp
  12. 14
      src/system_fsm/idle.cpp
  13. 4
      src/system_fsm/include/system_events.hpp
  14. 10
      src/system_fsm/include/system_fsm.hpp
  15. 9
      src/system_fsm/running.cpp
  16. 14
      src/system_fsm/system_fsm.cpp
  17. 2
      src/ui/CMakeLists.txt
  18. 29
      src/ui/include/screen_settings.hpp
  19. 8
      src/ui/include/ui_fsm.hpp
  20. 8
      src/ui/screen_playing.cpp
  21. 16
      src/ui/ui_fsm.cpp
  22. 23
      src/ui/widget_top_bar.cpp

@ -133,6 +133,12 @@ void Standby::react(const QueueUpdate& ev) {
sFileSource->SetPath(db->GetTrackPath(*current_track)); sFileSource->SetPath(db->GetTrackPath(*current_track));
} }
void Standby::react(const TogglePlayPause& ev) {
if (sCurrentTrack) {
transit<Playback>();
}
}
void Playback::entry() { void Playback::entry() {
ESP_LOGI(kTag, "beginning playback"); ESP_LOGI(kTag, "beginning playback");
sOutput->SetInUse(true); sOutput->SetInUse(true);
@ -142,8 +148,10 @@ void Playback::exit() {
ESP_LOGI(kTag, "finishing playback"); ESP_LOGI(kTag, "finishing playback");
// TODO(jacqueline): Second case where it's useful to wait for the i2s buffer // TODO(jacqueline): Second case where it's useful to wait for the i2s buffer
// to drain. // to drain.
vTaskDelay(pdMS_TO_TICKS(250)); vTaskDelay(pdMS_TO_TICKS(10));
sOutput->SetInUse(false); sOutput->SetInUse(false);
events::System().Dispatch(PlaybackFinished{});
} }
void Playback::react(const QueueUpdate& ev) { void Playback::react(const QueueUpdate& ev) {
@ -168,6 +176,10 @@ void Playback::react(const QueueUpdate& ev) {
sFileSource->SetPath(db->GetTrackPath(*current_track)); sFileSource->SetPath(db->GetTrackPath(*current_track));
} }
void Playback::react(const TogglePlayPause& ev) {
transit<Standby>();
}
void Playback::react(const PlaybackUpdate& ev) { void Playback::react(const PlaybackUpdate& ev) {
ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed, ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed,
ev.seconds_total); ev.seconds_total);

@ -25,6 +25,8 @@ struct PlaybackUpdate : tinyfsm::Event {
uint32_t seconds_total; uint32_t seconds_total;
}; };
struct PlaybackFinished : tinyfsm::Event {};
struct QueueUpdate : tinyfsm::Event { struct QueueUpdate : tinyfsm::Event {
bool current_changed; bool current_changed;
}; };
@ -35,6 +37,8 @@ struct PlayFile : tinyfsm::Event {
struct VolumeChanged : tinyfsm::Event {}; struct VolumeChanged : tinyfsm::Event {};
struct TogglePlayPause : tinyfsm::Event {};
namespace internal { namespace internal {
struct InputFileOpened : tinyfsm::Event {}; struct InputFileOpened : tinyfsm::Event {};

@ -57,6 +57,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
virtual void react(const PlayFile&) {} virtual void react(const PlayFile&) {}
virtual void react(const QueueUpdate&) {} virtual void react(const QueueUpdate&) {}
virtual void react(const PlaybackUpdate&) {} virtual void react(const PlaybackUpdate&) {}
virtual void react(const TogglePlayPause&) {}
virtual void react(const internal::InputFileOpened&) {} virtual void react(const internal::InputFileOpened&) {}
virtual void react(const internal::InputFileClosed&) {} virtual void react(const internal::InputFileClosed&) {}
@ -90,6 +91,7 @@ class Standby : public AudioState {
void react(const PlayFile&) override; void react(const PlayFile&) override;
void react(const internal::InputFileOpened&) override; void react(const internal::InputFileOpened&) override;
void react(const QueueUpdate&) override; void react(const QueueUpdate&) override;
void react(const TogglePlayPause&) override;
using AudioState::react; using AudioState::react;
}; };
@ -102,6 +104,7 @@ class Playback : public AudioState {
void react(const PlayFile&) override; void react(const PlayFile&) override;
void react(const QueueUpdate&) override; void react(const QueueUpdate&) override;
void react(const PlaybackUpdate&) override; void react(const PlaybackUpdate&) override;
void react(const TogglePlayPause&) override;
void react(const internal::InputFileOpened&) override; void react(const internal::InputFileOpened&) override;
void react(const internal::InputFileClosed&) override; void react(const internal::InputFileClosed&) override;

@ -0,0 +1,10 @@
# Copyright 2023 jacqueline <me@jacqueline.id.au>
#
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
SRCS "battery.cpp"
INCLUDE_DIRS "include"
REQUIRES "drivers" "events")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -0,0 +1,101 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "battery.hpp"
#include <cstdint>
#include "adc.hpp"
#include "event_queue.hpp"
#include "freertos/portmacro.h"
#include "samd.hpp"
#include "system_events.hpp"
namespace battery {
static const TickType_t kBatteryCheckPeriod = pdMS_TO_TICKS(60 * 1000);
/*
* Battery voltage, in millivolts, at which the battery charger IC will stop
* charging.
*/
static const uint32_t kFullChargeMilliVolts = 4200;
/*
* Battery voltage, in millivolts, at which *we* will consider the battery to
* be completely discharged. This is intentionally higher than the charger IC
* cut-off and the protection on the battery itself; we want to make sure we
* finish up and have everything unmounted and snoozing before the BMS cuts us
* off.
*/
static const uint32_t kEmptyChargeMilliVolts = 3200; // BMS limit is 3100.
using ChargeStatus = drivers::Samd::ChargeStatus;
void check_voltage_cb(TimerHandle_t timer) {
Battery* instance = reinterpret_cast<Battery*>(pvTimerGetTimerID(timer));
instance->Update();
}
Battery::Battery(drivers::Samd* samd, drivers::AdcBattery* adc)
: samd_(samd), adc_(adc) {
timer_ = xTimerCreate("BATTERY", kBatteryCheckPeriod, true, this,
check_voltage_cb);
xTimerStart(timer_, portMAX_DELAY);
Update();
}
Battery::~Battery() {
xTimerStop(timer_, portMAX_DELAY);
xTimerDelete(timer_, portMAX_DELAY);
}
auto Battery::Update() -> void {
std::lock_guard<std::mutex> lock{state_mutex_};
auto charge_state = samd_->GetChargeStatus();
if (!charge_state || *charge_state == ChargeStatus::kNoBattery) {
if (state_) {
EmitEvent();
}
state_.reset();
return;
}
// FIXME: So what we *should* do here is measure the actual real-life
// time from full battery -> empty battery, store it in NVS, then rely on
// that. If someone could please do this, it would be lovely. Thanks!
uint32_t mV = std::max(adc_->Millivolts(), kEmptyChargeMilliVolts);
uint_fast8_t percent = static_cast<uint_fast8_t>(std::min<double>(
std::max<double>(0.0, mV - kEmptyChargeMilliVolts) /
(kFullChargeMilliVolts - kEmptyChargeMilliVolts) * 100.0,
100.0));
bool is_charging = *charge_state == ChargeStatus::kChargingRegular ||
*charge_state == ChargeStatus::kChargingFast ||
*charge_state == ChargeStatus::kFullCharge;
if (!state_ || state_->is_charging != is_charging ||
state_->percent != percent) {
EmitEvent();
}
state_ = BatteryState{
.percent = percent,
.is_charging = is_charging,
};
}
auto Battery::State() -> std::optional<BatteryState> {
std::lock_guard<std::mutex> lock{state_mutex_};
return state_;
}
auto Battery::EmitEvent() -> void {
events::System().Dispatch(system_fsm::BatteryStateChanged{});
events::Ui().Dispatch(system_fsm::BatteryStateChanged{});
}
} // namespace battery

@ -0,0 +1,44 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <cstdint>
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "adc.hpp"
#include "samd.hpp"
namespace battery {
class Battery {
public:
Battery(drivers::Samd* samd, drivers::AdcBattery* adc);
~Battery();
auto Update() -> void;
struct BatteryState {
uint_fast8_t percent;
bool is_charging;
};
auto State() -> std::optional<BatteryState>;
private:
auto EmitEvent() -> void;
drivers::Samd* samd_;
drivers::AdcBattery* adc_;
TimerHandle_t timer_;
std::mutex state_mutex_;
std::optional<BatteryState> state_;
};
} // namespace battery

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
idf_component_register( idf_component_register(
SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "adc.cpp" "storage.cpp" "i2c.cpp"
"spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp" "spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp"
"nvs.cpp" "bluetooth.cpp" "nvs.cpp" "bluetooth.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "battery.hpp" #include "adc.hpp"
#include <cstdint> #include <cstdint>
#include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali.h"
@ -14,21 +14,6 @@
namespace drivers { namespace drivers {
/*
* Battery voltage, in millivolts, at which the battery charger IC will stop
* charging.
*/
static const uint32_t kFullChargeMilliVolts = 4200;
/*
* Battery voltage, in millivolts, at which *we* will consider the battery to
* be completely discharged. This is intentionally higher than the charger IC
* cut-off and the protection on the battery itself; we want to make sure we
* finish up and have everything unmounted and snoozing before the BMS cuts us
* off.
*/
static const uint32_t kEmptyChargeMilliVolts = 3200; // BMS limit is 3100.
static const adc_bitwidth_t kAdcBitWidth = ADC_BITWIDTH_12; static const adc_bitwidth_t kAdcBitWidth = ADC_BITWIDTH_12;
static const adc_unit_t kAdcUnit = ADC_UNIT_1; static const adc_unit_t kAdcUnit = ADC_UNIT_1;
// Max battery voltage should be a little over 2V due to our divider, so we need // Max battery voltage should be a little over 2V due to our divider, so we need
@ -37,7 +22,7 @@ static const adc_atten_t kAdcAttenuation = ADC_ATTEN_DB_11;
// Corresponds to SENSOR_VP. // Corresponds to SENSOR_VP.
static const adc_channel_t kAdcChannel = ADC_CHANNEL_0; static const adc_channel_t kAdcChannel = ADC_CHANNEL_0;
Battery::Battery() { AdcBattery::AdcBattery() {
adc_oneshot_unit_init_cfg_t unit_config = { adc_oneshot_unit_init_cfg_t unit_config = {
.unit_id = kAdcUnit, .unit_id = kAdcUnit,
}; };
@ -59,16 +44,14 @@ Battery::Battery() {
}; };
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting( ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(
&calibration_config, &adc_calibration_handle_)); &calibration_config, &adc_calibration_handle_));
UpdatePercent();
} }
Battery::~Battery() { AdcBattery::~AdcBattery() {
adc_cali_delete_scheme_line_fitting(adc_calibration_handle_); adc_cali_delete_scheme_line_fitting(adc_calibration_handle_);
ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_)); ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_));
} }
auto Battery::Millivolts() -> uint32_t { auto AdcBattery::Millivolts() -> uint32_t {
// GPIO 34 // GPIO 34
int raw = 0; int raw = 0;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, kAdcChannel, &raw)); ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, kAdcChannel, &raw));
@ -81,17 +64,4 @@ auto Battery::Millivolts() -> uint32_t {
return voltage * 2; return voltage * 2;
} }
auto Battery::UpdatePercent() -> bool {
auto old_percent = percent_;
// FIXME: So what we *should* do here is measure the actual real-life
// time from full battery -> empty battery, store it in NVS, then rely on
// that. If someone could please do this, it would be lovely. Thanks!
uint32_t mV = std::max(Millivolts(), kEmptyChargeMilliVolts);
percent_ = static_cast<uint_fast8_t>(std::min<double>(
std::max<double>(0.0, mV - kEmptyChargeMilliVolts) /
(kFullChargeMilliVolts - kEmptyChargeMilliVolts) * 100.0,
100.0));
return old_percent != percent_;
}
} // namespace drivers } // namespace drivers

@ -15,25 +15,23 @@
namespace drivers { namespace drivers {
class Battery { /*
* Handles measuring the battery's current voltage.
*/
class AdcBattery {
public: public:
static auto Create() -> Battery* { return new Battery(); } static auto Create() -> AdcBattery* { return new AdcBattery(); }
Battery(); AdcBattery();
~Battery(); ~AdcBattery();
/** /**
* Returns the current battery level in millivolts. * Returns the current battery level in millivolts.
*/ */
auto Millivolts() -> uint32_t; auto Millivolts() -> uint32_t;
auto UpdatePercent() -> bool;
auto Percent() -> uint_fast8_t { return percent_; }
private: private:
adc_oneshot_unit_handle_t adc_handle_; adc_oneshot_unit_handle_t adc_handle_;
adc_cali_handle_t adc_calibration_handle_; adc_cali_handle_t adc_calibration_handle_;
uint_fast8_t percent_;
}; };
} // namespace drivers } // namespace drivers

@ -5,5 +5,5 @@
idf_component_register( idf_component_register(
SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" "idle.cpp" SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" "idle.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "tinyfsm" "drivers" "database" "ui" "result" "events" "audio" "app_console") REQUIRES "tinyfsm" "drivers" "database" "ui" "result" "events" "audio" "app_console" "battery")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -38,12 +38,6 @@ namespace states {
static const char kTag[] = "BOOT"; static const char kTag[] = "BOOT";
static const TickType_t kBatteryCheckPeriod = pdMS_TO_TICKS(60 * 1000);
static void battery_timer_cb(TimerHandle_t timer) {
events::System().Dispatch(internal::BatteryTimerFired{});
}
auto Booting::entry() -> void { auto Booting::entry() -> void {
ESP_LOGI(kTag, "beginning tangara boot"); ESP_LOGI(kTag, "beginning tangara boot");
ESP_LOGI(kTag, "installing early drivers"); ESP_LOGI(kTag, "installing early drivers");
@ -53,33 +47,31 @@ auto Booting::entry() -> void {
ESP_ERROR_CHECK(drivers::init_spi()); ESP_ERROR_CHECK(drivers::init_spi());
sGpios.reset(drivers::Gpios::Create()); sGpios.reset(drivers::Gpios::Create());
sSamd.reset(drivers::Samd::Create());
sAdc.reset(drivers::AdcBattery::Create());
assert(sSamd.get() && sAdc.get());
sBattery.reset(new battery::Battery(sSamd.get(), sAdc.get()));
// Start bringing up LVGL now, since we have all of its prerequisites. // Start bringing up LVGL now, since we have all of its prerequisites.
sTrackQueue.reset(new audio::TrackQueue()); sTrackQueue.reset(new audio::TrackQueue());
ESP_LOGI(kTag, "starting ui"); ESP_LOGI(kTag, "starting ui");
if (!ui::UiState::Init(sGpios.get(), sTrackQueue.get())) { if (!ui::UiState::Init(sGpios.get(), sTrackQueue.get(), sBattery)) {
events::System().Dispatch(FatalError{}); events::System().Dispatch(FatalError{});
return; return;
} }
// Install everything else that is certain to be needed. // Install everything else that is certain to be needed.
ESP_LOGI(kTag, "installing remaining drivers"); ESP_LOGI(kTag, "installing remaining drivers");
sSamd.reset(drivers::Samd::Create());
sBattery.reset(drivers::Battery::Create());
sNvs.reset(drivers::NvsStorage::Open()); sNvs.reset(drivers::NvsStorage::Open());
sTagParser.reset(new database::TagParserImpl()); sTagParser.reset(new database::TagParserImpl());
if (!sSamd || !sBattery || !sNvs) { if (!sNvs) {
events::System().Dispatch(FatalError{}); events::System().Dispatch(FatalError{});
events::Ui().Dispatch(FatalError{}); events::Ui().Dispatch(FatalError{});
return; return;
} }
ESP_LOGI(kTag, "battery is at %u%% (= %lu mV)", sBattery->Percent(),
sBattery->Millivolts());
TimerHandle_t battery_timer = xTimerCreate("battery", kBatteryCheckPeriod,
true, NULL, battery_timer_cb);
xTimerStart(battery_timer, portMAX_DELAY);
// ESP_LOGI(kTag, "starting bluetooth"); // ESP_LOGI(kTag, "starting bluetooth");
// sBluetooth.reset(new drivers::Bluetooth(sNvs.get())); // sBluetooth.reset(new drivers::Bluetooth(sNvs.get()));
// sBluetooth->Enable(); // sBluetooth->Enable();

@ -49,12 +49,17 @@ void Idle::exit() {
} }
void Idle::react(const KeyLockChanged& ev) { void Idle::react(const KeyLockChanged& ev) {
if (!ev.falling) { if (ev.falling) {
transit<Running>(); transit<Running>();
} }
} }
void Idle::react(const internal::IdleTimeout& ev) { void Idle::react(const internal::IdleTimeout& ev) {
if (!IdleCondition()) {
// Defensively ensure that we didn't miss an idle-ending event.
transit<Running>();
return;
}
ESP_LOGI(kTag, "system shutting down"); ESP_LOGI(kTag, "system shutting down");
// FIXME: It would be neater to just free a bunch of our pointers, deinit the // FIXME: It would be neater to just free a bunch of our pointers, deinit the
@ -79,7 +84,12 @@ void Idle::react(const internal::IdleTimeout& ev) {
sGpios->Flush(); sGpios->Flush();
sSamd->PowerDown(); // 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();
vTaskDelay(pdMS_TO_TICKS(1000));
}
} }
} // namespace states } // namespace states

@ -53,7 +53,7 @@ struct HasPhonesChanged : tinyfsm::Event {
}; };
struct ChargingStatusChanged : tinyfsm::Event {}; struct ChargingStatusChanged : tinyfsm::Event {};
struct BatteryPercentChanged : tinyfsm::Event {}; struct BatteryStateChanged : tinyfsm::Event {};
namespace internal { namespace internal {
@ -62,8 +62,6 @@ struct SamdInterrupt : tinyfsm::Event {};
struct IdleTimeout : tinyfsm::Event {}; struct IdleTimeout : tinyfsm::Event {};
struct BatteryTimerFired : tinyfsm::Event {};
} // namespace internal } // namespace internal
} // namespace system_fsm } // namespace system_fsm

@ -9,6 +9,7 @@
#include <memory> #include <memory>
#include "app_console.hpp" #include "app_console.hpp"
#include "audio_events.hpp"
#include "battery.hpp" #include "battery.hpp"
#include "bluetooth.hpp" #include "bluetooth.hpp"
#include "database.hpp" #include "database.hpp"
@ -47,23 +48,26 @@ class SystemState : public tinyfsm::Fsm<SystemState> {
void react(const FatalError&); void react(const FatalError&);
void react(const internal::GpioInterrupt&); void react(const internal::GpioInterrupt&);
void react(const internal::SamdInterrupt&); void react(const internal::SamdInterrupt&);
void react(const internal::BatteryTimerFired&);
virtual void react(const DisplayReady&) {} virtual void react(const DisplayReady&) {}
virtual void react(const BootComplete&) {} virtual void react(const BootComplete&) {}
virtual void react(const StorageMounted&) {} virtual void react(const StorageMounted&) {}
virtual void react(const StorageError&) {} virtual void react(const StorageError&) {}
virtual void react(const KeyLockChanged&) {} virtual void react(const KeyLockChanged&) {}
virtual void react(const audio::PlaybackFinished&) {}
virtual void react(const internal::IdleTimeout&) {} virtual void react(const internal::IdleTimeout&) {}
protected: protected:
auto IdleCondition() -> bool;
static std::shared_ptr<drivers::Gpios> sGpios; static std::shared_ptr<drivers::Gpios> sGpios;
static std::shared_ptr<drivers::Samd> sSamd; static std::shared_ptr<drivers::Samd> sSamd;
static std::shared_ptr<drivers::NvsStorage> sNvs; static std::shared_ptr<drivers::NvsStorage> sNvs;
static std::shared_ptr<drivers::TouchWheel> sTouch; static std::shared_ptr<drivers::TouchWheel> sTouch;
static std::shared_ptr<drivers::RelativeWheel> sRelativeTouch; static std::shared_ptr<drivers::RelativeWheel> sRelativeTouch;
static std::shared_ptr<drivers::Battery> sBattery; 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::SdStorage> sStorage;
static std::shared_ptr<drivers::Display> sDisplay; static std::shared_ptr<drivers::Display> sDisplay;
static std::shared_ptr<drivers::Bluetooth> sBluetooth; static std::shared_ptr<drivers::Bluetooth> sBluetooth;
@ -101,6 +105,8 @@ class Running : public SystemState {
void react(const KeyLockChanged&) override; void react(const KeyLockChanged&) override;
void react(const StorageError&) override; void react(const StorageError&) override;
void react(const audio::PlaybackFinished&) override;
using SystemState::react; using SystemState::react;
}; };

@ -5,6 +5,7 @@
*/ */
#include "app_console.hpp" #include "app_console.hpp"
#include "audio_events.hpp"
#include "file_gatherer.hpp" #include "file_gatherer.hpp"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "result.hpp" #include "result.hpp"
@ -68,7 +69,13 @@ void Running::exit() {
} }
void Running::react(const KeyLockChanged& ev) { void Running::react(const KeyLockChanged& ev) {
if (!ev.falling && audio::AudioState::is_in_state<audio::states::Standby>()) { if (IdleCondition()) {
transit<Idle>();
}
}
void Running::react(const audio::PlaybackFinished& ev) {
if (IdleCondition()) {
transit<Idle>(); transit<Idle>();
} }
} }

@ -23,7 +23,8 @@ std::shared_ptr<drivers::NvsStorage> SystemState::sNvs;
std::shared_ptr<drivers::TouchWheel> SystemState::sTouch; std::shared_ptr<drivers::TouchWheel> SystemState::sTouch;
std::shared_ptr<drivers::RelativeWheel> SystemState::sRelativeTouch; std::shared_ptr<drivers::RelativeWheel> SystemState::sRelativeTouch;
std::shared_ptr<drivers::Battery> SystemState::sBattery; 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::SdStorage> SystemState::sStorage;
std::shared_ptr<drivers::Display> SystemState::sDisplay; std::shared_ptr<drivers::Display> SystemState::sDisplay;
std::shared_ptr<drivers::Bluetooth> SystemState::sBluetooth; std::shared_ptr<drivers::Bluetooth> SystemState::sBluetooth;
@ -95,14 +96,9 @@ void SystemState::react(const internal::SamdInterrupt&) {
} }
} }
void SystemState::react(const internal::BatteryTimerFired&) { auto SystemState::IdleCondition() -> bool {
ESP_LOGI(kTag, "checking battery"); return !sGpios->Get(drivers::IGpios::Pin::kKeyLock) &&
if (sBattery->UpdatePercent()) { audio::AudioState::is_in_state<audio::states::Standby>();
ESP_LOGI(kTag, "battery now at %u%%", sBattery->Percent());
BatteryPercentChanged ev{};
events::Ui().Dispatch(ev);
events::System().Dispatch(ev);
}
} }
} // namespace system_fsm } // namespace system_fsm

@ -9,5 +9,5 @@ idf_component_register(
"modal_progress.cpp" "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp" "modal_progress.cpp" "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp"
"splash.c" "font_fusion.c" "font_symbols.c" "splash.c" "font_fusion.c" "font_symbols.c"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer") REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -21,14 +21,15 @@ class Settings : public MenuScreen {
public: public:
Settings(); Settings();
~Settings(); ~Settings();
private:
std::shared_ptr<Screen> bluetooth_; private:
std::shared_ptr<Screen> headphones_; std::shared_ptr<Screen> bluetooth_;
std::shared_ptr<Screen> appearance_; std::shared_ptr<Screen> headphones_;
std::shared_ptr<Screen> input_method_; std::shared_ptr<Screen> appearance_;
std::shared_ptr<Screen> storage_; std::shared_ptr<Screen> input_method_;
std::shared_ptr<Screen> firmware_update_; std::shared_ptr<Screen> storage_;
std::shared_ptr<Screen> about_; std::shared_ptr<Screen> firmware_update_;
std::shared_ptr<Screen> about_;
}; };
class Bluetooth : public MenuScreen { class Bluetooth : public MenuScreen {
@ -36,32 +37,32 @@ class Bluetooth : public MenuScreen {
Bluetooth(); Bluetooth();
}; };
class Headphones : public MenuScreen { class Headphones : public MenuScreen {
public: public:
Headphones(); Headphones();
}; };
class Appearance : public MenuScreen { class Appearance : public MenuScreen {
public: public:
Appearance(); Appearance();
}; };
class InputMethod : public MenuScreen { class InputMethod : public MenuScreen {
public: public:
InputMethod(); InputMethod();
}; };
class Storage : public MenuScreen { class Storage : public MenuScreen {
public: public:
Storage(); Storage();
}; };
class FirmwareUpdate : public MenuScreen { class FirmwareUpdate : public MenuScreen {
public: public:
FirmwareUpdate(); FirmwareUpdate();
}; };
class About : public MenuScreen { class About : public MenuScreen {
public: public:
About(); About();
}; };

@ -10,6 +10,7 @@
#include <stack> #include <stack>
#include "audio_events.hpp" #include "audio_events.hpp"
#include "battery.hpp"
#include "relative_wheel.hpp" #include "relative_wheel.hpp"
#include "screen_playing.hpp" #include "screen_playing.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
@ -27,7 +28,9 @@ namespace ui {
class UiState : public tinyfsm::Fsm<UiState> { class UiState : public tinyfsm::Fsm<UiState> {
public: public:
static auto Init(drivers::IGpios*, audio::TrackQueue*) -> bool; static auto Init(drivers::IGpios*,
audio::TrackQueue*,
std::shared_ptr<battery::Battery>) -> bool;
virtual ~UiState() {} virtual ~UiState() {}
@ -41,7 +44,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
/* Fallback event handler. Does nothing. */ /* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {} void react(const tinyfsm::Event& ev) {}
void react(const system_fsm::BatteryPercentChanged&); void react(const system_fsm::BatteryStateChanged&);
virtual void react(const audio::PlaybackStarted&) {} virtual void react(const audio::PlaybackStarted&) {}
virtual void react(const audio::PlaybackUpdate&) {} virtual void react(const audio::PlaybackUpdate&) {}
@ -76,6 +79,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
static std::shared_ptr<drivers::TouchWheel> sTouchWheel; static std::shared_ptr<drivers::TouchWheel> sTouchWheel;
static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel; static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel;
static std::shared_ptr<drivers::Display> sDisplay; static std::shared_ptr<drivers::Display> sDisplay;
static std::shared_ptr<battery::Battery> sBattery;
static std::weak_ptr<database::Database> sDb; static std::weak_ptr<database::Database> sDb;
static std::stack<std::shared_ptr<Screen>> sScreens; static std::stack<std::shared_ptr<Screen>> sScreens;

@ -8,6 +8,7 @@
#include <sys/_stdint.h> #include <sys/_stdint.h>
#include <memory> #include <memory>
#include "audio_events.hpp"
#include "core/lv_event.h" #include "core/lv_event.h"
#include "core/lv_obj.h" #include "core/lv_obj.h"
#include "core/lv_obj_scroll.h" #include "core/lv_obj_scroll.h"
@ -63,6 +64,10 @@ static void below_fold_focus_cb(lv_event_t* ev) {
instance->OnFocusBelowFold(); instance->OnFocusBelowFold();
} }
static void play_pause_cb(lv_event_t* ev) {
events::Audio().Dispatch(audio::TogglePlayPause{});
}
static lv_style_t scrubber_style; static lv_style_t scrubber_style;
auto info_label(lv_obj_t* parent) -> lv_obj_t* { auto info_label(lv_obj_t* parent) -> lv_obj_t* {
@ -159,7 +164,10 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue)
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
play_pause_control_ = control_button(controls_container, LV_SYMBOL_PLAY); play_pause_control_ = control_button(controls_container, LV_SYMBOL_PLAY);
lv_obj_add_event_cb(play_pause_control_, play_pause_cb, LV_EVENT_CLICKED,
NULL);
lv_group_add_obj(group_, play_pause_control_); lv_group_add_obj(group_, play_pause_control_);
lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_PREV)); lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_PREV));
lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_NEXT)); lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_NEXT));
lv_group_add_obj(group_, lv_group_add_obj(group_,

@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include "battery.hpp"
#include "core/lv_obj.h" #include "core/lv_obj.h"
#include "misc/lv_gc.h" #include "misc/lv_gc.h"
@ -43,16 +44,19 @@ audio::TrackQueue* UiState::sQueue;
std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel; std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel;
std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel; std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel;
std::shared_ptr<drivers::Display> UiState::sDisplay; std::shared_ptr<drivers::Display> UiState::sDisplay;
std::shared_ptr<battery::Battery> UiState::sBattery;
std::weak_ptr<database::Database> UiState::sDb; std::weak_ptr<database::Database> UiState::sDb;
std::stack<std::shared_ptr<Screen>> UiState::sScreens; std::stack<std::shared_ptr<Screen>> UiState::sScreens;
std::shared_ptr<Screen> UiState::sCurrentScreen; std::shared_ptr<Screen> UiState::sCurrentScreen;
std::shared_ptr<Modal> UiState::sCurrentModal; std::shared_ptr<Modal> UiState::sCurrentModal;
auto UiState::Init(drivers::IGpios* gpio_expander, audio::TrackQueue* queue) auto UiState::Init(drivers::IGpios* gpio_expander,
-> bool { audio::TrackQueue* queue,
std::shared_ptr<battery::Battery> battery) -> bool {
sIGpios = gpio_expander; sIGpios = gpio_expander;
sQueue = queue; sQueue = queue;
sBattery = battery;
lv_init(); lv_init();
sDisplay.reset( sDisplay.reset(
@ -100,15 +104,17 @@ void UiState::react(const system_fsm::KeyLockChanged& ev) {
sRelativeWheel->SetEnabled(ev.falling); sRelativeWheel->SetEnabled(ev.falling);
} }
void UiState::react(const system_fsm::BatteryPercentChanged&) { void UiState::react(const system_fsm::BatteryStateChanged&) {
UpdateTopBar(); UpdateTopBar();
} }
void UiState::UpdateTopBar() { void UiState::UpdateTopBar() {
auto battery_state = sBattery->State();
widgets::TopBar::State state{ widgets::TopBar::State state{
.playback_state = widgets::TopBar::PlaybackState::kIdle, .playback_state = widgets::TopBar::PlaybackState::kIdle,
.battery_percent = 50, .battery_percent = static_cast<uint_fast8_t>(
.is_charging = true, battery_state.has_value() ? battery_state->percent : 100),
.is_charging = !battery_state.has_value() || battery_state->is_charging,
}; };
if (sCurrentScreen) { if (sCurrentScreen) {
sCurrentScreen->UpdateTopBar(state); sCurrentScreen->UpdateTopBar(state);

@ -68,17 +68,18 @@ auto TopBar::Update(const State& state) -> void {
break; break;
} }
if (state.battery_percent >= 95) { lv_label_set_text(battery_, std::to_string(state.battery_percent).c_str());
lv_label_set_text(battery_, "100"); // if (state.battery_percent >= 95) {
} else if (state.battery_percent >= 70) { // lv_label_set_text(battery_, "100");
lv_label_set_text(battery_, ">70"); // } else if (state.battery_percent >= 70) {
} else if (state.battery_percent >= 40) { // lv_label_set_text(battery_, ">70");
lv_label_set_text(battery_, ">40"); // } else if (state.battery_percent >= 40) {
} else if (state.battery_percent >= 10) { // lv_label_set_text(battery_, ">40");
lv_label_set_text(battery_, ">10"); // } else if (state.battery_percent >= 10) {
} else { // lv_label_set_text(battery_, ">10");
lv_label_set_text(battery_, "0"); // } else {
} // lv_label_set_text(battery_, "0");
// }
} }
} // namespace widgets } // namespace widgets

Loading…
Cancel
Save