From b63e897268bdbaa679cc68b6c9586ba4d5520b45 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 10 Jul 2024 15:18:55 +1000 Subject: Move the SPI interrupt alloc to the second core We're a bit close to the line on core0 allocs, so this helps balance things out a bit. --- src/drivers/spi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/drivers') diff --git a/src/drivers/spi.cpp b/src/drivers/spi.cpp index 632fe89f..40487197 100644 --- a/src/drivers/spi.cpp +++ b/src/drivers/spi.cpp @@ -41,7 +41,7 @@ esp_err_t init_spi(void) { // manages its own use of DMA-capable memory. .max_transfer_sz = 4096, .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_IOMUX_PINS, - .isr_cpu_id = ESP_INTR_CPU_AFFINITY_0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_1, .intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, }; -- cgit v1.2.3 From a3eb2dd9dc2399ce9c22cd3b07f482f080976440 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 11 Jul 2024 15:11:28 +1000 Subject: WIP improve bluetooth api and settings screen --- src/drivers/bluetooth.cpp | 338 ++++++++++++++---------- src/drivers/include/drivers/bluetooth.hpp | 116 +++++--- src/drivers/include/drivers/bluetooth_types.hpp | 7 +- src/drivers/include/drivers/nvs.hpp | 7 + src/drivers/nvs.cpp | 83 ++++++ 5 files changed, 368 insertions(+), 183 deletions(-) (limited to 'src/drivers') 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 #include +#include #include #include #include @@ -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::dispatch( - bluetooth::events::Enable{}); +auto Bluetooth::enable(bool en) -> void { + if (en) { + auto lock = bluetooth::BluetoothState::lock(); + tinyfsm::FsmList::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::dispatch( + bluetooth::events::Disable{}); + } +} +auto Bluetooth::enabled() -> bool { + auto lock = bluetooth::BluetoothState::lock(); return !bluetooth::BluetoothState::is_in_state(); } -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::dispatch( - bluetooth::events::Disable{}); + bluetooth::events::SourceChanged{}); } -auto Bluetooth::IsEnabled() -> bool { - auto lock = bluetooth::BluetoothState::lock(); - return !bluetooth::BluetoothState::is_in_state(); +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(); + if (bluetooth::BluetoothState::is_in_state()) { + return ConnectionState::kConnected; + } else if (bluetooth::BluetoothState::is_in_state()) { + return ConnectionState::kConnecting; + } + return ConnectionState::kDisconnected; } -auto Bluetooth::ConnectedDevice() -> std::optional { +auto Bluetooth::pairedDevice() -> std::optional { auto lock = bluetooth::BluetoothState::lock(); - if (!bluetooth::BluetoothState::is_in_state()) { - return {}; - } - return bluetooth::BluetoothState::preferred_device(); + return bluetooth::BluetoothState::pairedDevice(); } -auto Bluetooth::KnownDevices() -> std::vector { +auto Bluetooth::pairedDevice(std::optional dev) -> void { auto lock = bluetooth::BluetoothState::lock(); - std::vector 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 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::dispatch( - bluetooth::events::PreferredDeviceChanged{}); +auto Bluetooth::knownDevices() -> std::vector { + return nvs_.BluetoothNames(); } -auto Bluetooth::PreferredDevice() -> std::optional { - 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::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 cb) - -> void { - auto lock = bluetooth::BluetoothState::lock(); - bluetooth::BluetoothState::event_handler(cb); +auto Bluetooth::discoveredDevices() -> std::vector { + std::vector 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 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 BluetoothState::sDevices_{}; -std::optional BluetoothState::sPreferredDevice_{}; -std::optional BluetoothState::sConnectingDevice_{}; +std::map BluetoothState::sDiscoveredDevices_{}; +std::optional BluetoothState::sPairedWith_{}; +std::optional BluetoothState::sConnectingTo_{}; int BluetoothState::sConnectAttemptsRemaining_{0}; -std::atomic BluetoothState::sSource_; std::function 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::start(); } @@ -361,68 +386,85 @@ auto BluetoothState::lock() -> std::lock_guard { return std::lock_guard{sFsmMutex}; } -auto BluetoothState::devices() -> std::vector { - std::vector out; - for (const auto& device : sDevices_) { - out.push_back(device.second); - } - return out; +auto BluetoothState::pairedDevice() -> std::optional { + return sPairedWith_; } -auto BluetoothState::preferred_device() -> std::optional { - return sPreferredDevice_; -} +auto BluetoothState::pairedDevice(std::optional 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 addr) -> void { - sPreferredDevice_ = addr; + tinyfsm::FsmList::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 cb) -> void { - sEventHandler_ = cb; +auto BluetoothState::discoveredDevices() -> std::vector { + std::vector 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(); } } 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(); } -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(); } } void Connecting::react(const events::Disable& ev) { - // TODO: disconnect gracefully + esp_a2d_source_disconnect(sConnectingTo_->mac.data()); transit(); } -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(); + } } 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(); } -void Connected::react(const events::PreferredDeviceChanged& ev) { - sConnectingDevice_ = sPreferredDevice_; - transit(); +void Connected::react(const events::PairedDeviceChanged& ev) { + transit(); } 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 #include +#include #include #include #include @@ -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; + + 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; + + /* + * 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 dev) -> void; + + /* A list of devices that have previously been the paired device. */ + auto knownDevices() -> std::vector; + 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; - auto Enable() -> bool; - auto Disable() -> void; - auto IsEnabled() -> bool; - - auto IsConnected() -> bool; - auto ConnectedDevice() -> std::optional; - - auto KnownDevices() -> std::vector; - - auto SetPreferredDevice(std::optional dev) -> void; - auto PreferredDevice() -> std::optional; - - auto SetSource(PcmBuffer*) -> void; - auto SetVolumeFactor(float) -> void; - - auto SetEventHandler(std::function 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 { public: - static auto Init(NvsStorage& storage) -> void; + static auto Init(NvsStorage& storage, Bluetooth::EventHandler) -> void; static auto lock() -> std::lock_guard; - static auto devices() -> std::vector; - - static auto preferred_device() -> std::optional; - static auto preferred_device(std::optional) -> void; + static auto pairedDevice() -> std::optional; + static auto pairedDevice(std::optional) -> 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; + static auto discoveredDevices() -> std::vector; virtual ~BluetoothState(){}; @@ -131,7 +171,7 @@ class BluetoothState : public tinyfsm::Fsm { 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 { static Scanner* sScanner_; static std::mutex sFsmMutex; - static std::map sDevices_; - static std::optional sPreferredDevice_; - - static std::optional sConnectingDevice_; + static std::map sDiscoveredDevices_; + static std::optional sPairedWith_; + static std::optional sConnectingTo_; static int sConnectAttemptsRemaining_; - static std::atomic sSource_; static std::function 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; + auto BluetoothName(const bluetooth::mac_addr_t&, std::optional) + -> void; + enum class Output : uint8_t { kHeadphones = 0, kBluetooth = 1, @@ -154,7 +158,10 @@ class NvsStorage { Setting amp_left_bias_; Setting input_mode_; Setting output_mode_; + Setting bt_preferred_; + Setting> bt_names_; + Setting 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"; @@ -129,6 +130,44 @@ auto Setting::store(nvs_handle_t nvs, nvs_set_blob(nvs, name_, encoded.data(), encoded.size()); } +template <> +auto Setting>::load(nvs_handle_t nvs) + -> std::optional> { + auto raw = nvs_get_string(nvs, name_); + if (!raw) { + return {}; + } + auto [parsed, unused, err] = cppbor::parseWithViews( + reinterpret_cast(raw->data()), raw->size()); + if (parsed->type() != cppbor::MAP) { + return {}; + } + std::vector 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>::store( + nvs_handle_t nvs, + std::vector 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::load(nvs_handle_t nvs) -> std::optional { @@ -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 { + std::lock_guard lock{mutex_}; + return bt_names_.get().value_or(std::vector{}); +} + +auto NvsStorage::BluetoothName(const bluetooth::mac_addr_t& mac, + std::optional name) -> void { + std::lock_guard 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 lock{mutex_}; switch (output_mode_.get().value_or(0xFF)) { -- cgit v1.2.3 From f78de39a750d58bfe883a789aa6cc4b0a5d9b9e7 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 12 Jul 2024 14:40:54 +1000 Subject: Give Bluetooth settings a bit of a refresh It's now a bit more responsive to stuff happening, gives you more information, and remembers your previously paired devices for faster switching between them. --- src/drivers/bluetooth.cpp | 24 ++++++++++++++---------- src/drivers/include/drivers/nvs.hpp | 2 +- src/drivers/nvs.cpp | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp index 2edf5ad9..a0a318e9 100644 --- a/src/drivers/bluetooth.cpp +++ b/src/drivers/bluetooth.cpp @@ -603,16 +603,21 @@ void Disabled::react(const events::Enable&) { void Idle::entry() { ESP_LOGI(kTag, "bt is idle"); + std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); } -void Idle::exit() {} +void Idle::exit() { + std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); +} void Idle::react(const events::Disable& ev) { transit(); } void Idle::react(const events::PairedDeviceChanged& ev) { - connect(*sPairedWith_); + if (sPairedWith_) { + connect(*sPairedWith_); + } } void Idle::react(events::internal::Gap ev) { @@ -633,18 +638,10 @@ void Connecting::entry() { sTimeoutTimer = xTimerCreate("bt_timeout", pdMS_TO_TICKS(15000), false, NULL, timeoutCallback); xTimerStart(sTimeoutTimer, portMAX_DELAY); - - if (sEventHandler_) { - std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); - } } void Connecting::exit() { xTimerDelete(sTimeoutTimer, portMAX_DELAY); - - if (sEventHandler_) { - std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); - } } void Connecting::react(const events::ConnectTimedOut& ev) { @@ -751,12 +748,16 @@ void Connected::entry() { sStorage_->PreferredBluetoothDevice(sPairedWith_); } + std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); + // TODO: if we already have a source, immediately start playing } void Connected::exit() { ESP_LOGI(kTag, "exiting connected state"); esp_a2d_source_disconnect(connected_to_.data()); + + std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged); } void Connected::react(const events::Disable& ev) { @@ -765,6 +766,9 @@ void Connected::react(const events::Disable& ev) { void Connected::react(const events::PairedDeviceChanged& ev) { transit(); + if (sPairedWith_) { + connect(*sPairedWith_); + } } void Connected::react(const events::SourceChanged& ev) { diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index 2bc77a31..8eb28cc9 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -34,7 +34,7 @@ class Setting { dirty_ = true; } } - auto get() -> std::optional& { return val_; } + auto get() -> std::optional { return val_; } /* Reads the stored value from NVS and parses it into the correct type. */ auto load(nvs_handle_t) -> std::optional; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index c4d8dedc..e3c4aa06 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -391,7 +391,7 @@ auto NvsStorage::BluetoothNames() -> std::vector { auto NvsStorage::BluetoothName(const bluetooth::mac_addr_t& mac, std::optional name) -> void { std::lock_guard lock{mutex_}; - auto& val = bt_names_.get(); + auto val = bt_names_.get(); if (!val) { val.emplace(); } -- cgit v1.2.3 From 0cc75366848e9205ac88884afcc128925024ccec Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 24 Jul 2024 15:29:45 +1000 Subject: Add a settings screen with power+battery info Mostly for debugging, but also u can toggle fast charging off and on now --- src/drivers/include/drivers/nvs.hpp | 4 ++ src/drivers/include/drivers/samd.hpp | 13 +++++-- src/drivers/nvs.cpp | 12 ++++++ src/drivers/samd.cpp | 74 ++++++++++++++++++++++++++++++------ 4 files changed, 89 insertions(+), 14 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index 8eb28cc9..e298ffc3 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -90,6 +90,9 @@ class NvsStorage { auto LraCalibration() -> std::optional; auto LraCalibration(const LraData&) -> void; + auto FastCharge() -> bool; + auto FastCharge(bool) -> void; + auto PreferredBluetoothDevice() -> std::optional; auto PreferredBluetoothDevice(std::optional) -> void; @@ -150,6 +153,7 @@ class NvsStorage { Setting display_rows_; Setting haptic_motor_type_; Setting lra_calibration_; + Setting fast_charge_; Setting brightness_; Setting sensitivity_; diff --git a/src/drivers/include/drivers/samd.hpp b/src/drivers/include/drivers/samd.hpp index 897e78d6..ff479225 100644 --- a/src/drivers/include/drivers/samd.hpp +++ b/src/drivers/include/drivers/samd.hpp @@ -10,6 +10,7 @@ #include #include +#include "drivers/nvs.hpp" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -17,9 +18,7 @@ namespace drivers { class Samd { public: - static auto Create() -> Samd* { return new Samd(); } - - Samd(); + Samd(NvsStorage& nvs); ~Samd(); auto Version() -> std::string; @@ -37,8 +36,14 @@ class Samd { kChargingFast, // The battery is full charged, and we are still plugged in. kFullCharge, + // Charging failed. + kFault, + // The battery status returned isn't a known enum value. + kUnknown, }; + static auto chargeStatusToString(ChargeStatus) -> std::string; + auto GetChargeStatus() -> std::optional; auto UpdateChargeStatus() -> void; @@ -68,6 +73,8 @@ class Samd { Samd& operator=(const Samd&) = delete; private: + NvsStorage& nvs_; + uint8_t version_; std::optional charge_status_; UsbStatus usb_status_; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index e3c4aa06..6fac8c61 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -40,6 +40,7 @@ static constexpr char kKeyDisplayRows[] = "disprows"; static constexpr char kKeyHapticMotorType[] = "hapticmtype"; static constexpr char kKeyLraCalibration[] = "lra_cali"; static constexpr char kKeyDbAutoIndex[] = "dbautoindex"; +static constexpr char kKeyFastCharge[] = "fastchg"; static auto nvs_get_string(nvs_handle_t nvs, const char* key) -> std::optional { @@ -239,6 +240,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle) display_rows_(kKeyDisplayRows), haptic_motor_type_(kKeyHapticMotorType), lra_calibration_(kKeyLraCalibration), + fast_charge_(kKeyFastCharge), brightness_(kKeyBrightness), sensitivity_(kKeyScrollSensitivity), amp_max_vol_(kKeyAmpMaxVolume), @@ -444,6 +446,16 @@ auto NvsStorage::OutputMode(Output out) -> void { nvs_commit(handle_); } +auto NvsStorage::FastCharge() -> bool { + std::lock_guard lock{mutex_}; + return fast_charge_.get().value_or(true); +} + +auto NvsStorage::FastCharge(bool en) -> void { + std::lock_guard lock{mutex_}; + fast_charge_.set(en); +} + auto NvsStorage::ScreenBrightness() -> uint_fast8_t { std::lock_guard lock{mutex_}; return std::clamp(brightness_.get().value_or(50), 0, 100); diff --git a/src/drivers/samd.cpp b/src/drivers/samd.cpp index e4aa73ad..c2308760 100644 --- a/src/drivers/samd.cpp +++ b/src/drivers/samd.cpp @@ -5,11 +5,13 @@ */ #include "drivers/samd.hpp" +#include #include #include #include +#include "drivers/nvs.hpp" #include "esp_err.h" #include "esp_log.h" #include "hal/gpio_types.h" @@ -32,7 +34,29 @@ namespace drivers { static constexpr gpio_num_t kIntPin = GPIO_NUM_35; -Samd::Samd() { +auto Samd::chargeStatusToString(ChargeStatus status) -> std::string { + switch (status) { + case ChargeStatus::kNoBattery: + return "no_battery"; + case ChargeStatus::kBatteryCritical: + return "critical"; + case ChargeStatus::kDischarging: + return "discharging"; + case ChargeStatus::kChargingRegular: + return "charge_regular"; + case ChargeStatus::kChargingFast: + return "charge_fast"; + case ChargeStatus::kFullCharge: + return "full_charge"; + case ChargeStatus::kFault: + return "fault"; + case ChargeStatus::kUnknown: + default: + return "unknown"; + } +} + +Samd::Samd(NvsStorage& nvs) : nvs_(nvs) { gpio_set_direction(kIntPin, GPIO_MODE_INPUT); // Being able to interface with the SAMD properly is critical. To ensure we @@ -51,7 +75,7 @@ Samd::Samd() { UpdateChargeStatus(); UpdateUsbStatus(); - SetFastChargeEnabled(true); + SetFastChargeEnabled(nvs.FastCharge()); } Samd::~Samd() {} @@ -78,16 +102,38 @@ auto Samd::UpdateChargeStatus() -> void { return; } - // FIXME: Ideally we should be using the three 'charge status' bits to work - // out whether we're actually charging, or if we've got a full charge, - // critically low charge, etc. + // Lower two bits are the usb power status, next three are the BMS status. + // See 'gpio.c' in the SAMD21 firmware for how these bits get packed. + uint8_t charge_state = (raw_res & 0b11100) >> 2; uint8_t usb_state = raw_res & 0b11; - if (usb_state == 0) { - charge_status_ = ChargeStatus::kDischarging; - } else if (usb_state == 1) { - charge_status_ = ChargeStatus::kChargingRegular; - } else { - charge_status_ = ChargeStatus::kChargingFast; + switch (charge_state) { + case 0b000: + charge_status_ = ChargeStatus::kNoBattery; + break; + case 0b001: + // BMS says we're charging; work out how fast we're charging. + if (usb_state >= 0b10 && nvs_.FastCharge()) { + charge_status_ = ChargeStatus::kChargingFast; + } else { + charge_status_ = ChargeStatus::kChargingRegular; + } + break; + case 0b010: + charge_status_ = ChargeStatus::kFullCharge; + break; + case 0b011: + charge_status_ = ChargeStatus::kFault; + break; + case 0b100: + charge_status_ = ChargeStatus::kBatteryCritical; + break; + case 0b101: + charge_status_ = ChargeStatus::kDischarging; + break; + case 0b110: + case 0b111: + charge_status_ = ChargeStatus::kUnknown; + break; } } @@ -127,9 +173,15 @@ auto Samd::ResetToFlashSamd() -> void { } auto Samd::SetFastChargeEnabled(bool en) -> void { + // Always update NVS, so that the setting is right after the SAMD firmware is + // updated. + nvs_.FastCharge(en); + if (version_ < 4) { return; } + ESP_LOGI(kTag, "set fast charge %u", en); + I2CTransaction transaction; transaction.start() .write_addr(kAddress, I2C_MASTER_WRITE) -- cgit v1.2.3 From 1ff28233bd6a64fab97c56861477e122e4c3eac6 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 7 Aug 2024 12:09:23 +1000 Subject: Recalibrate the touchwheel after unlocking Also power it down whilst we're locked. This saves about half a milliamp. --- src/drivers/include/drivers/touchwheel.hpp | 3 ++- src/drivers/touchwheel.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/include/drivers/touchwheel.hpp b/src/drivers/include/drivers/touchwheel.hpp index 60902087..9cd925a6 100644 --- a/src/drivers/include/drivers/touchwheel.hpp +++ b/src/drivers/include/drivers/touchwheel.hpp @@ -39,7 +39,8 @@ class TouchWheel { auto Update() -> void; auto GetTouchWheelData() const -> TouchWheelData; - auto PowerDown() -> void; + auto Recalibrate() -> void; + auto LowPowerMode(bool en) -> void; private: TouchWheelData data_; diff --git a/src/drivers/touchwheel.cpp b/src/drivers/touchwheel.cpp index 5d55c6f2..402b839d 100644 --- a/src/drivers/touchwheel.cpp +++ b/src/drivers/touchwheel.cpp @@ -137,8 +137,12 @@ TouchWheelData TouchWheel::GetTouchWheelData() const { return data_; } -auto TouchWheel::PowerDown() -> void { - WriteRegister(LOW_POWER, 0); +auto TouchWheel::Recalibrate() -> void { + WriteRegister(CALIBRATE, 1); +} + +auto TouchWheel::LowPowerMode(bool en) -> void { + WriteRegister(LOW_POWER, en ? 0 : 1); } } // namespace drivers -- cgit v1.2.3 From d719f9c5017ad8006c21b6d546a5d70e846e9502 Mon Sep 17 00:00:00 2001 From: ailurux Date: Mon, 12 Aug 2024 03:19:03 +0000 Subject: daniel/theme-setting (#87) - Themes can be loaded from disk and built-in - Themes can be selected in a new themes menu of the settings screen - Some touch-ups to existing themes - The saved theme is persisted in nvs Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/87 Reviewed-by: cooljqln Co-authored-by: ailurux Co-committed-by: ailurux --- src/drivers/include/drivers/nvs.hpp | 5 +++++ src/drivers/nvs.cpp | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'src/drivers') diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index e298ffc3..e147c8c7 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -113,6 +113,9 @@ class NvsStorage { auto ScreenBrightness() -> uint_fast8_t; auto ScreenBrightness(uint_fast8_t) -> void; + auto InterfaceTheme() -> std::optional; + auto InterfaceTheme(std::string) -> void; + auto ScrollSensitivity() -> uint_fast8_t; auto ScrollSensitivity(uint_fast8_t) -> void; @@ -163,6 +166,8 @@ class NvsStorage { Setting input_mode_; Setting output_mode_; + Setting theme_; + Setting bt_preferred_; Setting> bt_names_; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index 6fac8c61..d004201b 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -29,6 +29,7 @@ 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 kKeyInterfaceTheme[] = "ui_theme"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpLeftBias[] = "hp_bias"; @@ -169,6 +170,31 @@ auto Setting>::store( nvs_set_blob(nvs, name_, encoded.data(), encoded.size()); } +template <> +auto Setting::store( + nvs_handle_t nvs, + std::string v) -> void { + cppbor::Tstr cbor{v}; + auto encoded = cbor.encode(); + nvs_set_blob(nvs, name_, encoded.data(), encoded.size()); +} + +template <> +auto Setting::load(nvs_handle_t nvs) + -> std::optional { + auto raw = nvs_get_string(nvs, name_); + if (!raw) { + return {}; + } + auto [parsed, unused, err] = cppbor::parseWithViews( + reinterpret_cast(raw->data()), raw->size()); + if (parsed->type() != cppbor::TSTR) { + return {}; + } + auto v = parsed->asViewTstr()->view(); + return std::string{v.begin(), v.end()}; +} + template <> auto Setting::load(nvs_handle_t nvs) -> std::optional { @@ -248,6 +274,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle) amp_left_bias_(kKeyAmpLeftBias), input_mode_(kKeyPrimaryInput), output_mode_(kKeyOutput), + theme_{kKeyInterfaceTheme}, bt_preferred_(kKeyBluetoothPreferred), bt_names_(kKeyBluetoothNames), db_auto_index_(kKeyDbAutoIndex), @@ -273,6 +300,7 @@ auto NvsStorage::Read() -> void { amp_left_bias_.read(handle_); input_mode_.read(handle_); output_mode_.read(handle_); + theme_.read(handle_); bt_preferred_.read(handle_); bt_names_.read(handle_); db_auto_index_.read(handle_); @@ -293,6 +321,7 @@ auto NvsStorage::Write() -> bool { amp_left_bias_.write(handle_); input_mode_.write(handle_); output_mode_.write(handle_); + theme_.write(handle_); bt_preferred_.write(handle_); bt_names_.write(handle_); db_auto_index_.write(handle_); @@ -466,6 +495,16 @@ auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> void { brightness_.set(val); } +auto NvsStorage::InterfaceTheme() -> std::optional { + std::lock_guard lock{mutex_}; + return theme_.get(); +} + +auto NvsStorage::InterfaceTheme(std::string themeFile) -> void { + std::lock_guard lock{mutex_}; + theme_.set(themeFile); +} + auto NvsStorage::ScrollSensitivity() -> uint_fast8_t { std::lock_guard lock{mutex_}; return std::clamp(sensitivity_.get().value_or(128), 0, 255); -- cgit v1.2.3 From 822c9dc93e868254059598ddeb58713135f0a4a1 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 13 Aug 2024 13:01:48 +1000 Subject: Fix build errors from stricter visibility requirements --- src/drivers/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/drivers') diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 33d25894..fea5780f 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -8,5 +8,5 @@ idf_component_register( "samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp" "pcm_buffer.cpp" INCLUDE_DIRS "include" REQUIRES "esp_adc" "fatfs" "result" "lvgl" "nvs_flash" "spiffs" "bt" - "tasks" "tinyfsm" "util" "libcppbor") + "tasks" "tinyfsm" "util" "libcppbor" "driver") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) -- cgit v1.2.3 From 6a2d259f46ac5ae9ede5733fb06cf93ca01fe162 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 13 Aug 2024 13:02:07 +1000 Subject: Move off of deprecated APIs --- src/drivers/bluetooth.cpp | 2 +- src/drivers/i2s_dac.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp index a0a318e9..8ec30395 100644 --- a/src/drivers/bluetooth.cpp +++ b/src/drivers/bluetooth.cpp @@ -528,7 +528,7 @@ void Disabled::react(const events::Enable&) { // Set a reasonable name for the device. std::pmr::string name = DeviceName(); - esp_bt_dev_set_device_name(name.c_str()); + esp_bt_gap_set_device_name(name.c_str()); // Initialise GAP. This controls advertising our device, and scanning for // other devices. diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp index b1044896..9c9bb793 100644 --- a/src/drivers/i2s_dac.cpp +++ b/src/drivers/i2s_dac.cpp @@ -46,12 +46,12 @@ extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle, if (event == nullptr || user_ctx == nullptr) { return false; } - if (event->data == nullptr || event->size == 0) { + if (event->dma_buf == nullptr || event->size == 0) { return false; } assert(event->size % 4 == 0); - uint8_t* buf = *reinterpret_cast(event->data); + uint8_t* buf = reinterpret_cast(event->dma_buf); auto* src = reinterpret_cast(user_ctx); BaseType_t ret = -- cgit v1.2.3 From 40c754a72a23a849321b60dbd77fa1303c77953b Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 14 Aug 2024 15:18:57 +1000 Subject: Always initialise bytes_cleared when clearing PcmBuffers --- src/drivers/pcm_buffer.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/pcm_buffer.cpp b/src/drivers/pcm_buffer.cpp index 3f4a0443..142a6376 100644 --- a/src/drivers/pcm_buffer.cpp +++ b/src/drivers/pcm_buffer.cpp @@ -66,10 +66,17 @@ IRAM_ATTR auto PcmBuffer::receive(std::span dest, bool isr) auto PcmBuffer::clear() -> void { while (!isEmpty()) { - size_t bytes_cleared; + size_t bytes_cleared = 0; void* data = xRingbufferReceive(ringbuf_, &bytes_cleared, 0); - vRingbufferReturnItem(ringbuf_, data); - received_ += bytes_cleared / sizeof(int16_t); + if (data) { + vRingbufferReturnItem(ringbuf_, data); + received_ += bytes_cleared / sizeof(int16_t); + } else { + // Defensively guard against looping forever if for some reason the + // buffer isn't draining. + ESP_LOGW(kTag, "PcmBuffer not draining"); + break; + } } } -- cgit v1.2.3 From 493f8e1200f73a921bf06a51fd1e6689396151ea Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 15 Aug 2024 11:44:49 +1000 Subject: Don't break early from clearing PcmBuffer --- src/drivers/pcm_buffer.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/pcm_buffer.cpp b/src/drivers/pcm_buffer.cpp index 142a6376..25762c50 100644 --- a/src/drivers/pcm_buffer.cpp +++ b/src/drivers/pcm_buffer.cpp @@ -71,11 +71,6 @@ auto PcmBuffer::clear() -> void { if (data) { vRingbufferReturnItem(ringbuf_, data); received_ += bytes_cleared / sizeof(int16_t); - } else { - // Defensively guard against looping forever if for some reason the - // buffer isn't draining. - ESP_LOGW(kTag, "PcmBuffer not draining"); - break; } } } -- cgit v1.2.3 From 275ade5d13f8e49e89ed14c7acff6644f5c644a1 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 26 Aug 2024 13:45:24 +1000 Subject: Move some hot driver functions into iram We've got the space for it now! Also turn SW radio coexistence off whilst we're here; the docs recommend this if you only use Bluetooth(R) --- src/drivers/display.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp index efc9df93..7321f20b 100644 --- a/src/drivers/display.cpp +++ b/src/drivers/display.cpp @@ -242,6 +242,7 @@ void Display::SendInitialisationSequence(const uint8_t* data) { spi_device_release_bus(handle_); } +IRAM_ATTR void Display::SendCommandWithData(uint8_t command, const uint8_t* data, size_t length) { @@ -249,17 +250,20 @@ void Display::SendCommandWithData(uint8_t command, SendData(data, length); } +IRAM_ATTR void Display::SendCmd(const uint8_t* data, size_t length) { SendTransaction(COMMAND, data, length); } +IRAM_ATTR void Display::SendData(const uint8_t* data, size_t length) { SendTransaction(DATA, data, length); } +IRAM_ATTR void Display::SendTransaction(TransactionType type, - const uint8_t* data, - size_t length) { + const uint8_t* data, + size_t length) { // TODO(jacqueline): What's sending this? if (length == 0) { return; @@ -290,6 +294,7 @@ void Display::SendTransaction(TransactionType type, ESP_ERROR_CHECK(spi_device_transmit(handle_, &sTransaction)); } +IRAM_ATTR void Display::OnLvglFlush(const lv_area_t* area, uint8_t* color_map) { // Swap the pixel byte order first, since we don't want to do this whilst // holding the SPI bus lock. -- cgit v1.2.3 From f253d2ee7568b61ce2fab962f7328a50e2da6adf Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 27 Aug 2024 21:17:53 +1000 Subject: Timeout when writing output samples throughout the audio pipeline This allows the audio pipeline to remain responsive even when the drain buffer has completely filled. This in turn means that you now see the track info in the 'now playing' screen change if the current track changes whilst you are paused. Since I was fucking around a lot in the audio processor anyway, I also added mono->stereo expansion so that playing mono tracks on Bluetooth no longer destroys your ears. --- src/drivers/include/drivers/pcm_buffer.hpp | 8 ++++++-- src/drivers/pcm_buffer.cpp | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/include/drivers/pcm_buffer.hpp b/src/drivers/include/drivers/pcm_buffer.hpp index 6630f720..8f53317e 100644 --- a/src/drivers/include/drivers/pcm_buffer.hpp +++ b/src/drivers/include/drivers/pcm_buffer.hpp @@ -28,8 +28,12 @@ class PcmBuffer { PcmBuffer(size_t size_in_samples); ~PcmBuffer(); - /* Adds samples to the buffer. */ - auto send(std::span) -> void; + /* + * Adds samples to the buffer. Returns the number of samples that were added, + * which may be less than the number of samples given if this PcmBuffer is + * close to full. + */ + auto send(std::span) -> size_t; /* * Fills the given span with samples. If enough samples are available in diff --git a/src/drivers/pcm_buffer.cpp b/src/drivers/pcm_buffer.cpp index 25762c50..071f5cea 100644 --- a/src/drivers/pcm_buffer.cpp +++ b/src/drivers/pcm_buffer.cpp @@ -17,6 +17,7 @@ #include "freertos/FreeRTOS.h" #include "esp_heap_caps.h" +#include "freertos/projdefs.h" #include "freertos/ringbuf.h" #include "portmacro.h" @@ -39,9 +40,13 @@ PcmBuffer::~PcmBuffer() { heap_caps_free(buf_); } -auto PcmBuffer::send(std::span data) -> void { - xRingbufferSend(ringbuf_, data.data(), data.size_bytes(), portMAX_DELAY); +auto PcmBuffer::send(std::span data) -> size_t { + if (!xRingbufferSend(ringbuf_, data.data(), data.size_bytes(), + pdMS_TO_TICKS(100))) { + return 0; + } sent_ += data.size(); + return data.size(); } IRAM_ATTR auto PcmBuffer::receive(std::span dest, bool isr) -- cgit v1.2.3 From 172d31ec6d69d7372d46e1ba712a2a2187e19d2a Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 6 Sep 2024 12:40:11 +1000 Subject: Ignore comments within playlist files Includes a general cleanup+restructure of playlist.cpp, and fixing the tests and benchmarks --- src/drivers/test/test_samd.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/drivers') diff --git a/src/drivers/test/test_samd.cpp b/src/drivers/test/test_samd.cpp index c466d88e..96248377 100644 --- a/src/drivers/test/test_samd.cpp +++ b/src/drivers/test/test_samd.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-only */ +#include "drivers/nvs.hpp" #include "drivers/samd.hpp" #include @@ -16,7 +17,8 @@ namespace drivers { TEST_CASE("samd21 interface", "[integration]") { I2CFixture i2c; - auto samd = std::make_unique(); + std::unique_ptr nvs{drivers::NvsStorage::OpenSync()}; + auto samd = std::make_unique(*nvs); REQUIRE(samd); -- cgit v1.2.3