Refactored and removed unneeded parts
This commit is contained in:
parent
b6882cd635
commit
3387c9edc2
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
tools
|
||||
receiver/build
|
||||
receiver/.cache
|
||||
sdkconfig.old
|
||||
|
|
|
@ -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
166
receiver/main/a2dp.cpp
Normal 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
7
receiver/main/a2dp.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace a2dp {
|
||||
void init();
|
||||
void connect_to_last();
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
@ -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
68
receiver/main/avrcp.cpp
Normal 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
8
receiver/main/avrcp.h
Normal 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
155
receiver/main/bluetooth.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
6
receiver/main/bluetooth.h
Normal file
6
receiver/main/bluetooth.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace bluetooth {
|
||||
void init();
|
||||
void set_scan_mode(bool connectable);
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
56
receiver/main/i2s.cpp
Normal 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
12
receiver/main/i2s.h
Normal 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);
|
||||
}
|
|
@ -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
81
receiver/main/storage.cpp
Normal 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
11
receiver/main/storage.h
Normal 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
65
receiver/main/wav.cpp
Normal 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
14
receiver/main/wav.h
Normal 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);
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user