summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-02-12 17:07:39 +1100
committerjacqueline <me@jacqueline.id.au>2024-02-12 17:07:39 +1100
commit26df5c4a7f54d493a09724a5f4f8f4a3a5c90f31 (patch)
tree168c313734ed388efa1847226d6cea61b8c0c7e0 /src
parenta7ac34eaa9b895e16aed816c504d167027898d7b (diff)
downloadtangara-fw-26df5c4a7f54d493a09724a5f4f8f4a3a5c90f31.tar.gz
Remember per-device bluetooth volume
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_fsm.cpp45
-rw-r--r--src/audio/include/audio_fsm.hpp7
-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/nvs.hpp6
-rw-r--r--src/drivers/nvs.cpp98
-rw-r--r--src/system_fsm/booting.cpp1
-rw-r--r--src/ui/ui_fsm.cpp13
-rw-r--r--src/util/include/lru_cache.hpp11
10 files changed, 174 insertions, 24 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index 54ffa51d..659340af 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,25 @@ 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;
}
+ auto vols = sServices->nvs().BluetoothVolumes();
+ sBtOutput->SetVolume(vols.Get(dev->mac).value_or(10));
+ 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 +80,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 +139,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 +160,22 @@ 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;
+ }
+ auto vols = sServices->nvs().BluetoothVolumes();
+ vols.Put(dev->mac, vol);
+ sServices->nvs().BluetoothVolumes(vols);
+ }
+}
+
auto AudioState::readyToPlay() -> bool {
return sCurrentTrack.has_value() && sIsPlaybackAllowed;
}
@@ -283,7 +320,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/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/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/nvs.hpp b/src/drivers/include/nvs.hpp
index 1184b72c..264d9784 100644
--- a/src/drivers/include/nvs.hpp
+++ b/src/drivers/include/nvs.hpp
@@ -15,6 +15,7 @@
#include "bluetooth_types.hpp"
#include "tasks.hpp"
+#include "lru_cache.hpp"
namespace drivers {
@@ -28,6 +29,11 @@ class NvsStorage {
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> bool;
+ using BtVolumes = util::LruCache<10, bluetooth::mac_addr_t, uint8_t> ;
+
+ auto BluetoothVolumes() -> BtVolumes;
+ auto BluetoothVolumes(const BtVolumes&) -> bool;
+
enum class Output : uint8_t {
kHeadphones = 0,
kBluetooth = 1,
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index ab623d01..f7687dcb 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"
@@ -27,6 +29,7 @@ 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 kKeyBluetoothVolumes[] = "bt_vols";
static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
@@ -135,6 +138,101 @@ auto NvsStorage::PreferredBluetoothDevice(
return nvs_commit(handle_) == ESP_OK;
}
+class VolumesParseClient : public cppbor::ParseClient {
+ public:
+ VolumesParseClient(NvsStorage::BtVolumes& 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) {
+ ESP_LOGI(kTag, "enter root");
+ state_ = State::kRoot;
+ } else if (state_ == State::kRoot) {
+ ESP_LOGI(kTag, "enter pair");
+ state_ = State::kPair;
+ }
+ } else if (item->type() == cppbor::BSTR && state_ == State::kPair) {
+ ESP_LOGI(kTag, "get str");
+ 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_;
+ NvsStorage::BtVolumes& out_;
+};
+
+auto NvsStorage::BluetoothVolumes() -> BtVolumes {
+ BtVolumes out;
+ size_t encoded_len = 0;
+ if (nvs_get_str(handle_, kKeyBluetoothVolumes, NULL, &encoded_len) !=
+ ESP_OK) {
+ return out;
+ }
+ auto encoded = std::unique_ptr<char[]>{new char[encoded_len]};
+ if (nvs_get_str(handle_, kKeyBluetoothVolumes, encoded.get(), &encoded_len) !=
+ ESP_OK) {
+ return out;
+ }
+ VolumesParseClient client{out};
+ auto data = reinterpret_cast<const uint8_t*>(encoded.get());
+ cppbor::parse(data, data + encoded_len, &client);
+ return out;
+}
+
+auto NvsStorage::BluetoothVolumes(const BtVolumes& vols) -> bool {
+ cppbor::Array enc;
+ auto vols_list = vols.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());
+ return nvs_commit(handle_) == ESP_OK;
+}
+
auto NvsStorage::OutputMode() -> Output {
uint8_t out = 0;
nvs_get_u8(handle_, kKeyOutput, &out);
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/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 12584ec7..630238e7 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -326,14 +326,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{});
}
@@ -457,9 +463,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