summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/drivers/bluetooth.cpp338
-rw-r--r--src/drivers/include/drivers/bluetooth.hpp116
-rw-r--r--src/drivers/include/drivers/bluetooth_types.hpp7
-rw-r--r--src/drivers/include/drivers/nvs.hpp7
-rw-r--r--src/drivers/nvs.cpp83
-rw-r--r--src/tangara/app_console/app_console.cpp17
-rw-r--r--src/tangara/audio/audio_fsm.cpp11
-rw-r--r--src/tangara/audio/bt_audio_output.cpp13
-rw-r--r--src/tangara/lua/property.cpp30
-rw-r--r--src/tangara/lua/property.hpp4
-rw-r--r--src/tangara/system_fsm/booting.cpp3
-rw-r--r--src/tangara/ui/ui_fsm.cpp114
-rw-r--r--src/tangara/ui/ui_fsm.hpp5
13 files changed, 486 insertions, 262 deletions
diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp
index 412cba1f..2edf5ad9 100644
--- a/src/drivers/bluetooth.cpp
+++ b/src/drivers/bluetooth.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <atomic>
+#include <iterator>
#include <mutex>
#include <ostream>
#include <sstream>
@@ -113,92 +114,111 @@ IRAM_ATTR auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t {
return buf_size;
}
-Bluetooth::Bluetooth(NvsStorage& storage, tasks::WorkerPool& bg_worker) {
+Bluetooth::Bluetooth(NvsStorage& storage,
+ tasks::WorkerPool& bg_worker,
+ EventHandler cb)
+ : nvs_(storage) {
sBgWorker = &bg_worker;
- bluetooth::BluetoothState::Init(storage);
+ bluetooth::BluetoothState::Init(storage, cb);
}
-auto Bluetooth::Enable() -> bool {
- auto lock = bluetooth::BluetoothState::lock();
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::Enable{});
+auto Bluetooth::enable(bool en) -> void {
+ if (en) {
+ auto lock = bluetooth::BluetoothState::lock();
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Enable{});
+ } else {
+ // FIXME: the BT tasks unfortunately call back into us while holding an
+ // internal lock, which then deadlocks with our fsm lock.
+ // auto lock = bluetooth::BluetoothState::lock();
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Disable{});
+ }
+}
+auto Bluetooth::enabled() -> bool {
+ auto lock = bluetooth::BluetoothState::lock();
return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
}
-auto Bluetooth::Disable() -> void {
- // FIXME: the BT tasks unfortunately call back into us while holding an
- // internal lock, which then deadlocks with our fsm lock.
- // auto lock = bluetooth::BluetoothState::lock();
+auto Bluetooth::source(PcmBuffer* src) -> void {
+ if (src == sStream) {
+ return;
+ }
+ auto lock = bluetooth::BluetoothState::lock();
+ sStream = src;
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::Disable{});
+ bluetooth::events::SourceChanged{});
}
-auto Bluetooth::IsEnabled() -> bool {
- auto lock = bluetooth::BluetoothState::lock();
- return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
+auto Bluetooth::softVolume(float f) -> void {
+ sVolumeFactor = f;
}
-auto Bluetooth::IsConnected() -> bool {
+auto Bluetooth::connectionState() -> ConnectionState {
auto lock = bluetooth::BluetoothState::lock();
- return bluetooth::BluetoothState::is_in_state<bluetooth::Connected>();
+ if (bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
+ return ConnectionState::kConnected;
+ } else if (bluetooth::BluetoothState::is_in_state<bluetooth::Connecting>()) {
+ return ConnectionState::kConnecting;
+ }
+ return ConnectionState::kDisconnected;
}
-auto Bluetooth::ConnectedDevice() -> std::optional<bluetooth::MacAndName> {
+auto Bluetooth::pairedDevice() -> std::optional<bluetooth::MacAndName> {
auto lock = bluetooth::BluetoothState::lock();
- if (!bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
- return {};
- }
- return bluetooth::BluetoothState::preferred_device();
+ return bluetooth::BluetoothState::pairedDevice();
}
-auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> {
+auto Bluetooth::pairedDevice(std::optional<bluetooth::MacAndName> dev) -> void {
auto lock = bluetooth::BluetoothState::lock();
- std::vector<bluetooth::Device> out = bluetooth::BluetoothState::devices();
- std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) -> bool {
- return a.signal_strength < b.signal_strength;
- });
- return out;
+ bluetooth::BluetoothState::pairedDevice(dev);
}
-auto Bluetooth::SetPreferredDevice(std::optional<bluetooth::MacAndName> dev)
- -> void {
- auto lock = bluetooth::BluetoothState::lock();
- auto cur = bluetooth::BluetoothState::preferred_device();
- if (dev && cur && dev->mac == cur->mac) {
- return;
- }
- ESP_LOGI(kTag, "preferred is '%s' (%u%u%u%u%u%u)", dev->name.c_str(),
- dev->mac[0], dev->mac[1], dev->mac[2], dev->mac[3], dev->mac[4],
- dev->mac[5]);
- bluetooth::BluetoothState::preferred_device(dev);
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::PreferredDeviceChanged{});
+auto Bluetooth::knownDevices() -> std::vector<bluetooth::MacAndName> {
+ return nvs_.BluetoothNames();
}
-auto Bluetooth::PreferredDevice() -> std::optional<bluetooth::MacAndName> {
- auto lock = bluetooth::BluetoothState::lock();
- return bluetooth::BluetoothState::preferred_device();
+auto Bluetooth::forgetKnownDevice(const bluetooth::mac_addr_t& mac) -> void {
+ nvs_.BluetoothName(mac, {});
}
-auto Bluetooth::SetSource(PcmBuffer* src) -> void {
+auto Bluetooth::discoveryEnabled(bool en) -> void {
auto lock = bluetooth::BluetoothState::lock();
- if (src == bluetooth::BluetoothState::source()) {
- return;
- }
- bluetooth::BluetoothState::source(src);
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::SourceChanged{});
+ bluetooth::BluetoothState::discovery(en);
}
-auto Bluetooth::SetVolumeFactor(float f) -> void {
- sVolumeFactor = f;
+auto Bluetooth::discoveryEnabled() -> bool {
+ auto lock = bluetooth::BluetoothState::lock();
+ return bluetooth::BluetoothState::discovery();
}
-auto Bluetooth::SetEventHandler(std::function<void(bluetooth::Event)> cb)
- -> void {
- auto lock = bluetooth::BluetoothState::lock();
- bluetooth::BluetoothState::event_handler(cb);
+auto Bluetooth::discoveredDevices() -> std::vector<bluetooth::MacAndName> {
+ std::vector<bluetooth::Device> discovered;
+ {
+ auto lock = bluetooth::BluetoothState::lock();
+ discovered = bluetooth::BluetoothState::discoveredDevices();
+ }
+
+ // Show devices with stronger signals first, since they're more likely to be
+ // physically close (and therefore more likely to be what the user wants).
+ std::sort(discovered.begin(), discovered.end(),
+ [](const auto& a, const auto& b) -> bool {
+ return a.signal_strength < b.signal_strength;
+ });
+
+ // Convert to the right format.
+ std::vector<bluetooth::MacAndName> out;
+ out.reserve(discovered.size());
+ std::transform(discovered.begin(), discovered.end(), std::back_inserter(out),
+ [&](const bluetooth::Device& dev) {
+ return bluetooth::MacAndName{
+ .mac = dev.address,
+ .name = {dev.name.data(), dev.name.size()},
+ };
+ });
+
+ return out;
}
static auto DeviceName() -> std::pmr::string {
@@ -251,6 +271,10 @@ auto Scanner::StopScanningNow() -> void {
}
}
+auto Scanner::enabled() -> bool {
+ return enabled_;
+}
+
auto Scanner::HandleGapEvent(const events::internal::Gap& ev) -> void {
switch (ev.type) {
case ESP_BT_GAP_DISC_RES_EVT:
@@ -343,17 +367,18 @@ NvsStorage* BluetoothState::sStorage_;
Scanner* BluetoothState::sScanner_;
std::mutex BluetoothState::sFsmMutex{};
-std::map<mac_addr_t, Device> BluetoothState::sDevices_{};
-std::optional<MacAndName> BluetoothState::sPreferredDevice_{};
-std::optional<MacAndName> BluetoothState::sConnectingDevice_{};
+std::map<mac_addr_t, Device> BluetoothState::sDiscoveredDevices_{};
+std::optional<MacAndName> BluetoothState::sPairedWith_{};
+std::optional<MacAndName> BluetoothState::sConnectingTo_{};
int BluetoothState::sConnectAttemptsRemaining_{0};
-std::atomic<PcmBuffer*> BluetoothState::sSource_;
std::function<void(Event)> BluetoothState::sEventHandler_;
-auto BluetoothState::Init(NvsStorage& storage) -> void {
+auto BluetoothState::Init(NvsStorage& storage, Bluetooth::EventHandler cb)
+ -> void {
sStorage_ = &storage;
- sPreferredDevice_ = storage.PreferredBluetoothDevice();
+ sEventHandler_ = cb;
+ sPairedWith_ = storage.PreferredBluetoothDevice();
tinyfsm::FsmList<bluetooth::BluetoothState>::start();
}
@@ -361,68 +386,85 @@ auto BluetoothState::lock() -> std::lock_guard<std::mutex> {
return std::lock_guard<std::mutex>{sFsmMutex};
}
-auto BluetoothState::devices() -> std::vector<Device> {
- std::vector<Device> out;
- for (const auto& device : sDevices_) {
- out.push_back(device.second);
- }
- return out;
+auto BluetoothState::pairedDevice() -> std::optional<MacAndName> {
+ return sPairedWith_;
}
-auto BluetoothState::preferred_device() -> std::optional<MacAndName> {
- return sPreferredDevice_;
-}
+auto BluetoothState::pairedDevice(std::optional<MacAndName> dev) -> void {
+ auto cur = sPairedWith_;
+ if (dev && cur && dev->mac == cur->mac) {
+ return;
+ }
+ if (dev) {
+ ESP_LOGI(kTag, "pairing with '%s' (%u%u%u%u%u%u)", dev->name.c_str(),
+ dev->mac[0], dev->mac[1], dev->mac[2], dev->mac[3], dev->mac[4],
+ dev->mac[5]);
+ }
+ sPairedWith_ = dev;
+ std::invoke(sEventHandler_, SimpleEvent::kDeviceDiscovered);
-auto BluetoothState::preferred_device(std::optional<MacAndName> addr) -> void {
- sPreferredDevice_ = addr;
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::PairedDeviceChanged{});
}
-auto BluetoothState::source() -> PcmBuffer* {
- return sSource_.load();
+auto BluetoothState::discovery() -> bool {
+ return sScanner_->enabled();
}
-auto BluetoothState::source(PcmBuffer* src) -> void {
- sSource_.store(src);
+auto BluetoothState::discovery(bool en) -> void {
+ if (en) {
+ if (!sScanner_->enabled()) {
+ sDiscoveredDevices_.clear();
+ }
+ sScanner_->ScanContinuously();
+ } else {
+ sScanner_->StopScanning();
+ }
}
-auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void {
- sEventHandler_ = cb;
+auto BluetoothState::discoveredDevices() -> std::vector<Device> {
+ std::vector<Device> out;
+ for (const auto& device : sDiscoveredDevices_) {
+ out.push_back(device.second);
+ }
+ return out;
}
auto BluetoothState::react(const events::DeviceDiscovered& ev) -> void {
- bool is_preferred = false;
- bool already_known = sDevices_.contains(ev.device.address);
- sDevices_[ev.device.address] = ev.device;
+ bool is_paired = false;
+ bool already_known = sDiscoveredDevices_.contains(ev.device.address);
+ sDiscoveredDevices_[ev.device.address] = ev.device;
- if (sPreferredDevice_ && ev.device.address == sPreferredDevice_->mac) {
- is_preferred = true;
+ if (sPairedWith_ && ev.device.address == sPairedWith_->mac) {
+ is_paired = true;
}
- if (sEventHandler_ && !already_known) {
- std::invoke(sEventHandler_, SimpleEvent::kKnownDevicesChanged);
+ if (!already_known) {
+ std::invoke(sEventHandler_, SimpleEvent::kDeviceDiscovered);
}
- if (is_preferred && sPreferredDevice_) {
- connect(*sPreferredDevice_);
+ if (is_paired && sPairedWith_) {
+ connect(*sPairedWith_);
}
}
auto BluetoothState::connect(const MacAndName& dev) -> bool {
- if (sConnectingDevice_ && sConnectingDevice_->mac == dev.mac) {
+ if (sConnectingTo_ && sConnectingTo_->mac == dev.mac) {
sConnectAttemptsRemaining_--;
} else {
sConnectAttemptsRemaining_ = 3;
}
if (sConnectAttemptsRemaining_ == 0) {
+ sConnectingTo_ = {};
return false;
}
- sConnectingDevice_ = dev;
+ sConnectingTo_ = dev;
ESP_LOGI(kTag, "connecting to '%s' (%u%u%u%u%u%u)", dev.name.c_str(),
dev.mac[0], dev.mac[1], dev.mac[2], dev.mac[3], dev.mac[4],
dev.mac[5]);
- if (esp_a2d_source_connect(sConnectingDevice_->mac.data()) != ESP_OK) {
+ if (esp_a2d_source_connect(sConnectingTo_->mac.data()) != ESP_OK) {
ESP_LOGI(kTag, "Connecting failed...");
if (sConnectAttemptsRemaining_ > 1) {
ESP_LOGI(kTag, "Will retry.");
@@ -508,11 +550,13 @@ void Disabled::react(const events::Enable&) {
// AVRCP Target
err = esp_avrc_tg_init();
if (err != ESP_OK) {
- ESP_LOGE(kTag, "Error during target init: %s %d", esp_err_to_name(err), err);
+ ESP_LOGE(kTag, "Error during target init: %s %d", esp_err_to_name(err),
+ err);
}
err = esp_avrc_tg_register_callback(avrcp_tg_cb);
if (err != ESP_OK) {
- ESP_LOGE(kTag, "Error registering AVRC tg callback: %s %d", esp_err_to_name(err), err);
+ ESP_LOGE(kTag, "Error registering AVRC tg callback: %s %d",
+ esp_err_to_name(err), err);
}
// Set the supported passthrough commands on the tg
@@ -522,19 +566,20 @@ void Disabled::react(const events::Enable&) {
do {
// Sleep for a bit
vTaskDelay(pdMS_TO_TICKS(10));
- err = esp_avrc_tg_get_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_ALLOWED_CMD, &psth);
+ err = esp_avrc_tg_get_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_ALLOWED_CMD,
+ &psth);
} while (err != ESP_OK);
- err = esp_avrc_tg_set_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD, &psth);
+ err = esp_avrc_tg_set_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD,
+ &psth);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err);
}
esp_avrc_rn_evt_cap_mask_t evt_set = {0};
esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set,
- ESP_AVRC_RN_VOLUME_CHANGE);
+ ESP_AVRC_RN_VOLUME_CHANGE);
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK);
-
// Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC
// encoder only supports 2 channels of interleaved 16 bit samples, at
// 44.1kHz, so there is no additional configuration to be done for the
@@ -547,37 +592,27 @@ void Disabled::react(const events::Enable&) {
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
ESP_LOGI(kTag, "bt enabled");
- if (sPreferredDevice_) {
- ESP_LOGI(kTag, "connecting to preferred device '%s'",
- sPreferredDevice_->name.c_str());
- connect(*sPreferredDevice_);
+ if (sPairedWith_) {
+ ESP_LOGI(kTag, "connecting to paired device '%s'",
+ sPairedWith_->name.c_str());
+ connect(*sPairedWith_);
} else {
- ESP_LOGI(kTag, "scanning for devices");
transit<Idle>();
}
}
void Idle::entry() {
ESP_LOGI(kTag, "bt is idle");
- sScanner_->ScanContinuously();
}
-void Idle::exit() {
- sScanner_->StopScanning();
-}
+void Idle::exit() {}
void Idle::react(const events::Disable& ev) {
transit<Disabled>();
}
-void Idle::react(const events::PreferredDeviceChanged& ev) {
- bool is_discovered = false;
- if (sPreferredDevice_ && sDevices_.contains(sPreferredDevice_->mac)) {
- is_discovered = true;
- }
- if (is_discovered) {
- connect(*sPreferredDevice_);
- }
+void Idle::react(const events::PairedDeviceChanged& ev) {
+ connect(*sPairedWith_);
}
void Idle::react(events::internal::Gap ev) {
@@ -599,7 +634,6 @@ void Connecting::entry() {
timeoutCallback);
xTimerStart(sTimeoutTimer, portMAX_DELAY);
- sScanner_->StopScanning();
if (sEventHandler_) {
std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
}
@@ -615,19 +649,24 @@ void Connecting::exit() {
void Connecting::react(const events::ConnectTimedOut& ev) {
ESP_LOGI(kTag, "timed out awaiting connection");
- esp_a2d_source_disconnect(sConnectingDevice_->mac.data());
- if (!connect(*sConnectingDevice_)) {
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
+ if (!connect(*sConnectingTo_)) {
transit<Idle>();
}
}
void Connecting::react(const events::Disable& ev) {
- // TODO: disconnect gracefully
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
transit<Disabled>();
}
-void Connecting::react(const events::PreferredDeviceChanged& ev) {
- // TODO. Cancel out and start again.
+void Connecting::react(const events::PairedDeviceChanged& ev) {
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
+ if (sPairedWith_) {
+ connect(*sPairedWith_);
+ } else {
+ transit<Idle>();
+ }
}
void Connecting::react(events::internal::Gap ev) {
@@ -698,15 +737,20 @@ void Connected::entry() {
ESP_LOGI(kTag, "entering connected state");
transaction_num_ = 0;
- connected_to_ = sConnectingDevice_->mac;
- sPreferredDevice_ = sConnectingDevice_;
- sConnectingDevice_ = {};
+ connected_to_ = sConnectingTo_->mac;
+ sPairedWith_ = sConnectingTo_;
+
+ sStorage_->BluetoothName(sConnectingTo_->mac, sConnectingTo_->name);
+ std::invoke(sEventHandler_, SimpleEvent::kKnownDevicesChanged);
+
+ sConnectingTo_ = {};
auto stored_pref = sStorage_->PreferredBluetoothDevice();
- if (!stored_pref || (sPreferredDevice_->name != stored_pref->name ||
- sPreferredDevice_->mac != stored_pref->mac)) {
- sStorage_->PreferredBluetoothDevice(sPreferredDevice_);
+ if (!stored_pref || (sPairedWith_->name != stored_pref->name ||
+ sPairedWith_->mac != stored_pref->mac)) {
+ sStorage_->PreferredBluetoothDevice(sPairedWith_);
}
+
// TODO: if we already have a source, immediately start playing
}
@@ -719,13 +763,11 @@ void Connected::react(const events::Disable& ev) {
transit<Disabled>();
}
-void Connected::react(const events::PreferredDeviceChanged& ev) {
- sConnectingDevice_ = sPreferredDevice_;
- transit<Connecting>();
+void Connected::react(const events::PairedDeviceChanged& ev) {
+ transit<Idle>();
}
void Connected::react(const events::SourceChanged& ev) {
- sStream = sSource_;
if (sStream != nullptr) {
ESP_LOGI(kTag, "checking source is ready");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY);
@@ -775,7 +817,8 @@ void Connected::react(events::internal::Avrc ev) {
switch (ev.type) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
if (ev.param.conn_stat.connected) {
- auto err = esp_avrc_ct_send_register_notification_cmd(4, ESP_AVRC_RN_VOLUME_CHANGE, 0);
+ auto err = esp_avrc_ct_send_register_notification_cmd(
+ 4, ESP_AVRC_RN_VOLUME_CHANGE, 0);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err);
}
@@ -787,15 +830,20 @@ void Connected::react(events::internal::Avrc ev) {
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
// The remote device is telling us about its capabilities! We don't
// currently care about any of them.
- ESP_LOGI(kTag, "Recieved capabilitites: %lu", ev.param.rmt_feats.feat_mask);
+ ESP_LOGI(kTag, "Recieved capabilitites: %lu",
+ ev.param.rmt_feats.feat_mask);
break;
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
if (ev.param.change_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
if (sEventHandler_) {
- std::invoke(sEventHandler_, bluetooth::RemoteVolumeChanged{.new_vol = ev.param.change_ntf.event_parameter.volume});
+ std::invoke(
+ sEventHandler_,
+ bluetooth::RemoteVolumeChanged{
+ .new_vol = ev.param.change_ntf.event_parameter.volume});
}
// Resubscribe to volume facts
- auto err = esp_avrc_ct_send_register_notification_cmd(4, ESP_AVRC_RN_VOLUME_CHANGE, 0);
+ auto err = esp_avrc_ct_send_register_notification_cmd(
+ 4, ESP_AVRC_RN_VOLUME_CHANGE, 0);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err);
}
@@ -809,16 +857,20 @@ void Connected::react(events::internal::Avrc ev) {
void Connected::react(const events::internal::Avrctg ev) {
switch (ev.type) {
case ESP_AVRC_TG_CONNECTION_STATE_EVT:
- ESP_LOGI(kTag, "Got connection event. Connected: %s", ev.param.conn_stat.connected ? "true" : "false");
+ ESP_LOGI(kTag, "Got connection event. Connected: %s",
+ ev.param.conn_stat.connected ? "true" : "false");
if (ev.param.conn_stat.connected) {
}
break;
case ESP_AVRC_TG_REMOTE_FEATURES_EVT:
- ESP_LOGI(kTag, "Got remote features feat flag %d", ev.param.rmt_feats.ct_feat_flag);
- ESP_LOGI(kTag, "Got remote features feat mask %lu", ev.param.rmt_feats.feat_mask);
+ ESP_LOGI(kTag, "Got remote features feat flag %d",
+ ev.param.rmt_feats.ct_feat_flag);
+ ESP_LOGI(kTag, "Got remote features feat mask %lu",
+ ev.param.rmt_feats.feat_mask);
break;
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT:
- ESP_LOGI(kTag, "Got passthrough event keycode: %x, %d", ev.param.psth_cmd.key_code, ev.param.psth_cmd.key_state);
+ ESP_LOGI(kTag, "Got passthrough event keycode: %x, %d",
+ ev.param.psth_cmd.key_code, ev.param.psth_cmd.key_state);
if (ev.param.psth_cmd.key_state == 1 && sEventHandler_) {
switch (ev.param.psth_cmd.key_code) {
case ESP_AVRC_PT_CMD_PLAY:
@@ -840,7 +892,8 @@ void Connected::react(const events::internal::Avrctg ev) {
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kBackward);
break;
default:
- ESP_LOGI(kTag, "Unhandled passthrough cmd. Key code: %d", ev.param.psth_cmd.key_code);
+ ESP_LOGI(kTag, "Unhandled passthrough cmd. Key code: %d",
+ ev.param.psth_cmd.key_code);
}
}
break;
@@ -848,14 +901,15 @@ void Connected::react(const events::internal::Avrctg ev) {
if (ev.param.reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
// TODO: actually do this lol
esp_avrc_rn_param_t rn_param;
- rn_param.volume = 64;
+ rn_param.volume = 64;
auto err = esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE,
- ESP_AVRC_RN_RSP_INTERIM, &rn_param);
+ ESP_AVRC_RN_RSP_INTERIM, &rn_param);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err);
}
} else {
- ESP_LOGW(kTag, "unhandled AVRC TG Register Notification event: %u", ev.param.reg_ntf.event_id);
+ ESP_LOGW(kTag, "unhandled AVRC TG Register Notification event: %u",
+ ev.param.reg_ntf.event_id);
}
break;
}
diff --git a/src/drivers/include/drivers/bluetooth.hpp b/src/drivers/include/drivers/bluetooth.hpp
index 94a85263..449812d6 100644
--- a/src/drivers/include/drivers/bluetooth.hpp
+++ b/src/drivers/include/drivers/bluetooth.hpp
@@ -3,6 +3,7 @@
#include <array>
#include <atomic>
+#include <functional>
#include <map>
#include <mutex>
#include <optional>
@@ -25,28 +26,68 @@
namespace drivers {
/*
- * A handle used to interact with the bluetooth state machine.
+ * A handle used to interact with the bluetooth state machine. This is the main
+ * API that the rest of the system should use to interact with Bluetooth.
*/
class Bluetooth {
public:
- Bluetooth(NvsStorage& storage, tasks::WorkerPool&);
+ /*
+ * Callback invoked when an event is generated by the Bluetooth stack. This
+ * callback is invoked synchronously from a Bluetooth task context, so
+ * implementations should immediately hop to a different task to process the
+ * event.
+ */
+ using EventHandler = std::function<void(bluetooth::Event)>;
+
+ Bluetooth(NvsStorage&, tasks::WorkerPool&, EventHandler);
+
+ /* Enables or disables the entire Bluetooth stack. */
+ auto enable(bool en) -> void;
+ auto enabled() -> bool;
+
+ auto source(PcmBuffer*) -> void;
+ auto softVolume(float) -> void;
+
+ enum class ConnectionState {
+ kConnected,
+ kConnecting,
+ kDisconnected,
+ };
+
+ auto connectionState() -> ConnectionState;
+
+ /*
+ * The 'paired' device is a device that will be preferred for connections.
+ * When Bluetooth is first enabled, we immediately try to connect to the
+ * paired device. If the paired device is seen during a scan, then we will
+ * also automatically connect to it.
+ */
+ auto pairedDevice() -> std::optional<bluetooth::MacAndName>;
+
+ /*
+ * Sets the preferred device. If a device is provided, a connection will be
+ * attempted immediately, even if the device has not been detected in a
+ * previous scan.
+ */
+ auto pairedDevice(std::optional<bluetooth::MacAndName> dev) -> void;
+
+ /* A list of devices that have previously been the paired device. */
+ auto knownDevices() -> std::vector<bluetooth::MacAndName>;
+ auto forgetKnownDevice(const bluetooth::mac_addr_t&) -> void;
+
+ /* Enables or disables scanning for nearby Bluetooth devices. */
+ auto discoveryEnabled(bool) -> void;
+ auto discoveryEnabled() -> bool;
+
+ /*
+ * A list of nearby devices that have been discovered since discovery was
+ * last enabled. This list may include the paired device, as well as devices
+ * that are also present in the known devices list.
+ */
+ auto discoveredDevices() -> std::vector<bluetooth::MacAndName>;
- auto Enable() -> bool;
- auto Disable() -> void;
- auto IsEnabled() -> bool;
-
- auto IsConnected() -> bool;
- auto ConnectedDevice() -> std::optional<bluetooth::MacAndName>;
-
- auto KnownDevices() -> std::vector<bluetooth::Device>;
-
- auto SetPreferredDevice(std::optional<bluetooth::MacAndName> dev) -> void;
- auto PreferredDevice() -> std::optional<bluetooth::MacAndName>;
-
- auto SetSource(PcmBuffer*) -> void;
- auto SetVolumeFactor(float) -> void;
-
- auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void;
+ private:
+ NvsStorage& nvs_;
};
namespace bluetooth {
@@ -56,7 +97,7 @@ struct Enable : public tinyfsm::Event {};
struct Disable : public tinyfsm::Event {};
struct ConnectTimedOut : public tinyfsm::Event {};
-struct PreferredDeviceChanged : public tinyfsm::Event {};
+struct PairedDeviceChanged : public tinyfsm::Event {};
struct SourceChanged : public tinyfsm::Event {};
struct DeviceDiscovered : public tinyfsm::Event {
const Device& device;
@@ -94,6 +135,8 @@ class Scanner {
auto StopScanning() -> void;
auto StopScanningNow() -> void;
+ auto enabled() -> bool;
+
auto HandleGapEvent(const events::internal::Gap&) -> void;
private:
@@ -103,25 +146,22 @@ class Scanner {
auto HandleDeviceDiscovery(const esp_bt_gap_cb_param_t& param) -> void;
};
+/*
+ * The main state machine for managing the state of the Bluetooth stack, and
+ * the current (if any) Bluetooth connection.
+ */
class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
public:
- static auto Init(NvsStorage& storage) -> void;
+ static auto Init(NvsStorage& storage, Bluetooth::EventHandler) -> void;
static auto lock() -> std::lock_guard<std::mutex>;
- static auto devices() -> std::vector<Device>;
-
- static auto preferred_device() -> std::optional<bluetooth::MacAndName>;
- static auto preferred_device(std::optional<bluetooth::MacAndName>) -> void;
+ static auto pairedDevice() -> std::optional<bluetooth::MacAndName>;
+ static auto pairedDevice(std::optional<bluetooth::MacAndName>) -> void;
- static auto scanning() -> bool;
static auto discovery() -> bool;
static auto discovery(bool) -> void;
-
- static auto source() -> PcmBuffer*;
- static auto source(PcmBuffer*) -> void;
-
- static auto event_handler(std::function<void(Event)>) -> void;
+ static auto discoveredDevices() -> std::vector<Device>;
virtual ~BluetoothState(){};
@@ -131,7 +171,7 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
virtual void react(const events::Enable& ev){};
virtual void react(const events::Disable& ev) = 0;
virtual void react(const events::ConnectTimedOut& ev){};
- virtual void react(const events::PreferredDeviceChanged& ev){};
+ virtual void react(const events::PairedDeviceChanged& ev){};
virtual void react(const events::SourceChanged& ev){};
virtual void react(const events::DeviceDiscovered&);
@@ -146,13 +186,11 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
static Scanner* sScanner_;
static std::mutex sFsmMutex;
- static std::map<mac_addr_t, Device> sDevices_;
- static std::optional<bluetooth::MacAndName> sPreferredDevice_;
-
- static std::optional<bluetooth::MacAndName> sConnectingDevice_;
+ static std::map<mac_addr_t, Device> sDiscoveredDevices_;
+ static std::optional<bluetooth::MacAndName> sPairedWith_;
+ static std::optional<bluetooth::MacAndName> sConnectingTo_;
static int sConnectAttemptsRemaining_;
- static std::atomic<PcmBuffer*> sSource_;
static std::function<void(Event)> sEventHandler_;
auto connect(const bluetooth::MacAndName&) -> bool;
@@ -177,7 +215,7 @@ class Idle : public BluetoothState {
void exit() override;
void react(const events::Disable& ev) override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(events::internal::Gap ev) override;
@@ -189,7 +227,7 @@ class Connecting : public BluetoothState {
void entry() override;
void exit() override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(const events::ConnectTimedOut& ev) override;
void react(const events::Disable& ev) override;
@@ -204,7 +242,7 @@ class Connected : public BluetoothState {
void entry() override;
void exit() override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(const events::SourceChanged& ev) override;
void react(const events::Disable& ev) override;
diff --git a/src/drivers/include/drivers/bluetooth_types.hpp b/src/drivers/include/drivers/bluetooth_types.hpp
index d2e55ee5..05caee47 100644
--- a/src/drivers/include/drivers/bluetooth_types.hpp
+++ b/src/drivers/include/drivers/bluetooth_types.hpp
@@ -27,9 +27,12 @@ struct Device {
};
enum class SimpleEvent {
- kKnownDevicesChanged,
kConnectionStateChanged,
- kPreferredDeviceChanged,
+ kPairedDeviceChanged,
+ kKnownDevicesChanged,
+ kDiscoveryChanged,
+ kDeviceDiscovered,
+
// Passthrough events
kPlayPause,
kStop,
diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp
index 88dd5ae0..2bc77a31 100644
--- a/src/drivers/include/drivers/nvs.hpp
+++ b/src/drivers/include/drivers/nvs.hpp
@@ -96,6 +96,10 @@ class NvsStorage {
auto BluetoothVolume(const bluetooth::mac_addr_t&) -> uint8_t;
auto BluetoothVolume(const bluetooth::mac_addr_t&, uint8_t) -> void;
+ auto BluetoothNames() -> std::vector<bluetooth::MacAndName>;
+ auto BluetoothName(const bluetooth::mac_addr_t&, std::optional<std::string>)
+ -> void;
+
enum class Output : uint8_t {
kHeadphones = 0,
kBluetooth = 1,
@@ -154,7 +158,10 @@ class NvsStorage {
Setting<int8_t> amp_left_bias_;
Setting<uint8_t> input_mode_;
Setting<uint8_t> output_mode_;
+
Setting<bluetooth::MacAndName> bt_preferred_;
+ Setting<std::vector<bluetooth::MacAndName>> bt_names_;
+
Setting<uint8_t> db_auto_index_;
util::LruCache<10, bluetooth::mac_addr_t, uint8_t> bt_volumes_;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 5c7d2218..c4d8dedc 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -26,6 +26,7 @@ static constexpr uint8_t kSchemaVersion = 1;
static constexpr char kKeyVersion[] = "ver";
static constexpr char kKeyBluetoothPreferred[] = "bt_dev";
static constexpr char kKeyBluetoothVolumes[] = "bt_vols";
+static constexpr char kKeyBluetoothNames[] = "bt_names";
static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
@@ -130,6 +131,44 @@ auto Setting<bluetooth::MacAndName>::store(nvs_handle_t nvs,
}
template <>
+auto Setting<std::vector<bluetooth::MacAndName>>::load(nvs_handle_t nvs)
+ -> std::optional<std::vector<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::MAP) {
+ return {};
+ }
+ std::vector<bluetooth::MacAndName> res;
+ for (const auto& i : *parsed->asMap()) {
+ auto mac = i.first->asViewBstr()->view();
+ auto name = i.second->asViewTstr()->view();
+ bluetooth::MacAndName entry{
+ .mac = {},
+ .name = {name.begin(), name.end()},
+ };
+ std::copy(mac.begin(), mac.end(), entry.mac.begin());
+ res.push_back(entry);
+ }
+ return res;
+}
+
+template <>
+auto Setting<std::vector<bluetooth::MacAndName>>::store(
+ nvs_handle_t nvs,
+ std::vector<bluetooth::MacAndName> v) -> void {
+ cppbor::Map cbor{};
+ for (const auto& i : v) {
+ cbor.add(cppbor::Bstr{{i.mac.data(), i.mac.size()}}, cppbor::Tstr{i.name});
+ }
+ auto encoded = cbor.encode();
+ nvs_set_blob(nvs, name_, encoded.data(), encoded.size());
+}
+
+template <>
auto Setting<NvsStorage::LraData>::load(nvs_handle_t nvs)
-> std::optional<NvsStorage::LraData> {
auto raw = nvs_get_string(nvs, name_);
@@ -208,6 +247,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
input_mode_(kKeyPrimaryInput),
output_mode_(kKeyOutput),
bt_preferred_(kKeyBluetoothPreferred),
+ bt_names_(kKeyBluetoothNames),
db_auto_index_(kKeyDbAutoIndex),
bt_volumes_(),
bt_volumes_dirty_(false) {}
@@ -232,6 +272,7 @@ auto NvsStorage::Read() -> void {
input_mode_.read(handle_);
output_mode_.read(handle_);
bt_preferred_.read(handle_);
+ bt_names_.read(handle_);
db_auto_index_.read(handle_);
readBtVolumes();
}
@@ -251,6 +292,7 @@ auto NvsStorage::Write() -> bool {
input_mode_.write(handle_);
output_mode_.write(handle_);
bt_preferred_.write(handle_);
+ bt_names_.write(handle_);
db_auto_index_.write(handle_);
writeBtVolumes();
return nvs_commit(handle_) == ESP_OK;
@@ -341,6 +383,47 @@ auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac, uint8_t vol)
bt_volumes_.Put(mac, vol);
}
+auto NvsStorage::BluetoothNames() -> std::vector<bluetooth::MacAndName> {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return bt_names_.get().value_or(std::vector<bluetooth::MacAndName>{});
+}
+
+auto NvsStorage::BluetoothName(const bluetooth::mac_addr_t& mac,
+ std::optional<std::string> name) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ auto& val = bt_names_.get();
+ if (!val) {
+ val.emplace();
+ }
+
+ bool mut = false;
+ bool found = false;
+ for (auto it = val->begin(); it != val->end(); it++) {
+ if (it->mac == mac) {
+ if (name) {
+ it->name = *name;
+ } else {
+ val->erase(it);
+ }
+ found = true;
+ mut = true;
+ break;
+ }
+ }
+
+ if (!found && name) {
+ val->push_back(bluetooth::MacAndName{
+ .mac = mac,
+ .name = *name,
+ });
+ mut = true;
+ }
+
+ if (mut) {
+ bt_names_.set(*val);
+ }
+}
+
auto NvsStorage::OutputMode() -> Output {
std::lock_guard<std::mutex> lock{mutex_};
switch (output_mode_.get().value_or(0xFF)) {
diff --git a/src/tangara/app_console/app_console.cpp b/src/tangara/app_console/app_console.cpp
index f3593e1b..af9061fe 100644
--- a/src/tangara/app_console/app_console.cpp
+++ b/src/tangara/app_console/app_console.cpp
@@ -418,28 +418,21 @@ int CmdBtList(int argc, char** argv) {
return 1;
}
- auto devices = AppConsole::sServices->bluetooth().KnownDevices();
+ auto devices = AppConsole::sServices->bluetooth().knownDevices();
if (argc == 2) {
int index = std::atoi(argv[1]);
if (index < 0 || index >= devices.size()) {
std::cout << "index out of range" << std::endl;
return -1;
}
- drivers::bluetooth::MacAndName dev{
- .mac = devices[index].address,
- .name = {devices[index].name.data(), devices[index].name.size()},
- };
- AppConsole::sServices->bluetooth().SetPreferredDevice(dev);
+ AppConsole::sServices->bluetooth().pairedDevice(devices[index]);
} else {
- std::cout << "mac\t\trssi\tname" << std::endl;
+ std::cout << "mac\t\tname" << std::endl;
for (const auto& device : devices) {
- for (size_t i = 0; i < device.address.size(); i++) {
+ for (size_t i = 0; i < device.mac.size(); i++) {
std::cout << std::hex << std::setfill('0') << std::setw(2)
- << static_cast<int>(device.address[i]);
+ << static_cast<int>(device.mac[i]);
}
- float perc =
- (static_cast<double>(device.signal_strength) + 127.0) / 256.0 * 100;
- std::cout << "\t" << std::fixed << std::setprecision(0) << perc << "%";
std::cout << "\t" << device.name << std::endl;
}
}
diff --git a/src/tangara/audio/audio_fsm.cpp b/src/tangara/audio/audio_fsm.cpp
index 24f287ac..fbc38f97 100644
--- a/src/tangara/audio/audio_fsm.cpp
+++ b/src/tangara/audio/audio_fsm.cpp
@@ -222,7 +222,12 @@ void AudioState::react(const system_fsm::BluetoothEvent& ev) {
auto simpleEvent = std::get<SimpleEvent>(ev.event);
switch (simpleEvent) {
case SimpleEvent::kConnectionStateChanged: {
- auto dev = sServices->bluetooth().ConnectedDevice();
+ auto bt = sServices->bluetooth();
+ if (bt.connectionState() !=
+ drivers::Bluetooth::ConnectionState::kConnected) {
+ return;
+ }
+ auto dev = sServices->bluetooth().pairedDevice();
if (!dev) {
return;
}
@@ -341,7 +346,7 @@ auto AudioState::commitVolume() -> void {
if (mode == drivers::NvsStorage::Output::kHeadphones) {
sServices->nvs().AmpCurrentVolume(vol);
} else if (mode == drivers::NvsStorage::Output::kBluetooth) {
- auto dev = sServices->bluetooth().ConnectedDevice();
+ auto dev = sServices->bluetooth().pairedDevice();
if (!dev) {
return;
}
@@ -372,7 +377,7 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
sOutput = sI2SOutput;
} else {
// Ensure Bluetooth gets enabled if it's the default sink.
- sServices->bluetooth().Enable();
+ sServices->bluetooth().enable(true);
sOutput = sBtOutput;
}
sOutput->mode(IAudioOutput::Modes::kOnPaused);
diff --git a/src/tangara/audio/bt_audio_output.cpp b/src/tangara/audio/bt_audio_output.cpp
index 616a385f..336fc758 100644
--- a/src/tangara/audio/bt_audio_output.cpp
+++ b/src/tangara/audio/bt_audio_output.cpp
@@ -13,6 +13,7 @@
#include <memory>
#include <variant>
+#include "drivers/bluetooth.hpp"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "freertos/portmacro.h"
@@ -32,6 +33,8 @@ namespace audio {
static constexpr uint16_t kVolumeRange = 60;
+using ConnectionState = drivers::Bluetooth::ConnectionState;
+
BluetoothAudioOutput::BluetoothAudioOutput(drivers::Bluetooth& bt,
drivers::PcmBuffer& buffer,
tasks::WorkerPool& p)
@@ -45,9 +48,9 @@ BluetoothAudioOutput::~BluetoothAudioOutput() {}
auto BluetoothAudioOutput::changeMode(Modes mode) -> void {
if (mode == Modes::kOnPlaying) {
- bluetooth_.SetSource(&buffer_);
+ bluetooth_.source(&buffer_);
} else {
- bluetooth_.SetSource(nullptr);
+ bluetooth_.source(nullptr);
}
}
@@ -60,7 +63,7 @@ auto BluetoothAudioOutput::SetVolume(uint16_t v) -> void {
bg_worker_.Dispatch<void>([&]() {
float factor =
pow(10, static_cast<double>(kVolumeRange) * (volume_ - 100) / 100 / 20);
- bluetooth_.SetVolumeFactor(factor);
+ bluetooth_.softVolume(factor);
});
}
@@ -95,7 +98,7 @@ auto BluetoothAudioOutput::SetVolumeDb(int_fast16_t val) -> bool {
}
auto BluetoothAudioOutput::AdjustVolumeUp() -> bool {
- if (volume_ == 100 || !bluetooth_.IsConnected()) {
+ if (volume_ == 100) {
return false;
}
volume_++;
@@ -104,7 +107,7 @@ auto BluetoothAudioOutput::AdjustVolumeUp() -> bool {
}
auto BluetoothAudioOutput::AdjustVolumeDown() -> bool {
- if (volume_ == 0 || !bluetooth_.IsConnected()) {
+ if (volume_ == 0) {
return false;
}
volume_--;
diff --git a/src/tangara/lua/property.cpp b/src/tangara/lua/property.cpp
index 2b93809d..7b4f0b97 100644
--- a/src/tangara/lua/property.cpp
+++ b/src/tangara/lua/property.cpp
@@ -289,13 +289,14 @@ static void pushTrack(lua_State* L, const audio::TrackInfo& track) {
lua_settable(L, -3);
}
-static void pushDevice(lua_State* L, const drivers::bluetooth::Device& dev) {
+static void pushDevice(lua_State* L,
+ const drivers::bluetooth::MacAndName& dev) {
lua_createtable(L, 0, 4);
lua_pushliteral(L, "address");
auto* mac = reinterpret_cast<drivers::bluetooth::mac_addr_t*>(
lua_newuserdata(L, sizeof(drivers::bluetooth::mac_addr_t)));
- *mac = dev.address;
+ *mac = dev.mac;
lua_rawset(L, -3);
// What I just did there was perfectly safe. Look, I can prove it:
@@ -308,14 +309,8 @@ static void pushDevice(lua_State* L, const drivers::bluetooth::Device& dev) {
lua_pushlstring(L, dev.name.data(), dev.name.size());
lua_rawset(L, -3);
- // FIXME: This field deserves a little more structure.
- lua_pushliteral(L, "class");
- lua_pushinteger(L, dev.class_of_device);
- lua_rawset(L, -3);
-
- lua_pushliteral(L, "signal_strength");
- lua_pushinteger(L, dev.signal_strength);
- lua_rawset(L, -3);
+ // FIXME: Plumbing through device classes to here could be useful if we ever
+ // want to show cute little icons.
}
auto Property::pushValue(lua_State& s) -> int {
@@ -332,10 +327,12 @@ auto Property::pushValue(lua_State& s) -> int {
lua_pushstring(&s, arg.c_str());
} else if constexpr (std::is_same_v<T, audio::TrackInfo>) {
pushTrack(&s, arg);
- } else if constexpr (std::is_same_v<T, drivers::bluetooth::Device>) {
+ } else if constexpr (std::is_same_v<T,
+ drivers::bluetooth::MacAndName>) {
pushDevice(&s, arg);
} else if constexpr (std::is_same_v<
- T, std::vector<drivers::bluetooth::Device>>) {
+ T,
+ std::vector<drivers::bluetooth::MacAndName>>) {
lua_createtable(&s, arg.size(), 0);
size_t i = 1;
for (const auto& dev : arg) {
@@ -364,15 +361,10 @@ auto popRichType(lua_State* L) -> LuaValue {
lua_pushliteral(L, "name");
lua_gettable(L, -2);
- std::pmr::string name = lua_tostring(L, -1);
+ std::string name = lua_tostring(L, -1);
lua_pop(L, 1);
- return drivers::bluetooth::Device{
- .address = mac,
- .name = name,
- .class_of_device = 0,
- .signal_strength = 0,
- };
+ return drivers::bluetooth::MacAndName{.mac = mac, .name = name};
}
return std::monostate{};
diff --git a/src/tangara/lua/property.hpp b/src/tangara/lua/property.hpp
index 9f925766..d45821bd 100644
--- a/src/tangara/lua/property.hpp
+++ b/src/tangara/lua/property.hpp
@@ -24,8 +24,8 @@ using LuaValue = std::variant<std::monostate,
bool,
std::string,
audio::TrackInfo,
- drivers::bluetooth::Device,
- std::vector<drivers::bluetooth::Device>>;
+ drivers::bluetooth::MacAndName,
+ std::vector<drivers::bluetooth::MacAndName>>;
using LuaFunction = std::function<int(lua_State*)>;
diff --git a/src/tangara/system_fsm/booting.cpp b/src/tangara/system_fsm/booting.cpp
index 9d505f81..86993767 100644
--- a/src/tangara/system_fsm/booting.cpp
+++ b/src/tangara/system_fsm/booting.cpp
@@ -104,8 +104,7 @@ auto Booting::entry() -> void {
ESP_LOGI(kTag, "init bluetooth");
sServices->bluetooth(std::make_unique<drivers::Bluetooth>(
- sServices->nvs(), sServices->bg_worker()));
- sServices->bluetooth().SetEventHandler(bt_event_cb);
+ sServices->nvs(), sServices->bg_worker(), bt_event_cb));
BootComplete ev{.services = sServices};
events::Audio().Dispatch(ev);
diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp
index 7c4147a3..c5ede83c 100644
--- a/src/tangara/ui/ui_fsm.cpp
+++ b/src/tangara/ui/ui_fsm.cpp
@@ -7,11 +7,13 @@
#include "ui/ui_fsm.hpp"
#include <stdint.h>
+#include <algorithm>
#include <memory>
#include <memory_resource>
#include <variant>
#include "FreeRTOSConfig.h"
+#include "drivers/bluetooth.hpp"
#include "lvgl.h"
#include "core/lv_group.h"
@@ -100,32 +102,57 @@ lua::Property UiState::sBluetoothEnabled{
if (!std::holds_alternative<bool>(val)) {
return false;
}
+ // Note we always write the OutputMode NVS change before actually
+ // modifying the peripheral. We do this because ESP-IDF's Bluetooth stack
+ // breaks in surprising ways when repeatedly initialised/uninitialised.
if (std::get<bool>(val)) {
sServices->nvs().OutputMode(drivers::NvsStorage::Output::kBluetooth);
- sServices->bluetooth().Enable();
+ sServices->bluetooth().enable(true);
} else {
sServices->nvs().OutputMode(drivers::NvsStorage::Output::kHeadphones);
- sServices->bluetooth().Disable();
+ sServices->bluetooth().enable(false);
}
events::Audio().Dispatch(audio::OutputModeChanged{});
return true;
}};
+lua::Property UiState::sBluetoothConnecting{false};
lua::Property UiState::sBluetoothConnected{false};
+
+lua::Property UiState::sBluetoothDiscovering{
+ false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ // Note we always write the OutputMode NVS change before actually
+ // modifying the peripheral. We do this because ESP-IDF's Bluetooth stack
+ // breaks in surprising ways when repeatedly initialised/uninitialised.
+ if (std::get<bool>(val)) {
+ sServices->bluetooth().discoveryEnabled(true);
+ } else {
+ sServices->bluetooth().discoveryEnabled(false);
+ }
+ return true;
+ }};
+
lua::Property UiState::sBluetoothPairedDevice{
std::monostate{}, [](const lua::LuaValue& val) {
- if (std::holds_alternative<drivers::bluetooth::Device>(val)) {
- auto dev = std::get<drivers::bluetooth::Device>(val);
- sServices->bluetooth().SetPreferredDevice(
- drivers::bluetooth::MacAndName{
- .mac = dev.address,
- .name = {dev.name.data(), dev.name.size()},
- });
+ if (std::holds_alternative<drivers::bluetooth::MacAndName>(val)) {
+ auto dev = std::get<drivers::bluetooth::MacAndName>(val);
+ sServices->bluetooth().pairedDevice(dev);
+ } else if (std::holds_alternative<std::monostate>(val)) {
+ sServices->bluetooth().pairedDevice({});
+ } else {
+ // Don't accept any other types.
+ return false;
}
- return false;
+ return true;
}};
-lua::Property UiState::sBluetoothDevices{
- std::vector<drivers::bluetooth::Device>{}};
+
+lua::Property UiState::sBluetoothKnownDevices{
+ std::vector<drivers::bluetooth::MacAndName>{}};
+lua::Property UiState::sBluetoothDiscoveredDevices{
+ std::vector<drivers::bluetooth::MacAndName>{}};
lua::Property UiState::sPlaybackPlaying{
false, [](const lua::LuaValue& val) {
@@ -412,8 +439,13 @@ void UiState::react(const audio::VolumeLimitChanged& ev) {
void UiState::react(const system_fsm::BluetoothEvent& ev) {
using drivers::bluetooth::SimpleEvent;
+ using ConnectionState = drivers::Bluetooth::ConnectionState;
+ ConnectionState state;
auto bt = sServices->bluetooth();
- auto dev = bt.ConnectedDevice();
+
+ std::optional<drivers::bluetooth::MacAndName> dev;
+ std::vector<drivers::bluetooth::MacAndName> devs;
+
if (std::holds_alternative<SimpleEvent>(ev.event)) {
switch (std::get<SimpleEvent>(ev.event)) {
case SimpleEvent::kPlayPause:
@@ -438,30 +470,36 @@ void UiState::react(const system_fsm::BluetoothEvent& ev) {
break;
case SimpleEvent::kFastForward:
break;
- case SimpleEvent::kKnownDevicesChanged:
- sBluetoothDevices.setDirect(bt.KnownDevices());
- break;
case SimpleEvent::kConnectionStateChanged:
- sBluetoothConnected.setDirect(bt.IsConnected());
+ state = bt.connectionState();
+ sBluetoothConnected.setDirect(state == ConnectionState::kConnected);
+ sBluetoothConnecting.setDirect(state == ConnectionState::kConnecting);
+ break;
+ case SimpleEvent::kPairedDeviceChanged:
+ dev = bt.pairedDevice();
if (dev) {
- sBluetoothPairedDevice.setDirect(drivers::bluetooth::Device{
- .address = dev->mac,
- .name = {dev->name.data(), dev->name.size()},
- .class_of_device = 0,
- .signal_strength = 0,
- });
+ sBluetoothPairedDevice.setDirect(*dev);
} else {
sBluetoothPairedDevice.setDirect(std::monostate{});
}
break;
- case SimpleEvent::kPreferredDeviceChanged:
+ case SimpleEvent::kKnownDevicesChanged:
+ sBluetoothKnownDevices.setDirect(bt.knownDevices());
+ break;
+ case SimpleEvent::kDiscoveryChanged:
+ sBluetoothDiscovering.setDirect(bt.discoveryEnabled());
+ // Dump the old list of discovered devices when discovery is toggled.
+ sBluetoothDiscoveredDevices.setDirect(bt.discoveredDevices());
+ break;
+ case SimpleEvent::kDeviceDiscovered:
+ sBluetoothDiscoveredDevices.setDirect(bt.discoveredDevices());
break;
default:
break;
}
} else if (std::holds_alternative<drivers::bluetooth::RemoteVolumeChanged>(
ev.event)) {
- // Todo: Do something with this (ie, bt volume alert)
+ // TODO: Do something with this (ie, bt volume alert)
ESP_LOGI(
kTag, "Recieved volume changed event with new volume: %d",
std::get<drivers::bluetooth::RemoteVolumeChanged>(ev.event).new_vol);
@@ -517,13 +555,16 @@ void Lua::entry() {
{"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging},
});
- registry.AddPropertyModule("bluetooth",
- {
- {"enabled", &sBluetoothEnabled},
- {"connected", &sBluetoothConnected},
- {"paired_device", &sBluetoothPairedDevice},
- {"devices", &sBluetoothDevices},
- });
+ registry.AddPropertyModule(
+ "bluetooth", {
+ {"enabled", &sBluetoothEnabled},
+ {"connected", &sBluetoothConnected},
+ {"connecting", &sBluetoothConnecting},
+ {"discovering", &sBluetoothDiscovering},
+ {"paired_device", &sBluetoothPairedDevice},
+ {"discovered_devices", &sBluetoothDiscoveredDevices},
+ {"known_devices", &sBluetoothKnownDevices},
+ });
registry.AddPropertyModule("playback", {
{"playing", &sPlaybackPlaying},
{"track", &sPlaybackTrack},
@@ -601,9 +642,12 @@ void Lua::entry() {
sDatabaseAutoUpdate.setDirect(sServices->nvs().DbAutoIndex());
auto bt = sServices->bluetooth();
- sBluetoothEnabled.setDirect(bt.IsEnabled());
- sBluetoothConnected.setDirect(bt.IsConnected());
- sBluetoothDevices.setDirect(bt.KnownDevices());
+ sBluetoothEnabled.setDirect(bt.enabled());
+ auto paired = bt.pairedDevice();
+ if (paired) {
+ sBluetoothPairedDevice.setDirect(*paired);
+ }
+ sBluetoothKnownDevices.setDirect(bt.knownDevices());
if (sServices->sd() == drivers::SdState::kMounted) {
sLua->RunScript("/sdcard/config.lua");
diff --git a/src/tangara/ui/ui_fsm.hpp b/src/tangara/ui/ui_fsm.hpp
index 7e34db34..72688fa0 100644
--- a/src/tangara/ui/ui_fsm.hpp
+++ b/src/tangara/ui/ui_fsm.hpp
@@ -102,9 +102,12 @@ class UiState : public tinyfsm::Fsm<UiState> {
static lua::Property sBatteryCharging;
static lua::Property sBluetoothEnabled;
+ static lua::Property sBluetoothConnecting;
static lua::Property sBluetoothConnected;
+ static lua::Property sBluetoothDiscovering;
static lua::Property sBluetoothPairedDevice;
- static lua::Property sBluetoothDevices;
+ static lua::Property sBluetoothKnownDevices;
+ static lua::Property sBluetoothDiscoveredDevices;
static lua::Property sPlaybackPlaying;