daniel/bluetooth-avrc (#80)

Have a squizzy and lemme know if any issues @cooljqln 🐝

Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/80
Co-authored-by: ailurux <ailuruxx@gmail.com>
Co-committed-by: ailurux <ailuruxx@gmail.com>
custom
ailurux 11 months ago committed by cooljqln
parent 1242a199e3
commit 8de07fe8fa
  1. 143
      src/drivers/bluetooth.cpp
  2. 6
      src/drivers/include/drivers/bluetooth.hpp
  3. 19
      src/drivers/include/drivers/bluetooth_types.hpp
  4. 6
      src/tangara/audio/audio_events.hpp
  5. 34
      src/tangara/audio/audio_fsm.cpp
  6. 70
      src/tangara/ui/ui_fsm.cpp
  7. 1
      src/tangara/ui/ui_fsm.hpp

@ -73,6 +73,16 @@ auto avrcp_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param)
} }
} }
auto avrcp_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t* param)
-> void {
esp_avrc_tg_cb_param_t copy = *param;
sBgWorker->Dispatch<void>([=]() {
auto lock = bluetooth::BluetoothState::lock();
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
bluetooth::events::internal::Avrctg{.type = event, .param = copy});
});
}
auto a2dp_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) -> void { auto a2dp_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) -> void {
esp_a2d_cb_param_t copy = *param; esp_a2d_cb_param_t copy = *param;
sBgWorker->Dispatch<void>([=]() { sBgWorker->Dispatch<void>([=]() {
@ -229,7 +239,6 @@ auto Scanner::ScanOnce() -> void {
} }
auto Scanner::StopScanning() -> void { auto Scanner::StopScanning() -> void {
ESP_LOGI(kTag, "stopping scan");
enabled_ = false; enabled_ = false;
} }
@ -263,7 +272,7 @@ auto Scanner::HandleGapEvent(const events::internal::Gap& ev) -> void {
break; break;
case ESP_BT_GAP_MODE_CHG_EVT: case ESP_BT_GAP_MODE_CHG_EVT:
// todo: mode change. is this important? // todo: mode change. is this important?
ESP_LOGI(kTag, "GAP mode changed"); ESP_LOGI(kTag, "GAP mode changed %d", ev.param.mode_chg.mode);
break; break;
default: default:
ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type); ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type);
@ -390,7 +399,7 @@ auto BluetoothState::react(const events::DeviceDiscovered& ev) -> void {
} }
if (sEventHandler_ && !already_known) { if (sEventHandler_ && !already_known) {
std::invoke(sEventHandler_, Event::kKnownDevicesChanged); std::invoke(sEventHandler_, SimpleEvent::kKnownDevicesChanged);
} }
if (is_preferred && sPreferredDevice_) { if (is_preferred && sPreferredDevice_) {
@ -439,6 +448,7 @@ void Disabled::entry() {
esp_a2d_source_deinit(); esp_a2d_source_deinit();
esp_avrc_ct_deinit(); esp_avrc_ct_deinit();
esp_avrc_tg_deinit();
esp_bluedroid_disable(); esp_bluedroid_disable();
esp_bluedroid_deinit(); esp_bluedroid_deinit();
esp_bt_controller_disable(); esp_bt_controller_disable();
@ -480,11 +490,50 @@ void Disabled::react(const events::Enable&) {
// Initialise GAP. This controls advertising our device, and scanning for // Initialise GAP. This controls advertising our device, and scanning for
// other devices. // other devices.
esp_bt_gap_register_callback(gap_cb); err = esp_bt_gap_register_callback(gap_cb);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error initialising GAP: %s %d", esp_err_to_name(err), err);
}
// Initialise AVRCP. This handles playback controls; play/pause/volume/etc. // Initialise AVRCP. This handles playback controls; play/pause/volume/etc.
esp_avrc_ct_register_callback(avrcp_cb); err = esp_avrc_ct_init();
esp_avrc_ct_init(); if (err != ESP_OK) {
ESP_LOGE(kTag, "Error initialising AVRC: %s %d", esp_err_to_name(err), err);
}
err = esp_avrc_ct_register_callback(avrcp_cb);
if (err != ESP_OK) {
ESP_LOGE(kTag, "Error registering AVRC: %s %d", esp_err_to_name(err), err);
}
// 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);
}
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);
}
// Set the supported passthrough commands on the tg
esp_avrc_psth_bit_mask_t psth;
// Retry this until successful
// this indicates that the bt stack is ready
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);
} while (err != ESP_OK);
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);
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK);
// Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC // Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC
// encoder only supports 2 channels of interleaved 16 bit samples, at // encoder only supports 2 channels of interleaved 16 bit samples, at
@ -552,7 +601,7 @@ void Connecting::entry() {
sScanner_->StopScanning(); sScanner_->StopScanning();
if (sEventHandler_) { if (sEventHandler_) {
std::invoke(sEventHandler_, Event::kConnectionStateChanged); std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
} }
} }
@ -560,7 +609,7 @@ void Connecting::exit() {
xTimerDelete(sTimeoutTimer, portMAX_DELAY); xTimerDelete(sTimeoutTimer, portMAX_DELAY);
if (sEventHandler_) { if (sEventHandler_) {
std::invoke(sEventHandler_, Event::kConnectionStateChanged); std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
} }
} }
@ -726,7 +775,10 @@ void Connected::react(events::internal::Avrc ev) {
switch (ev.type) { switch (ev.type) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT: case ESP_AVRC_CT_CONNECTION_STATE_EVT:
if (ev.param.conn_stat.connected) { if (ev.param.conn_stat.connected) {
// TODO: tell the target about our capabilities 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);
}
} }
// Don't worry about disconnect events; if there's a serious problem // Don't worry about disconnect events; if there's a serious problem
// then the entire bluetooth connection will drop out, which is handled // then the entire bluetooth connection will drop out, which is handled
@ -735,9 +787,80 @@ void Connected::react(events::internal::Avrc ev) {
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
// The remote device is telling us about its capabilities! We don't // The remote device is telling us about its capabilities! We don't
// currently care about any of them. // currently care about any of them.
ESP_LOGI(kTag, "Recieved capabilitites: %lu", ev.param.rmt_feats.feat_mask);
break; 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});
}
// Resubscribe to volume facts
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);
}
}
break;
default:
ESP_LOGI(kTag, "unhandled AVRC event: %u", ev.type);
}
}
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");
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);
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);
if (ev.param.psth_cmd.key_state == 1 && sEventHandler_) {
switch (ev.param.psth_cmd.key_code) {
case ESP_AVRC_PT_CMD_PLAY:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kPlayPause);
break;
case ESP_AVRC_PT_CMD_PAUSE:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kPlayPause);
break;
case ESP_AVRC_PT_CMD_STOP:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kStop);
break;
case ESP_AVRC_PT_CMD_MUTE:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kMute);
break;
case ESP_AVRC_PT_CMD_FORWARD:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kForward);
break;
case ESP_AVRC_PT_CMD_BACKWARD:
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kBackward);
break;
default:
ESP_LOGI(kTag, "Unhandled passthrough cmd. Key code: %d", ev.param.psth_cmd.key_code);
}
}
break;
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: {
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;
auto err = esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE,
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);
}
break;
}
default: default:
ESP_LOGW(kTag, "unhandled AVRC event: %u", ev.type); ESP_LOGW(kTag, "unhandled AVRC TG event: %u", ev.type);
} }
} }

@ -75,6 +75,10 @@ struct Avrc : public tinyfsm::Event {
esp_avrc_ct_cb_event_t type; esp_avrc_ct_cb_event_t type;
esp_avrc_ct_cb_param_t param; esp_avrc_ct_cb_param_t param;
}; };
struct Avrctg : public tinyfsm::Event {
esp_avrc_tg_cb_event_t type;
esp_avrc_tg_cb_param_t param;
};
} // namespace internal } // namespace internal
} // namespace events } // namespace events
@ -135,6 +139,7 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
virtual void react(events::internal::Gap ev) = 0; virtual void react(events::internal::Gap ev) = 0;
virtual void react(events::internal::A2dp ev){}; virtual void react(events::internal::A2dp ev){};
virtual void react(events::internal::Avrc ev){}; virtual void react(events::internal::Avrc ev){};
virtual void react(events::internal::Avrctg ev){};
protected: protected:
static NvsStorage* sStorage_; static NvsStorage* sStorage_;
@ -206,6 +211,7 @@ class Connected : public BluetoothState {
void react(events::internal::Gap ev) override; void react(events::internal::Gap ev) override;
void react(events::internal::A2dp ev) override; void react(events::internal::A2dp ev) override;
void react(events::internal::Avrc ev) override; void react(events::internal::Avrc ev) override;
void react(const events::internal::Avrctg ev) override;
using BluetoothState::react; using BluetoothState::react;

@ -5,6 +5,7 @@
#include <string> #include <string>
#include "memory_resource.hpp" #include "memory_resource.hpp"
#include <variant>
namespace drivers { namespace drivers {
namespace bluetooth { namespace bluetooth {
@ -25,11 +26,27 @@ struct Device {
int8_t signal_strength; int8_t signal_strength;
}; };
enum class Event { enum class SimpleEvent {
kKnownDevicesChanged, kKnownDevicesChanged,
kConnectionStateChanged, kConnectionStateChanged,
kPreferredDeviceChanged, kPreferredDeviceChanged,
// Passthrough events
kPlayPause,
kStop,
kMute,
kVolUp,
kVolDown,
kForward,
kBackward,
kRewind,
kFastForward,
}; };
struct RemoteVolumeChanged {
uint8_t new_vol;
};
using Event = std::variant<SimpleEvent, RemoteVolumeChanged>;
} // namespace bluetooth } // namespace bluetooth
} // namespace drivers } // namespace drivers

@ -116,6 +116,12 @@ struct SetVolumeBalance : tinyfsm::Event {
int left_bias; int left_bias;
}; };
/*
Event emitted when the hardware volume for a connected Bluetooth device has changed.
*/
struct RemoteVolumeChanged : tinyfsm::Event {
uint_fast8_t value; // 0..127
};
struct VolumeChanged : tinyfsm::Event { struct VolumeChanged : tinyfsm::Event {
uint_fast8_t percent; uint_fast8_t percent;
int db; int db;

@ -217,18 +217,32 @@ void AudioState::react(const internal::StreamEnded& ev) {
} }
void AudioState::react(const system_fsm::BluetoothEvent& ev) { void AudioState::react(const system_fsm::BluetoothEvent& ev) {
if (ev.event != drivers::bluetooth::Event::kConnectionStateChanged) { using drivers::bluetooth::SimpleEvent;
return; if (std::holds_alternative<SimpleEvent>(ev.event)) {
auto simpleEvent = std::get<SimpleEvent>(ev.event);
switch (simpleEvent) {
case SimpleEvent::kConnectionStateChanged: {
auto dev = sServices->bluetooth().ConnectedDevice();
if (!dev) {
return;
}
sBtOutput->SetVolume(sServices->nvs().BluetoothVolume(dev->mac));
events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
});
break;
}
default:
break;
}
} }
auto dev = sServices->bluetooth().ConnectedDevice(); if (std::holds_alternative<drivers::bluetooth::RemoteVolumeChanged>(ev.event)) {
if (!dev) { auto volume_chg = std::get<drivers::bluetooth::RemoteVolumeChanged>(ev.event).new_vol;
return; events::Ui().Dispatch(RemoteVolumeChanged{
.value = volume_chg
});
} }
sBtOutput->SetVolume(sServices->nvs().BluetoothVolume(dev->mac));
events::Ui().Dispatch(VolumeChanged{
.percent = sOutput->GetVolumePct(),
.db = sOutput->GetVolumeDb(),
});
} }
void AudioState::react(const StepUpVolume& ev) { void AudioState::react(const StepUpVolume& ev) {

@ -387,6 +387,10 @@ void UiState::react(const audio::VolumeChanged& ev) {
sVolumeCurrentDb.setDirect(static_cast<int>(ev.db)); sVolumeCurrentDb.setDirect(static_cast<int>(ev.db));
} }
void UiState::react(const audio::RemoteVolumeChanged& ev) {
// TODO: Show dialog
}
void UiState::react(const audio::VolumeBalanceChanged& ev) { void UiState::react(const audio::VolumeBalanceChanged& ev) {
sVolumeLeftBias.setDirect(ev.left_bias); sVolumeLeftBias.setDirect(ev.left_bias);
} }
@ -396,27 +400,55 @@ void UiState::react(const audio::VolumeLimitChanged& ev) {
} }
void UiState::react(const system_fsm::BluetoothEvent& ev) { void UiState::react(const system_fsm::BluetoothEvent& ev) {
using drivers::bluetooth::SimpleEvent;
auto bt = sServices->bluetooth(); auto bt = sServices->bluetooth();
auto dev = bt.ConnectedDevice(); auto dev = bt.ConnectedDevice();
switch (ev.event) { if (std::holds_alternative<SimpleEvent>(ev.event)) {
case drivers::bluetooth::Event::kKnownDevicesChanged: switch (std::get<SimpleEvent>(ev.event)) {
sBluetoothDevices.setDirect(bt.KnownDevices()); case SimpleEvent::kPlayPause:
break; events::Audio().Dispatch(audio::TogglePlayPause{});
case drivers::bluetooth::Event::kConnectionStateChanged: break;
sBluetoothConnected.setDirect(bt.IsConnected()); case SimpleEvent::kStop:
if (dev) { events::Audio().Dispatch(audio::TogglePlayPause{.set_to = false});
sBluetoothPairedDevice.setDirect(drivers::bluetooth::Device{ break;
.address = dev->mac, case SimpleEvent::kMute:
.name = {dev->name.data(), dev->name.size()}, break;
.class_of_device = 0, case SimpleEvent::kVolUp:
.signal_strength = 0, break;
}); case SimpleEvent::kVolDown:
} else { break;
sBluetoothPairedDevice.setDirect(std::monostate{}); case SimpleEvent::kForward:
} break;
break; case SimpleEvent::kBackward:
case drivers::bluetooth::Event::kPreferredDeviceChanged: break;
break; case SimpleEvent::kRewind:
break;
case SimpleEvent::kFastForward:
break;
case SimpleEvent::kKnownDevicesChanged:
sBluetoothDevices.setDirect(bt.KnownDevices());
break;
case SimpleEvent::kConnectionStateChanged:
sBluetoothConnected.setDirect(bt.IsConnected());
if (dev) {
sBluetoothPairedDevice.setDirect(drivers::bluetooth::Device{
.address = dev->mac,
.name = {dev->name.data(), dev->name.size()},
.class_of_device = 0,
.signal_strength = 0,
});
} else {
sBluetoothPairedDevice.setDirect(std::monostate{});
}
break;
case SimpleEvent::kPreferredDeviceChanged:
break;
default:
break;
}
} else if (std::holds_alternative<drivers::bluetooth::RemoteVolumeChanged>(ev.event)) {
// 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);
} }
} }

@ -67,6 +67,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
void react(const audio::VolumeChanged&); void react(const audio::VolumeChanged&);
void react(const audio::VolumeBalanceChanged&); void react(const audio::VolumeBalanceChanged&);
void react(const audio::VolumeLimitChanged&); void react(const audio::VolumeLimitChanged&);
void react(const audio::RemoteVolumeChanged& ev);
void react(const system_fsm::KeyLockChanged&); void react(const system_fsm::KeyLockChanged&);
void react(const system_fsm::SamdUsbStatusChanged&); void react(const system_fsm::SamdUsbStatusChanged&);

Loading…
Cancel
Save