Remember per-device bluetooth volume

custom
jacqueline 1 year ago
parent a7ac34eaa9
commit 26df5c4a7f
  1. 45
      src/audio/audio_fsm.cpp
  2. 7
      src/audio/include/audio_fsm.hpp
  3. 2
      src/drivers/CMakeLists.txt
  4. 13
      src/drivers/bluetooth.cpp
  5. 2
      src/drivers/include/bluetooth.hpp
  6. 6
      src/drivers/include/nvs.hpp
  7. 98
      src/drivers/nvs.cpp
  8. 1
      src/system_fsm/booting.cpp
  9. 13
      src/ui/ui_fsm.cpp
  10. 11
      src/util/include/lru_cache.hpp

@ -12,6 +12,7 @@
#include <variant> #include <variant>
#include "audio_sink.hpp" #include "audio_sink.hpp"
#include "bluetooth_types.hpp"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
@ -51,14 +52,25 @@ std::shared_ptr<IAudioOutput> AudioState::sOutput;
std::optional<database::TrackId> AudioState::sCurrentTrack; std::optional<database::TrackId> AudioState::sCurrentTrack;
bool AudioState::sIsPlaybackAllowed; bool AudioState::sIsPlaybackAllowed;
void AudioState::react(const system_fsm::KeyLockChanged& ev) { void AudioState::react(const system_fsm::BluetoothEvent& ev) {
if (ev.locking && sServices) { if (ev.event != drivers::bluetooth::Event::kConnectionStateChanged) {
sServices->nvs().AmpCurrentVolume(sOutput->GetVolume()); 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) { void AudioState::react(const StepUpVolume& ev) {
if (sOutput->AdjustVolumeUp()) { if (sOutput->AdjustVolumeUp()) {
commitVolume();
events::Ui().Dispatch(VolumeChanged{ events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(), .percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(), .db = sOutput->GetVolumeDb(),
@ -68,6 +80,7 @@ void AudioState::react(const StepUpVolume& ev) {
void AudioState::react(const StepDownVolume& ev) { void AudioState::react(const StepDownVolume& ev) {
if (sOutput->AdjustVolumeDown()) { if (sOutput->AdjustVolumeDown()) {
commitVolume();
events::Ui().Dispatch(VolumeChanged{ events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(), .percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(), .db = sOutput->GetVolumeDb(),
@ -126,6 +139,14 @@ void AudioState::react(const OutputModeChanged& ev) {
} }
sOutput->SetMode(IAudioOutput::Modes::kOnPaused); sOutput->SetMode(IAudioOutput::Modes::kOnPaused);
sSampleConverter->SetOutput(sOutput); 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 { 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 { auto AudioState::readyToPlay() -> bool {
return sCurrentTrack.has_value() && sIsPlaybackAllowed; 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 // Stash the current volume now, in case it changed during playback, since we
// might be powering off soon. // might be powering off soon.
sServices->nvs().AmpCurrentVolume(sOutput->GetVolume()); commitVolume();
events::System().Dispatch(PlaybackStopped{}); events::System().Dispatch(PlaybackStopped{});
events::Ui().Dispatch(PlaybackStopped{}); events::Ui().Dispatch(PlaybackStopped{});

@ -52,8 +52,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const OutputModeChanged&); void react(const OutputModeChanged&);
virtual void react(const system_fsm::BootComplete&) {} 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::StorageMounted&) {}
virtual void react(const system_fsm::BluetoothEvent&);
virtual void react(const PlayFile&) {} virtual void react(const PlayFile&) {}
virtual void react(const QueueUpdate&) {} virtual void react(const QueueUpdate&) {}
@ -67,6 +68,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
protected: protected:
auto playTrack(database::TrackId id) -> void; auto playTrack(database::TrackId id) -> void;
auto commitVolume() -> void;
static std::shared_ptr<system_fsm::ServiceLocator> sServices; static std::shared_ptr<system_fsm::ServiceLocator> sServices;
@ -88,6 +90,9 @@ namespace states {
class Uninitialised : public AudioState { class Uninitialised : public AudioState {
public: public:
void react(const system_fsm::BootComplete&) override; void react(const system_fsm::BootComplete&) override;
void react(const system_fsm::BluetoothEvent&) override {};
using AudioState::react; using AudioState::react;
}; };

@ -9,5 +9,5 @@ idf_component_register(
"spiffs.cpp" "spiffs.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs" REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs"
"bt" "tinyfsm") "bt" "tinyfsm" "util")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -103,21 +103,12 @@ auto Bluetooth::IsConnected() -> bool {
return bluetooth::BluetoothState::is_in_state<bluetooth::Connected>(); 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(); auto lock = bluetooth::BluetoothState::lock();
if (!bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) { if (!bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
return {}; return {};
} }
auto looking_for = bluetooth::BluetoothState::preferred_device(); return bluetooth::BluetoothState::preferred_device();
if (!looking_for) {
return {};
}
for (const auto& dev : bluetooth::BluetoothState::devices()) {
if (dev.address == looking_for->mac) {
return dev;
}
}
return {};
} }
auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> { auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> {

@ -35,7 +35,7 @@ class Bluetooth {
auto IsEnabled() -> bool; auto IsEnabled() -> bool;
auto IsConnected() -> bool; auto IsConnected() -> bool;
auto ConnectedDevice() -> std::optional<bluetooth::Device>; auto ConnectedDevice() -> std::optional<bluetooth::MacAndName>;
auto KnownDevices() -> std::vector<bluetooth::Device>; auto KnownDevices() -> std::vector<bluetooth::Device>;

@ -15,6 +15,7 @@
#include "bluetooth_types.hpp" #include "bluetooth_types.hpp"
#include "tasks.hpp" #include "tasks.hpp"
#include "lru_cache.hpp"
namespace drivers { namespace drivers {
@ -28,6 +29,11 @@ class NvsStorage {
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>; auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> bool; 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 { enum class Output : uint8_t {
kHeadphones = 0, kHeadphones = 0,
kBluetooth = 1, kBluetooth = 1,

@ -13,6 +13,8 @@
#include "bluetooth.hpp" #include "bluetooth.hpp"
#include "bluetooth_types.hpp" #include "bluetooth_types.hpp"
#include "cppbor.h"
#include "cppbor_parse.h"
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "nvs.h"
#include "nvs_flash.h" #include "nvs_flash.h"
@ -27,6 +29,7 @@ static constexpr uint8_t kSchemaVersion = 1;
static constexpr char kKeyVersion[] = "ver"; static constexpr char kKeyVersion[] = "ver";
static constexpr char kKeyBluetoothMac[] = "bt_mac"; static constexpr char kKeyBluetoothMac[] = "bt_mac";
static constexpr char kKeyBluetoothName[] = "bt_name"; static constexpr char kKeyBluetoothName[] = "bt_name";
static constexpr char kKeyBluetoothVolumes[] = "bt_vols";
static constexpr char kKeyOutput[] = "out"; static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright"; static constexpr char kKeyBrightness[] = "bright";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
@ -135,6 +138,101 @@ auto NvsStorage::PreferredBluetoothDevice(
return nvs_commit(handle_) == ESP_OK; 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 { auto NvsStorage::OutputMode() -> Output {
uint8_t out = 0; uint8_t out = 0;
nvs_get_u8(handle_, kKeyOutput, &out); nvs_get_u8(handle_, kKeyOutput, &out);

@ -47,6 +47,7 @@ namespace states {
static auto bt_event_cb(drivers::bluetooth::Event ev) -> void { static auto bt_event_cb(drivers::bluetooth::Event ev) -> void {
events::Ui().Dispatch(BluetoothEvent{.event = ev}); events::Ui().Dispatch(BluetoothEvent{.event = ev});
events::Audio().Dispatch(BluetoothEvent{.event = ev});
} }
static const TickType_t kInterruptCheckPeriod = pdMS_TO_TICKS(100); static const TickType_t kInterruptCheckPeriod = pdMS_TO_TICKS(100);

@ -326,14 +326,20 @@ void UiState::react(const audio::VolumeLimitChanged& ev) {
void UiState::react(const system_fsm::BluetoothEvent& ev) { void UiState::react(const system_fsm::BluetoothEvent& ev) {
auto bt = sServices->bluetooth(); auto bt = sServices->bluetooth();
auto dev = bt.ConnectedDevice();
switch (ev.event) { switch (ev.event) {
case drivers::bluetooth::Event::kKnownDevicesChanged: case drivers::bluetooth::Event::kKnownDevicesChanged:
sBluetoothDevices.Update(bt.KnownDevices()); sBluetoothDevices.Update(bt.KnownDevices());
break; break;
case drivers::bluetooth::Event::kConnectionStateChanged: case drivers::bluetooth::Event::kConnectionStateChanged:
sBluetoothConnected.Update(bt.IsConnected()); sBluetoothConnected.Update(bt.IsConnected());
if (bt.ConnectedDevice()) { if (dev) {
sBluetoothPairedDevice.Update(bt.ConnectedDevice().value()); sBluetoothPairedDevice.Update(drivers::bluetooth::Device{
.address = dev->mac,
.name = {dev->name.data(), dev->name.size()},
.class_of_device = 0,
.signal_strength = 0,
});
} else { } else {
sBluetoothPairedDevice.Update(std::monostate{}); sBluetoothPairedDevice.Update(std::monostate{});
} }
@ -457,9 +463,6 @@ void Lua::entry() {
auto bt = sServices->bluetooth(); auto bt = sServices->bluetooth();
sBluetoothEnabled.Update(bt.IsEnabled()); sBluetoothEnabled.Update(bt.IsEnabled());
sBluetoothConnected.Update(bt.IsConnected()); sBluetoothConnected.Update(bt.IsConnected());
if (bt.ConnectedDevice()) {
sBluetoothPairedDevice.Update(bt.ConnectedDevice().value());
}
sBluetoothDevices.Update(bt.KnownDevices()); sBluetoothDevices.Update(bt.KnownDevices());
sCurrentScreen.reset(); sCurrentScreen.reset();

@ -10,9 +10,11 @@
#include <bitset> #include <bitset>
#include <cstdint> #include <cstdint>
#include <list> #include <list>
#include <map>
#include <optional> #include <optional>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "memory_resource.hpp" #include "memory_resource.hpp"
namespace util { namespace util {
@ -64,9 +66,16 @@ class LruCache {
key_to_it_.clear(); 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: private:
std::pmr::list<std::pair<K, V>> entries_; 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 } // namespace util

Loading…
Cancel
Save