Moved software into seperate folder
This commit is contained in:
3
software/main/CMakeLists.txt
Normal file
3
software/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "src/main.cpp" "src/helper.cpp" "src/wav.cpp" "src/storage.cpp" "src/i2s.cpp" "src/bluetooth.cpp" "src/avrcp.cpp" "src/a2dp.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
EMBED_FILES "assets/connect.wav" "assets/disconnect.wav")
|
||||
BIN
software/main/assets/connect.wav
Normal file
BIN
software/main/assets/connect.wav
Normal file
Binary file not shown.
BIN
software/main/assets/disconnect.wav
Normal file
BIN
software/main/assets/disconnect.wav
Normal file
Binary file not shown.
7
software/main/include/a2dp.h
Normal file
7
software/main/include/a2dp.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace a2dp {
|
||||
void init();
|
||||
void connect_to_last();
|
||||
}
|
||||
5
software/main/include/avrcp.h
Normal file
5
software/main/include/avrcp.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace avrcp {
|
||||
void init();
|
||||
}
|
||||
6
software/main/include/bluetooth.h
Normal file
6
software/main/include/bluetooth.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace bluetooth {
|
||||
void init();
|
||||
void set_scan_mode(bool connectable);
|
||||
}
|
||||
8
software/main/include/helper.h
Normal file
8
software/main/include/helper.h
Normal file
@@ -0,0 +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);
|
||||
|
||||
12
software/main/include/i2s.h
Normal file
12
software/main/include/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);
|
||||
}
|
||||
11
software/main/include/storage.h
Normal file
11
software/main/include/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();
|
||||
}
|
||||
14
software/main/include/wav.h
Normal file
14
software/main/include/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);
|
||||
}
|
||||
168
software/main/src/a2dp.cpp
Normal file
168
software/main/src/a2dp.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#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!");
|
||||
}
|
||||
} else {
|
||||
bluetooth::set_scan_mode(true);
|
||||
}
|
||||
}
|
||||
|
||||
56
software/main/src/avrcp.cpp
Normal file
56
software/main/src/avrcp.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_avrc_api.h"
|
||||
|
||||
#include "avrcp.h"
|
||||
#include "helper.h"
|
||||
|
||||
#define AVRCP_TAG "APP_AVRCP"
|
||||
|
||||
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 = 127;
|
||||
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");
|
||||
}
|
||||
}
|
||||
155
software/main/src/bluetooth.cpp
Normal file
155
software/main/src/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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
software/main/src/helper.cpp
Normal file
14
software/main/src/helper.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "helper.h"
|
||||
|
||||
const char* addr_to_str(esp_bd_addr_t bda) {
|
||||
static char bda_str[18];
|
||||
sprintf(bda_str, "%02x:%02x:%02x:%02x:%02x:%02x", bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
return (const char*)bda_str;
|
||||
}
|
||||
|
||||
const char* connection_state_to_str(esp_a2d_connection_state_t state) {
|
||||
const char* states[4] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
|
||||
return states[state];
|
||||
}
|
||||
56
software/main/src/i2s.cpp
Normal file
56
software/main/src/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;
|
||||
}
|
||||
29
software/main/src/main.cpp
Normal file
29
software/main/src/main.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_system.h"
|
||||
|
||||
#include "storage.h"
|
||||
#include "i2s.h"
|
||||
#include "bluetooth.h"
|
||||
#include "avrcp.h"
|
||||
#include "a2dp.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
81
software/main/src/storage.cpp
Normal file
81
software/main/src/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;
|
||||
}
|
||||
65
software/main/src/wav.cpp
Normal file
65
software/main/src/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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user