make bluetooth pairing ui functional

custom
jacqueline 2 years ago
parent cbd99b2134
commit b192975cb1
  1. 25
      src/audio/audio_fsm.cpp
  2. 6
      src/audio/bt_audio_output.cpp
  3. 2
      src/audio/include/audio_events.hpp
  4. 2
      src/audio/include/audio_fsm.hpp
  5. 4
      src/audio/include/bt_audio_output.hpp
  6. 34
      src/drivers/bluetooth.cpp
  7. 9
      src/drivers/include/bluetooth.hpp
  8. 5
      src/drivers/include/bluetooth_types.hpp
  9. 5
      src/drivers/nvs.cpp
  10. 4
      src/events/include/event_queue.hpp
  11. 19
      src/system_fsm/booting.cpp
  12. 2
      src/system_fsm/include/system_events.hpp
  13. 22
      src/ui/include/screen_settings.hpp
  14. 9
      src/ui/include/ui_fsm.hpp
  15. 152
      src/ui/screen_settings.cpp
  16. 13
      src/ui/ui_fsm.cpp

@ -26,6 +26,7 @@
#include "future_fetcher.hpp"
#include "i2s_audio_output.hpp"
#include "i2s_dac.hpp"
#include "nvs.hpp"
#include "service_locator.hpp"
#include "system_events.hpp"
#include "track.hpp"
@ -42,6 +43,7 @@ std::shared_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<Decoder> AudioState::sDecoder;
std::shared_ptr<SampleConverter> AudioState::sSampleConverter;
std::shared_ptr<I2SAudioOutput> AudioState::sI2SOutput;
std::shared_ptr<BluetoothAudioOutput> AudioState::sBtOutput;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
std::optional<database::TrackId> AudioState::sCurrentTrack;
@ -75,6 +77,20 @@ void AudioState::react(const ChangeMaxVolume& ev) {
sServices->nvs().AmpMaxVolume(ev.new_max);
}
void AudioState::react(const OutputModeChanged& ev) {
// TODO: handle SetInUse
ESP_LOGI(kTag, "output mode changed");
auto new_mode = sServices->nvs().OutputMode();
switch (new_mode.get()) {
case drivers::NvsStorage::Output::kBluetooth:
sOutput = sBtOutput;
break;
case drivers::NvsStorage::Output::kHeadphones:
sOutput = sI2SOutput;
break;
}
}
namespace states {
void Uninitialised::react(const system_fsm::BootComplete& ev) {
@ -90,13 +106,18 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
sFileSource.reset(new FatfsAudioInput(sServices->tag_parser()));
sI2SOutput.reset(new I2SAudioOutput(sServices->gpios(),
std::unique_ptr<drivers::I2SDac>{*dac}));
sBtOutput.reset(new BluetoothAudioOutput(sServices->bluetooth()));
auto& nvs = sServices->nvs();
sI2SOutput->SetMaxVolume(nvs.AmpMaxVolume().get());
sI2SOutput->SetVolumeDb(nvs.AmpCurrentVolume().get());
sOutput = sI2SOutput;
// sOutput.reset(new BluetoothAudioOutput(bluetooth));
if (sServices->nvs().OutputMode().get() ==
drivers::NvsStorage::Output::kHeadphones) {
sOutput = sI2SOutput;
} else {
sOutput = sBtOutput;
}
sSampleConverter.reset(new SampleConverter());
sSampleConverter->SetOutput(sOutput);

@ -29,16 +29,16 @@ namespace audio {
static constexpr size_t kDrainBufferSize = 48 * 1024;
BluetoothAudioOutput::BluetoothAudioOutput(drivers::Bluetooth* bt)
BluetoothAudioOutput::BluetoothAudioOutput(drivers::Bluetooth& bt)
: IAudioOutput(kDrainBufferSize, MALLOC_CAP_SPIRAM), bluetooth_(bt) {}
BluetoothAudioOutput::~BluetoothAudioOutput() {}
auto BluetoothAudioOutput::SetInUse(bool in_use) -> void {
if (in_use) {
bluetooth_->SetSource(stream());
bluetooth_.SetSource(stream());
} else {
bluetooth_->SetSource(nullptr);
bluetooth_.SetSource(nullptr);
}
}

@ -41,6 +41,8 @@ struct ChangeMaxVolume : tinyfsm::Event {
struct TogglePlayPause : tinyfsm::Event {};
struct OutputModeChanged : tinyfsm::Event {};
namespace internal {
struct InputFileOpened : tinyfsm::Event {};

@ -45,6 +45,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const system_fsm::KeyDownChanged&);
void react(const system_fsm::HasPhonesChanged&);
void react(const ChangeMaxVolume&);
void react(const OutputModeChanged&);
virtual void react(const system_fsm::BootComplete&) {}
@ -65,6 +66,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::unique_ptr<Decoder> sDecoder;
static std::shared_ptr<SampleConverter> sSampleConverter;
static std::shared_ptr<I2SAudioOutput> sI2SOutput;
static std::shared_ptr<BluetoothAudioOutput> sBtOutput;
static std::shared_ptr<IAudioOutput> sOutput;
static std::optional<database::TrackId> sCurrentTrack;

@ -21,7 +21,7 @@ namespace audio {
class BluetoothAudioOutput : public IAudioOutput {
public:
BluetoothAudioOutput(drivers::Bluetooth* bt);
BluetoothAudioOutput(drivers::Bluetooth& bt);
~BluetoothAudioOutput();
auto SetInUse(bool) -> void override;
@ -39,7 +39,7 @@ class BluetoothAudioOutput : public IAudioOutput {
BluetoothAudioOutput& operator=(const BluetoothAudioOutput&) = delete;
private:
drivers::Bluetooth* bluetooth_;
drivers::Bluetooth& bluetooth_;
};
} // namespace audio

@ -8,6 +8,7 @@
#include <ostream>
#include <sstream>
#include "bluetooth_types.hpp"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_bt.h"
@ -56,7 +57,7 @@ auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t {
return xStreamBufferReceive(stream, buf, buf_size, 0);
}
Bluetooth::Bluetooth(NvsStorage* storage) {
Bluetooth::Bluetooth(NvsStorage& storage) {
bluetooth::BluetoothState::Init(storage);
}
@ -72,6 +73,10 @@ auto Bluetooth::Disable() -> void {
bluetooth::events::Disable{});
}
auto Bluetooth::IsEnabled() -> bool {
return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
}
auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> {
std::vector<bluetooth::Device> out = bluetooth::BluetoothState::devices();
std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) -> bool {
@ -98,6 +103,11 @@ auto Bluetooth::SetSource(StreamBufferHandle_t src) -> void {
bluetooth::events::SourceChanged{});
}
auto Bluetooth::SetEventHandler(std::function<void(bluetooth::Event)> cb)
-> void {
bluetooth::BluetoothState::event_handler(cb);
}
auto DeviceName() -> std::string {
uint8_t mac[8]{0};
esp_efuse_mac_get_default(mac);
@ -111,16 +121,17 @@ namespace bluetooth {
NvsStorage* BluetoothState::sStorage_;
std::mutex BluetoothState::sDevicesMutex_;
std::map<mac_addr_t, Device> BluetoothState::sDevices_;
std::optional<mac_addr_t> BluetoothState::sPreferredDevice_;
std::mutex BluetoothState::sDevicesMutex_{};
std::map<mac_addr_t, Device> BluetoothState::sDevices_{};
std::optional<mac_addr_t> BluetoothState::sPreferredDevice_{};
mac_addr_t BluetoothState::sCurrentDevice_;
std::atomic<StreamBufferHandle_t> BluetoothState::sSource_;
std::function<void(Event)> BluetoothState::sEventHandler_;
auto BluetoothState::Init(NvsStorage* storage) -> void {
sStorage_ = storage;
sPreferredDevice_ = storage->PreferredBluetoothDevice().get();
auto BluetoothState::Init(NvsStorage& storage) -> void {
sStorage_ = &storage;
sPreferredDevice_ = storage.PreferredBluetoothDevice().get();
tinyfsm::FsmList<bluetooth::BluetoothState>::start();
}
@ -153,6 +164,11 @@ auto BluetoothState::source(StreamBufferHandle_t src) -> void {
sSource_.store(src);
}
auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void {
std::lock_guard lock{sDevicesMutex_};
sEventHandler_ = cb;
}
static bool sIsFirstEntry = true;
void Disabled::entry() {
@ -303,6 +319,10 @@ auto Scanning::OnDeviceDiscovered(esp_bt_gap_cb_param_t* param) -> void {
sCurrentDevice_ = device.address;
is_preferred = true;
}
if (sEventHandler_) {
std::invoke(sEventHandler_, Event::kKnownDevicesChanged);
}
}
if (is_preferred) {

@ -26,15 +26,17 @@ namespace drivers {
*/
class Bluetooth {
public:
Bluetooth(NvsStorage* storage);
Bluetooth(NvsStorage& storage);
auto Enable() -> bool;
auto Disable() -> void;
auto IsEnabled() -> bool;
auto KnownDevices() -> std::vector<bluetooth::Device>;
auto SetPreferredDevice(const bluetooth::mac_addr_t& mac) -> void;
auto SetSource(StreamBufferHandle_t) -> void;
auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void;
};
namespace bluetooth {
@ -64,7 +66,7 @@ struct Avrc : public tinyfsm::Event {
class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
public:
static auto Init(NvsStorage* storage) -> void;
static auto Init(NvsStorage& storage) -> void;
static auto devices() -> std::vector<Device>;
static auto preferred_device() -> std::optional<mac_addr_t>;
@ -73,6 +75,8 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
static auto source() -> StreamBufferHandle_t;
static auto source(StreamBufferHandle_t) -> void;
static auto event_handler(std::function<void(Event)>) -> void;
virtual ~BluetoothState(){};
virtual void entry() {}
@ -96,6 +100,7 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
static mac_addr_t sCurrentDevice_;
static std::atomic<StreamBufferHandle_t> sSource_;
static std::function<void(Event)> sEventHandler_;
};
class Disabled : public BluetoothState {

@ -16,5 +16,10 @@ struct Device {
int8_t signal_strength;
};
enum class Event {
kKnownDevicesChanged,
kConnectionStateChanged,
};
} // namespace bluetooth
} // namespace drivers

@ -128,7 +128,7 @@ auto NvsStorage::OutputMode() -> std::future<Output> {
nvs_get_u8(handle_, kKeyOutput, &out);
switch (out) {
case static_cast<uint8_t>(Output::kBluetooth):
return Output::kHeadphones;
return Output::kBluetooth;
case static_cast<uint8_t>(Output::kHeadphones):
default:
return Output::kHeadphones;
@ -138,7 +138,8 @@ auto NvsStorage::OutputMode() -> std::future<Output> {
auto NvsStorage::OutputMode(Output out) -> std::future<bool> {
return writer_->Dispatch<bool>([&]() {
nvs_set_u8(handle_, kKeyOutput, static_cast<uint8_t>(out));
uint8_t as_int = static_cast<uint8_t>(out);
nvs_set_u8(handle_, kKeyOutput, as_int);
return nvs_commit(handle_) == ESP_OK;
});
}

@ -80,6 +80,10 @@ class Dispatcher {
queue_->Add(dispatch_fn);
}
auto RunOnTask(const std::function<void(void)>& fn) -> void {
queue_->Add(fn);
}
Dispatcher(Dispatcher const&) = delete;
void operator=(Dispatcher const&) = delete;

@ -11,6 +11,7 @@
#include "audio_fsm.hpp"
#include "battery.hpp"
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
#include "core/lv_obj.h"
#include "display_init.hpp"
#include "esp_err.h"
@ -41,6 +42,12 @@ namespace states {
static const char kTag[] = "BOOT";
static auto bt_event_cb(drivers::bluetooth::Event ev) -> void {
if (ev == drivers::bluetooth::Event::kKnownDevicesChanged) {
events::Ui().Dispatch(BluetoothDevicesChanged{});
}
}
auto Booting::entry() -> void {
ESP_LOGI(kTag, "beginning tangara boot");
sServices.reset(new ServiceLocator());
@ -71,9 +78,15 @@ auto Booting::entry() -> void {
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();
ESP_LOGI(kTag, "init bluetooth");
sServices->bluetooth(std::make_unique<drivers::Bluetooth>(sServices->nvs()));
sServices->bluetooth().SetEventHandler(bt_event_cb);
if (sServices->nvs().OutputMode().get() ==
drivers::NvsStorage::Output::kBluetooth) {
ESP_LOGI(kTag, "enabling bluetooth");
sServices->bluetooth().Enable();
}
BootComplete ev{.services = sServices};
events::Audio().Dispatch(ev);

@ -56,6 +56,8 @@ struct HasPhonesChanged : tinyfsm::Event {
struct ChargingStatusChanged : tinyfsm::Event {};
struct BatteryStateChanged : tinyfsm::Event {};
struct BluetoothDevicesChanged : tinyfsm::Event {};
namespace internal {
struct GpioInterrupt : tinyfsm::Event {};

@ -8,9 +8,12 @@
#include <stdint.h>
#include <cstdint>
#include <list>
#include <memory>
#include <vector>
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
#include "display.hpp"
#include "index.hpp"
#include "lvgl.h"
@ -28,7 +31,24 @@ class Settings : public MenuScreen {
class Bluetooth : public MenuScreen {
public:
Bluetooth();
Bluetooth(drivers::Bluetooth& bt, drivers::NvsStorage& nvs);
auto ChangeEnabledState(bool enabled) -> void;
auto RefreshDevicesList() -> void;
auto OnDeviceSelected(size_t index) -> void;
private:
auto RemoveAllDevices() -> void;
auto AddPreferredDevice(const drivers::bluetooth::Device&) -> void;
auto AddDevice(const drivers::bluetooth::Device&) -> void;
drivers::Bluetooth& bt_;
drivers::NvsStorage& nvs_;
lv_obj_t* devices_list_;
lv_obj_t* preferred_device_;
std::list<drivers::bluetooth::mac_addr_t> macs_in_list_;
};
class Headphones : public MenuScreen {

@ -17,6 +17,7 @@
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "screen_playing.hpp"
#include "screen_settings.hpp"
#include "service_locator.hpp"
#include "tinyfsm.hpp"
@ -72,6 +73,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
virtual void react(const system_fsm::DisplayReady&) {}
virtual void react(const system_fsm::BootComplete&) {}
virtual void react(const system_fsm::StorageMounted&) {}
virtual void react(const system_fsm::BluetoothDevicesChanged&) {}
protected:
void PushScreen(std::shared_ptr<Screen>);
@ -112,6 +114,7 @@ class Onboarding : public UiState {
};
class Browse : public UiState {
public:
void entry() override;
void react(const internal::RecordSelected&) override;
@ -122,10 +125,16 @@ class Browse : public UiState {
void react(const internal::ShowSettingsPage&) override;
void react(const system_fsm::StorageMounted&) override;
void react(const system_fsm::BluetoothDevicesChanged&) override;
using UiState::react;
private:
std::weak_ptr<screens::Bluetooth> bluetooth_screen_;
};
class Playing : public UiState {
public:
void entry() override;
void exit() override;

@ -9,8 +9,11 @@
#include <string>
#include "audio_events.hpp"
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
#include "core/lv_event.h"
#include "core/lv_obj.h"
#include "core/lv_obj_tree.h"
#include "display.hpp"
#include "esp_log.h"
@ -100,7 +103,18 @@ static auto label_pair(lv_obj_t* parent,
return right_label;
}
Bluetooth::Bluetooth() : MenuScreen("Bluetooth") {
static auto toggle_bt_cb(lv_event_t* ev) {
Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data);
instance->ChangeEnabledState(lv_obj_has_state(ev->target, LV_STATE_CHECKED));
}
static auto select_device_cb(lv_event_t* ev) {
Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data);
instance->OnDeviceSelected(lv_obj_get_index(ev->target));
}
Bluetooth::Bluetooth(drivers::Bluetooth& bt, drivers::NvsStorage& nvs)
: MenuScreen("Bluetooth"), bt_(bt), nvs_(nvs) {
lv_obj_t* toggle_container = settings_container(content_);
lv_obj_t* toggle_label = lv_label_create(toggle_container);
lv_label_set_text(toggle_label, "Enable");
@ -108,20 +122,134 @@ Bluetooth::Bluetooth() : MenuScreen("Bluetooth") {
lv_obj_t* toggle = lv_switch_create(toggle_container);
lv_group_add_obj(group_, toggle);
if (bt.IsEnabled()) {
lv_obj_add_state(toggle, LV_STATE_CHECKED);
}
lv_obj_add_event_cb(toggle, toggle_bt_cb, LV_EVENT_VALUE_CHANGED, this);
lv_obj_t* devices_label = lv_label_create(content_);
lv_label_set_text(devices_label, "Devices");
lv_obj_t* devices_list = lv_list_create(content_);
lv_list_add_text(devices_list, "My Headphones");
lv_group_add_obj(group_,
lv_list_add_btn(devices_list, NULL, "Something else"));
lv_group_add_obj(group_, lv_list_add_btn(devices_list, NULL, "A car??"));
lv_group_add_obj(group_,
lv_list_add_btn(devices_list, NULL, "OLED TV ANDROID"));
lv_group_add_obj(
group_, lv_list_add_btn(devices_list, NULL, "there could be another"));
lv_group_add_obj(group_, lv_list_add_btn(devices_list, NULL,
"this one has a really long name"));
devices_list_ = lv_list_create(content_);
RefreshDevicesList();
}
auto Bluetooth::ChangeEnabledState(bool enabled) -> void {
if (enabled) {
events::System().RunOnTask([&]() { bt_.Enable(); });
nvs_.OutputMode(drivers::NvsStorage::Output::kBluetooth).get();
} else {
events::System().RunOnTask([&]() { bt_.Disable(); });
nvs_.OutputMode(drivers::NvsStorage::Output::kHeadphones).get();
}
events::Audio().Dispatch(audio::OutputModeChanged{});
RefreshDevicesList();
}
auto Bluetooth::RefreshDevicesList() -> void {
if (!bt_.IsEnabled()) {
// Bluetooth is disabled, so we just clear the list.
RemoveAllDevices();
return;
}
auto devices = bt_.KnownDevices();
std::optional<drivers::bluetooth::mac_addr_t> preferred_device =
nvs_.PreferredBluetoothDevice().get();
// If the user's current selection is within the devices list, then we need
// to be careful not to rearrange the list items underneath them.
lv_obj_t* current_selection = lv_group_get_focused(group_);
bool is_in_list = current_selection != NULL &&
lv_obj_get_parent(current_selection) == devices_list_;
if (!is_in_list) {
// The user isn't in the list! We can blow everything away and recreate it
// without issues.
RemoveAllDevices();
// First look to see if the user's preferred device is in the list. If it
// is, we hoist it up to the top of the list.
if (preferred_device) {
for (size_t i = 0; i < devices.size(); i++) {
if (devices[i].address == *preferred_device) {
AddPreferredDevice(devices[i]);
devices.erase(devices.begin() + i);
break;
}
}
}
// The rest of the list is already sorted by signal strength.
for (const auto& device : devices) {
AddDevice(device);
}
} else {
// The user's selection is within the device list. We need to work out
// which devices are new, then add them to the end.
for (const auto& mac : macs_in_list_) {
auto pos = std::find_if(
devices.begin(), devices.end(),
[&mac](const auto& device) { return device.address == mac; });
if (pos != devices.end()) {
devices.erase(pos);
}
}
// The remaining list is now just the new devices.
for (const auto& device : devices) {
if (preferred_device && device.address == *preferred_device) {
AddPreferredDevice(device);
} else {
AddDevice(device);
}
}
}
}
auto Bluetooth::RemoveAllDevices() -> void {
while (lv_obj_get_child_cnt(devices_list_) > 0) {
lv_obj_del(lv_obj_get_child(devices_list_, 0));
}
macs_in_list_.clear();
preferred_device_ = nullptr;
}
auto Bluetooth::AddPreferredDevice(const drivers::bluetooth::Device& dev)
-> void {
preferred_device_ = lv_list_add_btn(devices_list_, NULL, dev.name.c_str());
macs_in_list_.push_back(dev.address);
}
auto Bluetooth::AddDevice(const drivers::bluetooth::Device& dev) -> void {
lv_obj_t* item = lv_list_add_btn(devices_list_, NULL, dev.name.c_str());
lv_group_add_obj(group_, item);
lv_obj_add_event_cb(item, select_device_cb, LV_EVENT_CLICKED, this);
macs_in_list_.push_back(dev.address);
}
auto Bluetooth::OnDeviceSelected(size_t index) -> void {
// Tell the bluetooth driver that our preference changed.
auto it = macs_in_list_.begin();
std::advance(it, index);
events::System().RunOnTask([=]() { bt_.SetPreferredDevice(*it); });
// Update which devices are selectable.
if (preferred_device_) {
lv_group_add_obj(group_, preferred_device_);
// Bubble the newly added object up to its visible position in the list.
size_t pos = lv_obj_get_index(preferred_device_);
while (pos > 0) {
lv_group_swap_obj(preferred_device_,
lv_obj_get_child(devices_list_, pos - 1));
pos--;
}
}
preferred_device_ = lv_obj_get_child(devices_list_, index);
lv_group_remove_obj(preferred_device_);
}
static void change_vol_limit_cb(lv_event_t* ev) {

@ -241,12 +241,16 @@ void Browse::react(const internal::ShowNowPlaying& ev) {
void Browse::react(const internal::ShowSettingsPage& ev) {
std::shared_ptr<Screen> screen;
std::shared_ptr<screens::Bluetooth> bt_screen;
switch (ev.page) {
case internal::ShowSettingsPage::Page::kRoot:
screen.reset(new screens::Settings());
break;
case internal::ShowSettingsPage::Page::kBluetooth:
screen.reset(new screens::Bluetooth());
bt_screen = std::make_shared<screens::Bluetooth>(sServices->bluetooth(),
sServices->nvs());
screen = bt_screen;
bluetooth_screen_ = bt_screen;
break;
case internal::ShowSettingsPage::Page::kHeadphones:
screen.reset(new screens::Headphones(sServices->nvs()));
@ -315,6 +319,13 @@ void Browse::react(const internal::BackPressed& ev) {
PopScreen();
}
void Browse::react(const system_fsm::BluetoothDevicesChanged&) {
auto bt = bluetooth_screen_.lock();
if (bt) {
bt->RefreshDevicesList();
}
}
static std::shared_ptr<screens::Playing> sPlayingScreen;
void Playing::entry() {

Loading…
Cancel
Save