From 3387c9edc271bba81fb1b6ae054d178a53cbefb6 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Sat, 21 May 2022 00:53:42 +0200 Subject: [PATCH] Refactored and removed unneeded parts --- .gitignore | 1 + receiver/main/CMakeLists.txt | 2 +- receiver/main/a2dp.cpp | 166 +++++++++++ receiver/main/a2dp.h | 7 + receiver/main/app.cpp | 537 ----------------------------------- receiver/main/app.h | 17 -- receiver/main/avrcp.cpp | 68 +++++ receiver/main/avrcp.h | 8 + receiver/main/bluetooth.cpp | 155 ++++++++++ receiver/main/bluetooth.h | 6 + receiver/main/common.h | 9 - receiver/main/helper.cpp | 112 -------- receiver/main/helper.h | 9 +- receiver/main/i2s.cpp | 56 ++++ receiver/main/i2s.h | 12 + receiver/main/main.cpp | 246 ++-------------- receiver/main/storage.cpp | 81 ++++++ receiver/main/storage.h | 11 + receiver/main/wav.cpp | 65 +++++ receiver/main/wav.h | 14 + receiver/sdkconfig | 4 +- 21 files changed, 680 insertions(+), 906 deletions(-) create mode 100644 receiver/main/a2dp.cpp create mode 100644 receiver/main/a2dp.h delete mode 100644 receiver/main/app.cpp delete mode 100644 receiver/main/app.h create mode 100644 receiver/main/avrcp.cpp create mode 100644 receiver/main/avrcp.h create mode 100644 receiver/main/bluetooth.cpp create mode 100644 receiver/main/bluetooth.h delete mode 100644 receiver/main/common.h create mode 100644 receiver/main/i2s.cpp create mode 100644 receiver/main/i2s.h create mode 100644 receiver/main/storage.cpp create mode 100644 receiver/main/storage.h create mode 100644 receiver/main/wav.cpp create mode 100644 receiver/main/wav.h diff --git a/.gitignore b/.gitignore index 2ceac01..bbfd100 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ tools receiver/build receiver/.cache +sdkconfig.old diff --git a/receiver/main/CMakeLists.txt b/receiver/main/CMakeLists.txt index 3197aed..f022d60 100644 --- a/receiver/main/CMakeLists.txt +++ b/receiver/main/CMakeLists.txt @@ -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") diff --git a/receiver/main/a2dp.cpp b/receiver/main/a2dp.cpp new file mode 100644 index 0000000..e4c58ba --- /dev/null +++ b/receiver/main/a2dp.cpp @@ -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!"); + } + } +} + diff --git a/receiver/main/a2dp.h b/receiver/main/a2dp.h new file mode 100644 index 0000000..8d83b9c --- /dev/null +++ b/receiver/main/a2dp.h @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace a2dp { + void init(); + void connect_to_last(); +} diff --git a/receiver/main/app.cpp b/receiver/main/app.cpp deleted file mode 100644 index f96503b..0000000 --- a/receiver/main/app.cpp +++ /dev/null @@ -1,537 +0,0 @@ -#include -#include -#include -#include - -#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!"); - } -} - diff --git a/receiver/main/app.h b/receiver/main/app.h deleted file mode 100644 index cf89d64..0000000 --- a/receiver/main/app.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include - -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(); -} diff --git a/receiver/main/avrcp.cpp b/receiver/main/avrcp.cpp new file mode 100644 index 0000000..febd4c1 --- /dev/null +++ b/receiver/main/avrcp.cpp @@ -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); +} diff --git a/receiver/main/avrcp.h b/receiver/main/avrcp.h new file mode 100644 index 0000000..7d9117c --- /dev/null +++ b/receiver/main/avrcp.h @@ -0,0 +1,8 @@ +#pragma once + +namespace avrcp { + void init(); + + uint8_t get_volume(); + void set_volume(uint8_t volume); +} diff --git a/receiver/main/bluetooth.cpp b/receiver/main/bluetooth.cpp new file mode 100644 index 0000000..afa3b8e --- /dev/null +++ b/receiver/main/bluetooth.cpp @@ -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 + +#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"); + } +} + + diff --git a/receiver/main/bluetooth.h b/receiver/main/bluetooth.h new file mode 100644 index 0000000..3320330 --- /dev/null +++ b/receiver/main/bluetooth.h @@ -0,0 +1,6 @@ +#pragma once + +namespace bluetooth { + void init(); + void set_scan_mode(bool connectable); +} diff --git a/receiver/main/common.h b/receiver/main/common.h deleted file mode 100644 index 1e01809..0000000 --- a/receiver/main/common.h +++ /dev/null @@ -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 - diff --git a/receiver/main/helper.cpp b/receiver/main/helper.cpp index 7393026..89b9003 100644 --- a/receiver/main/helper.cpp +++ b/receiver/main/helper.cpp @@ -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 #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"); -} - diff --git a/receiver/main/helper.h b/receiver/main/helper.h index af2bb62..c24311d 100644 --- a/receiver/main/helper.h +++ b/receiver/main/helper.h @@ -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[]); diff --git a/receiver/main/i2s.cpp b/receiver/main/i2s.cpp new file mode 100644 index 0000000..011f820 --- /dev/null +++ b/receiver/main/i2s.cpp @@ -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; +} diff --git a/receiver/main/i2s.h b/receiver/main/i2s.h new file mode 100644 index 0000000..7ee8571 --- /dev/null +++ b/receiver/main/i2s.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define I2S_PORT I2S_NUM_0 + +namespace i2s { + void init(); + + uint32_t get_sample_rate(); + void set_sample_rate(uint32_t sample_rate); +} diff --git a/receiver/main/main.cpp b/receiver/main/main.cpp index 1fbb40e..2b2bbbc 100644 --- a/receiver/main/main.cpp +++ b/receiver/main/main.cpp @@ -1,227 +1,33 @@ -#include -#include -#include -#include - #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); */ -} diff --git a/receiver/main/storage.cpp b/receiver/main/storage.cpp new file mode 100644 index 0000000..e131c0a --- /dev/null +++ b/receiver/main/storage.cpp @@ -0,0 +1,81 @@ +#include + +#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; +} diff --git a/receiver/main/storage.h b/receiver/main/storage.h new file mode 100644 index 0000000..ec0e077 --- /dev/null +++ b/receiver/main/storage.h @@ -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(); +} diff --git a/receiver/main/wav.cpp b/receiver/main/wav.cpp new file mode 100644 index 0000000..951aa24 --- /dev/null +++ b/receiver/main/wav.cpp @@ -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"); + } +} + diff --git a/receiver/main/wav.h b/receiver/main/wav.h new file mode 100644 index 0000000..9786af6 --- /dev/null +++ b/receiver/main/wav.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#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); +} diff --git a/receiver/sdkconfig b/receiver/sdkconfig index f6e36ec..7d2b94d 100644 --- a/receiver/sdkconfig +++ b/receiver/sdkconfig @@ -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