summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorailurux <ailuruxx@gmail.com>2024-02-13 10:32:59 +1100
committerailurux <ailuruxx@gmail.com>2024-02-13 10:32:59 +1100
commit15f3da0f8ca9b85125ac557aa15c222e50fc8c8a (patch)
tree3f24a7429ed922781c22084c1dc0aeb319d96675 /src
parent26ae027d6721510e4b4a8107e95acc57efaaf2c6 (diff)
parentcb379f4bc3c51eacf80b786566ab3c2675191164 (diff)
downloadtangara-fw-15f3da0f8ca9b85125ac557aa15c222e50fc8c8a.tar.gz
Merge branch 'main' into scroll-sensitivity
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_fsm.cpp46
-rw-r--r--src/audio/fatfs_audio_input.cpp3
-rw-r--r--src/audio/include/audio_fsm.hpp7
-rw-r--r--src/database/track.cpp2
-rw-r--r--src/drivers/CMakeLists.txt2
-rw-r--r--src/drivers/bluetooth.cpp13
-rw-r--r--src/drivers/include/bluetooth.hpp2
-rw-r--r--src/drivers/include/bluetooth_types.hpp2
-rw-r--r--src/drivers/include/nvs.hpp84
-rw-r--r--src/drivers/nvs.cpp371
-rw-r--r--src/system_fsm/booting.cpp1
-rw-r--r--src/system_fsm/idle.cpp3
-rw-r--r--src/ui/ui_fsm.cpp13
-rw-r--r--src/util/include/lru_cache.hpp11
14 files changed, 433 insertions, 127 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index b80e8c30..ba6e5ffe 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -12,6 +12,7 @@
#include <variant>
#include "audio_sink.hpp"
+#include "bluetooth_types.hpp"
#include "esp_log.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
@@ -51,14 +52,24 @@ std::shared_ptr<IAudioOutput> AudioState::sOutput;
std::optional<database::TrackId> AudioState::sCurrentTrack;
bool AudioState::sIsPlaybackAllowed;
-void AudioState::react(const system_fsm::KeyLockChanged& ev) {
- if (ev.locking && sServices) {
- sServices->nvs().AmpCurrentVolume(sOutput->GetVolume());
+void AudioState::react(const system_fsm::BluetoothEvent& ev) {
+ if (ev.event != drivers::bluetooth::Event::kConnectionStateChanged) {
+ return;
+ }
+ auto dev = sServices->bluetooth().ConnectedDevice();
+ if (!dev) {
+ return;
}
+ sBtOutput->SetVolume(sServices->nvs().BluetoothVolume(dev->mac));
+ events::Ui().Dispatch(VolumeChanged{
+ .percent = sOutput->GetVolumePct(),
+ .db = sOutput->GetVolumeDb(),
+ });
}
void AudioState::react(const StepUpVolume& ev) {
if (sOutput->AdjustVolumeUp()) {
+ commitVolume();
events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
@@ -68,6 +79,7 @@ void AudioState::react(const StepUpVolume& ev) {
void AudioState::react(const StepDownVolume& ev) {
if (sOutput->AdjustVolumeDown()) {
+ commitVolume();
events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
@@ -126,6 +138,14 @@ void AudioState::react(const OutputModeChanged& ev) {
}
sOutput->SetMode(IAudioOutput::Modes::kOnPaused);
sSampleConverter->SetOutput(sOutput);
+
+ // Bluetooth volume isn't 'changed' until we've connected to a device.
+ if (new_mode == drivers::NvsStorage::Output::kHeadphones) {
+ events::Ui().Dispatch(VolumeChanged{
+ .percent = sOutput->GetVolumePct(),
+ .db = sOutput->GetVolumeDb(),
+ });
+ }
}
auto AudioState::playTrack(database::TrackId id) -> void {
@@ -139,6 +159,20 @@ auto AudioState::playTrack(database::TrackId id) -> void {
});
}
+auto AudioState::commitVolume() -> void {
+ auto mode = sServices->nvs().OutputMode();
+ auto vol = sOutput->GetVolume();
+ if (mode == drivers::NvsStorage::Output::kHeadphones) {
+ sServices->nvs().AmpCurrentVolume(vol);
+ } else if (mode == drivers::NvsStorage::Output::kBluetooth) {
+ auto dev = sServices->bluetooth().ConnectedDevice();
+ if (!dev) {
+ return;
+ }
+ sServices->nvs().BluetoothVolume(dev->mac, vol);
+ }
+}
+
auto AudioState::readyToPlay() -> bool {
return sCurrentTrack.has_value() && sIsPlaybackAllowed;
}
@@ -162,11 +196,11 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
sServices = ev.services;
constexpr size_t kDrainBufferSize =
- drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 8;
+ drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2 * 8;
ESP_LOGI(kTag, "allocating drain buffer, size %u KiB",
kDrainBufferSize / 1024);
StreamBufferHandle_t stream = xStreamBufferCreateWithCaps(
- kDrainBufferSize, sizeof(sample::Sample) * 2, MALLOC_CAP_DMA);
+ kDrainBufferSize, sizeof(sample::Sample), MALLOC_CAP_DMA);
sFileSource.reset(
new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker()));
@@ -283,7 +317,7 @@ void Playback::exit() {
// Stash the current volume now, in case it changed during playback, since we
// might be powering off soon.
- sServices->nvs().AmpCurrentVolume(sOutput->GetVolume());
+ commitVolume();
events::System().Dispatch(PlaybackStopped{});
events::Ui().Dispatch(PlaybackStopped{});
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index de64b14b..7726a94a 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -111,6 +111,9 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
ESP_LOGE(kTag, "failed to read tags");
return false;
}
+ if (!tags->title()) {
+ tags->title(path);
+ }
auto stream_type = ContainerToStreamType(tags->encoding());
if (!stream_type.has_value()) {
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index 884af8a8..29ec489a 100644
--- a/src/audio/include/audio_fsm.hpp
+++ b/src/audio/include/audio_fsm.hpp
@@ -52,8 +52,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const OutputModeChanged&);
virtual void react(const system_fsm::BootComplete&) {}
- virtual void react(const system_fsm::KeyLockChanged&);
+ virtual void react(const system_fsm::KeyLockChanged&) {};
virtual void react(const system_fsm::StorageMounted&) {}
+ virtual void react(const system_fsm::BluetoothEvent&);
virtual void react(const PlayFile&) {}
virtual void react(const QueueUpdate&) {}
@@ -67,6 +68,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
protected:
auto playTrack(database::TrackId id) -> void;
+ auto commitVolume() -> void;
static std::shared_ptr<system_fsm::ServiceLocator> sServices;
@@ -88,6 +90,9 @@ namespace states {
class Uninitialised : public AudioState {
public:
void react(const system_fsm::BootComplete&) override;
+
+ void react(const system_fsm::BluetoothEvent&) override {};
+
using AudioState::react;
};
diff --git a/src/database/track.cpp b/src/database/track.cpp
index 943606ce..141a9b7e 100644
--- a/src/database/track.cpp
+++ b/src/database/track.cpp
@@ -302,6 +302,6 @@ auto Track::TitleOrFilename() const -> std::pmr::string {
if (start == std::pmr::string::npos) {
return data().filepath;
}
- return data().filepath.substr(start);
+ return data().filepath.substr(start + 1);
}
} // namespace database
diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt
index 4155ed66..0b7ead94 100644
--- a/src/drivers/CMakeLists.txt
+++ b/src/drivers/CMakeLists.txt
@@ -9,5 +9,5 @@ idf_component_register(
"spiffs.cpp"
INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs"
- "bt" "tinyfsm")
+ "bt" "tinyfsm" "util")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp
index 1a303748..84c81de0 100644
--- a/src/drivers/bluetooth.cpp
+++ b/src/drivers/bluetooth.cpp
@@ -103,21 +103,12 @@ auto Bluetooth::IsConnected() -> bool {
return bluetooth::BluetoothState::is_in_state<bluetooth::Connected>();
}
-auto Bluetooth::ConnectedDevice() -> std::optional<bluetooth::Device> {
+auto Bluetooth::ConnectedDevice() -> std::optional<bluetooth::MacAndName> {
auto lock = bluetooth::BluetoothState::lock();
if (!bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
return {};
}
- auto looking_for = bluetooth::BluetoothState::preferred_device();
- if (!looking_for) {
- return {};
- }
- for (const auto& dev : bluetooth::BluetoothState::devices()) {
- if (dev.address == looking_for->mac) {
- return dev;
- }
- }
- return {};
+ return bluetooth::BluetoothState::preferred_device();
}
auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> {
diff --git a/src/drivers/include/bluetooth.hpp b/src/drivers/include/bluetooth.hpp
index 5fdd527c..988c7e93 100644
--- a/src/drivers/include/bluetooth.hpp
+++ b/src/drivers/include/bluetooth.hpp
@@ -35,7 +35,7 @@ class Bluetooth {
auto IsEnabled() -> bool;
auto IsConnected() -> bool;
- auto ConnectedDevice() -> std::optional<bluetooth::Device>;
+ auto ConnectedDevice() -> std::optional<bluetooth::MacAndName>;
auto KnownDevices() -> std::vector<bluetooth::Device>;
diff --git a/src/drivers/include/bluetooth_types.hpp b/src/drivers/include/bluetooth_types.hpp
index 74434182..7cfb236f 100644
--- a/src/drivers/include/bluetooth_types.hpp
+++ b/src/drivers/include/bluetooth_types.hpp
@@ -14,6 +14,8 @@ typedef std::array<uint8_t, 6> mac_addr_t;
struct MacAndName {
mac_addr_t mac;
std::string name;
+
+ bool operator==(const MacAndName&) const = default;
};
struct Device {
diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp
index 560cdefd..f862b43e 100644
--- a/src/drivers/include/nvs.hpp
+++ b/src/drivers/include/nvs.hpp
@@ -14,44 +14,90 @@
#include "nvs.h"
#include "bluetooth_types.hpp"
+#include "lru_cache.hpp"
#include "tasks.hpp"
namespace drivers {
+/*
+ * Wrapper for a single NVS setting, with its backing value cached in memory.
+ * NVS values that are just plain old data should generally use these for
+ * simpler implementation.
+ */
+template <typename T>
+class Setting {
+ public:
+ Setting(const char* name) : name_(name), val_(), dirty_(false) {}
+
+ auto set(const std::optional<T>&& v) -> void {
+ if (val_.has_value() != v.has_value() || *val_ != *v) {
+ val_ = v;
+ dirty_ = true;
+ }
+ }
+ auto get() -> std::optional<T>& { return val_; }
+
+ /* Reads the stored value from NVS and parses it into the correct type. */
+ auto load(nvs_handle_t) -> std::optional<T>;
+ /* Encodes the given value and writes it to NVS. */
+ auto store(nvs_handle_t, T v) -> void;
+
+ auto read(nvs_handle_t nvs) -> void { val_ = load(nvs); }
+ auto write(nvs_handle_t nvs) -> void {
+ if (!dirty_) {
+ return;
+ }
+ dirty_ = false;
+ if (val_) {
+ store(nvs, *val_);
+ } else {
+ nvs_erase_key(nvs, name_);
+ }
+ }
+
+ private:
+ const char* name_;
+ std::optional<T> val_;
+ bool dirty_;
+};
+
class NvsStorage {
public:
static auto OpenSync() -> NvsStorage*;
+ auto Read() -> void;
+ auto Write() -> bool;
+
auto LockPolarity() -> bool;
- auto LockPolarity(bool) -> bool;
+ auto LockPolarity(bool) -> void;
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
- auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> bool;
+ auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> void;
+
+ auto BluetoothVolume(const bluetooth::mac_addr_t&) -> uint8_t;
+ auto BluetoothVolume(const bluetooth::mac_addr_t&, uint8_t) -> void;
enum class Output : uint8_t {
kHeadphones = 0,
kBluetooth = 1,
};
auto OutputMode() -> Output;
- auto OutputMode(Output) -> bool;
+ auto OutputMode(Output) -> void;
auto ScreenBrightness() -> uint_fast8_t;
- auto ScreenBrightness(uint_fast8_t) -> bool;
+ auto ScreenBrightness(uint_fast8_t) -> void;
auto ScrollSensitivity() -> uint_fast8_t;
auto ScrollSensitivity(uint_fast8_t) -> bool;
auto AmpMaxVolume() -> uint16_t;
- auto AmpMaxVolume(uint16_t) -> bool;
+ auto AmpMaxVolume(uint16_t) -> void;
auto AmpCurrentVolume() -> uint16_t;
- auto AmpCurrentVolume(uint16_t) -> bool;
+ auto AmpCurrentVolume(uint16_t) -> void;
auto AmpLeftBias() -> int_fast8_t;
- auto AmpLeftBias(int_fast8_t) -> bool;
-
- auto HasShownOnboarding() -> bool;
- auto HasShownOnboarding(bool) -> bool;
+ auto AmpLeftBias(int_fast8_t) -> void;
enum class InputModes : uint8_t {
kButtonsOnly = 0,
@@ -61,7 +107,7 @@ class NvsStorage {
};
auto PrimaryInput() -> InputModes;
- auto PrimaryInput(InputModes) -> bool;
+ auto PrimaryInput(InputModes) -> void;
explicit NvsStorage(nvs_handle_t);
~NvsStorage();
@@ -70,7 +116,23 @@ class NvsStorage {
auto DowngradeSchemaSync() -> bool;
auto SchemaVersionSync() -> uint8_t;
+ std::mutex mutex_;
nvs_handle_t handle_;
+
+ Setting<uint8_t> lock_polarity_;
+ Setting<uint8_t> brightness_;
+ Setting<uint16_t> amp_max_vol_;
+ Setting<uint16_t> amp_cur_vol_;
+ Setting<int8_t> amp_left_bias_;
+ Setting<uint8_t> input_mode_;
+ Setting<uint8_t> output_mode_;
+ Setting<bluetooth::MacAndName> bt_preferred_;
+
+ util::LruCache<10, bluetooth::mac_addr_t, uint8_t> bt_volumes_;
+ bool bt_volumes_dirty_;
+
+ auto readBtVolumes() -> void;
+ auto writeBtVolumes() -> void;
};
} // namespace drivers
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 7f764852..bcfddd92 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -13,6 +13,8 @@
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
+#include "cppbor.h"
+#include "cppbor_parse.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
@@ -25,18 +27,106 @@ namespace drivers {
static constexpr uint8_t kSchemaVersion = 1;
static constexpr char kKeyVersion[] = "ver";
-static constexpr char kKeyBluetoothMac[] = "bt_mac";
-static constexpr char kKeyBluetoothName[] = "bt_name";
+static constexpr char kKeyBluetoothPreferred[] = "bt_dev";
+static constexpr char kKeyBluetoothVolumes[] = "bt_vols";
static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
static constexpr char kKeyAmpLeftBias[] = "hp_bias";
-static constexpr char kKeyOnboarded[] = "intro";
static constexpr char kKeyPrimaryInput[] = "in_pri";
static constexpr char kKeyScrollSensitivity[] = "scroll";
static constexpr char kKeyLockPolarity[] = "lockpol";
+static auto nvs_get_string(nvs_handle_t nvs, const char* key)
+ -> std::optional<std::string> {
+ size_t len = 0;
+ if (nvs_get_blob(nvs, key, NULL, &len) != ESP_OK) {
+ return {};
+ }
+ auto raw = std::unique_ptr<char[]>{new char[len]};
+ if (nvs_get_blob(nvs, key, raw.get(), &len) != ESP_OK) {
+ return {};
+ }
+ return {{raw.get(), len}};
+}
+
+template <>
+auto Setting<uint16_t>::load(nvs_handle_t nvs) -> std::optional<uint16_t> {
+ uint16_t out;
+ if (nvs_get_u16(nvs, name_, &out) != ESP_OK) {
+ return {};
+ }
+ return out;
+}
+
+template <>
+auto Setting<uint16_t>::store(nvs_handle_t nvs, uint16_t v) -> void {
+ nvs_set_u16(nvs, name_, v);
+}
+
+template <>
+auto Setting<uint8_t>::load(nvs_handle_t nvs) -> std::optional<uint8_t> {
+ uint8_t out;
+ if (nvs_get_u8(nvs, name_, &out) != ESP_OK) {
+ return {};
+ }
+ return out;
+}
+
+template <>
+auto Setting<uint8_t>::store(nvs_handle_t nvs, uint8_t v) -> void {
+ nvs_set_u8(nvs, name_, v);
+}
+
+template <>
+auto Setting<int8_t>::load(nvs_handle_t nvs) -> std::optional<int8_t> {
+ int8_t out;
+ if (nvs_get_i8(nvs, name_, &out) != ESP_OK) {
+ return {};
+ }
+ return out;
+}
+
+template <>
+auto Setting<int8_t>::store(nvs_handle_t nvs, int8_t v) -> void {
+ nvs_set_i8(nvs, name_, v);
+}
+
+template <>
+auto Setting<bluetooth::MacAndName>::load(nvs_handle_t nvs)
+ -> std::optional<bluetooth::MacAndName> {
+ auto raw = nvs_get_string(nvs, name_);
+ if (!raw) {
+ return {};
+ }
+ auto [parsed, unused, err] = cppbor::parseWithViews(
+ reinterpret_cast<const uint8_t*>(raw->data()), raw->size());
+ if (parsed->type() != cppbor::ARRAY) {
+ return {};
+ }
+ auto arr = parsed->asArray();
+ auto mac = arr->get(1)->asViewBstr()->view();
+ auto name = arr->get(0)->asViewTstr()->view();
+ bluetooth::MacAndName res{
+ .mac = {},
+ .name = {name.begin(), name.end()},
+ };
+ std::copy(mac.begin(), mac.end(), res.mac.begin());
+ return res;
+}
+
+template <>
+auto Setting<bluetooth::MacAndName>::store(nvs_handle_t nvs,
+ bluetooth::MacAndName v) -> void {
+ cppbor::Array cbor{
+ cppbor::Tstr{v.name},
+ cppbor::Bstr{{v.mac.data(), v.mac.size()}},
+ };
+ auto encoded = cbor.encode();
+ nvs_set_blob(nvs, name_, encoded.data(), encoded.size());
+}
+
auto NvsStorage::OpenSync() -> NvsStorage* {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
@@ -62,17 +152,57 @@ auto NvsStorage::OpenSync() -> NvsStorage* {
return nullptr;
}
+ instance->Read();
+
ESP_LOGI(kTag, "nvm storage initialised okay");
return instance.release();
}
-NvsStorage::NvsStorage(nvs_handle_t handle) : handle_(handle) {}
+NvsStorage::NvsStorage(nvs_handle_t handle)
+ : handle_(handle),
+ lock_polarity_(kKeyLockPolarity),
+ brightness_(kKeyBrightness),
+ amp_max_vol_(kKeyAmpMaxVolume),
+ amp_cur_vol_(kKeyAmpCurrentVolume),
+ amp_left_bias_(kKeyAmpLeftBias),
+ input_mode_(kKeyPrimaryInput),
+ output_mode_(kKeyOutput),
+ bt_preferred_(kKeyBluetoothPreferred),
+ bt_volumes_(),
+ bt_volumes_dirty_(false) {}
NvsStorage::~NvsStorage() {
nvs_close(handle_);
nvs_flash_deinit();
}
+auto NvsStorage::Read() -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ lock_polarity_.read(handle_);
+ brightness_.read(handle_);
+ amp_max_vol_.read(handle_);
+ amp_cur_vol_.read(handle_);
+ amp_left_bias_.read(handle_);
+ input_mode_.read(handle_);
+ output_mode_.read(handle_);
+ bt_preferred_.read(handle_);
+ readBtVolumes();
+}
+
+auto NvsStorage::Write() -> bool {
+ std::lock_guard<std::mutex> lock{mutex_};
+ lock_polarity_.write(handle_);
+ brightness_.write(handle_);
+ amp_max_vol_.write(handle_);
+ amp_cur_vol_.write(handle_);
+ amp_left_bias_.write(handle_);
+ input_mode_.write(handle_);
+ output_mode_.write(handle_);
+ bt_preferred_.write(handle_);
+ writeBtVolumes();
+ return nvs_commit(handle_) == ESP_OK;
+}
+
auto NvsStorage::DowngradeSchemaSync() -> bool {
ESP_LOGW(kTag, "namespace needs downgrading");
nvs_erase_all(handle_);
@@ -89,57 +219,45 @@ auto NvsStorage::SchemaVersionSync() -> uint8_t {
}
auto NvsStorage::LockPolarity() -> bool {
- uint8_t res;
- if (nvs_get_u8(handle_, kKeyLockPolarity, &res) != ESP_OK) {
- return false;
- }
- return res > 0;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return lock_polarity_.get().value_or(0) > 0;
}
-auto NvsStorage::LockPolarity(bool p) -> bool {
- nvs_set_u8(handle_, kKeyLockPolarity, p);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::LockPolarity(bool p) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ lock_polarity_.set(p);
}
auto NvsStorage::PreferredBluetoothDevice()
-> std::optional<bluetooth::MacAndName> {
- bluetooth::mac_addr_t mac{0};
- size_t size = mac.size();
- if (nvs_get_blob(handle_, kKeyBluetoothMac, mac.data(), &size) != ESP_OK) {
- return {};
- }
- size_t name_len = 0;
- if (nvs_get_str(handle_, kKeyBluetoothName, NULL, &name_len) != ESP_OK) {
- }
- char* raw_name = new char[name_len];
- if (nvs_get_str(handle_, kKeyBluetoothName, raw_name, &name_len) != ESP_OK) {
- delete[] raw_name;
- return {};
- }
- bluetooth::MacAndName out{
- .mac = mac,
- .name = {raw_name, name_len},
- };
- delete[] raw_name;
- return out;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return bt_preferred_.get();
}
auto NvsStorage::PreferredBluetoothDevice(
- std::optional<bluetooth::MacAndName> dev) -> bool {
- if (!dev) {
- nvs_erase_key(handle_, kKeyBluetoothMac);
- nvs_erase_key(handle_, kKeyBluetoothName);
- } else {
- nvs_set_blob(handle_, kKeyBluetoothMac, dev->mac.data(), dev->mac.size());
- nvs_set_str(handle_, kKeyBluetoothName, dev->name.c_str());
- }
- return nvs_commit(handle_) == ESP_OK;
+ std::optional<bluetooth::MacAndName> dev) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ bt_preferred_.set(std::move(dev));
+}
+
+auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac) -> uint8_t {
+ std::lock_guard<std::mutex> lock{mutex_};
+ // Note we don't set the dirty flag here, even though it's an LRU cache, so
+ // that we can avoid constantly re-writing this setting to flash when the
+ // user hasn't actually been changing their volume.
+ return bt_volumes_.Get(mac).value_or(10);
+}
+
+auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac, uint8_t vol)
+ -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ bt_volumes_dirty_ = true;
+ bt_volumes_.Put(mac, vol);
}
auto NvsStorage::OutputMode() -> Output {
- uint8_t out = 0;
- nvs_get_u8(handle_, kKeyOutput, &out);
- switch (out) {
+ std::lock_guard<std::mutex> lock{mutex_};
+ switch (output_mode_.get().value_or(0xFF)) {
case static_cast<uint8_t>(Output::kBluetooth):
return Output::kBluetooth;
case static_cast<uint8_t>(Output::kHeadphones):
@@ -148,21 +266,23 @@ auto NvsStorage::OutputMode() -> Output {
}
}
-auto NvsStorage::OutputMode(Output out) -> bool {
- uint8_t as_int = static_cast<uint8_t>(out);
- nvs_set_u8(handle_, kKeyOutput, as_int);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::OutputMode(Output out) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ output_mode_.set(static_cast<uint8_t>(out));
+ // Always write this immediately, to guard against any crashes caused by
+ // toggling the output mode.
+ output_mode_.write(handle_);
+ nvs_commit(handle_);
}
auto NvsStorage::ScreenBrightness() -> uint_fast8_t {
- uint8_t out = 50;
- nvs_get_u8(handle_, kKeyBrightness, &out);
- return out;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return std::clamp<uint8_t>(brightness_.get().value_or(50), 0, 100);
}
-auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> bool {
- nvs_set_u8(handle_, kKeyBrightness, val);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ brightness_.set(val);
}
auto NvsStorage::ScrollSensitivity() -> uint_fast8_t {
@@ -177,53 +297,38 @@ auto NvsStorage::ScrollSensitivity(uint_fast8_t val) -> bool {
}
auto NvsStorage::AmpMaxVolume() -> uint16_t {
- uint16_t out = wm8523::kDefaultMaxVolume;
- nvs_get_u16(handle_, kKeyAmpMaxVolume, &out);
- return out;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return amp_max_vol_.get().value_or(wm8523::kDefaultMaxVolume);
}
-auto NvsStorage::AmpMaxVolume(uint16_t val) -> bool {
- nvs_set_u16(handle_, kKeyAmpMaxVolume, val);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::AmpMaxVolume(uint16_t val) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ amp_max_vol_.set(val);
}
auto NvsStorage::AmpCurrentVolume() -> uint16_t {
- uint16_t out = wm8523::kDefaultVolume;
- nvs_get_u16(handle_, kKeyAmpCurrentVolume, &out);
- return out;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return amp_cur_vol_.get().value_or(wm8523::kDefaultVolume);
}
-auto NvsStorage::AmpCurrentVolume(uint16_t val) -> bool {
- nvs_set_u16(handle_, kKeyAmpCurrentVolume, val);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::AmpCurrentVolume(uint16_t val) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ amp_cur_vol_.set(val);
}
auto NvsStorage::AmpLeftBias() -> int_fast8_t {
- int8_t out = 0;
- nvs_get_i8(handle_, kKeyAmpLeftBias, &out);
- return out;
+ std::lock_guard<std::mutex> lock{mutex_};
+ return amp_left_bias_.get().value_or(0);
}
-auto NvsStorage::AmpLeftBias(int_fast8_t val) -> bool {
- nvs_set_i8(handle_, kKeyAmpLeftBias, val);
- return nvs_commit(handle_) == ESP_OK;
-}
-
-auto NvsStorage::HasShownOnboarding() -> bool {
- uint8_t out = false;
- nvs_get_u8(handle_, kKeyOnboarded, &out);
- return out;
-}
-
-auto NvsStorage::HasShownOnboarding(bool val) -> bool {
- nvs_set_u8(handle_, kKeyOnboarded, val);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::AmpLeftBias(int_fast8_t val) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ amp_left_bias_.set(val);
}
auto NvsStorage::PrimaryInput() -> InputModes {
- uint8_t out = 3;
- nvs_get_u8(handle_, kKeyPrimaryInput, &out);
- switch (out) {
+ std::lock_guard<std::mutex> lock{mutex_};
+ switch (input_mode_.get().value_or(3)) {
case static_cast<uint8_t>(InputModes::kButtonsOnly):
return InputModes::kButtonsOnly;
case static_cast<uint8_t>(InputModes::kButtonsWithWheel):
@@ -237,10 +342,98 @@ auto NvsStorage::PrimaryInput() -> InputModes {
}
}
-auto NvsStorage::PrimaryInput(InputModes mode) -> bool {
- uint8_t as_int = static_cast<uint8_t>(mode);
- nvs_set_u8(handle_, kKeyPrimaryInput, as_int);
- return nvs_commit(handle_) == ESP_OK;
+auto NvsStorage::PrimaryInput(InputModes mode) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ input_mode_.set(static_cast<uint8_t>(mode));
+}
+
+class VolumesParseClient : public cppbor::ParseClient {
+ public:
+ VolumesParseClient(util::LruCache<10, bluetooth::mac_addr_t, uint8_t>& out)
+ : state_(State::kInit), mac_(), vol_(), out_(out) {}
+
+ ParseClient* item(std::unique_ptr<cppbor::Item>& item,
+ const uint8_t* hdrBegin,
+ const uint8_t* valueBegin,
+ const uint8_t* end) override {
+ if (item->type() == cppbor::ARRAY) {
+ if (state_ == State::kInit) {
+ state_ = State::kRoot;
+ } else if (state_ == State::kRoot) {
+ state_ = State::kPair;
+ }
+ } else if (item->type() == cppbor::BSTR && state_ == State::kPair) {
+ auto data = item->asBstr()->value();
+ mac_.emplace();
+ std::copy(data.begin(), data.end(), mac_->begin());
+ } else if (item->type() == cppbor::UINT && state_ == State::kPair) {
+ vol_ =
+ std::clamp<uint64_t>(item->asUint()->unsignedValue(), 0, UINT8_MAX);
+ }
+ return this;
+ }
+
+ ParseClient* itemEnd(std::unique_ptr<cppbor::Item>& item,
+ const uint8_t* hdrBegin,
+ const uint8_t* valueBegin,
+ const uint8_t* end) override {
+ if (item->type() == cppbor::ARRAY) {
+ if (state_ == State::kRoot) {
+ state_ = State::kFinished;
+ } else if (state_ == State::kPair) {
+ if (vol_ && mac_) {
+ out_.Put(*mac_, *vol_);
+ }
+ mac_.reset();
+ vol_.reset();
+ state_ = State::kRoot;
+ }
+ }
+ return this;
+ }
+
+ void error(const uint8_t* position,
+ const std::string& errorMessage) override {}
+
+ private:
+ enum class State {
+ kInit,
+ kRoot,
+ kPair,
+ kFinished,
+ };
+
+ State state_;
+ std::optional<bluetooth::mac_addr_t> mac_;
+ std::optional<uint8_t> vol_;
+ util::LruCache<10, bluetooth::mac_addr_t, uint8_t>& out_;
+};
+
+auto NvsStorage::readBtVolumes() -> void {
+ bt_volumes_.Clear();
+ auto raw = nvs_get_string(handle_, kKeyBluetoothVolumes);
+ if (!raw) {
+ return;
+ }
+ VolumesParseClient client{bt_volumes_};
+ auto data = reinterpret_cast<const uint8_t*>(raw->data());
+ cppbor::parse(data, data + raw->size(), &client);
+}
+
+auto NvsStorage::writeBtVolumes() -> void {
+ if (!bt_volumes_dirty_) {
+ return;
+ }
+ bt_volumes_dirty_ = false;
+
+ cppbor::Array enc;
+ auto vols_list = bt_volumes_.Get();
+ for (auto vol = vols_list.rbegin(); vol < vols_list.rend(); vol++) {
+ enc.add(cppbor::Array{cppbor::Bstr{{vol->first.data(), vol->first.size()}},
+ cppbor::Uint{vol->second}});
+ }
+ std::string encoded = enc.toString();
+ nvs_set_str(handle_, kKeyBluetoothVolumes, encoded.c_str());
}
} // namespace drivers
diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp
index b166a02c..41f46df2 100644
--- a/src/system_fsm/booting.cpp
+++ b/src/system_fsm/booting.cpp
@@ -47,6 +47,7 @@ namespace states {
static auto bt_event_cb(drivers::bluetooth::Event ev) -> void {
events::Ui().Dispatch(BluetoothEvent{.event = ev});
+ events::Audio().Dispatch(BluetoothEvent{.event = ev});
}
static const TickType_t kInterruptCheckPeriod = pdMS_TO_TICKS(100);
diff --git a/src/system_fsm/idle.cpp b/src/system_fsm/idle.cpp
index 640f95cd..b6bb2572 100644
--- a/src/system_fsm/idle.cpp
+++ b/src/system_fsm/idle.cpp
@@ -34,6 +34,9 @@ static void timer_callback(TimerHandle_t timer) {
*/
void Idle::entry() {
ESP_LOGI(kTag, "system became idle");
+
+ sServices->nvs().Write();
+
events::Audio().Dispatch(OnIdle{});
events::Ui().Dispatch(OnIdle{});
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index da8d8999..24145ead 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -368,14 +368,20 @@ void UiState::react(const audio::VolumeLimitChanged& ev) {
void UiState::react(const system_fsm::BluetoothEvent& ev) {
auto bt = sServices->bluetooth();
+ auto dev = bt.ConnectedDevice();
switch (ev.event) {
case drivers::bluetooth::Event::kKnownDevicesChanged:
sBluetoothDevices.Update(bt.KnownDevices());
break;
case drivers::bluetooth::Event::kConnectionStateChanged:
sBluetoothConnected.Update(bt.IsConnected());
- if (bt.ConnectedDevice()) {
- sBluetoothPairedDevice.Update(bt.ConnectedDevice().value());
+ if (dev) {
+ sBluetoothPairedDevice.Update(drivers::bluetooth::Device{
+ .address = dev->mac,
+ .name = {dev->name.data(), dev->name.size()},
+ .class_of_device = 0,
+ .signal_strength = 0,
+ });
} else {
sBluetoothPairedDevice.Update(std::monostate{});
}
@@ -505,9 +511,6 @@ void Lua::entry() {
auto bt = sServices->bluetooth();
sBluetoothEnabled.Update(bt.IsEnabled());
sBluetoothConnected.Update(bt.IsConnected());
- if (bt.ConnectedDevice()) {
- sBluetoothPairedDevice.Update(bt.ConnectedDevice().value());
- }
sBluetoothDevices.Update(bt.KnownDevices());
sCurrentScreen.reset();
diff --git a/src/util/include/lru_cache.hpp b/src/util/include/lru_cache.hpp
index 41293901..606a6387 100644
--- a/src/util/include/lru_cache.hpp
+++ b/src/util/include/lru_cache.hpp
@@ -10,9 +10,11 @@
#include <bitset>
#include <cstdint>
#include <list>
+#include <map>
#include <optional>
#include <unordered_map>
#include <utility>
+
#include "memory_resource.hpp"
namespace util {
@@ -64,9 +66,16 @@ class LruCache {
key_to_it_.clear();
}
+ auto Get() const -> std::vector<std::pair<K, V>> {
+ std::vector<std::pair<K, V>> out;
+ out.resize(entries_.size());
+ std::copy(entries_.begin(), entries_.end(), out.begin());
+ return out;
+ }
+
private:
std::pmr::list<std::pair<K, V>> entries_;
- std::pmr::unordered_map<K, decltype(entries_.begin())> key_to_it_;
+ std::pmr::map<K, decltype(entries_.begin())> key_to_it_;
};
} // namespace util