Refactored and removed unneeded parts

This commit is contained in:
Dreaded_X 2022-05-21 00:53:42 +02:00
parent b6882cd635
commit 3387c9edc2
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
21 changed files with 680 additions and 906 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
tools
receiver/build
receiver/.cache
sdkconfig.old

View File

@ -1,3 +1,3 @@
idf_component_register(SRCS "main.cpp" "app.cpp" "helper.cpp"
idf_component_register(SRCS "main.cpp" "helper.cpp" "wav.cpp" "storage.cpp" "i2s.cpp" "bluetooth.cpp" "avrcp.cpp" "a2dp.cpp"
INCLUDE_DIRS "."
EMBED_FILES "connect.wav" "disconnect.wav")

166
receiver/main/a2dp.cpp Normal file
View File

@ -0,0 +1,166 @@
#include "esp_log.h"
#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"
#include "driver/i2s.h"
#include "a2dp.h"
#include "helper.h"
#include "wav.h"
#include "storage.h"
#include "i2s.h"
#include "bluetooth.h"
#define A2DP_TAG "APP_A2DP"
static void handle_connection_state(uint16_t event, esp_a2d_cb_param_t* a2d) {
ESP_LOGI(A2DP_TAG, "partner address: %s", addr_to_str(a2d->conn_stat.remote_bda));
ESP_LOGI(A2DP_TAG, "A2DP connection state: %s, [%s]", connection_state_to_str(a2d->conn_stat.state), addr_to_str(a2d->conn_stat.remote_bda));
static int retry_count = 0;
static bool was_connected = false;
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_DISCONNECTED");
if (a2d->conn_stat.disc_rsn == ESP_A2D_DISC_RSN_ABNORMAL && retry_count < 3) {
ESP_LOGI(A2DP_TAG,"Connection try number: %d", retry_count);
a2dp::connect_to_last();
} else {
bluetooth::set_scan_mode(true);
if (was_connected) {
vTaskDelay(300 / portTICK_PERIOD_MS);
WAV_PLAY(disconnect);
}
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTED");
bluetooth::set_scan_mode(false);
retry_count = 0;
was_connected = true;
WAV_PLAY(connect);
// record current connection
nvs::set_last_connection(a2d->conn_stat.remote_bda);
if (esp_bt_gap_read_remote_name(a2d->conn_stat.remote_bda) != ESP_OK) {
ESP_LOGE(A2DP_TAG, "esp_bt_gap_read_remote_name failed");
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING){
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTING");
retry_count++;
}
}
static void handle_audio_cfg(uint16_t event, esp_a2d_cb_param_t* a2d) {
ESP_LOGI(A2DP_TAG, "a2dp audio_cfg_cb , codec type %d", a2d->audio_cfg.mcc.type);
char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
if (oct0 & (0x01 << 6)) {
i2s::set_sample_rate(32000);
} else if (oct0 & (0x01 << 5)) {
i2s::set_sample_rate(44100);
} else if (oct0 & (0x01 << 4)) {
i2s::set_sample_rate(48000);
} else {
i2s::set_sample_rate(16000);
}
ESP_LOGI(A2DP_TAG, "a2dp audio_cfg_cb , sample_rate %d", i2s::get_sample_rate());
}
static void a2d_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* a2d) {
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_CONNECTION_STATE_EVT", __func__);
handle_connection_state(event, a2d);
break;
case ESP_A2D_AUDIO_STATE_EVT:
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_AUDIO_STATE_EVT", __func__);
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_AUDIO_CFG_EVT", __func__);
handle_audio_cfg(event, a2d);
break;
case ESP_A2D_PROF_STATE_EVT:
if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state) {
ESP_LOGI(A2DP_TAG,"A2DP PROF STATE: Init Compl\n");
} else {
ESP_LOGI(A2DP_TAG,"A2DP PROF STATE: Deinit Compl\n");
}
break;
default:
ESP_LOGE(A2DP_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
/**
* @brief Utility structure that can be used to split a int32_t up into 2 separate channels with int16_t data.
* @author Phil Schatzmann
* @copyright Apache License Version 2
*/
struct __attribute__((packed)) Frame {
int16_t channel1;
int16_t channel2;
Frame(int v=0){
channel1 = channel2 = v;
}
Frame(int ch1, int ch2){
channel1 = ch1;
channel2 = ch2;
}
};
static void audio_data_callback(const uint8_t* data, uint32_t len) {
Frame* frame = (Frame*)data;
for (int i = 0; i < len/4; i++) {
int16_t temp = frame[i].channel1;
frame[i].channel1 = frame[i].channel2;
frame[i].channel2 = temp;
}
size_t bytes_written;
if (i2s_write(I2S_PORT, data, len, &bytes_written, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(A2DP_TAG, "i2s_write has failed");
}
if (bytes_written < len) {
ESP_LOGE(A2DP_TAG, "Timeout: not all bytes were written to I2S");
}
}
void a2dp::init() {
ESP_LOGI(A2DP_TAG, "Initializing A2DP");
// Initialize A2DP sink
if (esp_a2d_register_callback(a2d_callback) != ESP_OK){
ESP_LOGE(A2DP_TAG,"esp_a2d_register_callback failed");
}
if (esp_a2d_sink_register_data_callback(audio_data_callback) != ESP_OK){
ESP_LOGE(A2DP_TAG,"esp_a2d_sink_register_data_callback failed");
}
if (esp_a2d_sink_init() != ESP_OK){
ESP_LOGE(A2DP_TAG,"esp_a2d_sink_init failed");
}
}
void a2dp::connect_to_last() {
if (nvs::has_last_connection()) {
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
nvs::get_last_connection(last_connection);
if (esp_a2d_sink_connect(last_connection) == ESP_FAIL) {
ESP_LOGE(A2DP_TAG, "Failed connecting to device!");
}
}
}

7
receiver/main/a2dp.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <cstdint>
namespace a2dp {
void init();
void connect_to_last();
}

View File

@ -1,537 +0,0 @@
#include <cstdio>
#include <iostream>
#include <functional>
#include <cstring>
#include "esp_log.h"
#include "esp_system.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_spp_api.h"
#include "esp_avrc_api.h"
#include "esp_a2dp_api.h"
#include "driver/i2s.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "common.h"
#include "helper.h"
#include "app.h"
QueueHandle_t queue = nullptr;
TaskHandle_t handle = nullptr;
esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
int sample_rate = 44100;
uint32_t app::get_sample_rate() {
return sample_rate;
}
void handler(void* args) {
app::Message msg;
for (;;) {
if (!queue) {
ESP_LOGE(BT_APP_TAG, "%s, app_task_queue is null", __func__);
vTaskDelay(100 / portTICK_PERIOD_MS);
} else if (xQueueReceive(queue, &msg, portMAX_DELAY)) {
if (msg.callback) {
msg.callback(msg.event, msg.param);
}
if (msg.param) {
free(msg.param);
}
} else {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
}
// Not quite sure what this function is for, it seems to just add null termination to a string
/* // @TODO Pretty sure this also leaks memory, since we never free the original text before overwriting it */
/* void create_metadata_buffer(esp_avrc_ct_cb_param_t *param) { */
/* uint8_t* attr_text = (uint8_t*)malloc(param->meta_rsp.attr_length + 1); */
/* memcpy(attr_text, param->meta_rsp.attr_text, param->meta_rsp.attr_length); */
/* attr_text[param->meta_rsp.attr_length] = 0; */
/* param->meta_rsp.attr_text = attr_text; */
/* } */
// @TODO Not quite sure why this is called new track
void av_new_track() {
esp_avrc_ct_send_metadata_cmd(0, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_PLAYING_TIME);
// @TODO Pretty sure we can listen for play/pause and pos here ESP_AVRC_RN_PLAY_STATUS_CHANGE and ESP_AVRC_RN_PLAY_POS_CHANGED
esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_TRACK_CHANGE, 0);
}
// @TODO Here we can implement the notifications that we are registered for
void av_notify_evt_handler(uint8_t& id, esp_avrc_rn_param_t& param) {
switch (id) {
case ESP_AVRC_RN_TRACK_CHANGE:
ESP_LOGD(BT_AV_TAG, "%s ESP_AVRC_RN_TRACK_CHANGE %d", __func__, id);
// @TODO Do we need to reregister it every time we get the event?
av_new_track();
break;
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, id);
break;
}
}
void av_hdl_avrc_evt(uint16_t event, void* param) {
ESP_LOGI(BT_AV_TAG, "%s evt %d", __func__, event);
esp_avrc_ct_cb_param_t* rc = (esp_avrc_ct_cb_param_t*)(param);
switch (event) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
ESP_LOGI(BT_AV_TAG, "AVRC conn_state evt: state %d, [%s]", rc->conn_stat.connected, addr_to_str(rc->conn_stat.remote_bda));
if (rc->conn_stat.connected) {
av_new_track();
// get remote supported event_ids of peer AVRCP Target
esp_avrc_ct_send_get_rn_capabilities_cmd(0);
} else {
// clear peer notification capability record
s_avrc_peer_rn_cap.bits = 0;
}
break;
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
ESP_LOGI(BT_AV_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
break;
case ESP_AVRC_CT_METADATA_RSP_EVT:
ESP_LOGI(BT_AV_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
// @TODO Do something with the metadata
break;
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
//ESP_LOGI(BT_AV_TAG, "AVRC event notification: %d, param: %d", (int)rc->change_ntf.event_id, (int)rc->change_ntf.event_parameter);
av_notify_evt_handler(rc->change_ntf.event_id, rc->change_ntf.event_parameter);
break;
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
ESP_LOGI(BT_AV_TAG, "AVRC remote features %x", rc->rmt_feats.feat_mask);
break;
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
ESP_LOGI(BT_AV_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
rc->get_rn_caps_rsp.evt_set.bits);
s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
av_new_track();
break;
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
void av_hdl_avrc_tg_evt(uint16_t event, void* p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param);
switch (event) {
case ESP_AVRC_TG_CONNECTION_STATE_EVT: {
ESP_LOGI(BT_AV_TAG, "AVRC conn_state evt: state %d, [%s]",rc->conn_stat.connected, addr_to_str(rc->conn_stat.remote_bda));
break;
}
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: {
ESP_LOGI(BT_AV_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state);
break;
}
case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: {
ESP_LOGI(BT_AV_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100/ 0x7f);
/* volume_set_by_controller(rc->set_abs_vol.volume); */
break;
}
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: {
ESP_LOGI(BT_AV_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter);
if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
ESP_LOGI(BT_AV_TAG, "AVRC Volume Changes Supported");
/* s_volume_notify = true; */
esp_avrc_rn_param_t rn_param;
rn_param.volume = 128/2;
esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param);
} else {
ESP_LOGW(BT_AV_TAG, "AVRC Volume Changes NOT Supported");
}
break;
}
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: {
ESP_LOGI(BT_AV_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
// @TODO This can be simplified a lot if we strip out the logging
void rc_ct_callback(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param) {
switch (event) {
case ESP_AVRC_CT_METADATA_RSP_EVT:
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
app::dispatch(av_hdl_avrc_evt, event, param, sizeof(esp_avrc_ct_cb_param_t));
break;
default:
ESP_LOGE(BT_AV_TAG, "Invalid AVRC event: %d", event);
break;
}
}
void rc_tg_callback(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param)
{
ESP_LOGD(BT_AV_TAG, "%s", __func__);
switch (event) {
case ESP_AVRC_TG_CONNECTION_STATE_EVT:
case ESP_AVRC_TG_REMOTE_FEATURES_EVT:
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT:
case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT:
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT:
case ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT:
app::dispatch(av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t));
break;
default:
ESP_LOGE(BT_AV_TAG, "Unsupported AVRC event: %d", event);
break;
}
}
void handle_connection_state(uint16_t event, esp_a2d_cb_param_t* a2d) {
static int retry_count = 0;
// determine remote BDA
esp_bd_addr_t peer_bd_addr = {0,0,0,0,0,0};
memcpy(peer_bd_addr, a2d->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "partner address: %s", addr_to_str(peer_bd_addr));
// @TODO Here we can handle on connection state change things
ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%s]", connection_state_to_str(a2d->conn_stat.state), addr_to_str(a2d->conn_stat.remote_bda));
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_DISCONNECTED");
/* ESP_LOGI(BT_AV_TAG, "i2s_stop"); */
/* i2s_stop(i2s_port); */
/* i2s_zero_dma_buffer(i2s_port); */
extern const uint8_t disconnect_wav_start[] asm("_binary_disconnect_wav_start");
extern const uint8_t disconnect_wav_end[] asm("_binary_disconnect_wav_end");
vTaskDelay(500 / portTICK_PERIOD_MS);
play_wav(disconnect_wav_start, disconnect_wav_end);
// @TODO Hope this works correctly
if (a2d->conn_stat.disc_rsn == ESP_A2D_DISC_RSN_ABNORMAL && has_last_connection()) {
if (retry_count < 3 ){
ESP_LOGI(BT_AV_TAG,"Connection try number: %d", retry_count);
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
if (esp_a2d_sink_connect(last_connection) == ESP_FAIL) {
ESP_LOGE(BT_AV_TAG, "Failed connecting to device!");
}
} else {
clean_last_connection();
set_scan_mode(true);
}
} else {
set_scan_mode(true);
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTED");
set_scan_mode(false);
retry_count = 0;
/* ESP_LOGI(BT_AV_TAG,"i2s_start"); */
/* if (i2s_start(i2s_port)!=ESP_OK){ */
/* ESP_LOGE(BT_AV_TAG, "i2s_start"); */
/* } */
extern const uint8_t connect_wav_start[] asm("_binary_connect_wav_start");
extern const uint8_t connect_wav_end[] asm("_binary_connect_wav_end");
play_wav(connect_wav_start, connect_wav_end);
vTaskDelay(500 / portTICK_PERIOD_MS);
// record current connection
set_last_connection(a2d->conn_stat.remote_bda);
if (esp_bt_gap_read_remote_name(a2d->conn_stat.remote_bda) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "esp_bt_gap_read_remote_name failed");
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING){
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTING");
retry_count++;
}
}
void handle_audio_state(uint16_t event, esp_a2d_cb_param_t* a2d) {
if (a2d->audio_stat.state == ESP_A2D_AUDIO_STATE_STARTED) {
/* if (i2s_start(I2S_PORT) != ESP_OK){ */
/* ESP_LOGE(BT_AV_TAG, "i2s_start failed"); */
/* } */
} else {
/* i2s_stop(I2S_PORT); */
/* i2s_zero_dma_buffer(I2S_PORT); */
}
}
void handle_audio_cfg(uint16_t event, esp_a2d_cb_param_t* a2d) {
sample_rate = 16000;
ESP_LOGI(BT_AV_TAG, "a2dp audio_cfg_cb , codec type %d", a2d->audio_cfg.mcc.type);
char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
if (oct0 & (0x01 << 6)) {
sample_rate = 32000;
} else if (oct0 & (0x01 << 5)) {
sample_rate = 44100;
} else if (oct0 & (0x01 << 4)) {
sample_rate = 48000;
}
ESP_LOGI(BT_AV_TAG, "a2dp audio_cfg_cb , sample_rate %d", sample_rate);
if (i2s_set_clk(I2S_PORT, sample_rate, 16, I2S_CHANNEL_STEREO) != ESP_OK){
ESP_LOGE(BT_AV_TAG, "i2s_set_clk failed with samplerate=%d", sample_rate);
} else {
ESP_LOGI(BT_AV_TAG, "audio player configured, samplerate=%d", sample_rate);
}
}
void av_hdl_a2d_evt(uint16_t event, void* param) {
esp_a2d_cb_param_t* a2d = (esp_a2d_cb_param_t*)(param);
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
ESP_LOGD(BT_AV_TAG, "%s ESP_A2D_CONNECTION_STATE_EVT", __func__);
handle_connection_state(event, a2d);
break;
case ESP_A2D_AUDIO_STATE_EVT:
ESP_LOGD(BT_AV_TAG, "%s ESP_A2D_AUDIO_STATE_EVT", __func__);
handle_audio_state(event, a2d);
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOGD(BT_AV_TAG, "%s ESP_A2D_AUDIO_CFG_EVT", __func__);
handle_audio_cfg(event, a2d);
break;
case ESP_A2D_PROF_STATE_EVT:
if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state) {
ESP_LOGI(BT_AV_TAG,"A2DP PROF STATE: Init Compl\n");
} else {
ESP_LOGI(BT_AV_TAG,"A2DP PROF STATE: Deinit Compl\n");
}
break;
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
void a2d_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) {
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
case ESP_A2D_AUDIO_STATE_EVT:
case ESP_A2D_AUDIO_CFG_EVT:
case ESP_A2D_PROF_STATE_EVT:
app::dispatch(av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t));
break;
default:
ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event);
break;
}
}
/**
* @brief Utility structure that can be used to split a int32_t up into 2 separate channels with int16_t data.
* @author Phil Schatzmann
* @copyright Apache License Version 2
*/
struct __attribute__((packed)) Frame {
int16_t channel1;
int16_t channel2;
Frame(int v=0){
channel1 = channel2 = v;
}
Frame(int ch1, int ch2){
channel1 = ch1;
channel2 = ch2;
}
};
void audio_data_callback(const uint8_t* data, uint32_t len) {
Frame* frame = (Frame*)data;
for (int i = 0; i < len/4; i++) {
int16_t temp = frame[i].channel1;
frame[i].channel1 = frame[i].channel2;
frame[i].channel2 = temp;
}
size_t bytes_written;
if (i2s_write(I2S_PORT, data, len, &bytes_written, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "i2s_write has failed");
}
if (bytes_written < len) {
ESP_LOGE(BT_AV_TAG, "Timeout: not all bytes were written to I2S");
}
}
void setup_stack(uint16_t event, void* param) {
ESP_LOGI(BT_APP_TAG, "Setting up stack");
esp_bt_dev_set_device_name("Car Stereo");
// Initialize AVRCP controller
esp_err_t result = esp_avrc_ct_init();
if (result == ESP_OK) {
result = esp_avrc_ct_register_callback(rc_ct_callback);
if (result == ESP_OK) {
ESP_LOGI(BT_AV_TAG, "AVRCP controller initialized");
} else {
ESP_LOGE(BT_AV_TAG, "esp_avrc_ct_register_callback: %d", result);
}
} else {
ESP_LOGE(BT_AV_TAG, "esp_avrc_ct_init: %d", result);
}
// Initialize AVRCP target
if (esp_avrc_tg_init() == ESP_OK) {
esp_avrc_tg_register_callback(rc_tg_callback);
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);
if (esp_avrc_tg_set_rn_evt_cap(&evt_set) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "esp_avrc_tg_set_rn_evt_cap failed");
}
} else {
ESP_LOGE(BT_AV_TAG, "esp_avrc_tg_init failed");
}
// Initialize A2DP sink
if (esp_a2d_register_callback(a2d_callback) != ESP_OK){
ESP_LOGE(BT_AV_TAG,"esp_a2d_register_callback failed");
}
if (esp_a2d_sink_register_data_callback(audio_data_callback) != ESP_OK){
ESP_LOGE(BT_AV_TAG,"esp_a2d_sink_register_data_callback failed");
}
if (esp_a2d_sink_init() != ESP_OK){
ESP_LOGE(BT_AV_TAG,"esp_a2d_sink_init failed");
}
if (has_last_connection()) {
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
if (esp_a2d_sink_connect(last_connection) == ESP_FAIL) {
ESP_LOGE(BT_AV_TAG, "Failed connecting to device!");
}
}
set_scan_mode(true);
}
void app::start() {
ESP_LOGI(BT_AV_TAG, "Starting app task");
if (queue == nullptr) {
queue = xQueueCreate(10, sizeof(Message));
ESP_LOGI(BT_AV_TAG, "Created queue");
}
if (handle == nullptr) {
if (xTaskCreate(handler, "BtAppT", 2048, nullptr, configMAX_PRIORITIES - 3, &handle) != pdPASS) {
ESP_LOGE(BT_APP_TAG, "Failed to create app task");
}
ESP_LOGI(BT_AV_TAG, "Created task");
}
dispatch(setup_stack, 0, nullptr, 0);
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
esp_bt_pin_code_t pin_code;
pin_code[0] = '6';
pin_code[1] = '9';
pin_code[2] = '4';
pin_code[3] = '2';
pin_code[4] = '0';
esp_bt_gap_set_pin(ESP_BT_PIN_TYPE_FIXED, 5, pin_code);
}
/* void app::stop() { */
/* ESP_LOGI(BT_AV_TAG, "Destroying task app task"); */
/* if (handle != nullptr) { */
/* vTaskDelete(handle); */
/* handle = nullptr; */
/* } */
/* if (queue != nullptr) { */
/* vQueueDelete(queue); */
/* queue = nullptr; */
/* } */
/* } */
void send_msg(app::Message* msg) {
ESP_LOGI(BT_APP_TAG, "Sending message");
if (msg == nullptr || queue == nullptr) {
ESP_LOGE(BT_APP_TAG, "send_msg failed");
return;
}
if (xQueueSend(queue, msg, 10 / portTICK_PERIOD_MS) != pdTRUE) {
ESP_LOGE(BT_APP_TAG, "xQueue send failed");
}
}
void app::dispatch(Callback callback, uint16_t event, void* param, int param_len) {
ESP_LOGI(BT_APP_TAG, "event 0x%x, param len %d", event, param_len);
Message msg;
memset(&msg, 0, sizeof(msg));
msg.event = event;
msg.callback = callback;
if (param_len == 0) {
send_msg(&msg);
} else if (param && param_len > 0) {
if ((msg.param = malloc(param_len)) != nullptr) {
memcpy(msg.param, param, param_len);
send_msg(&msg);
}
} else {
ESP_LOGE(BT_APP_TAG, "Failed to dispatch!");
}
}

View File

@ -1,17 +0,0 @@
#pragma once
#include <cstdint>
namespace app {
typedef void (*Callback) (uint16_t event, void *param);
struct Message {
uint16_t event;
Callback callback;
void* param;
};
void start();
/* void stop(); */
void dispatch(Callback callback, uint16_t event, void* param, int param_len);
uint32_t get_sample_rate();
}

68
receiver/main/avrcp.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "esp_log.h"
#include "esp_avrc_api.h"
#include "avrcp.h"
#include "helper.h"
#define AVRCP_TAG "APP_AVRCP"
static uint8_t volume = 127;
static void rc_tg_callback(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t* param) {
ESP_LOGD(AVRCP_TAG, "%s evt %d", __func__, event);
switch (event) {
case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT:
ESP_LOGI(AVRCP_TAG, "AVRC set absolute volume: %d%%", (int)param->set_abs_vol.volume * 100/ 0x7f);
// The phone want to change the volume
break;
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT:
ESP_LOGI(AVRCP_TAG, "AVRC register event notification: %d, param: 0x%x", param->reg_ntf.event_id, param->reg_ntf.event_parameter);
if (param->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
ESP_LOGI(AVRCP_TAG, "AVRC Volume Changes Supported");
// Notify the phone of the current volume
// @TODO This does not appear to set the volume correctly on my OnePlus 7T Pro
// However for now it is not really relevant as the volume control is doen by the car
// In the future however it might be nice to sync the value on the phone, the esp and the car
// This will require some sort of CAN bus access
esp_avrc_rn_param_t rn_param;
rn_param.volume = volume;
esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param);
} else {
ESP_LOGW(AVRCP_TAG, "AVRC Volume Changes NOT Supported");
}
break;
default:
ESP_LOGE(AVRCP_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
void avrcp::init() {
ESP_LOGI(AVRCP_TAG, "Initializing AVRCP");
// Initialize AVRCP target
if (esp_avrc_tg_init() == ESP_OK) {
esp_avrc_tg_register_callback(rc_tg_callback);
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);
if (esp_avrc_tg_set_rn_evt_cap(&evt_set) != ESP_OK) {
ESP_LOGE(AVRCP_TAG, "esp_avrc_tg_set_rn_evt_cap failed");
}
} else {
ESP_LOGE(AVRCP_TAG, "esp_avrc_tg_init failed");
}
}
uint8_t avrcp::get_volume() {
return volume;
}
void avrcp::set_volume(uint8_t v) {
volume = v;
esp_avrc_rn_param_t rn_param;
rn_param.volume = volume;
esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param);
}

8
receiver/main/avrcp.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
namespace avrcp {
void init();
uint8_t get_volume();
void set_volume(uint8_t volume);
}

155
receiver/main/bluetooth.cpp Normal file
View File

@ -0,0 +1,155 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_spp_api.h"
#include "bluetooth.h"
#include "helper.h"
#include <cstring>
#define BT_TAG "APP_BT"
static bool start() {
ESP_LOGI(BT_TAG, "BT Start");
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGI(BT_TAG, "Already enabled");
return true;
}
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
ESP_LOGI(BT_TAG, "Idle");
esp_bt_controller_init(&cfg);
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {}
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) {
ESP_LOGE(BT_TAG, "BT Enable failed");
return false;
}
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGI(BT_TAG, "Enabled");
return true;
}
ESP_LOGE(BT_TAG, "BT Start failed");
return false;
}
static void app_gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
switch (event) {
case ESP_BT_GAP_AUTH_CMPL_EVT: {
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(BT_TAG, "authentication success: %s", param->auth_cmpl.device_name);
} else {
ESP_LOGE(BT_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
}
break;
}
case ESP_BT_GAP_PIN_REQ_EVT: {
esp_bd_addr_t peer_bd_addr = {0,0,0,0,0,0};
memcpy(peer_bd_addr, param->pin_req.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_TAG, "partner address: %s", addr_to_str(peer_bd_addr));
break;
}
case ESP_BT_GAP_CFM_REQ_EVT: {
esp_bd_addr_t peer_bd_addr = {0,0,0,0,0,0};
memcpy(peer_bd_addr, param->cfm_req.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_TAG, "partner address: %s", addr_to_str(peer_bd_addr));
ESP_LOGI(BT_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please confirm the passkey: %d", param->cfm_req.num_val);
break;
}
case ESP_BT_GAP_KEY_NOTIF_EVT: {
ESP_LOGI(BT_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
break;
}
case ESP_BT_GAP_KEY_REQ_EVT: {
ESP_LOGI(BT_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
break;
}
case ESP_BT_GAP_READ_REMOTE_NAME_EVT: {
ESP_LOGI(BT_TAG, "ESP_BT_GAP_READ_REMOTE_NAME_EVT stat:%d", param->read_rmt_name.stat);
if (param->read_rmt_name.stat == ESP_BT_STATUS_SUCCESS ) {
ESP_LOGI(BT_TAG, "ESP_BT_GAP_READ_REMOTE_NAME_EVT remote name:%s", param->read_rmt_name.rmt_name);
char remote_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
memcpy(remote_name, param->read_rmt_name.rmt_name, ESP_BT_GAP_MAX_BDNAME_LEN );
}
break;
}
default: {
ESP_LOGI(BT_TAG, "%s invalid event: %d", __func__, event);
break;
}
}
return;
}
void bluetooth::init() {
ESP_LOGI(BT_TAG, "Initializing bluetooth");
if (!start()) {
ESP_LOGE(BT_TAG, "Failed to initialize controller");
return;
}
ESP_LOGI(BT_TAG, "Controller initialized");
esp_bluedroid_status_t bt_stack_status = esp_bluedroid_get_status();
if (bt_stack_status == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_TAG, "Failed to initialize bluedroid");
return;
}
ESP_LOGI(BT_TAG, "Bluedroid initialized");
}
while (bt_stack_status != ESP_BLUEDROID_STATUS_ENABLED) {
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_TAG, "Failed to enable bluedroid");
vTaskDelay(100 / portTICK_PERIOD_MS);
} else {
ESP_LOGI(BT_TAG, "Bluedroid enabled");
}
bt_stack_status = esp_bluedroid_get_status();
}
if (esp_bt_gap_register_callback(app_gap_callback) != ESP_OK) {
ESP_LOGE(BT_TAG,"gap register failed");
return;
}
esp_bt_dev_set_device_name("207 Music");
esp_bt_pin_code_t pin_code;
pin_code[0] = '6';
pin_code[1] = '9';
pin_code[2] = '4';
pin_code[3] = '2';
pin_code[4] = '0';
esp_bt_gap_set_pin(ESP_BT_PIN_TYPE_FIXED, 5, pin_code);
}
void bluetooth::set_scan_mode(bool connectable) {
if (esp_bt_gap_set_scan_mode(connectable ? ESP_BT_CONNECTABLE : ESP_BT_NON_CONNECTABLE, connectable ? ESP_BT_GENERAL_DISCOVERABLE : ESP_BT_NON_DISCOVERABLE)) {
ESP_LOGE(BT_TAG,"esp_bt_gap_set_scan_mode failed");
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace bluetooth {
void init();
void set_scan_mode(bool connectable);
}

View File

@ -1,9 +0,0 @@
#pragma once
#define BT_AV_TAG "BT_AV"
#define BT_APP_TAG "BT_API"
#define I2S_BUFFER_SIZE 512
#define I2S_PORT I2S_NUM_0
#define WAV_HEADER_SIZE 44

View File

@ -1,14 +1,6 @@
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "driver/i2s.h"
#include <cstring>
#include "helper.h"
#include "common.h"
#include "app.h"
const char* addr_to_str(esp_bd_addr_t bda) {
static char bda_str[18];
@ -20,107 +12,3 @@ const char* connection_state_to_str(esp_a2d_connection_state_t state) {
const char* states[4] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
return states[state];
}
void set_scan_mode(bool connectable) {
if (esp_bt_gap_set_scan_mode(connectable ? ESP_BT_CONNECTABLE : ESP_BT_NON_CONNECTABLE, connectable ? ESP_BT_GENERAL_DISCOVERABLE : ESP_BT_NON_DISCOVERABLE)) {
ESP_LOGE(BT_AV_TAG,"esp_bt_gap_set_scan_mode");
}
}
void get_last_connection(esp_bd_addr_t last_connection) {
nvs_handle handle;
esp_err_t err = nvs_open("connected_dba", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "NVS OPEN ERRRO");
}
esp_bd_addr_t bda;
size_t size = sizeof(bda);
err = nvs_get_blob(handle, "last_bda", bda, &size);
if (err != ESP_OK) {
if (err == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGI(BT_AV_TAG, "nvs_blob does not exists");
} else {
ESP_LOGE(BT_AV_TAG, "get_nvs_blob failed");
}
}
nvs_close(handle);
if (err == ESP_OK) {
memcpy(last_connection, bda, size);
}
}
void set_last_connection(esp_bd_addr_t bda) {
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
if (memcmp(bda, last_connection, ESP_BD_ADDR_LEN) == 0) {
// Nothing has changed
return;
}
nvs_handle handle;
esp_err_t err = nvs_open("connected_dba", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "NVS OPEN ERRRO");
}
err = nvs_set_blob(handle, "last_bda", bda, ESP_BD_ADDR_LEN);
if (err == ESP_OK) {
err = nvs_commit(handle);
} else {
ESP_LOGE(BT_AV_TAG, "NVS_WRITE_ERROR");
}
if (err != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "NVS COMMIT ERROR");
}
nvs_close(handle);
}
bool has_last_connection() {
esp_bd_addr_t empty = {0,0,0,0,0,0};
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
int result = memcmp(last_connection, empty, ESP_BD_ADDR_LEN);
return result != 0;
}
void clean_last_connection() {
esp_bd_addr_t empty = {0,0,0,0,0,0};
set_last_connection(empty);
}
void play_wav(const uint8_t start[], const uint8_t end[]) {
uint32_t len = (end - start - WAV_HEADER_SIZE);
ESP_LOGI(BT_AV_TAG, "Playing sample...");
// Switch to mono since the samples are all in mono to save space
i2s_zero_dma_buffer(I2S_PORT);
i2s_set_clk(I2S_PORT, 44100, 16, I2S_CHANNEL_MONO);
for (uint32_t index = 0; index < len; index += I2S_BUFFER_SIZE) {
int rest = len - index;
size_t byte_size = I2S_BUFFER_SIZE;
if (rest < I2S_BUFFER_SIZE) {
byte_size = rest;
}
const unsigned char* current = start + WAV_HEADER_SIZE + index;
size_t bytes_written;
if (i2s_write(I2S_PORT, current, byte_size, &bytes_written, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "i2s_write has failed");
}
}
// Switch back to stereo
i2s_zero_dma_buffer(I2S_PORT);
i2s_set_clk(I2S_PORT, app::get_sample_rate(), 16, I2S_CHANNEL_STEREO);
ESP_LOGI(BT_AV_TAG, "Done");
}

View File

@ -1,15 +1,8 @@
#pragma once
#include "esp_bt_device.h"
#include "esp_a2dp_api.h"
const char* addr_to_str(esp_bd_addr_t bda);
const char* connection_state_to_str(esp_a2d_connection_state_t state);
void set_scan_mode(bool connectable);
void get_last_connection(esp_bd_addr_t last_connection);
void set_last_connection(esp_bd_addr_t last_connection);
bool has_last_connection();
void clean_last_connection();
void play_wav(const uint8_t start[], const uint8_t end[]);

56
receiver/main/i2s.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "esp_log.h"
#include "driver/i2s.h"
#include "i2s.h"
#define I2S_TAG "APP_I2S"
void i2s::init() {
ESP_LOGI(I2S_TAG, "Initializing i2s");
i2s_port_t i2s_port = I2S_PORT;
i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = get_sample_rate(),
.bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0, // default interrupt priority
.dma_desc_num = 8,
.dma_frame_num = 64,
.use_apll = false,
.tx_desc_auto_clear = true // avoiding noise in case of data unavailability
};
if (i2s_driver_install(i2s_port, &i2s_config, 0, nullptr) != ESP_OK) {
ESP_LOGE(I2S_TAG, "i2s_driver_install failed");
}
i2s_pin_config_t pin_config = {
.bck_io_num = 26,
.ws_io_num = 25,
.data_out_num = 33,
.data_in_num = I2S_PIN_NO_CHANGE
};
if (i2s_set_pin(i2s_port, &pin_config) != ESP_OK) {
ESP_LOGE(I2S_TAG, "i2s_set_pin failed");
}
}
static uint32_t sample_rate = 44100;
void i2s::set_sample_rate(uint32_t sp) {
sample_rate = sp;
if (i2s_set_clk(I2S_PORT, sample_rate, 16, I2S_CHANNEL_STEREO) != ESP_OK){
ESP_LOGE(I2S_TAG, "i2s_set_clk failed with samplerate=%d", sample_rate);
} else {
ESP_LOGI(I2S_TAG, "samplerate=%d", sample_rate);
}
}
uint32_t i2s::get_sample_rate() {
return sample_rate;
}

12
receiver/main/i2s.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
#define I2S_PORT I2S_NUM_0
namespace i2s {
void init();
uint32_t get_sample_rate();
void set_sample_rate(uint32_t sample_rate);
}

View File

@ -1,227 +1,33 @@
#include <cstdio>
#include <iostream>
#include <functional>
#include <cstring>
#include "esp_log.h"
#include "esp_system.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_spp_api.h"
#include "esp_avrc_api.h"
#include "esp_a2dp_api.h"
#include "driver/i2s.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "common.h"
#include "helper.h"
#include "app.h"
#include "storage.h"
#include "i2s.h"
#include "bluetooth.h"
#include "avrcp.h"
#include "a2dp.h"
void init_nvs() {
ESP_LOGI(BT_AV_TAG, "Initializing nvs");
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND){
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
#define APP_TAG "APP"
extern "C" void app_main() {
ESP_LOGI(APP_TAG, "Starting Car Stereo");
ESP_LOGI(APP_TAG, "Available Heap: %u", esp_get_free_heap_size());
nvs::init();
i2s::init();
bluetooth::init();
avrcp::init();
a2dp::init();
a2dp::connect_to_last();
vTaskDelay(10000 / portTICK_PERIOD_MS);
ESP_LOGI(APP_TAG, "Changing volume");
avrcp::set_volume(127/2);
}
void init_i2s() {
ESP_LOGI(BT_AV_TAG, "Initializing i2s");
i2s_port_t i2s_port = I2S_PORT;
i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = app::get_sample_rate(),
.bits_per_sample = (i2s_bits_per_sample_t)16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0, // default interrupt priority
.dma_desc_num = 8,
.dma_frame_num = 64,
.use_apll = false,
.tx_desc_auto_clear = true // avoiding noise in case of data unavailability
};
if (i2s_driver_install(i2s_port, &i2s_config, 0, nullptr) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "i2s_driver_install failed");
}
ESP_LOGI(BT_AV_TAG, "SAMPLE_RATE: %i", i2s_config.sample_rate);
ESP_LOGI(BT_AV_TAG, "BITS_PER_SAMPLE: %i", i2s_config.bits_per_sample);
i2s_pin_config_t pin_config = {
.bck_io_num = 26,
.ws_io_num = 25,
.data_out_num = 33,
.data_in_num = I2S_PIN_NO_CHANGE
};
if (i2s_set_pin(i2s_port, &pin_config) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "i2s_set_pin failed");
}
}
bool bluetooth_start() {
ESP_LOGI(BT_APP_TAG, "BT Start");
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGI(BT_APP_TAG, "Already enabled");
return true;
}
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
ESP_LOGI(BT_APP_TAG, "Idle");
esp_bt_controller_init(&cfg);
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {}
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) {
ESP_LOGE(BT_APP_TAG, "BT Enable failed");
return false;
}
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGI(BT_APP_TAG, "Enabled");
return true;
}
ESP_LOGE(BT_APP_TAG, "BT Start failed");
return false;
}
void app_gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
switch (event) {
case ESP_BT_GAP_AUTH_CMPL_EVT: {
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name);
} else {
ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
}
break;
}
case ESP_BT_GAP_PIN_REQ_EVT: {
esp_bd_addr_t peer_bd_addr = {0,0,0,0,0,0};
memcpy(peer_bd_addr, param->pin_req.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "partner address: %s", addr_to_str(peer_bd_addr));
break;
}
case ESP_BT_GAP_CFM_REQ_EVT: {
esp_bd_addr_t peer_bd_addr = {0,0,0,0,0,0};
memcpy(peer_bd_addr, param->cfm_req.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "partner address: %s", addr_to_str(peer_bd_addr));
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please confirm the passkey: %d", param->cfm_req.num_val);
break;
}
case ESP_BT_GAP_KEY_NOTIF_EVT: {
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
break;
}
case ESP_BT_GAP_KEY_REQ_EVT: {
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
break;
}
case ESP_BT_GAP_READ_REMOTE_NAME_EVT: {
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_READ_REMOTE_NAME_EVT stat:%d", param->read_rmt_name.stat);
if (param->read_rmt_name.stat == ESP_BT_STATUS_SUCCESS ) {
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_READ_REMOTE_NAME_EVT remote name:%s", param->read_rmt_name.rmt_name);
char remote_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
memcpy(remote_name, param->read_rmt_name.rmt_name, ESP_BT_GAP_MAX_BDNAME_LEN );
}
break;
}
default: {
ESP_LOGI(BT_AV_TAG, "%s invalid event: %d", __func__, event);
break;
}
}
return;
}
void init_bluetooth() {
ESP_LOGI(BT_AV_TAG, "Initializing bluetooth");
if (!bluetooth_start()) {
ESP_LOGE(BT_AV_TAG, "Failed to initialize controller");
return;
}
ESP_LOGI(BT_AV_TAG, "Controller initialized");
esp_bluedroid_status_t bt_stack_status = esp_bluedroid_get_status();
if (bt_stack_status == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "Failed to initialize bluedroid");
return;
}
ESP_LOGI(BT_AV_TAG, "Bluedroid initialized");
}
while (bt_stack_status != ESP_BLUEDROID_STATUS_ENABLED) {
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "Failed to enable bluedroid");
vTaskDelay(100 / portTICK_PERIOD_MS);
} else {
ESP_LOGI(BT_AV_TAG, "Bluedroid enabled");
}
bt_stack_status = esp_bluedroid_get_status();
}
if (esp_bt_gap_register_callback(app_gap_callback) != ESP_OK) {
ESP_LOGE(BT_AV_TAG,"gap register failed");
return;
}
// @TODO Do we need this?
// Check menuconfig
if ((esp_spp_init(ESP_SPP_MODE_CB)) != ESP_OK) {
ESP_LOGE(BT_AV_TAG,"esp_spp_init failed");
return;
}
}
extern "C" {
void app_main();
}
void app_main() {
ESP_LOGI(BT_AV_TAG, "Starting Car Stereo");
ESP_LOGI(BT_AV_TAG, "Available Heap: %u", esp_get_free_heap_size());
init_nvs();
init_i2s();
init_bluetooth();
app::start();
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
ESP_LOGI(BT_AV_TAG, "Last connection: %s", addr_to_str(last_connection));
/* extern const uint8_t connect_wav_start[] asm("_binary_connect_wav_start"); */
/* extern const uint8_t connect_wav_end[] asm("_binary_connect_wav_end"); */
/* extern const uint8_t disconnect_wav_start[] asm("_binary_disconnect_wav_start"); */
/* extern const uint8_t disconnect_wav_end[] asm("_binary_disconnect_wav_end"); */
/* play_wav(connect_wav_start, connect_wav_end); */
/* vTaskDelay(1000 / portTICK_PERIOD_MS); */
/* play_wav(disconnect_wav_start, disconnect_wav_end); */
}

81
receiver/main/storage.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <cstring>
#include "esp_log.h"
#include "nvs_flash.h"
#include "storage.h"
#define NVS_TAG "APP_NVS"
void nvs::init() {
ESP_LOGI(NVS_TAG, "Initializing nvs");
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND){
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
}
void nvs::get_last_connection(esp_bd_addr_t last_connection) {
nvs_handle handle;
esp_err_t err = nvs_open("connected_dba", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(NVS_TAG, "NVS OPEN ERRRO");
}
esp_bd_addr_t bda;
size_t size = sizeof(bda);
err = nvs_get_blob(handle, "last_bda", bda, &size);
if (err != ESP_OK) {
if (err == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGI(NVS_TAG, "nvs_blob does not exists");
} else {
ESP_LOGE(NVS_TAG, "get_nvs_blob failed");
}
}
nvs_close(handle);
if (err == ESP_OK) {
memcpy(last_connection, bda, size);
}
}
void nvs::set_last_connection(esp_bd_addr_t bda) {
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
if (memcmp(bda, last_connection, ESP_BD_ADDR_LEN) == 0) {
// Nothing has changed
return;
}
nvs_handle handle;
esp_err_t err = nvs_open("connected_dba", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(NVS_TAG, "NVS OPEN ERRRO");
}
err = nvs_set_blob(handle, "last_bda", bda, ESP_BD_ADDR_LEN);
if (err == ESP_OK) {
err = nvs_commit(handle);
} else {
ESP_LOGE(NVS_TAG, "NVS_WRITE_ERROR");
}
if (err != ESP_OK) {
ESP_LOGE(NVS_TAG, "NVS COMMIT ERROR");
}
nvs_close(handle);
}
bool nvs::has_last_connection() {
esp_bd_addr_t empty = {0,0,0,0,0,0};
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
get_last_connection(last_connection);
int result = memcmp(last_connection, empty, ESP_BD_ADDR_LEN);
return result != 0;
}

11
receiver/main/storage.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "esp_bt_device.h"
namespace nvs {
void init();
void get_last_connection(esp_bd_addr_t last_connection);
void set_last_connection(esp_bd_addr_t last_connection);
bool has_last_connection();
}

65
receiver/main/wav.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "esp_log.h"
#include "driver/i2s.h"
#include "wav.h"
#include "i2s.h"
#define WAV_TAG "APP_WAV"
#define WAV_HEADER_SIZE 44
#define I2S_BUFFER_SIZE 512
struct PlayWavParam {
const uint8_t* data;
uint32_t len;
};
static void task(void* param) {
ESP_LOGI(WAV_TAG, "Playing sample...");
PlayWavParam* p = (PlayWavParam*)param;
// Switch to mono since the samples are all in mono to save space
i2s_zero_dma_buffer(I2S_PORT);
i2s_set_clk(I2S_PORT, 44100, 16, I2S_CHANNEL_MONO);
for (uint32_t index = 0; index < p->len; index += I2S_BUFFER_SIZE) {
int rest = p->len - index;
size_t byte_size = I2S_BUFFER_SIZE;
if (rest < I2S_BUFFER_SIZE) {
byte_size = rest;
}
const unsigned char* current = p->data + WAV_HEADER_SIZE + index;
size_t bytes_written;
if (i2s_write(I2S_PORT, current, byte_size, &bytes_written, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(WAV_TAG, "i2s_write has failed");
}
if (bytes_written < byte_size) {
ESP_LOGE(WAV_TAG, "Timeout: not all bytes were written to I2S");
}
}
// Switch back to stereo with the correct samplerate
i2s_zero_dma_buffer(I2S_PORT);
i2s_set_clk(I2S_PORT, i2s::get_sample_rate(), 16, I2S_CHANNEL_STEREO);
ESP_LOGI(WAV_TAG, "Done");
free(p);
vTaskDelete(nullptr);
}
void wav::play(const uint8_t* start, const uint8_t* end) {
ESP_LOGI(WAV_TAG, "Creating Play Wav Task");
uint32_t len = (end - start - WAV_HEADER_SIZE);
PlayWavParam* param = new PlayWavParam;
param->data = start;
param->len = len;
if (xTaskCreate(task, "PlayWav", 2048, param, configMAX_PRIORITIES - 3, nullptr) != pdPASS) {
ESP_LOGE(WAV_TAG, "Failed to create play wav task");
}
}

14
receiver/main/wav.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
#define WAV_PLAY_HELPER(str) #str
#define WAV_PLAY(NAME) {\
extern const uint8_t _binary_ ## NAME ## _wav_start[]; \
extern const uint8_t _binary_ ## NAME ## _wav_end[]; \
wav::play(_binary_ ## NAME ## _wav_start, _binary_ ## NAME ## _wav_end); \
}
namespace wav {
void play(const uint8_t* start, const uint8_t* end);
}

View File

@ -393,10 +393,10 @@ CONFIG_BT_BTU_TASK_STACK_SIZE=4096
# CONFIG_BT_BLUEDROID_MEM_DEBUG is not set
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_SPP_ENABLED=y
# CONFIG_BT_SPP_ENABLED is not set
# CONFIG_BT_HFP_ENABLE is not set
# CONFIG_BT_HID_ENABLED is not set
CONFIG_BT_SSP_ENABLED=y
# CONFIG_BT_SSP_ENABLED is not set
# CONFIG_BT_BLE_ENABLED is not set
# CONFIG_BT_STACK_NO_LOG is not set