/*!
 * @file  customBluetooth.ino
 * @brief  完全自定义蓝牙配置和使用，代码更倾向于被底层API（应用程序编程接口）调用
 * @details  尝试配置与蓝牙相关的回调函数来改进您的蓝牙音箱！
 * @note  本示例在未修改时与bluetoothAmplifier.ino功能相同。
 * @copyright  Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @license  The MIT License (MIT)
 * @author  [qsjhyy](yihuan.huang@dfrobot.com)
 * @version  V1.0
 * @date  2022-01-27
 * @url  https://github.com/DFRobot/DFRobot_MAX98357A
 */
#include <Arduino.h>

#include <esp_bt_main.h>   // 蓝牙
#include <esp_bt_device.h>
#include <esp_gap_bt_api.h>
#include <esp_a2dp_api.h>   // 蓝牙A2DP协议
#include <esp_avrc_api.h>   // 蓝牙AVRC协议

#include <driver/i2s.h>   // I2S通信


uint8_t remoteAddress[6];   // 已连接的远程蓝牙设备地址

float _volume = 1.0;   // 改变音频信号音量的系数
int32_t _sampleRate = 44100;   // I2S通信频率
bool _avrcConnected = false;   // AVRC连接状态

String _metadata = "";   // 元数据
uint8_t _metaFlag = 0;   // 元数据刷新标志

/**************************************************************
                     初始化与主循环                           
**************************************************************/

void setup(void)
{
  Serial.begin(115200);

  // 初始化I2S
  if (!initI2S(/*_bclk=*/GPIO_NUM_25, /*_lrclk=*/GPIO_NUM_26, /*_din=*/GPIO_NUM_27)){
    Serial.println("初始化I2S失败！");
  }

  // 初始化蓝牙
  if (!initBluetooth("counstomAmplifier")){
    Serial.println("初始化蓝牙失败！");
  }

}

void loop()
{
  // 详细参见：https://github.com/espressif/esp-idf/blob/master/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h
  esp_avrc_ct_send_metadata_cmd(6, ESP_AVRC_MD_ATTR_TITLE);   // 通过AVRC命令从远程蓝牙设备请求元数据
  delay(3000);
}

/**************************************************************
                           初始化                             
**************************************************************/

bool initI2S(int _bclk, int _lrclk, int _din)
{
  static const i2s_config_t i2s_config = {
    .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX),   // 作为主机工作，仅发送数据
    .sample_rate = _sampleRate,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,   // 每个采样16位
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,   // 双声道
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,   // I2S通信I2S飞利浦标准，数据在第二个BCK发射
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,   // 中断级别1
    .dma_buf_count = 4,   // 缓冲区数量，最大128
    .dma_buf_len = 400,   // 每个缓冲区的大小，如果值太高可能会影响AVRC通信
    .use_apll = false,   // 对于高精度时钟的应用，在16至128 MHz频率范围内选择APLL_CLK时钟源。此处不需要，所以选择false
    .tx_desc_auto_clear = true
  };

  static const i2s_pin_config_t pin_config = {
    .bck_io_num = _bclk,   // 串行时钟(SCK)，也称位时钟(BCK)
    .ws_io_num = _lrclk,   // 字选择(WS)，即命令(通道)选择，用于切换左右声道数据
    .data_out_num = _din,   // 串行数据信号(SD)，用于以二进制补码格式传输音频数据
    .data_in_num = I2S_PIN_NO_CHANGE   // 未使用
  };

  if (i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){
    Serial.println("安装并启动I2S驱动失败！");
    return false;
  }
  if (i2s_set_pin(I2S_NUM_0, &pin_config)){
    Serial.println("设置I2S引脚号失败！");
    return false;
  }

  return true;
}

bool initBluetooth(const char * _btName)
{
  // 初始化bluedroid
  if (!btStarted() && !btStart()){
    Serial.println("初始化控制器失败");
    return false;
  }
  esp_bluedroid_status_t bt_state = esp_bluedroid_get_status();
  if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){
    if (esp_bluedroid_init()) {
      Serial.println("初始化bluedroid失败！");
      return false;
    }
  }
  if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){
    if (esp_bluedroid_enable()) {
      Serial.println("启用bluedroid失败！");
      return false;
    }
  }
  if (esp_bt_dev_set_device_name(_btName)){
    Serial.println("设置设备名称失败！");
    return false;
  }

  // 初始化AVRCP
  if (esp_avrc_ct_init()){
    Serial.println("初始化蓝牙AVRCP控制器模块失败！");
    return false;
  }
  if (esp_avrc_ct_register_callback(avrcCallback)){   // 非必需，蓝牙AVRC协议的回调
    Serial.println("向AVRCP模块注册应用回调失败！");
    return false;
  }

  // 初始化A2DP
  if (esp_a2d_sink_init()){
    Serial.println("初始化蓝牙A2DP接收模块失败！");
    return false;
  }
  if (esp_a2d_register_callback(a2dpCallback)){   // 非必需，蓝牙A2DP协议的回调
    Serial.println("向A2DP模块注册应用回调失败！");
    return false;
  }
  if (esp_a2d_sink_register_data_callback(audioDataProcessCallback)){   // 必需，处理蓝牙接收数据的回调
    Serial.println("注册A2DP接收数据输出函数失败！");
    return false;
  }


  // 设置传统蓝牙的可发现性和可连接性模式
  if (esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE)){
    Serial.println("设置传统蓝牙的可发现性和可连接性模式失败！");
    return false;
  }

  return true;
}

/**************************************************************
                           回调函数                             
**************************************************************/

// 此回调函数是必需的，用于将蓝牙接收的音频流数据通过I2S传输到放大器，可以在此处理音频流数据。
// 参考：https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c
void audioDataProcessCallback(const uint8_t *data, uint32_t len)
{
  int16_t* data16 = (int16_t*)data;   // 转换为16位采样数据
  int16_t processedData;   // 存储处理后的音频数据
  int count = len/2;   // 要处理的int16_t音频数据数量
  size_t i2s_bytes_write = 0;   // i2s_write()存储要写入数据数量的变量

  for(int i=0; i<count; i++){
    processedData = (int16_t)((*data16) * _volume);   // 改变音频数据的音量
    data16++;

    i2s_write(I2S_NUM_0, &processedData, 2, &i2s_bytes_write, 10);   // 通过I2S将音频数据传输到放大器
  }
}

// 非必需，蓝牙AVRC协议的回调，某些事件通过发送相应命令触发
// 例如，元数据请求事件：esp_avrc_ct_send_metadata_cmd(num, type);   // 通过AVRC命令从远程蓝牙设备请求元数据
void avrcCallback(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
{
  esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param);

  switch (event) {
    /*!< 元数据响应事件 */
    case ESP_AVRC_CT_METADATA_RSP_EVT: {
        char *attr_text = (char *) malloc (rc->meta_rsp.attr_length + 1);
        memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length);
        attr_text[rc->meta_rsp.attr_length] = 0;
        _metadata = String(attr_text);
        _metaFlag = rc->meta_rsp.attr_id;
        Serial.print("_metadata : ");
        Serial.println(_metadata);
        // Serial.println(rc->meta_rsp.attr_id);

        free(attr_text);
        break;
      }
    /*!< 连接状态改变事件 */
    case ESP_AVRC_CT_CONNECTION_STATE_EVT:{
        /*!< 连接已建立 */
        _avrcConnected = rc->conn_stat.connected;
        if(_avrcConnected){
          uint8_t * p = rc->conn_stat.remote_bda;
          Serial.print("remoteAddress : ");
          for(uint8_t i=0; i<6; i++){
            remoteAddress[i] = *(p + i);
            Serial.print(remoteAddress[i], HEX);
            Serial.print("-");
          }
        /*!< 断开远程设备 */
        }else{
          memset(remoteAddress, 0, 6);
        }
        break;
      }
    /*!< 透传响应事件 */
    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:
    /*!< 播放状态响应事件 */
    case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT:
    /*!< 设置绝对音量响应事件 */
    case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
      break;
    default:
      // "未处理的AVRC事件：%d", event
      break;
  }
}

// 非必需，蓝牙A2DP协议的回调，某些事件通过发送相应命令触发
void a2dpCallback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t*param)
{
  esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param);

  switch (event) {
    /*!<
     * 音频编解码器已配置，仅用于A2DP接收端
     *
     * @brief ESP_A2D_AUDIO_CFG_EVT
     *
     * struct a2d_audio_cfg_param {
     *     esp_bd_addr_t remote_bda;              /*!< 远程蓝牙设备地址
     *     esp_a2d_mcc_t mcc;                     /*!< A2DP媒体编解码器能力信息
     * } audio_cfg;                               /*!< 媒体编解码器配置信息
     */
    case ESP_A2D_AUDIO_CFG_EVT:
    /*!<
     * 连接状态改变事件
     *
     * @brief  ESP_A2D_CONNECTION_STATE_EVT
     *
     * struct a2d_conn_stat_param {
     *     esp_a2d_connection_state_t state;      /*!< esp_a2d_connection_state_t中的一个值
     *     esp_bd_addr_t remote_bda;              /*!< 远程蓝牙设备地址
     *     esp_a2d_disc_rsn_t disc_rsn;           /*!< "已断开连接"的断开原因
     * } conn_stat;                               /*!< A2DP连接状态
     */
    case ESP_A2D_CONNECTION_STATE_EVT:
    /*!< 音频流传输状态改变事件 */
    case ESP_A2D_AUDIO_STATE_EVT:
    /*!< 响应媒体控制命令的确认事件 */
    case ESP_A2D_MEDIA_CTRL_ACK_EVT:
    /*!< 指示a2dp初始化和反初始化完成 */
    case ESP_A2D_PROF_STATE_EVT:
      break;
    default:
      // "a2dp无效回调事件：%d", event
      break;
  }
}
