|
|
@ -23,6 +23,7 @@ |
|
|
|
#include "esp_wifi.h" |
|
|
|
#include "esp_wifi.h" |
|
|
|
#include "esp_wifi_types.h" |
|
|
|
#include "esp_wifi_types.h" |
|
|
|
#include "freertos/portmacro.h" |
|
|
|
#include "freertos/portmacro.h" |
|
|
|
|
|
|
|
#include "freertos/projdefs.h" |
|
|
|
#include "memory_resource.hpp" |
|
|
|
#include "memory_resource.hpp" |
|
|
|
#include "nvs.hpp" |
|
|
|
#include "nvs.hpp" |
|
|
|
#include "tinyfsm/include/tinyfsm.hpp" |
|
|
|
#include "tinyfsm/include/tinyfsm.hpp" |
|
|
@ -89,8 +90,11 @@ auto Bluetooth::ConnectedDevice() -> std::optional<bluetooth::Device> { |
|
|
|
return {}; |
|
|
|
return {}; |
|
|
|
} |
|
|
|
} |
|
|
|
auto looking_for = bluetooth::BluetoothState::preferred_device(); |
|
|
|
auto looking_for = bluetooth::BluetoothState::preferred_device(); |
|
|
|
|
|
|
|
if (!looking_for) { |
|
|
|
|
|
|
|
return {}; |
|
|
|
|
|
|
|
} |
|
|
|
for (const auto& dev : bluetooth::BluetoothState::devices()) { |
|
|
|
for (const auto& dev : bluetooth::BluetoothState::devices()) { |
|
|
|
if (dev.address == looking_for) { |
|
|
|
if (dev.address == looking_for->mac) { |
|
|
|
return dev; |
|
|
|
return dev; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -118,16 +122,21 @@ auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> { |
|
|
|
return out; |
|
|
|
return out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto Bluetooth::SetPreferredDevice(const bluetooth::mac_addr_t& mac) -> void { |
|
|
|
auto Bluetooth::SetPreferredDevice(std::optional<bluetooth::MacAndName> dev) |
|
|
|
if (mac == bluetooth::BluetoothState::preferred_device()) { |
|
|
|
-> void { |
|
|
|
|
|
|
|
auto cur = bluetooth::BluetoothState::preferred_device(); |
|
|
|
|
|
|
|
if (dev && cur && dev->mac == cur->mac) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
bluetooth::BluetoothState::preferred_device(mac); |
|
|
|
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( |
|
|
|
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch( |
|
|
|
bluetooth::events::PreferredDeviceChanged{}); |
|
|
|
bluetooth::events::PreferredDeviceChanged{}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto Bluetooth::PreferredDevice() -> std::optional<bluetooth::mac_addr_t> { |
|
|
|
auto Bluetooth::PreferredDevice() -> std::optional<bluetooth::MacAndName> { |
|
|
|
return bluetooth::BluetoothState::preferred_device(); |
|
|
|
return bluetooth::BluetoothState::preferred_device(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -291,8 +300,8 @@ Scanner* BluetoothState::sScanner_; |
|
|
|
|
|
|
|
|
|
|
|
std::mutex BluetoothState::sDevicesMutex_{}; |
|
|
|
std::mutex BluetoothState::sDevicesMutex_{}; |
|
|
|
std::map<mac_addr_t, Device> BluetoothState::sDevices_{}; |
|
|
|
std::map<mac_addr_t, Device> BluetoothState::sDevices_{}; |
|
|
|
std::optional<mac_addr_t> BluetoothState::sPreferredDevice_{}; |
|
|
|
std::optional<MacAndName> BluetoothState::sPreferredDevice_{}; |
|
|
|
std::optional<Device> BluetoothState::sCurrentDevice_{}; |
|
|
|
std::optional<MacAndName> BluetoothState::sConnectingDevice_{}; |
|
|
|
bool BluetoothState::sIsDiscoveryAllowed_{false}; |
|
|
|
bool BluetoothState::sIsDiscoveryAllowed_{false}; |
|
|
|
|
|
|
|
|
|
|
|
std::atomic<StreamBufferHandle_t> BluetoothState::sSource_; |
|
|
|
std::atomic<StreamBufferHandle_t> BluetoothState::sSource_; |
|
|
@ -313,12 +322,12 @@ auto BluetoothState::devices() -> std::vector<Device> { |
|
|
|
return out; |
|
|
|
return out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::preferred_device() -> std::optional<mac_addr_t> { |
|
|
|
auto BluetoothState::preferred_device() -> std::optional<MacAndName> { |
|
|
|
std::lock_guard lock{sDevicesMutex_}; |
|
|
|
std::lock_guard lock{sDevicesMutex_}; |
|
|
|
return sPreferredDevice_; |
|
|
|
return sPreferredDevice_; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::preferred_device(std::optional<mac_addr_t> addr) -> void { |
|
|
|
auto BluetoothState::preferred_device(std::optional<MacAndName> addr) -> void { |
|
|
|
std::lock_guard lock{sDevicesMutex_}; |
|
|
|
std::lock_guard lock{sDevicesMutex_}; |
|
|
|
sPreferredDevice_ = addr; |
|
|
|
sPreferredDevice_ = addr; |
|
|
|
} |
|
|
|
} |
|
|
@ -349,18 +358,18 @@ auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::react(const events::DeviceDiscovered& ev) -> void { |
|
|
|
auto BluetoothState::react(const events::DeviceDiscovered& ev) -> void { |
|
|
|
ESP_LOGI(kTag, "discovered device %s", ev.device.name.c_str()); |
|
|
|
|
|
|
|
bool is_preferred = false; |
|
|
|
bool is_preferred = false; |
|
|
|
{ |
|
|
|
{ |
|
|
|
std::lock_guard<std::mutex> lock{sDevicesMutex_}; |
|
|
|
std::lock_guard<std::mutex> lock{sDevicesMutex_}; |
|
|
|
|
|
|
|
bool already_known = sDevices_.contains(ev.device.address); |
|
|
|
sDevices_[ev.device.address] = ev.device; |
|
|
|
sDevices_[ev.device.address] = ev.device; |
|
|
|
|
|
|
|
|
|
|
|
if (ev.device.address == sPreferredDevice_) { |
|
|
|
if (sPreferredDevice_ && ev.device.address == sPreferredDevice_->mac) { |
|
|
|
sCurrentDevice_ = ev.device; |
|
|
|
sConnectingDevice_ = sPreferredDevice_; |
|
|
|
is_preferred = true; |
|
|
|
is_preferred = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (sEventHandler_) { |
|
|
|
if (sEventHandler_ && !already_known) { |
|
|
|
std::invoke(sEventHandler_, Event::kKnownDevicesChanged); |
|
|
|
std::invoke(sEventHandler_, Event::kKnownDevicesChanged); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -395,39 +404,39 @@ void Disabled::entry() { |
|
|
|
esp_bluedroid_disable(); |
|
|
|
esp_bluedroid_disable(); |
|
|
|
esp_bluedroid_deinit(); |
|
|
|
esp_bluedroid_deinit(); |
|
|
|
esp_bt_controller_disable(); |
|
|
|
esp_bt_controller_disable(); |
|
|
|
|
|
|
|
esp_bt_controller_deinit(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Disabled::exit() { |
|
|
|
void Disabled::exit() { |
|
|
|
if (sIsDiscoveryAllowed_) { |
|
|
|
if (sIsDiscoveryAllowed_) { |
|
|
|
ESP_LOGI(kTag, "bt enabled, beginning discovery"); |
|
|
|
ESP_LOGI(kTag, "bt enabled, beginning discovery"); |
|
|
|
sScanner_->ScanContinuously(); |
|
|
|
sScanner_->ScanContinuously(); |
|
|
|
} else if (sPreferredDevice_) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "bt enabled, checking for preferred device"); |
|
|
|
|
|
|
|
sScanner_->ScanOnce(); |
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
ESP_LOGI(kTag, "bt enabled, but not scanning"); |
|
|
|
ESP_LOGI(kTag, "bt enabled, scanning once"); |
|
|
|
|
|
|
|
sScanner_->ScanOnce(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Disabled::react(const events::Enable&) { |
|
|
|
void Disabled::react(const events::Enable&) { |
|
|
|
esp_bt_controller_config_t config = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); |
|
|
|
esp_bt_controller_config_t config = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); |
|
|
|
if (esp_bt_controller_init(&config) != ESP_OK) { |
|
|
|
esp_err_t err; |
|
|
|
ESP_LOGE(kTag, "initialize controller failed"); |
|
|
|
if ((err = esp_bt_controller_init(&config) != ESP_OK)) { |
|
|
|
|
|
|
|
ESP_LOGE(kTag, "initialize controller failed %s", esp_err_to_name(err)); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { |
|
|
|
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK)) { |
|
|
|
ESP_LOGE(kTag, "enable controller failed"); |
|
|
|
ESP_LOGE(kTag, "enable controller failed %s", esp_err_to_name(err)); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (esp_bluedroid_init() != ESP_OK) { |
|
|
|
if ((err = esp_bluedroid_init() != ESP_OK)) { |
|
|
|
ESP_LOGE(kTag, "initialize bluedroid failed"); |
|
|
|
ESP_LOGE(kTag, "initialize bluedroid failed %s", esp_err_to_name(err)); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (esp_bluedroid_enable() != ESP_OK) { |
|
|
|
if ((err = esp_bluedroid_enable() != ESP_OK)) { |
|
|
|
ESP_LOGE(kTag, "enable bluedroid failed"); |
|
|
|
ESP_LOGE(kTag, "enable bluedroid failed %s", esp_err_to_name(err)); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -459,7 +468,12 @@ void Disabled::react(const events::Enable&) { |
|
|
|
// Don't let anyone interact with us before we're ready.
|
|
|
|
// Don't let anyone interact with us before we're ready.
|
|
|
|
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); |
|
|
|
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (sPreferredDevice_) { |
|
|
|
|
|
|
|
sConnectingDevice_ = sPreferredDevice_; |
|
|
|
|
|
|
|
transit<Connecting>(); |
|
|
|
|
|
|
|
} else { |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Idle::entry() { |
|
|
|
void Idle::entry() { |
|
|
@ -474,12 +488,12 @@ void Idle::react(const events::PreferredDeviceChanged& ev) { |
|
|
|
bool is_discovered = false; |
|
|
|
bool is_discovered = false; |
|
|
|
{ |
|
|
|
{ |
|
|
|
std::lock_guard<std::mutex> lock{sDevicesMutex_}; |
|
|
|
std::lock_guard<std::mutex> lock{sDevicesMutex_}; |
|
|
|
if (sPreferredDevice_ && sDevices_.contains(sPreferredDevice_.value())) { |
|
|
|
if (sPreferredDevice_ && sDevices_.contains(sPreferredDevice_->mac)) { |
|
|
|
is_discovered = true; |
|
|
|
is_discovered = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (is_discovered) { |
|
|
|
if (is_discovered) { |
|
|
|
ESP_LOGI(kTag, "selected known device"); |
|
|
|
sConnectingDevice_ = sPreferredDevice_; |
|
|
|
transit<Connecting>(); |
|
|
|
transit<Connecting>(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -489,8 +503,13 @@ void Idle::react(const events::internal::Gap& ev) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connecting::entry() { |
|
|
|
void Connecting::entry() { |
|
|
|
ESP_LOGI(kTag, "connecting to device"); |
|
|
|
sScanner_->StopScanning(); |
|
|
|
esp_a2d_source_connect(sPreferredDevice_.value().data()); |
|
|
|
|
|
|
|
|
|
|
|
auto dev = sConnectingDevice_; |
|
|
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
esp_a2d_source_connect(sConnectingDevice_->mac.data()); |
|
|
|
|
|
|
|
|
|
|
|
if (sEventHandler_) { |
|
|
|
if (sEventHandler_) { |
|
|
|
std::invoke(sEventHandler_, Event::kConnectionStateChanged); |
|
|
|
std::invoke(sEventHandler_, Event::kConnectionStateChanged); |
|
|
@ -498,6 +517,7 @@ void Connecting::entry() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connecting::exit() { |
|
|
|
void Connecting::exit() { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "connecting finished"); |
|
|
|
if (sEventHandler_) { |
|
|
|
if (sEventHandler_) { |
|
|
|
std::invoke(sEventHandler_, Event::kConnectionStateChanged); |
|
|
|
std::invoke(sEventHandler_, Event::kConnectionStateChanged); |
|
|
|
} |
|
|
|
} |
|
|
@ -518,7 +538,7 @@ void Connecting::react(const events::internal::Gap& ev) { |
|
|
|
case ESP_BT_GAP_AUTH_CMPL_EVT: |
|
|
|
case ESP_BT_GAP_AUTH_CMPL_EVT: |
|
|
|
if (ev.param->auth_cmpl.stat != ESP_BT_STATUS_SUCCESS) { |
|
|
|
if (ev.param->auth_cmpl.stat != ESP_BT_STATUS_SUCCESS) { |
|
|
|
ESP_LOGE(kTag, "auth failed"); |
|
|
|
ESP_LOGE(kTag, "auth failed"); |
|
|
|
sPreferredDevice_ = {}; |
|
|
|
sConnectingDevice_ = {}; |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
@ -528,22 +548,22 @@ void Connecting::react(const events::internal::Gap& ev) { |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_BT_GAP_PIN_REQ_EVT: |
|
|
|
case ESP_BT_GAP_PIN_REQ_EVT: |
|
|
|
ESP_LOGW(kTag, "device needs a pin to connect"); |
|
|
|
ESP_LOGW(kTag, "device needs a pin to connect"); |
|
|
|
sPreferredDevice_ = {}; |
|
|
|
sConnectingDevice_ = {}; |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_BT_GAP_CFM_REQ_EVT: |
|
|
|
case ESP_BT_GAP_CFM_REQ_EVT: |
|
|
|
ESP_LOGW(kTag, "user needs to do cfm. idk man."); |
|
|
|
ESP_LOGW(kTag, "user needs to do cfm. idk man."); |
|
|
|
sPreferredDevice_ = {}; |
|
|
|
sConnectingDevice_ = {}; |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_BT_GAP_KEY_NOTIF_EVT: |
|
|
|
case ESP_BT_GAP_KEY_NOTIF_EVT: |
|
|
|
ESP_LOGW(kTag, "the device is telling us a password??"); |
|
|
|
ESP_LOGW(kTag, "the device is telling us a password??"); |
|
|
|
sPreferredDevice_ = {}; |
|
|
|
sConnectingDevice_ = {}; |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_BT_GAP_KEY_REQ_EVT: |
|
|
|
case ESP_BT_GAP_KEY_REQ_EVT: |
|
|
|
ESP_LOGW(kTag, "the device wants a password!"); |
|
|
|
ESP_LOGW(kTag, "the device wants a password!"); |
|
|
|
sPreferredDevice_ = {}; |
|
|
|
sConnectingDevice_ = {}; |
|
|
|
transit<Idle>(); |
|
|
|
transit<Idle>(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_BT_GAP_MODE_CHG_EVT: |
|
|
|
case ESP_BT_GAP_MODE_CHG_EVT: |
|
|
@ -583,8 +603,13 @@ void Connecting::react(const events::internal::A2dp& ev) { |
|
|
|
void Connected::entry() { |
|
|
|
void Connected::entry() { |
|
|
|
ESP_LOGI(kTag, "entering connected state"); |
|
|
|
ESP_LOGI(kTag, "entering connected state"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
connected_to_ = sConnectingDevice_->mac; |
|
|
|
|
|
|
|
sPreferredDevice_ = sConnectingDevice_; |
|
|
|
|
|
|
|
sConnectingDevice_ = {}; |
|
|
|
|
|
|
|
|
|
|
|
auto stored_pref = sStorage_->PreferredBluetoothDevice(); |
|
|
|
auto stored_pref = sStorage_->PreferredBluetoothDevice(); |
|
|
|
if (stored_pref != sPreferredDevice_) { |
|
|
|
if (!stored_pref || (sPreferredDevice_->name != stored_pref->name || |
|
|
|
|
|
|
|
sPreferredDevice_->mac != stored_pref->mac)) { |
|
|
|
sStorage_->PreferredBluetoothDevice(sPreferredDevice_); |
|
|
|
sStorage_->PreferredBluetoothDevice(sPreferredDevice_); |
|
|
|
} |
|
|
|
} |
|
|
|
// TODO: if we already have a source, immediately start playing
|
|
|
|
// TODO: if we already have a source, immediately start playing
|
|
|
@ -592,15 +617,16 @@ void Connected::entry() { |
|
|
|
|
|
|
|
|
|
|
|
void Connected::exit() { |
|
|
|
void Connected::exit() { |
|
|
|
ESP_LOGI(kTag, "exiting connected state"); |
|
|
|
ESP_LOGI(kTag, "exiting connected state"); |
|
|
|
|
|
|
|
esp_a2d_source_disconnect(connected_to_.data()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connected::react(const events::Disable& ev) { |
|
|
|
void Connected::react(const events::Disable& ev) { |
|
|
|
// TODO: disconnect gracefully
|
|
|
|
|
|
|
|
transit<Disabled>(); |
|
|
|
transit<Disabled>(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connected::react(const events::PreferredDeviceChanged& ev) { |
|
|
|
void Connected::react(const events::PreferredDeviceChanged& ev) { |
|
|
|
// TODO: disconnect, move to connecting? or scanning?
|
|
|
|
sConnectingDevice_ = sPreferredDevice_; |
|
|
|
|
|
|
|
transit<Connecting>(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connected::react(const events::SourceChanged& ev) { |
|
|
|
void Connected::react(const events::SourceChanged& ev) { |
|
|
|