/main/bt.c
#include <string.h>
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "audio.h"
#include "bt.h"
#include "gfx.h"
#include "play.h"
void _bt_set_mode(bt_mode m);
bt_mode mode = BT_INACTIVE;
esp_bd_addr_t pairing_peer = {0};
char bt_addr_buf[18];
bt_playback_state playback_state = {0};
char* bt_addr_str(esp_bd_addr_t addr) {
snprintf(bt_addr_buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
return bt_addr_buf;
}
void print_hex(uint8_t* v, int len) {
for (int i = 0; i < len; i++) {
printf("%02x ", v[i]);
}
}
char* arr_to_str(void* val, int len) {
char* s = (char*) malloc(len + 1);
memcpy(s, val, len);
s[len] = 0;
return s;
}
int max(int a, int b) {
return a > b ? a : b;
}
void set_last_connected(esp_bd_addr_t* bda) {
const char TAG[] = "set_last_connected";
nvs_handle_t h;
esp_err_t ret;
ret = nvs_open("bluetooth", NVS_READWRITE, &h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
return;
}
ret = nvs_set_blob(h, "lastconn", bda, sizeof(esp_bd_addr_t));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not set last connected in NVS: %s", esp_err_to_name(ret));
goto bt_set_last_connected_cleanup;
}
ret = nvs_commit(h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not commit NVS: %s", esp_err_to_name(ret));
}
ESP_LOGI(TAG, "Set last connected device to %s", bt_addr_str(*bda));
bt_set_last_connected_cleanup:
nvs_close(h);
}
int get_last_connected(esp_bd_addr_t* addr) {
const char TAG[] = "get_last_connected";
nvs_handle_t h;
esp_err_t ret;
ret = nvs_open("bluetooth", NVS_READONLY, &h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
return 0;
}
size_t len = sizeof(esp_bd_addr_t);
ret = (bt_mode) nvs_get_blob(h, "lastconn", (uint8_t*) addr, &len);
if (ret == ESP_ERR_NVS_NOT_FOUND) {
nvs_close(h);
return 0;
} else if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not retrieve last connected from NVS: %s", esp_err_to_name(ret));
nvs_close(h);
return 0;
}
if (len != sizeof(esp_bd_addr_t)) {
ESP_LOGE(TAG, "Short read for last connected: %d bytes", len);
return 0;
}
nvs_close(h);
return 1;
}
void a2dp_connection_state_event(struct a2d_conn_stat_param* p) {
char* event_str;
char* extra = "";
switch (p->state) {
case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
event_str = "Disconnected from";
extra = p->disc_rsn == ESP_A2D_DISC_RSN_NORMAL ? " (normal)" : " (abnormal)";
break;
case ESP_A2D_CONNECTION_STATE_CONNECTED:
event_str = "Connected to";
set_last_connected(&p->remote_bda);
play_set_media_type(PLAY_MEDIA_BLUETOOTH);
break;
case ESP_A2D_CONNECTION_STATE_CONNECTING:
event_str = "Connecting to";
break;
case ESP_A2D_CONNECTION_STATE_DISCONNECTING:
event_str = "Disconnecting from";
break;
default:
event_str = "Unknown event from";
}
ESP_LOGI("BT", "%s %s%s", event_str, bt_addr_str(p->remote_bda), extra);
}
void a2dp_audio_state_event(struct a2d_audio_stat_param *p) {
int r;
switch (p->state) {
case ESP_A2D_AUDIO_STATE_STARTED:
ESP_LOGI("BT", "Started audio from %s", bt_addr_str(p->remote_bda));
r = audio_open();
if (!r) {
ESP_LOGE("BT", "Could not open audio for BT stream");
}
break;
case ESP_A2D_AUDIO_STATE_STOPPED:
ESP_LOGI("BT", "Stopped audio from %s", bt_addr_str(p->remote_bda));
audio_close();
break;
case ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND:
ESP_LOGI("BT", "Remote suspend audio from %s", bt_addr_str(p->remote_bda));
audio_close();
break;
}
}
void a2dp_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) {
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
a2dp_connection_state_event(¶m->conn_stat);
break;
case ESP_A2D_AUDIO_STATE_EVT:
a2dp_audio_state_event(¶m->audio_stat);
break;
case ESP_A2D_AUDIO_CFG_EVT:
// param->audio_cfg;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
// param->media_ctrl_stat;
default:
ESP_LOGI(__func__, "%s evt %d", __func__, event);
}
}
void grab_metadata() {
esp_err_t ret;
ret = esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_PLAYING_TIME);
if (ret != ESP_OK) {
ESP_LOGE("BT", "Could not send metadata request: %s", esp_err_to_name(ret));
}
}
void a2dp_data_callback(const uint8_t *data, uint32_t len) {
audio_send((uint16_t*)data, len / 2);
}
void avrc_callback(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param) {
const char TAG[] = "avrc_callback";
char *text;
esp_err_t ret;
int len;
switch (event) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
ESP_LOGI(TAG, "connection state connected:%d addr:%s", param->conn_stat.connected, bt_addr_str(param->conn_stat.remote_bda));
if (param->conn_stat.connected) {
grab_metadata();
}
break;
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
// param->psth_rsp
ESP_LOGI(TAG, "passthrough response tl:%d key_code:%d, key_state:%d", param->psth_rsp.tl, param->psth_rsp.key_code, param->psth_rsp.key_state);
break;
case ESP_AVRC_CT_METADATA_RSP_EVT:
// param->meta_rsp
text = arr_to_str(param->meta_rsp.attr_text, param->meta_rsp.attr_length);
ESP_LOGI(TAG, "metadata response attr_id:%d text:%s", param->meta_rsp.attr_id, text);
gfx_data_t* gd = gfx_acquire_data(SCENE_NO_CHANGE);
switch(param->meta_rsp.attr_id) {
case ESP_AVRC_MD_ATTR_TITLE:
len = max(param->meta_rsp.attr_length, META_STR_MAX);
memcpy(gd->playing_meta.title, param->meta_rsp.attr_text, len);
gd->playing_meta.title[META_STR_MAX - 1] = 0;
break;
case ESP_AVRC_MD_ATTR_ARTIST:
len = max(param->meta_rsp.attr_length, META_STR_MAX);
memcpy(gd->playing_meta.artist, param->meta_rsp.attr_text, len);
gd->playing_meta.artist[META_STR_MAX - 1] = 0;
break;
case ESP_AVRC_MD_ATTR_PLAYING_TIME:
gd->playing_meta.track_length = atoi(text) / 1000;
break;
}
gfx_release_data();
free(text);
break;
case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT:
// ???
ESP_LOGI(TAG, "play status response");
break;
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
// param->change_ntf
switch (param->change_ntf.event_id) {
case ESP_AVRC_RN_VOLUME_CHANGE:
ESP_LOGI(TAG, "volume change:%d", param->change_ntf.event_parameter.volume);
break;
case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
text = "unknown";
switch (param->change_ntf.event_parameter.playback) {
case ESP_AVRC_PLAYBACK_STOPPED:
text = "stopped";
break;
case ESP_AVRC_PLAYBACK_PLAYING:
text = "playing";
break;
case ESP_AVRC_PLAYBACK_PAUSED:
text = "paused";
break;
case ESP_AVRC_PLAYBACK_FWD_SEEK:
text = "fwd seek";
break;
case ESP_AVRC_PLAYBACK_REV_SEEK:
text = "rev seek";
break;
case ESP_AVRC_PLAYBACK_ERROR:
text = "error";
break;
}
ESP_LOGI(TAG, "playback status changed:%s", text);
grab_metadata();
playback_state.state = param->change_ntf.event_parameter.playback;
ret = esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register play status change");
}
break;
case ESP_AVRC_RN_TRACK_CHANGE:
ESP_LOGI(TAG, "track change:");
print_hex(param->change_ntf.event_parameter.elm_id, 8);
printf("\n");
grab_metadata();
ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register track change");
}
break;
case ESP_AVRC_RN_PLAY_POS_CHANGED:
ESP_LOGI(TAG, "play position change:%d", param->change_ntf.event_parameter.play_pos);
ret = esp_avrc_ct_send_register_notification_cmd(2, ESP_AVRC_RN_PLAY_POS_CHANGED, 1);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register play position change");
}
break;
case ESP_AVRC_RN_BATTERY_STATUS_CHANGE:
ESP_LOGI(TAG, "battery status change:%d", param->change_ntf.event_parameter.batt);
break;
}
break;
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
// param->rmt_feats
ESP_LOGI(TAG, "remote features feat_mask:%08x tg_feat_flag:%04x addr:%s", param->rmt_feats.feat_mask, param->rmt_feats.tg_feat_flag, bt_addr_str(param->rmt_feats.remote_bda));
if (param->rmt_feats.feat_mask & ESP_AVRC_FEAT_RCTG) {
esp_avrc_ct_send_get_rn_capabilities_cmd(0);
}
break;
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
// param->get_rn_caps_rsp
ESP_LOGI(TAG, "remote notification capabilities count:%d set:%04x", param->get_rn_caps_rsp.cap_count, param->get_rn_caps_rsp.evt_set.bits);
esp_avrc_rn_evt_cap_mask_t* events = ¶m->get_rn_caps_rsp.evt_set;
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_PLAY_STATUS_CHANGE)) {
ret = esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register play status change");
}
ESP_LOGI(TAG, "play status notification registered");
} else {
ESP_LOGI(TAG, "play status not supported");
}
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_PLAY_POS_CHANGED)) {
ret = esp_avrc_ct_send_register_notification_cmd(2, ESP_AVRC_RN_PLAY_POS_CHANGED, 1);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register play position change");
}
ESP_LOGI(TAG, "play position notification registered");
} else {
ESP_LOGI(TAG, "play position not supported");
}
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_VOLUME_CHANGE)) {
ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_VOLUME_CHANGE, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register volume change");
}
ESP_LOGI(TAG, "volume change notification registered");
} else {
ESP_LOGI(TAG, "volume change not supported");
}
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_TRACK_CHANGE)) {
ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register track change");
}
ESP_LOGI(TAG, "track change notification registered");
} else {
ESP_LOGI(TAG, "track change not supported");
}
break;
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
// param->set_volume_rsp
ESP_LOGI(TAG, "absolute volume response volume:%d", param->set_volume_rsp.volume);
break;
}
}
void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
const char TAG[] = "bt_app_gap_cb";
switch (event) {
case ESP_BT_GAP_DISC_RES_EVT: {
struct disc_res_param* p = ¶m->disc_res;
ESP_LOGI(TAG, "discovered %s", bt_addr_str(p->bda));
for (int i = 0; i < p->num_prop; i++) {
esp_bt_gap_dev_prop_t *dp = &p->prop[i];
char *s;
uint32_t v;
switch (dp->type) {
case ESP_BT_GAP_DEV_PROP_BDNAME:
s = arr_to_str(dp->val, dp->len);
printf("\tname: %s\n", s);
free(s);
break;
case ESP_BT_GAP_DEV_PROP_COD:
v = *(uint32_t*)dp->val;
printf("\tCoD: %d (%08x)\n", v, v);
break;
case ESP_BT_GAP_DEV_PROP_RSSI:
printf("\tRSSI: %d\n", *(uint8_t*)dp->val);
break;
case ESP_BT_GAP_DEV_PROP_EIR:
printf("\tEIR: ");
print_hex(dp->val, dp->len);
printf("\n");
break;
}
}
break;
}
case ESP_BT_GAP_AUTH_CMPL_EVT: {
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "auth success: %s", param->auth_cmpl.device_name);
} else {
ESP_LOGE(TAG, "auth failed: %d", param->auth_cmpl.stat);
}
break;
}
case ESP_BT_GAP_PIN_REQ_EVT: {
ESP_LOGI(TAG, "PIN REQ: min_16_digit:%d", param->pin_req.min_16_digit);
if (param->pin_req.min_16_digit) {
esp_bt_pin_code_t pin_code = {0};
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
} else {
esp_bt_pin_code_t pin_code = {'1','2','3','4', 0};
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
}
break;
}
case ESP_BT_GAP_CFM_REQ_EVT:
ESP_LOGI(TAG, "confirm %d", param->cfm_req.num_val);
memcpy(pairing_peer, param->cfm_req.bda, sizeof(esp_bd_addr_t));
gfx_data_t* gd = gfx_acquire_data(SCENE_NO_CHANGE);
gd->bt.confirm = param->cfm_req.num_val;
gfx_release_data();
break;
case ESP_BT_GAP_KEY_NOTIF_EVT:
ESP_LOGI(TAG, "passkey: %d", param->key_notif.passkey);
break;
case ESP_BT_GAP_KEY_REQ_EVT:
ESP_LOGI(TAG, "enter passkey!");
break;
case ESP_BT_GAP_MODE_CHG_EVT:
ESP_LOGI(TAG, "mode change: %d", param->mode_chg.mode);
break;
default:
ESP_LOGI(TAG, "event %d\n", event);
}
}
int bt_init() {
const char TAG[] = "bt_start";
esp_err_t ret;
ret = esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
ESP_ERROR_CHECK(ret);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not initialize BT controller: %s", esp_err_to_name(ret));
return 0;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not enable BT controller: %s", esp_err_to_name(ret));
return 0;
}
ESP_LOGI(TAG, "BT controller enabled");
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not initialize bluedroid: %s", esp_err_to_name(ret));
return 0;
}
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not enable bluedroid: %s", esp_err_to_name(ret));
return 0;
}
ESP_LOGI(TAG, "Bluedroid enabled");
ret = esp_bt_dev_set_device_name("Clock Radio");
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not set device name: %s", esp_err_to_name(ret));
return 0;
}
esp_bt_gap_register_callback(bt_app_gap_cb);
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &iocap, sizeof(esp_bt_io_cap_t));
esp_bt_gap_set_pin(ESP_BT_PIN_TYPE_VARIABLE, 0, NULL);
ret = esp_avrc_ct_register_callback(avrc_callback);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not register AVRC callback: %s", esp_err_to_name(ret));
return 0;
}
ret = esp_avrc_ct_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not initialize AVRC: %s", esp_err_to_name(ret));
return 0;
}
esp_a2d_register_callback(&a2dp_callback);
esp_a2d_sink_register_data_callback(&a2dp_data_callback);
ret = esp_a2d_sink_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not initialize A2DP sink: %s", esp_err_to_name(ret));
return 0;
}
nvs_handle_t h;
ret = nvs_open("bluetooth", NVS_READONLY, &h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
return 0;
}
uint8_t v;
ret = (bt_mode) nvs_get_u8(h, "mode", &v);
if (ret == ESP_ERR_NVS_NOT_FOUND) {
_bt_set_mode(BT_INACTIVE);
} else if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could retrieve mode from NVS: %s", esp_err_to_name(ret));
_bt_set_mode(BT_INACTIVE);
} else {
ESP_LOGI(TAG, "Restored mode to %d", v);
_bt_set_mode((bt_mode)v);
}
nvs_close(h);
esp_bd_addr_t bda = {0};
if (get_last_connected(&bda)) {
ret = esp_a2d_sink_connect(bda);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error connecting to %s", bt_addr_str(bda));
}
}
return 1;
}
void bt_discover() {
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
bt_mode bt_get_mode() {
return mode;
}
void _bt_set_mode(bt_mode m) {
const char TAG[] = "bt_set_mode";
esp_err_t ret = 0;
mode = m;
switch (mode) {
case BT_INACTIVE:
ret = esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
break;
case BT_ACTIVE:
ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
break;
case BT_DISCOVERABLE:
ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
break;
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not set scan mode: %s", esp_err_to_name(ret));
return;
}
gfx_data_t *gd = gfx_acquire_data(SCENE_NO_CHANGE);
gd->bt.mode = mode;
gfx_release_data();
}
void bt_set_mode(bt_mode m) {
const char TAG[] = "bt_set_mode";
esp_err_t ret;
_bt_set_mode(m);
nvs_handle_t h;
ret = nvs_open("bluetooth", NVS_READWRITE, &h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not open NVS: %s", esp_err_to_name(ret));
return;
}
ret = nvs_set_u8(h, "mode", (uint8_t)m);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not set mode in NVS: %s", esp_err_to_name(ret));
goto bt_set_mode_cleanup;
}
ret = nvs_commit(h);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not commit NVS: %s", esp_err_to_name(ret));
}
bt_set_mode_cleanup:
nvs_close(h);
}
void bt_confirm_pair(int accept) {
esp_bt_gap_ssp_confirm_reply(pairing_peer, accept);
memset(pairing_peer, 0, sizeof(esp_bd_addr_t));
gfx_data_t *gd = gfx_acquire_data(SCENE_NO_CHANGE);
gd->bt.confirm = 0;
gfx_release_data();
}
void bt_send_key(esp_avrc_pt_cmd_t cmd, esp_avrc_pt_cmd_state_t state) {
esp_avrc_ct_send_passthrough_cmd(1, cmd, state);
}
bt_playback_state* bt_get_playback_state() {
return &playback_state;
}