mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-20 18:05:54 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(a2dp_sink)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := a2dp_sink
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF A2DP-SINK demo
|
||||
======================
|
||||
|
||||
Demo of A2DP audio sink role
|
||||
|
||||
This is the demo of API implementing Advanced Audio Distribution Profile to receive an audio stream.
|
||||
|
||||
This example involves the use of Bluetooth legacy profile A2DP for audio stream reception, AVRCP for media information notifications, and I2S for audio stream output interface.
|
||||
|
||||
Applications such as bluetooth speakers can take advantage of this example as a reference of basic functionalities.
|
||||
|
||||
## How to use this example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To play the sound, there is a need of loudspeaker and possibly an external I2S codec. Otherwise the example will only show a count of audio data packets received silently. Internal DAC can be selected and in this case external I2S codec may not be needed.
|
||||
|
||||
For the I2S codec, pick whatever chip or board works for you; this code was written using a PCM5102 chip, but other I2S boards and chips will probably work as well. The default I2S connections are shown below, but these can be changed in menuconfig:
|
||||
|
||||
| ESP pin | I2S signal |
|
||||
| :-------- | :----------- |
|
||||
| GPIO22 | LRCK |
|
||||
| GPIO25 | DATA |
|
||||
| GPIO26 | BCK |
|
||||
|
||||
If the internal DAC is selected, analog audio will be available on GPIO25 and GPIO26. The output resolution on these pins will always be limited to 8 bit because of the internal structure of the DACs.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Set the use of external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration
|
||||
|
||||
* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output.
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
## Example Output
|
||||
|
||||
After the program is started, the example starts inquiry scan and page scan, awaiting being discovered and connected. Other bluetooth devices such as smart phones can discover a device named "ESP_SPEAKER". A smartphone or another ESP-IDF example of A2DP source can be used to connect to the local device.
|
||||
|
||||
Once A2DP connection is set up, there will be a notification message with the remote device's bluetooth MAC address like the following:
|
||||
|
||||
```
|
||||
I (106427) BT_AV: A2DP connection state: Connected, [64:a2:f9:69:57:a4]
|
||||
```
|
||||
|
||||
If a smartphone is used to connect to local device, starting to play music with an APP will result in the transmission of audio stream. The transmitting of audio stream will be visible in the application log including a count of audio data packets, like this:
|
||||
|
||||
```
|
||||
I (120627) BT_AV: A2DP audio state: Started
|
||||
I (122697) BT_AV: Audio packet count 100
|
||||
I (124697) BT_AV: Audio packet count 200
|
||||
I (126697) BT_AV: Audio packet count 300
|
||||
I (128697) BT_AV: Audio packet count 400
|
||||
|
||||
```
|
||||
|
||||
Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence.
|
||||
|
||||
## Troubleshooting
|
||||
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC data stream is transmitted to A2DP sink and then decoded into PCM samples as output. The PCM data format is normally of 44.1kHz sampling rate, two-channel 16-bit sample stream. Other decoder configurations in ESP32 A2DP sink is supported but need additional modifications of protocol stack settings.
|
||||
* As a usage limitation, ESP32 A2DP sink can support at most one connection with remote A2DP source devices. Also, A2DP sink cannot be used together with A2DP source at the same time, but can be used with other profiles such as SPP and HFP.
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "bt_app_av.c"
|
||||
"bt_app_core.c"
|
||||
"main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,42 @@
|
||||
menu "A2DP Example Configuration"
|
||||
|
||||
choice EXAMPLE_A2DP_SINK_OUTPUT
|
||||
prompt "A2DP Sink Output"
|
||||
default EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
help
|
||||
Select to use Internal DAC or external I2S driver
|
||||
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
bool "Internal DAC"
|
||||
help
|
||||
Select this to use Internal DAC sink output
|
||||
|
||||
config EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
bool "External I2S Codec"
|
||||
help
|
||||
Select this to use External I2S sink output
|
||||
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_I2S_LRCK_PIN
|
||||
int "I2S LRCK (WS) GPIO"
|
||||
default 22
|
||||
depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
help
|
||||
GPIO number to use for I2S LRCK(WS) Driver.
|
||||
|
||||
config EXAMPLE_I2S_BCK_PIN
|
||||
int "I2S BCK GPIO"
|
||||
default 26
|
||||
depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
help
|
||||
GPIO number to use for I2S BCK Driver.
|
||||
|
||||
config EXAMPLE_I2S_DATA_PIN
|
||||
int "I2S DATA GPIO"
|
||||
default 25
|
||||
depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S
|
||||
help
|
||||
GPIO number to use for I2S Data Driver.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,361 @@
|
||||
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "bt_app_core.h"
|
||||
#include "bt_app_av.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 "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2s.h"
|
||||
|
||||
#include "sys/lock.h"
|
||||
|
||||
// AVRCP used transaction label
|
||||
#define APP_RC_CT_TL_GET_CAPS (0)
|
||||
#define APP_RC_CT_TL_GET_META_DATA (1)
|
||||
#define APP_RC_CT_TL_RN_TRACK_CHANGE (2)
|
||||
#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3)
|
||||
#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4)
|
||||
|
||||
/* a2dp event handler */
|
||||
static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param);
|
||||
/* avrc CT event handler */
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
|
||||
/* avrc TG event handler */
|
||||
static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param);
|
||||
|
||||
static uint32_t s_pkt_cnt = 0;
|
||||
static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED;
|
||||
static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
|
||||
static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"};
|
||||
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
|
||||
static _lock_t s_volume_lock;
|
||||
static xTaskHandle s_vcs_task_hdl = NULL;
|
||||
static uint8_t s_volume = 0;
|
||||
static bool s_volume_notify;
|
||||
|
||||
/* callback for A2DP sink */
|
||||
void bt_app_a2d_cb(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: {
|
||||
bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
write_ringbuf(data, len);
|
||||
if (++s_pkt_cnt % 100 == 0) {
|
||||
ESP_LOGI(BT_AV_TAG, "Audio packet count %u", s_pkt_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param)
|
||||
{
|
||||
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param);
|
||||
uint8_t *attr_text = (uint8_t *) 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;
|
||||
|
||||
rc->meta_rsp.attr_text = attr_text;
|
||||
}
|
||||
|
||||
void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_AVRC_CT_METADATA_RSP_EVT:
|
||||
bt_app_alloc_meta_buffer(param);
|
||||
/* fall through */
|
||||
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: {
|
||||
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param)
|
||||
{
|
||||
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:
|
||||
bt_app_work_dispatch(bt_av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
uint8_t *bda = a2d->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
bt_i2s_task_shut_down();
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
|
||||
bt_i2s_task_start_up();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]);
|
||||
s_audio_state = a2d->audio_stat.state;
|
||||
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
|
||||
s_pkt_cnt = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_CFG_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type);
|
||||
// for now only SBC stream is supported
|
||||
if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
|
||||
int sample_rate = 16000;
|
||||
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;
|
||||
}
|
||||
i2s_set_clk(0, sample_rate, 16, 2);
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x",
|
||||
a2d->audio_cfg.mcc.cie.sbc[0],
|
||||
a2d->audio_cfg.mcc.cie.sbc[1],
|
||||
a2d->audio_cfg.mcc.cie.sbc[2],
|
||||
a2d->audio_cfg.mcc.cie.sbc[3]);
|
||||
ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_new_track(void)
|
||||
{
|
||||
// request metadata
|
||||
uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE;
|
||||
esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask);
|
||||
|
||||
// register notification if peer support the event_id
|
||||
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
|
||||
ESP_AVRC_RN_TRACK_CHANGE)) {
|
||||
esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_TRACK_CHANGE, ESP_AVRC_RN_TRACK_CHANGE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_playback_changed(void)
|
||||
{
|
||||
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
|
||||
ESP_AVRC_RN_PLAY_STATUS_CHANGE)) {
|
||||
esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAYBACK_CHANGE, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_play_pos_changed(void)
|
||||
{
|
||||
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
|
||||
ESP_AVRC_RN_PLAY_POS_CHANGED)) {
|
||||
esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAY_POS_CHANGE, ESP_AVRC_RN_PLAY_POS_CHANGED, 10);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
|
||||
{
|
||||
switch (event_id) {
|
||||
case ESP_AVRC_RN_TRACK_CHANGE:
|
||||
bt_av_new_track();
|
||||
break;
|
||||
case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
|
||||
ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback);
|
||||
bt_av_playback_changed();
|
||||
break;
|
||||
case ESP_AVRC_RN_PLAY_POS_CHANGED:
|
||||
ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos);
|
||||
bt_av_play_pos_changed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
|
||||
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
|
||||
switch (event) {
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
|
||||
if (rc->conn_stat.connected) {
|
||||
// get remote supported event_ids of peer AVRCP Target
|
||||
esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
|
||||
} else {
|
||||
// clear peer notification capability record
|
||||
s_avrc_peer_rn_cap.bits = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_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_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
|
||||
free(rc->meta_rsp.attr_text);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
|
||||
bt_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_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_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;
|
||||
bt_av_new_track();
|
||||
bt_av_playback_changed();
|
||||
bt_av_play_pos_changed();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void volume_set_by_controller(uint8_t volume)
|
||||
{
|
||||
ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f);
|
||||
_lock_acquire(&s_volume_lock);
|
||||
s_volume = volume;
|
||||
_lock_release(&s_volume_lock);
|
||||
}
|
||||
|
||||
static void volume_set_by_local_host(uint8_t volume)
|
||||
{
|
||||
ESP_LOGI(BT_RC_TG_TAG, "Volume is set locally to: %d%%", (uint32_t)volume * 100 / 0x7f);
|
||||
_lock_acquire(&s_volume_lock);
|
||||
s_volume = volume;
|
||||
_lock_release(&s_volume_lock);
|
||||
|
||||
if (s_volume_notify) {
|
||||
esp_avrc_rn_param_t rn_param;
|
||||
rn_param.volume = s_volume;
|
||||
esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param);
|
||||
s_volume_notify = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void volume_change_simulation(void *arg)
|
||||
{
|
||||
ESP_LOGI(BT_RC_TG_TAG, "start volume change simulation");
|
||||
|
||||
for (;;) {
|
||||
vTaskDelay(10000 / portTICK_RATE_MS);
|
||||
|
||||
uint8_t volume = (s_volume + 5) & 0x7f;
|
||||
volume_set_by_local_host(volume);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_RC_TG_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: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
if (rc->conn_stat.connected) {
|
||||
// create task to simulate volume change
|
||||
xTaskCreate(volume_change_simulation, "vcsT", 2048, NULL, 5, &s_vcs_task_hdl);
|
||||
} else {
|
||||
vTaskDelete(s_vcs_task_hdl);
|
||||
ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: {
|
||||
ESP_LOGI(BT_RC_TG_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_RC_TG_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_RC_TG_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) {
|
||||
s_volume_notify = true;
|
||||
esp_avrc_rn_param_t rn_param;
|
||||
rn_param.volume = s_volume;
|
||||
esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: {
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_RC_TG_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_AV_H__
|
||||
#define __BT_APP_AV_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
|
||||
#define BT_AV_TAG "BT_AV"
|
||||
#define BT_RC_TG_TAG "RCTG"
|
||||
#define BT_RC_CT_TAG "RCCT"
|
||||
|
||||
/**
|
||||
* @brief callback function for A2DP sink
|
||||
*/
|
||||
void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
|
||||
|
||||
/**
|
||||
* @brief callback function for A2DP sink audio data stream
|
||||
*/
|
||||
void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief callback function for AVRCP controller
|
||||
*/
|
||||
void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param);
|
||||
|
||||
/**
|
||||
* @brief callback function for AVRCP target
|
||||
*/
|
||||
void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param);
|
||||
|
||||
#endif /* __BT_APP_AV_H__*/
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
|
||||
static void bt_app_task_handler(void *arg);
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg);
|
||||
|
||||
static xQueueHandle s_bt_app_task_queue = NULL;
|
||||
static xTaskHandle s_bt_app_task_handle = NULL;
|
||||
static xTaskHandle s_bt_i2s_task_handle = NULL;
|
||||
static RingbufHandle_t s_ringbuf_i2s = NULL;;
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
bt_app_msg_t msg;
|
||||
memset(&msg, 0, sizeof(bt_app_msg_t));
|
||||
|
||||
msg.sig = BT_APP_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return bt_app_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_task_handler(void *arg)
|
||||
{
|
||||
bt_app_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case BT_APP_SIG_WORK_DISPATCH:
|
||||
bt_app_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
} // switch (msg.sig)
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
|
||||
xTaskCreate(bt_app_task_handler, "BtAppT", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (s_bt_app_task_handle) {
|
||||
vTaskDelete(s_bt_app_task_handle);
|
||||
s_bt_app_task_handle = NULL;
|
||||
}
|
||||
if (s_bt_app_task_queue) {
|
||||
vQueueDelete(s_bt_app_task_queue);
|
||||
s_bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_i2s_task_handler(void *arg)
|
||||
{
|
||||
uint8_t *data = NULL;
|
||||
size_t item_size = 0;
|
||||
size_t bytes_written = 0;
|
||||
|
||||
for (;;) {
|
||||
data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (portTickType)portMAX_DELAY);
|
||||
if (item_size != 0){
|
||||
i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY);
|
||||
vRingbufferReturnItem(s_ringbuf_i2s,(void *)data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_i2s_task_start_up(void)
|
||||
{
|
||||
s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF);
|
||||
if(s_ringbuf_i2s == NULL){
|
||||
return;
|
||||
}
|
||||
|
||||
xTaskCreate(bt_i2s_task_handler, "BtI2ST", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_i2s_task_shut_down(void)
|
||||
{
|
||||
if (s_bt_i2s_task_handle) {
|
||||
vTaskDelete(s_bt_i2s_task_handle);
|
||||
s_bt_i2s_task_handle = NULL;
|
||||
}
|
||||
|
||||
if (s_ringbuf_i2s) {
|
||||
vRingbufferDelete(s_ringbuf_i2s);
|
||||
s_ringbuf_i2s = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t write_ringbuf(const uint8_t *data, size_t size)
|
||||
{
|
||||
BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (portTickType)portMAX_DELAY);
|
||||
if(done){
|
||||
return size;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to bt_app_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
bt_app_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
|
||||
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
void bt_i2s_task_start_up(void);
|
||||
|
||||
void bt_i2s_task_shut_down(void);
|
||||
|
||||
size_t write_ringbuf(const uint8_t *data, size_t size);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "bt_app_av.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 "driver/i2s.h"
|
||||
|
||||
/* event for handler "bt_av_hdl_stack_up */
|
||||
enum {
|
||||
BT_APP_EVT_STACK_UP = 0,
|
||||
};
|
||||
|
||||
/* handler for bluetooth stack enabled events */
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param);
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
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);
|
||||
|
||||
i2s_config_t i2s_config = {
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
|
||||
#else
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX
|
||||
#endif
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = 16,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_MSB,
|
||||
.dma_buf_count = 6,
|
||||
.dma_buf_len = 60,
|
||||
.intr_alloc_flags = 0, //Default interrupt priority
|
||||
.tx_desc_auto_clear = true //Auto clear tx descriptor on underflow
|
||||
};
|
||||
|
||||
|
||||
i2s_driver_install(0, &i2s_config, 0, NULL);
|
||||
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
|
||||
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
|
||||
i2s_set_pin(0, NULL);
|
||||
#else
|
||||
i2s_pin_config_t pin_config = {
|
||||
.bck_io_num = CONFIG_EXAMPLE_I2S_BCK_PIN,
|
||||
.ws_io_num = CONFIG_EXAMPLE_I2S_LRCK_PIN,
|
||||
.data_out_num = CONFIG_EXAMPLE_I2S_DATA_PIN,
|
||||
.data_in_num = -1 //Not used
|
||||
};
|
||||
|
||||
i2s_set_pin(0, &pin_config);
|
||||
#endif
|
||||
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
/* create application task */
|
||||
bt_app_task_start_up();
|
||||
|
||||
/* Bluetooth device name, connection mode and profile set up */
|
||||
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use fixed pin code
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_set_pin(pin_type, 4, pin_code);
|
||||
|
||||
}
|
||||
|
||||
void bt_app_gap_cb(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);
|
||||
esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
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;
|
||||
#endif
|
||||
|
||||
default: {
|
||||
ESP_LOGI(BT_AV_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
|
||||
switch (event) {
|
||||
case BT_APP_EVT_STACK_UP: {
|
||||
/* set up device name */
|
||||
char *dev_name = "ESP_SPEAKER";
|
||||
esp_bt_dev_set_device_name(dev_name);
|
||||
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
/* initialize AVRCP controller */
|
||||
esp_avrc_ct_init();
|
||||
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
|
||||
/* initialize AVRCP target */
|
||||
assert (esp_avrc_tg_init() == ESP_OK);
|
||||
esp_avrc_tg_register_callback(bt_app_rc_tg_cb);
|
||||
|
||||
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);
|
||||
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK);
|
||||
|
||||
/* initialize A2DP sink */
|
||||
esp_a2d_register_callback(&bt_app_a2d_cb);
|
||||
esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb);
|
||||
esp_a2d_sink_init();
|
||||
|
||||
/* set discoverable and connectable mode, wait to be connected */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled and BT_DRAM_RELEASE is disabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_A2DP_ENABLE=y
|
||||
CONFIG_BT_SPP_ENABLED=n
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(a2dp_source)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := a2dp_source
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF A2DP-SOURCE demo
|
||||
========================
|
||||
|
||||
Demo of A2DP audio source role
|
||||
|
||||
This is the demo of using Advanced Audio Distribution Profile APIs to transmit audio stream. Application can take advantage of this example to implement portable audio players or microphones to transmit audio stream to A2DP sink devices.
|
||||
|
||||
## How to use this example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example is able to run on any commonly available ESP32 development board. And is supposed to connect to A2DP sink example in ESP-IDF.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output.
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
## Example Output
|
||||
|
||||
For the first step, this example performs device discovery to search for a target device(A2DP sink) whose device name is "ESP_SPEAKER" and whose "Rendering" bit of its Service Class field is set in its Class of Device. If a candidate target is found, the local device will initiate connection with it.
|
||||
|
||||
After connection with A2DP sink is established, the example performs the following running loop 1-2-3-4-1:
|
||||
1. audio transmission starts and lasts for a while
|
||||
2. audio transmission stops
|
||||
3. disconnect with target device
|
||||
4. reconnect to target device
|
||||
|
||||
The example implements an event loop triggered by a periodic "heart beat" timer and events from Bluetooth protocol stack callback functions.
|
||||
|
||||
After the local device discovers the target device and initiates connection, there will be logging message like this:
|
||||
|
||||
```
|
||||
I (4090) BT_AV: Found a target device, address 24:0a:c4:02:0e:ee, name ESP_SPEAKER
|
||||
I (4090) BT_AV: Cancel device discovery ...
|
||||
I (4100) BT_AV: Device discovery stopped.
|
||||
I (4100) BT_AV: a2dp connecting to peer: ESP_SPEAKER
|
||||
```
|
||||
|
||||
If connection is set up successfully, there will be such message:
|
||||
|
||||
```
|
||||
I (5100) BT_AV: a2dp connected
|
||||
```
|
||||
|
||||
Start of audio transmission has the following notification message:
|
||||
|
||||
```
|
||||
I (10880) BT_AV: a2dp media ready checking ...
|
||||
...
|
||||
I (10880) BT_AV: a2dp media ready, starting ...
|
||||
...
|
||||
I (11400) BT_AV: a2dp media start successfully.
|
||||
```
|
||||
|
||||
Stop of audio transmission, and disconnection with remote device generate the following notification message:
|
||||
|
||||
```
|
||||
I (110880) BT_AV: a2dp media stopping...
|
||||
...
|
||||
I (110920) BT_AV: a2dp media stopped successfully, disconnecting...
|
||||
...
|
||||
I (111040) BT_AV: a2dp disconnected
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate, two-channel 16-bit sample data. Other SBC configurations can be supported but there is a need for additional modifications to the protocol stack.
|
||||
* The raw PCM media stream in the example is generated by a sequence of random number, so the sound played on the sink side will be piercing noise.
|
||||
* As a usage limitation, ESP32 A2DP source can support at most one connection with remote A2DP sink devices. Also, A2DP source cannot be used together with A2DP sink at the same time, but can be used with other profiles such as SPP and HFP.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "bt_app_core.c"
|
||||
"main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
|
||||
static void bt_app_task_handler(void *arg);
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg);
|
||||
|
||||
static xQueueHandle s_bt_app_task_queue = NULL;
|
||||
static xTaskHandle s_bt_app_task_handle = NULL;
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
bt_app_msg_t msg;
|
||||
memset(&msg, 0, sizeof(bt_app_msg_t));
|
||||
|
||||
msg.sig = BT_APP_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return bt_app_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_task_handler(void *arg)
|
||||
{
|
||||
bt_app_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case BT_APP_SIG_WORK_DISPATCH:
|
||||
bt_app_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
} // switch (msg.sig)
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
|
||||
xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (s_bt_app_task_handle) {
|
||||
vTaskDelete(s_bt_app_task_handle);
|
||||
s_bt_app_task_handle = NULL;
|
||||
}
|
||||
if (s_bt_app_task_queue) {
|
||||
vQueueDelete(s_bt_app_task_queue);
|
||||
s_bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to bt_app_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
bt_app_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
|
||||
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "bt_app_core.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"
|
||||
|
||||
#define BT_AV_TAG "BT_AV"
|
||||
#define BT_RC_CT_TAG "RCCT"
|
||||
|
||||
// AVRCP used transaction label
|
||||
#define APP_RC_CT_TL_GET_CAPS (0)
|
||||
#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1)
|
||||
|
||||
/* event for handler "bt_av_hdl_stack_up */
|
||||
enum {
|
||||
BT_APP_EVT_STACK_UP = 0,
|
||||
};
|
||||
|
||||
/* A2DP global state */
|
||||
enum {
|
||||
APP_AV_STATE_IDLE,
|
||||
APP_AV_STATE_DISCOVERING,
|
||||
APP_AV_STATE_DISCOVERED,
|
||||
APP_AV_STATE_UNCONNECTED,
|
||||
APP_AV_STATE_CONNECTING,
|
||||
APP_AV_STATE_CONNECTED,
|
||||
APP_AV_STATE_DISCONNECTING,
|
||||
};
|
||||
|
||||
/* sub states of APP_AV_STATE_CONNECTED */
|
||||
enum {
|
||||
APP_AV_MEDIA_STATE_IDLE,
|
||||
APP_AV_MEDIA_STATE_STARTING,
|
||||
APP_AV_MEDIA_STATE_STARTED,
|
||||
APP_AV_MEDIA_STATE_STOPPING,
|
||||
};
|
||||
|
||||
#define BT_APP_HEART_BEAT_EVT (0xff00)
|
||||
|
||||
/// handler for bluetooth stack enabled events
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param);
|
||||
|
||||
/// callback function for A2DP source
|
||||
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
|
||||
|
||||
/// callback function for A2DP source audio data stream
|
||||
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len);
|
||||
|
||||
/// callback function for AVRCP controller
|
||||
static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param);
|
||||
|
||||
static void a2d_app_heart_beat(void *arg);
|
||||
|
||||
/// A2DP application state machine
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
|
||||
|
||||
/// avrc CT event handler
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
|
||||
|
||||
/* A2DP application state machine handler for each state */
|
||||
static void bt_app_av_state_unconnected(uint16_t event, void *param);
|
||||
static void bt_app_av_state_connecting(uint16_t event, void *param);
|
||||
static void bt_app_av_state_connected(uint16_t event, void *param);
|
||||
static void bt_app_av_state_disconnecting(uint16_t event, void *param);
|
||||
|
||||
static esp_bd_addr_t s_peer_bda = {0};
|
||||
static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
|
||||
static int s_a2d_state = APP_AV_STATE_IDLE;
|
||||
static int s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
static int s_intv_cnt = 0;
|
||||
static int s_connecting_intv = 0;
|
||||
static uint32_t s_pkt_cnt = 0;
|
||||
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
|
||||
static TimerHandle_t s_tmr;
|
||||
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *p = bda;
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
|
||||
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_init() != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_enable() != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* create application task */
|
||||
bt_app_task_start_up();
|
||||
|
||||
/* Bluetooth device name, connection mode and profile set up */
|
||||
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
char bda_str[18];
|
||||
uint32_t cod = 0;
|
||||
int32_t rssi = -129; /* invalid value */
|
||||
uint8_t *eir = NULL;
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18));
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%x", cod);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
ESP_LOGI(BT_AV_TAG, "--RSSI: %d", rssi);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_EIR:
|
||||
eir = (uint8_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* search for device named "ESP_SPEAKER" in its extended inqury response */
|
||||
if (eir) {
|
||||
get_name_from_eir(eir, s_peer_bdname, NULL);
|
||||
if (strcmp((char *)s_peer_bdname, "ESP_SPEAKER") != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERED;
|
||||
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
filter_inquiry_scan_result(param);
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
|
||||
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
} else {
|
||||
// not discovered, continue to discover
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Discovery started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT:
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
break;
|
||||
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);
|
||||
esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT: {
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(BT_AV_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
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;
|
||||
#endif
|
||||
|
||||
default: {
|
||||
ESP_LOGI(BT_AV_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
|
||||
switch (event) {
|
||||
case BT_APP_EVT_STACK_UP: {
|
||||
/* set up device name */
|
||||
char *dev_name = "ESP_A2DP_SRC";
|
||||
esp_bt_dev_set_device_name(dev_name);
|
||||
|
||||
/* register GAP callback function */
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
/* initialize AVRCP controller */
|
||||
esp_avrc_ct_init();
|
||||
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
|
||||
|
||||
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);
|
||||
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK);
|
||||
|
||||
/* initialize A2DP source */
|
||||
esp_a2d_register_callback(&bt_app_a2d_cb);
|
||||
esp_a2d_source_register_data_callback(bt_app_a2d_data_cb);
|
||||
esp_a2d_source_init();
|
||||
|
||||
/* set discoverable and connectable mode */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
|
||||
/* start device discovery */
|
||||
ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
|
||||
/* create and start heart beat timer */
|
||||
do {
|
||||
int tmr_id = 0;
|
||||
s_tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS),
|
||||
pdTRUE, (void *)tmr_id, a2d_app_heart_beat);
|
||||
xTimerStart(s_tmr, portMAX_DELAY);
|
||||
} while (0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
|
||||
{
|
||||
bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
|
||||
}
|
||||
|
||||
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
|
||||
{
|
||||
if (len < 0 || data == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate random sequence
|
||||
int val = rand() % (1 << 16);
|
||||
for (int i = 0; i < (len >> 1); i++) {
|
||||
data[(i << 1)] = val & 0xff;
|
||||
data[(i << 1) + 1] = (val >> 8) & 0xff;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void a2d_app_heart_beat(void *arg)
|
||||
{
|
||||
bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
ESP_LOGI(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, s_a2d_state, event);
|
||||
switch (s_a2d_state) {
|
||||
case APP_AV_STATE_DISCOVERING:
|
||||
case APP_AV_STATE_DISCOVERED:
|
||||
break;
|
||||
case APP_AV_STATE_UNCONNECTED:
|
||||
bt_app_av_state_unconnected(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTING:
|
||||
bt_app_av_state_connecting(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTED:
|
||||
bt_app_av_state_connected(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_DISCONNECTING:
|
||||
bt_app_av_state_disconnecting(event, param);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, s_a2d_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_unconnected(uint16_t event, void *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
uint8_t *p = s_peer_bda;
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
s_connecting_intv = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connecting(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connected");
|
||||
s_a2d_state = APP_AV_STATE_CONNECTED;
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT:
|
||||
if (++s_connecting_intv >= 2) {
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
s_connecting_intv = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_media_proc(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (s_media_state) {
|
||||
case APP_AV_MEDIA_STATE_IDLE: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY);
|
||||
} else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STARTING;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media start successfully.");
|
||||
s_intv_cnt = 0;
|
||||
s_media_state = APP_AV_MEDIA_STATE_STARTED;
|
||||
} else {
|
||||
// not started succesfully, transfer to idle state
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media start failed.");
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTED: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
if (++s_intv_cnt >= 10) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
|
||||
s_intv_cnt = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STOPPING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media stopped successfully, disconnecting...");
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
esp_a2d_source_disconnect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_DISCONNECTING;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connected(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
|
||||
s_pkt_cnt = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
// not suppposed to occur for A2DP source
|
||||
break;
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
bt_app_av_media_proc(event, param);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_disconnecting(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
case BT_APP_HEART_BEAT_EVT:
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_rc_ct_cb(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:
|
||||
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
|
||||
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_volume_changed(void)
|
||||
{
|
||||
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
|
||||
ESP_AVRC_RN_VOLUME_CHANGE)) {
|
||||
esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
|
||||
{
|
||||
switch (event_id) {
|
||||
case ESP_AVRC_RN_VOLUME_CHANGE:
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5);
|
||||
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
|
||||
bt_av_volume_changed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
|
||||
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
|
||||
switch (event) {
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
|
||||
if (rc->conn_stat.connected) {
|
||||
// get remote supported event_ids of peer AVRCP Target
|
||||
esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
|
||||
} else {
|
||||
// clear peer notification capability record
|
||||
s_avrc_peer_rn_cap.bits = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_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_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
|
||||
free(rc->meta_rsp.attr_text);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
|
||||
bt_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_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_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;
|
||||
|
||||
bt_av_volume_changed();
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume rsp: volume %d", rc->set_volume_rsp.volume);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_A2DP_ENABLE=y
|
||||
CONFIG_BT_SPP_ENABLED=n
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_discovery)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := bt_discovery
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
================= =====
|
||||
Supported Targets ESP32
|
||||
================= =====
|
||||
|
||||
ESP-IDF BT-INQUIRY demo
|
||||
======================
|
||||
|
||||
Demo of Classic Bluetooth Device and Service Discovery
|
||||
|
||||
This is the demo for user to use ESP_APIs to perform inquiry to search for a target device and then performs service search via SDP.
|
||||
|
||||
Options choose step:
|
||||
1. idf.py menuconfig.
|
||||
2. enter menuconfig "Component config", choose "Bluetooth"
|
||||
3. enter menu Bluetooth, choose "Classic Bluetooth" and do not choose "Release DRAM from Classic BT controller"
|
||||
4. choose your options.
|
||||
|
||||
After the program started, the device will start inquiry to search for a device with Major device type "PHONE" in the Class of Device Field. Then it will cancel the inquiry and started to perform service discovering on this remote device.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "bt_discovery.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for Classic Bluetooth device and service discovery Demo.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.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"
|
||||
|
||||
#define GAP_TAG "GAP"
|
||||
|
||||
typedef enum {
|
||||
APP_GAP_STATE_IDLE = 0,
|
||||
APP_GAP_STATE_DEVICE_DISCOVERING,
|
||||
APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE,
|
||||
APP_GAP_STATE_SERVICE_DISCOVERING,
|
||||
APP_GAP_STATE_SERVICE_DISCOVER_COMPLETE,
|
||||
} app_gap_state_t;
|
||||
|
||||
typedef struct {
|
||||
bool dev_found;
|
||||
uint8_t bdname_len;
|
||||
uint8_t eir_len;
|
||||
uint8_t rssi;
|
||||
uint32_t cod;
|
||||
uint8_t eir[ESP_BT_GAP_EIR_DATA_LEN];
|
||||
uint8_t bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
|
||||
esp_bd_addr_t bda;
|
||||
app_gap_state_t state;
|
||||
} app_gap_cb_t;
|
||||
|
||||
static app_gap_cb_t m_dev_info;
|
||||
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *p = bda;
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *uuid2str(esp_bt_uuid_t *uuid, char *str, size_t size)
|
||||
{
|
||||
if (uuid == NULL || str == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (uuid->len == 2 && size >= 5) {
|
||||
sprintf(str, "%04x", uuid->uuid.uuid16);
|
||||
} else if (uuid->len == 4 && size >= 9) {
|
||||
sprintf(str, "%08x", uuid->uuid.uuid32);
|
||||
} else if (uuid->len == 16 && size >= 37) {
|
||||
uint8_t *p = uuid->uuid.uuid128;
|
||||
sprintf(str, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
p[15], p[14], p[13], p[12], p[11], p[10], p[9], p[8],
|
||||
p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_device_info(esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
char bda_str[18];
|
||||
uint32_t cod = 0;
|
||||
int32_t rssi = -129; /* invalid value */
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
ESP_LOGI(GAP_TAG, "Device found: %s", bda2str(param->disc_res.bda, bda_str, 18));
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
ESP_LOGI(GAP_TAG, "--Class of Device: 0x%x", cod);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
ESP_LOGI(GAP_TAG, "--RSSI: %d", rssi);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
if (p_dev->dev_found && 0 != memcmp(param->disc_res.bda, p_dev->bda, ESP_BD_ADDR_LEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_major_dev(cod) == ESP_BT_COD_MAJOR_DEV_PHONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(p_dev->bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
p_dev->dev_found = true;
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
p_dev->cod = *(uint32_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
p_dev->rssi = *(int8_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME: {
|
||||
uint8_t len = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN :
|
||||
(uint8_t)p->len;
|
||||
memcpy(p_dev->bdname, (uint8_t *)(p->val), len);
|
||||
p_dev->bdname[len] = '\0';
|
||||
p_dev->bdname_len = len;
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_DEV_PROP_EIR: {
|
||||
memcpy(p_dev->eir, (uint8_t *)(p->val), p->len);
|
||||
p_dev->eir_len = p->len;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_dev->eir && p_dev->bdname_len == 0) {
|
||||
get_name_from_eir(p_dev->eir, p_dev->bdname, &p_dev->bdname_len);
|
||||
ESP_LOGI(GAP_TAG, "Found a target device, address %s, name %s", bda_str, p_dev->bdname);
|
||||
p_dev->state = APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE;
|
||||
ESP_LOGI(GAP_TAG, "Cancel device discovery ...");
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_gap_init(void)
|
||||
{
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
memset(p_dev, 0, sizeof(app_gap_cb_t));
|
||||
}
|
||||
|
||||
void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
char bda_str[18];
|
||||
char uuid_str[37];
|
||||
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
update_device_info(param);
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
|
||||
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
|
||||
ESP_LOGI(GAP_TAG, "Device discovery stopped.");
|
||||
if ( (p_dev->state == APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE ||
|
||||
p_dev->state == APP_GAP_STATE_DEVICE_DISCOVERING)
|
||||
&& p_dev->dev_found) {
|
||||
p_dev->state = APP_GAP_STATE_SERVICE_DISCOVERING;
|
||||
ESP_LOGI(GAP_TAG, "Discover services ...");
|
||||
esp_bt_gap_get_remote_services(p_dev->bda);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(GAP_TAG, "Discovery started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT: {
|
||||
if (memcmp(param->rmt_srvcs.bda, p_dev->bda, ESP_BD_ADDR_LEN) == 0 &&
|
||||
p_dev->state == APP_GAP_STATE_SERVICE_DISCOVERING) {
|
||||
p_dev->state = APP_GAP_STATE_SERVICE_DISCOVER_COMPLETE;
|
||||
if (param->rmt_srvcs.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(GAP_TAG, "Services for device %s found", bda2str(p_dev->bda, bda_str, 18));
|
||||
for (int i = 0; i < param->rmt_srvcs.num_uuids; i++) {
|
||||
esp_bt_uuid_t *u = param->rmt_srvcs.uuid_list + i;
|
||||
ESP_LOGI(GAP_TAG, "--%s", uuid2str(u, uuid_str, 37));
|
||||
// ESP_LOGI(GAP_TAG, "--%d", u->len);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(GAP_TAG, "Services for device %s not found", bda2str(p_dev->bda, bda_str, 18));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
default: {
|
||||
ESP_LOGI(GAP_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_gap_start_up(void)
|
||||
{
|
||||
char *dev_name = "ESP_GAP_INQRUIY";
|
||||
esp_bt_dev_set_device_name(dev_name);
|
||||
|
||||
/* set discoverable and connectable mode, wait to be connected */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
|
||||
/* register GAP callback function */
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
/* inititialize device information and status */
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
memset(p_dev, 0, sizeof(app_gap_cb_t));
|
||||
|
||||
/* start to discover nearby Bluetooth devices */
|
||||
p_dev->state = APP_GAP_STATE_DEVICE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
bt_app_gap_start_up();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled and BT_DRAM_RELEASE is disabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_A2DP_ENABLE=n
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_spp_acceptor_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := bt_spp_acceptor_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,20 @@
|
||||
================= =====
|
||||
Supported Targets ESP32
|
||||
================= =====
|
||||
|
||||
ESP-IDF BT-SPP-ACCEPTOR demo
|
||||
======================
|
||||
|
||||
Demo of SPP acceptor role
|
||||
|
||||
This is the demo for user to use ESP_APIs to create a SPP acceptor.
|
||||
|
||||
Options choose step:
|
||||
1. idf.py menuconfig.
|
||||
2. enter menuconfig "Component config", choose "Bluetooth"
|
||||
3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile"
|
||||
4. choose your options.
|
||||
|
||||
Then set SPP_SHOW_MODE as SPP_SHOW_DATA or SPP_SHOW_SPEED in code(should be same with bt_spp_initator).
|
||||
|
||||
After the program started, bt_spp_initator will connect it and send data.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "example_spp_acceptor_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_spp_api.h"
|
||||
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
#define SPP_TAG "SPP_ACCEPTOR_DEMO"
|
||||
#define SPP_SERVER_NAME "SPP_SERVER"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_SPP_ACCEPTOR"
|
||||
#define SPP_SHOW_DATA 0
|
||||
#define SPP_SHOW_SPEED 1
|
||||
#define SPP_SHOW_MODE SPP_SHOW_SPEED /*Choose show mode: show data or speed*/
|
||||
|
||||
static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;
|
||||
|
||||
static struct timeval time_new, time_old;
|
||||
static long data_num = 0;
|
||||
|
||||
static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
|
||||
static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;
|
||||
|
||||
static void print_speed(void)
|
||||
{
|
||||
float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0;
|
||||
float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0;
|
||||
float time_interval = time_new_s - time_old_s;
|
||||
float speed = data_num * 8 / time_interval / 1000.0;
|
||||
ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed);
|
||||
data_num = 0;
|
||||
time_old.tv_sec = time_new.tv_sec;
|
||||
time_old.tv_usec = time_new.tv_usec;
|
||||
}
|
||||
|
||||
static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_SPP_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
|
||||
esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME);
|
||||
break;
|
||||
case ESP_SPP_DISCOVERY_COMP_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
|
||||
break;
|
||||
case ESP_SPP_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
|
||||
break;
|
||||
case ESP_SPP_CLOSE_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT");
|
||||
break;
|
||||
case ESP_SPP_START_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
|
||||
break;
|
||||
case ESP_SPP_CL_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
|
||||
break;
|
||||
case ESP_SPP_DATA_IND_EVT:
|
||||
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT len=%d handle=%d",
|
||||
param->data_ind.len, param->data_ind.handle);
|
||||
esp_log_buffer_hex("",param->data_ind.data,param->data_ind.len);
|
||||
#else
|
||||
gettimeofday(&time_new, NULL);
|
||||
data_num += param->data_ind.len;
|
||||
if (time_new.tv_sec - time_old.tv_sec >= 3) {
|
||||
print_speed();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case ESP_SPP_CONG_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT");
|
||||
break;
|
||||
case ESP_SPP_WRITE_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT");
|
||||
break;
|
||||
case ESP_SPP_SRV_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
|
||||
gettimeofday(&time_old, NULL);
|
||||
break;
|
||||
case ESP_SPP_SRV_STOP_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_STOP_EVT");
|
||||
break;
|
||||
case ESP_SPP_UNINIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_UNINIT_EVT");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void esp_bt_gap_cb(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(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT:{
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: {
|
||||
ESP_LOGI(SPP_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
CONFIG_BT_SPP_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_spp_initiator_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := bt_spp_initiator_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,20 @@
|
||||
================= =====
|
||||
Supported Targets ESP32
|
||||
================= =====
|
||||
|
||||
ESP-IDF BT-SPP-INITATOR demo
|
||||
======================
|
||||
|
||||
Demo of SPP initator role
|
||||
|
||||
This is the demo for user to use ESP_APIs to create a SPP initator.
|
||||
|
||||
Options choose step:
|
||||
1. idf.py menuconfig.
|
||||
2. enter menuconfig "Component config", choose "Bluetooth"
|
||||
3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile"
|
||||
4. choose your options.
|
||||
|
||||
Then set SPP_SHOW_MODE as SPP_SHOW_DATA or SPP_SHOW_SPEED in code(should be same with bt_spp_acceptor).
|
||||
|
||||
After the program started, It will connect to bt_spp_acceptor and send data.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "example_spp_initiator_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_spp_api.h"
|
||||
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
#define SPP_TAG "SPP_INITIATOR_DEMO"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_SPP_INITIATOR"
|
||||
|
||||
#define SPP_SHOW_DATA 0
|
||||
#define SPP_SHOW_SPEED 1
|
||||
#define SPP_SHOW_MODE SPP_SHOW_SPEED /*Choose show mode: show data or speed*/
|
||||
|
||||
static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;
|
||||
|
||||
static struct timeval time_new, time_old;
|
||||
static long data_num = 0;
|
||||
|
||||
static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
|
||||
static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER;
|
||||
|
||||
static esp_bd_addr_t peer_bd_addr;
|
||||
static uint8_t peer_bdname_len;
|
||||
static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
|
||||
static const char remote_device_name[] = "ESP_SPP_ACCEPTOR";
|
||||
static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY;
|
||||
static const uint8_t inq_len = 30;
|
||||
static const uint8_t inq_num_rsps = 0;
|
||||
|
||||
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
|
||||
#define SPP_DATA_LEN 20
|
||||
#else
|
||||
#define SPP_DATA_LEN ESP_SPP_MAX_MTU
|
||||
#endif
|
||||
static uint8_t spp_data[SPP_DATA_LEN];
|
||||
|
||||
static void print_speed(void)
|
||||
{
|
||||
float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0;
|
||||
float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0;
|
||||
float time_interval = time_new_s - time_old_s;
|
||||
float speed = data_num * 8 / time_interval / 1000.0;
|
||||
ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed);
|
||||
data_num = 0;
|
||||
time_old.tv_sec = time_new.tv_sec;
|
||||
time_old.tv_usec = time_new.tv_usec;
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_SPP_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
|
||||
esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps);
|
||||
|
||||
break;
|
||||
case ESP_SPP_DISCOVERY_COMP_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT status=%d scn_num=%d",param->disc_comp.status, param->disc_comp.scn_num);
|
||||
if (param->disc_comp.status == ESP_SPP_SUCCESS) {
|
||||
esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], peer_bd_addr);
|
||||
}
|
||||
break;
|
||||
case ESP_SPP_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
|
||||
esp_spp_write(param->open.handle, SPP_DATA_LEN, spp_data);
|
||||
gettimeofday(&time_old, NULL);
|
||||
break;
|
||||
case ESP_SPP_CLOSE_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT");
|
||||
break;
|
||||
case ESP_SPP_START_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
|
||||
break;
|
||||
case ESP_SPP_CL_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
|
||||
break;
|
||||
case ESP_SPP_DATA_IND_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT");
|
||||
break;
|
||||
case ESP_SPP_CONG_EVT:
|
||||
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT cong=%d", param->cong.cong);
|
||||
#endif
|
||||
if (param->cong.cong == 0) {
|
||||
esp_spp_write(param->cong.handle, SPP_DATA_LEN, spp_data);
|
||||
}
|
||||
break;
|
||||
case ESP_SPP_WRITE_EVT:
|
||||
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT len=%d cong=%d", param->write.len , param->write.cong);
|
||||
esp_log_buffer_hex("",spp_data,SPP_DATA_LEN);
|
||||
#else
|
||||
gettimeofday(&time_new, NULL);
|
||||
data_num += param->write.len;
|
||||
if (time_new.tv_sec - time_old.tv_sec >= 3) {
|
||||
print_speed();
|
||||
}
|
||||
#endif
|
||||
if (param->write.cong == 0) {
|
||||
esp_spp_write(param->write.handle, SPP_DATA_LEN, spp_data);
|
||||
}
|
||||
break;
|
||||
case ESP_SPP_SRV_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
|
||||
break;
|
||||
case ESP_SPP_UNINIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_UNINIT_EVT");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
switch(event){
|
||||
case ESP_BT_GAP_DISC_RES_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_RES_EVT");
|
||||
esp_log_buffer_hex(SPP_TAG, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++){
|
||||
if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR
|
||||
&& get_name_from_eir(param->disc_res.prop[i].val, peer_bdname, &peer_bdname_len)){
|
||||
esp_log_buffer_char(SPP_TAG, peer_bdname, peer_bdname_len);
|
||||
if (strlen(remote_device_name) == peer_bdname_len
|
||||
&& strncmp(peer_bdname, remote_device_name, peer_bdname_len) == 0) {
|
||||
memcpy(peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
esp_spp_start_discovery(peer_bd_addr);
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_STATE_CHANGED_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVCS_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT:{
|
||||
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT:{
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
for (int i = 0; i < SPP_DATA_LEN; ++i) {
|
||||
spp_data[i] = i;
|
||||
}
|
||||
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
CONFIG_BT_SPP_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_spp_vfs_acceptor_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := bt_spp_vfs_acceptor_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,18 @@
|
||||
================= =====
|
||||
Supported Targets ESP32
|
||||
================= =====
|
||||
|
||||
ESP-IDF BT-SPP-VFS-ACCEPTOR demo
|
||||
======================
|
||||
|
||||
Demo of SPP acceptor role
|
||||
|
||||
This is the demo for user to use ESP_APIs to create a SPP acceptor.
|
||||
|
||||
Options choose step:
|
||||
1. idf.py menuconfig.
|
||||
2. enter menuconfig "Component config", choose "Bluetooth"
|
||||
3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile"
|
||||
4. choose your options.
|
||||
|
||||
After the program started, bt_spp_vfs_initator will connect it and send data.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "example_spp_vfs_acceptor_demo.c"
|
||||
"spp_task.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for bt_spp_vfs_acceptor demo. It can create servers, wait for connected and receive data.
|
||||
* run bt_spp_vfs_acceptor demo, the bt_spp_vfs_initiator demo will automatically connect the bt_spp_vfs_acceptor demo,
|
||||
* then receive data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_spp_api.h"
|
||||
#include "spp_task.h"
|
||||
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
#include "esp_vfs.h"
|
||||
#include "sys/unistd.h"
|
||||
|
||||
#define SPP_TAG "SPP_ACCEPTOR_DEMO"
|
||||
#define SPP_SERVER_NAME "SPP_SERVER"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_SPP_ACCEPTOR"
|
||||
|
||||
static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_VFS;
|
||||
|
||||
static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
|
||||
static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;
|
||||
|
||||
#define SPP_DATA_LEN 100
|
||||
static uint8_t spp_data[SPP_DATA_LEN];
|
||||
|
||||
static void spp_read_handle(void * param)
|
||||
{
|
||||
int size = 0;
|
||||
int fd = (int)param;
|
||||
do {
|
||||
/* controll the log frequency, retry after 1s */
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
size = read (fd, spp_data, SPP_DATA_LEN);
|
||||
ESP_LOGI(SPP_TAG, "fd = %d data_len = %d", fd, size);
|
||||
if (size == -1) {
|
||||
break;
|
||||
}
|
||||
esp_log_buffer_hex(SPP_TAG, spp_data, size);
|
||||
if (size == 0) {
|
||||
/*read fail due to there is no data, retry after 1s*/
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
} while (1);
|
||||
spp_wr_task_shut_down();
|
||||
}
|
||||
|
||||
static void esp_spp_cb(uint16_t e, void *p)
|
||||
{
|
||||
esp_spp_cb_event_t event = e;
|
||||
esp_spp_cb_param_t *param = p;
|
||||
|
||||
switch (event) {
|
||||
case ESP_SPP_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
|
||||
esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME);
|
||||
break;
|
||||
case ESP_SPP_DISCOVERY_COMP_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
|
||||
break;
|
||||
case ESP_SPP_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
|
||||
break;
|
||||
case ESP_SPP_CLOSE_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT");
|
||||
break;
|
||||
case ESP_SPP_START_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
|
||||
break;
|
||||
case ESP_SPP_CL_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
|
||||
break;
|
||||
case ESP_SPP_SRV_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
|
||||
spp_wr_task_start_up(spp_read_handle, param->srv_open.fd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_spp_stack_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
|
||||
{
|
||||
spp_task_work_dispatch(esp_spp_cb, event, param, sizeof(esp_spp_cb_param_t), NULL);
|
||||
}
|
||||
|
||||
void esp_bt_gap_cb(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(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT:{
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: {
|
||||
ESP_LOGI(SPP_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_init() != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_enable() != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable bluedroid failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_spp_register_callback(esp_spp_stack_cb) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp register failed", __func__);
|
||||
return;
|
||||
}
|
||||
esp_spp_vfs_register();
|
||||
spp_task_task_start_up();
|
||||
|
||||
if (esp_spp_init(esp_spp_mode) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp init failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "spp_task.h"
|
||||
|
||||
static void spp_task_task_handler(void *arg);
|
||||
static bool spp_task_send_msg(spp_task_msg_t *msg);
|
||||
static void spp_task_work_dispatched(spp_task_msg_t *msg);
|
||||
|
||||
static xQueueHandle spp_task_task_queue = NULL;
|
||||
static xTaskHandle spp_task_task_handle = NULL;
|
||||
|
||||
bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(SPP_TASK_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
spp_task_msg_t msg;
|
||||
memset(&msg, 0, sizeof(spp_task_msg_t));
|
||||
|
||||
msg.sig = SPP_TASK_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return spp_task_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return spp_task_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool spp_task_send_msg(spp_task_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(spp_task_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(SPP_TASK_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void spp_task_work_dispatched(spp_task_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void spp_task_task_handler(void *arg)
|
||||
{
|
||||
spp_task_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(spp_task_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(SPP_TASK_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case SPP_TASK_SIG_WORK_DISPATCH:
|
||||
spp_task_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(SPP_TASK_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spp_task_task_start_up(void)
|
||||
{
|
||||
spp_task_task_queue = xQueueCreate(10, sizeof(spp_task_msg_t));
|
||||
xTaskCreate(spp_task_task_handler, "SPPAppT", 2048, NULL, 10, spp_task_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void spp_task_task_shut_down(void)
|
||||
{
|
||||
if (spp_task_task_handle) {
|
||||
vTaskDelete(spp_task_task_handle);
|
||||
spp_task_task_handle = NULL;
|
||||
}
|
||||
if (spp_task_task_queue) {
|
||||
vQueueDelete(spp_task_task_queue);
|
||||
spp_task_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd)
|
||||
{
|
||||
xTaskCreate(p_cback, "write_read", 2048, (void *)fd, 5, NULL);
|
||||
}
|
||||
void spp_wr_task_shut_down(void)
|
||||
{
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __SPP_TASK_H__
|
||||
#define __SPP_TASK_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define SPP_TASK_TAG "SPP_TASK"
|
||||
|
||||
#define SPP_TASK_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* spp_task_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to spp_task_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
spp_task_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} spp_task_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* spp_task_copy_cb_t) (spp_task_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback);
|
||||
|
||||
void spp_task_task_start_up(void);
|
||||
|
||||
void spp_task_task_shut_down(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief handler for write and read
|
||||
*/
|
||||
typedef void (* spp_wr_task_cb_t) (void *fd);
|
||||
|
||||
void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd);
|
||||
|
||||
void spp_wr_task_shut_down(void);
|
||||
|
||||
#endif ///__SPP_TASK_H__
|
||||
@@ -0,0 +1,10 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
CONFIG_BT_SPP_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_spp_vfs_initiator_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := bt_spp_vfs_initiator_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,18 @@
|
||||
================= =====
|
||||
Supported Targets ESP32
|
||||
================= =====
|
||||
|
||||
ESP-IDF BT-SPP-VFS-INITATOR demo
|
||||
======================
|
||||
|
||||
Demo of SPP initator role
|
||||
|
||||
This is the demo for user to use ESP_APIs to create a SPP initator.
|
||||
|
||||
Options choose step:
|
||||
1. idf.py menuconfig.
|
||||
2. enter menuconfig "Component config", choose "Bluetooth"
|
||||
3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile"
|
||||
4. choose your options.
|
||||
|
||||
After the program started, It will connect to bt_spp_vfs_acceptor and send data.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "example_spp_vfs_initiator_demo.c"
|
||||
"spp_task.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for bt_spp_vfs_initiator demo. It can discovery servers, connect one device and send data.
|
||||
* run bt_spp_vfs_initiator demo, the bt_spp_vfs_initiator demo will automatically connect the bt_spp_vfs_acceptor demo,
|
||||
* then send data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_spp_api.h"
|
||||
#include "spp_task.h"
|
||||
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
#include "esp_vfs.h"
|
||||
#include "sys/unistd.h"
|
||||
|
||||
#define SPP_TAG "SPP_INITIATOR_DEMO"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_SPP_INITIATOR"
|
||||
|
||||
static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_VFS;
|
||||
|
||||
static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
|
||||
static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER;
|
||||
|
||||
static esp_bd_addr_t peer_bd_addr;
|
||||
static uint8_t peer_bdname_len;
|
||||
static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
|
||||
static const char remote_device_name[] = "ESP_SPP_ACCEPTOR";
|
||||
static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY;
|
||||
static const uint8_t inq_len = 30;
|
||||
static const uint8_t inq_num_rsps = 0;
|
||||
|
||||
#define SPP_DATA_LEN 20
|
||||
static uint8_t spp_data[SPP_DATA_LEN];
|
||||
|
||||
static void spp_write_handle(void * param)
|
||||
{
|
||||
int size = 0;
|
||||
int fd = (int)param;
|
||||
printf("%s %d %p\n", __func__,fd,param);
|
||||
do {
|
||||
/*Controll the log frequency, retry after 1s*/
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
size = write (fd, spp_data, SPP_DATA_LEN);
|
||||
ESP_LOGI(SPP_TAG, "fd = %d data_len = %d",fd, size);
|
||||
if (size == -1) {
|
||||
break;
|
||||
} else if ( size == 0) {
|
||||
/*write fail due to ringbuf is full, retry after 1s*/
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
} while (1);
|
||||
spp_wr_task_shut_down();
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void esp_spp_cb(uint16_t e, void *p)
|
||||
{
|
||||
esp_spp_cb_event_t event = e;
|
||||
esp_spp_cb_param_t *param = p;
|
||||
|
||||
switch (event) {
|
||||
case ESP_SPP_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
|
||||
esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps);
|
||||
|
||||
break;
|
||||
case ESP_SPP_DISCOVERY_COMP_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT status=%d scn_num=%d",param->disc_comp.status, param->disc_comp.scn_num);
|
||||
if (param->disc_comp.status == ESP_SPP_SUCCESS) {
|
||||
esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], peer_bd_addr);
|
||||
}
|
||||
break;
|
||||
case ESP_SPP_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT");
|
||||
spp_wr_task_start_up(spp_write_handle, param->open.fd);
|
||||
break;
|
||||
case ESP_SPP_CLOSE_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT");
|
||||
break;
|
||||
case ESP_SPP_START_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT");
|
||||
break;
|
||||
case ESP_SPP_CL_INIT_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
|
||||
break;
|
||||
case ESP_SPP_SRV_OPEN_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
switch(event){
|
||||
case ESP_BT_GAP_DISC_RES_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_RES_EVT");
|
||||
esp_log_buffer_hex(SPP_TAG, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++){
|
||||
if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR
|
||||
&& get_name_from_eir(param->disc_res.prop[i].val, peer_bdname, &peer_bdname_len)){
|
||||
esp_log_buffer_char(SPP_TAG, peer_bdname, peer_bdname_len);
|
||||
if (strlen(remote_device_name) == peer_bdname_len
|
||||
&& strncmp(peer_bdname, remote_device_name, peer_bdname_len) == 0) {
|
||||
memcpy(peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
esp_spp_start_discovery(peer_bd_addr);
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_STATE_CHANGED_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVCS_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT");
|
||||
break;
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT:{
|
||||
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT:{
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(SPP_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_spp_stack_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
|
||||
{
|
||||
spp_task_work_dispatch(esp_spp_cb, event, param, sizeof(esp_spp_cb_param_t), NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
for (int i = 0; i < SPP_DATA_LEN; ++i) {
|
||||
spp_data[i] = i;
|
||||
}
|
||||
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_init() != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_enable() != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s enable bluedroid failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s gap register failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_spp_register_callback(esp_spp_stack_cb) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp register failed", __func__);
|
||||
return;
|
||||
}
|
||||
esp_spp_vfs_register();
|
||||
spp_task_task_start_up();
|
||||
if (esp_spp_init(esp_spp_mode) != ESP_OK) {
|
||||
ESP_LOGE(SPP_TAG, "%s spp init failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "spp_task.h"
|
||||
|
||||
static void spp_task_task_handler(void *arg);
|
||||
static bool spp_task_send_msg(spp_task_msg_t *msg);
|
||||
static void spp_task_work_dispatched(spp_task_msg_t *msg);
|
||||
|
||||
static xQueueHandle spp_task_task_queue = NULL;
|
||||
static xTaskHandle spp_task_task_handle = NULL;
|
||||
|
||||
bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(SPP_TASK_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
spp_task_msg_t msg;
|
||||
memset(&msg, 0, sizeof(spp_task_msg_t));
|
||||
|
||||
msg.sig = SPP_TASK_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return spp_task_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return spp_task_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool spp_task_send_msg(spp_task_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(spp_task_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(SPP_TASK_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void spp_task_work_dispatched(spp_task_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void spp_task_task_handler(void *arg)
|
||||
{
|
||||
spp_task_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(spp_task_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(SPP_TASK_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case SPP_TASK_SIG_WORK_DISPATCH:
|
||||
spp_task_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(SPP_TASK_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spp_task_task_start_up(void)
|
||||
{
|
||||
spp_task_task_queue = xQueueCreate(10, sizeof(spp_task_msg_t));
|
||||
xTaskCreate(spp_task_task_handler, "SPPAppT", 2048, NULL, 10, &spp_task_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void spp_task_task_shut_down(void)
|
||||
{
|
||||
if (spp_task_task_handle) {
|
||||
vTaskDelete(spp_task_task_handle);
|
||||
spp_task_task_handle = NULL;
|
||||
}
|
||||
if (spp_task_task_queue) {
|
||||
vQueueDelete(spp_task_task_queue);
|
||||
spp_task_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd)
|
||||
{
|
||||
xTaskCreate(p_cback, "write_read", 2048, (void *)fd, 5, NULL);
|
||||
}
|
||||
void spp_wr_task_shut_down(void)
|
||||
{
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __SPP_TASK_H__
|
||||
#define __SPP_TASK_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define SPP_TASK_TAG "SPP_TASK"
|
||||
|
||||
#define SPP_TASK_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* spp_task_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to spp_task_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
spp_task_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} spp_task_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* spp_task_copy_cb_t) (spp_task_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback);
|
||||
|
||||
void spp_task_task_start_up(void);
|
||||
|
||||
void spp_task_task_shut_down(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief handler for write and read
|
||||
*/
|
||||
typedef void (* spp_wr_task_cb_t) (void *fd);
|
||||
|
||||
void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd);
|
||||
|
||||
void spp_wr_task_shut_down(void);
|
||||
|
||||
#endif ///__SPP_TASK_H__
|
||||
@@ -0,0 +1,10 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
CONFIG_BT_SPP_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(hfp_ag)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := hfp_ag
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Hands-Free Audio Gateway
|
||||
|
||||
This example is to show how to use the APIs of Hands-Free (HF) Audio Gateway (AG) Component and the effects of them by providing a set of commands. You can use this example to communicate with a Hands-Free Unit (e.g. a headphone set). This example uses UART for user commands.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example is designed to run on commonly available ESP32 development board, e.g. ESP32-DevKitC. To operate this example, it should be connected to a Hands-Free Unit running on a Headphone/Headset or on another ESP32 development board loaded with Hands Free Unit (hfp_hf) example from ESP-IDF.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
ESP32 supports two types of audio data paths: PCM and HCI. Because the default sdkconfig of this example does not configured the data path in a specific way. You should config the data path you want:
|
||||
|
||||
- PCM : When using PCM, audio data stream is directed to GPIO pins and you should link these GPIO pins to a speaker via I2S port. You should choose PCM in `menuconfig` path: `Component config --> Bluetooth controller --> BR/EDR Sync(SCO/eSCO) default data path --> PCM`and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> PCM`.
|
||||
- HCI : When using HCI, audio data stream will be transported to HF unit app via HCI. You should choose HCI in `menuconfig` path: `Component config -->Bluetooth controller -->BR/EDR Sync(SCO/eSCO) default data path --> HCI` and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> HCI`.
|
||||
|
||||
**Note: Wide Band Speech is disabled by default, if you want to use it please select it in the menuconfig path: `Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech`.**
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board. Then, run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
When you flash and monitor this example, the commands help table prints the following log at the very begining:
|
||||
|
||||
```
|
||||
########################################################################
|
||||
HFP AG command usage manual
|
||||
HFP AG commands begins with "hf" and end with ";"
|
||||
Supported commands are as follows, arguments are embraced with < and >
|
||||
|
||||
hf con; -- set up connection with peer device
|
||||
hf dis; -- release connection with peer device
|
||||
hf cona; -- set up audio connection with peer device
|
||||
hf disa; -- release audio connection with peer device
|
||||
hf vron; -- start voice recognition
|
||||
hf vroff; -- stop voice recognition
|
||||
hf vu <tgt> <vol>; -- volume update
|
||||
tgt: 0-speaker, 1-microphone
|
||||
vol: volume gain ranges from 0 to 15
|
||||
hf ind <call> <ntk> <callsetup> <sig>; -- unsolicited indication device status to HF Client
|
||||
call: call status [0,1]
|
||||
callsetup: call setup status [0,3]
|
||||
ntk: network status [0,1]
|
||||
sig: signal strength value from 0~5
|
||||
hf ate <rep> <err>; -- send extended at error code
|
||||
rep: response code from 0 to 7
|
||||
err: error code from 0 to 32
|
||||
hf iron; -- in-band ring tone provided
|
||||
hf iroff; -- in-band ring tone not provided
|
||||
hf ac; -- Answer Incoming Call from AG
|
||||
hf rc; -- Reject Incoming Call from AG
|
||||
hf d <num>; -- Dial Number by AG, e.g. hf d 11223344
|
||||
hf end; -- End a call by AG
|
||||
hf h; -- to see the command for HFP AG
|
||||
########################################################################
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- This command help table will print out in monitor whenever you type `hf h;` or if you input a command that is not required by the command parse rule.
|
||||
- The command you type will not echo in monitor and your command should always start with `hf` and end with `;` or the example will not respond.
|
||||
- The command you type in will not echo in monitor.
|
||||
|
||||
### Service Level Connection and Disconnection
|
||||
|
||||
You can type `hf con;` to establish a service level connection with HF Unit device and log prints such as:
|
||||
|
||||
```
|
||||
W (2211) BT_APPL: new conn_srvc id:5, app_id:0
|
||||
I (2221) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (2221) BT_APP_HF: --connection state CONNECTED, peer feats 0x0, chld_feats 0x0
|
||||
I (2291) BT_APP_HF: APP HFP event: CIND_RESPONSE_EVT
|
||||
I (2291) BT_APP_HF: --CIND Start.
|
||||
I (2331) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (2331) BT_APP_HF: --connection state SLC_CONNECTED, peer feats 0xff, chld_feats 0x4010
|
||||
```
|
||||
|
||||
**Note: Only after Hands-free Profile(HFP) service is initialized and a service level connection exists between an HF Unit and an AG device, could other commands be available.**
|
||||
|
||||
You can type `hf dis;` to disconnect with the connected HF Unit device, and log prints such as:
|
||||
|
||||
```
|
||||
E (100147) CNSL: Command [hf dis;]
|
||||
disconnect
|
||||
W (77321) BT_RFCOMM: port_rfc_closed RFCOMM connection in state 2 closed: Closed (res: 19)
|
||||
I (77321) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (77321) BT_APP_HF: --connection state DISCONNECTED, peer feats 0x0, chld_feats 0x0
|
||||
W (77381) BT_RFCOMM: rfc_find_lcid_mcb LCID reused LCID:0x41 current:0x0
|
||||
W (77381) BT_RFCOMM: RFCOMM_DisconnectInd LCID:0x41
|
||||
```
|
||||
|
||||
### Audio Connection and Disconnection
|
||||
|
||||
You can type `hf cona;` to establish the audio connection between HF Unit and AG device. Also, you can type `hf disa;` to close the audio data stream.
|
||||
|
||||
#### Scenarios for Audio Connection
|
||||
|
||||
- Answer an incoming call
|
||||
- Enable voice recognition
|
||||
- Dial an outgoing call
|
||||
|
||||
#### Scenarios for Audio Disconnection
|
||||
|
||||
- Reject an incoming call
|
||||
- Disable the voice recognition
|
||||
|
||||
#### Choice of Codec
|
||||
|
||||
ESP32 supports both CVSD and mSBC codec. HF Unit and AG device determine which codec to use by exchanging features during service level connection. The choice of codec also depends on the your configuration in `menuconfig`.
|
||||
|
||||
Since CVSD is the default codec in HFP, we just show the scenarios using mSBC:
|
||||
|
||||
- If you enable `BT_HFP_WBS_ENABLE` in `menuconfig`, mSBC will be available.
|
||||
- If both HF Unit and AG support mSBC and `BT_HFP_WBS_ENABLE` is enabled, ESP32 chooses mSBC.
|
||||
- If you use PCM data path, mSBC is not available.
|
||||
|
||||
### Answer or Reject an Incoming Call
|
||||
|
||||
#### Answer an Incoming Call
|
||||
|
||||
You can type `hf ac;` to answer an incoming call and log prints such as:
|
||||
|
||||
```
|
||||
E (1066280) CNSL: Command [hf ac;]
|
||||
Answer Call from AG.
|
||||
W (1066280) BT_APPL: BTA_AG_SCO_CODEC_ST: Ignoring event 1
|
||||
I (1067200) BT_APP_HF: APP HFP event: BCS_EVT
|
||||
I (1067200) BT_APP_HF: --AG choose codec mode: CVSD Only
|
||||
E (1067230) BT_BTM: btm_sco_connected, handle 180
|
||||
I (1067240) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (1067240) BT_APP_HF: --Audio State connected
|
||||
```
|
||||
|
||||
#### Reject an Incoming Call
|
||||
|
||||
You can type `hf rc;` to reject an incoming call and log prints such as:
|
||||
|
||||
```
|
||||
E (10040) CNSL: Command [hf rc;]
|
||||
Reject Call from AG.
|
||||
I (1067240) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (1067240) BT_APP_HF: --Audio State disconnected
|
||||
```
|
||||
|
||||
#### End a Call
|
||||
|
||||
You can type `hf end;` to end the current ongoing call and log prints such as:
|
||||
|
||||
```
|
||||
E (157741) CNSL: Command [hf end;]
|
||||
End Call from AG.
|
||||
W (157741) BT_APPL: BTA_AG_SCO_CLOSING_ST: Ignoring event 3
|
||||
I (159311) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (159311) BT_APP_HF: --Audio State disconnected
|
||||
I (159311) BT_APP_HF: --ESP AG Audio Connection Disconnected.
|
||||
```
|
||||
|
||||
### Dial Number
|
||||
|
||||
You can type `hf d <num>;` to dial `<num>` from AG and log prints such as:
|
||||
|
||||
```
|
||||
E (207351) CNSL: Command [hf d 123456;]
|
||||
Dial number 123456
|
||||
I (207361) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (207361) BT_APP_HF: --Audio State connecting
|
||||
W (207361) BT_APPL: BTA_AG_SCO_OPENING_ST: Ignoring event 1
|
||||
W (207371) BT_APPL: BTA_AG_SCO_OPENING_ST: Ignoring event 1
|
||||
E (208801) BT_BTM: btm_sco_connected, handle 181
|
||||
I (208811) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (208811) BT_APP_HF: --Audio State connected
|
||||
```
|
||||
|
||||
### Volume Control
|
||||
|
||||
You can type `hf vu <tgt> <vol>;` to update the volume of a headset or microphone. The parameter should be set as follows:
|
||||
|
||||
- `<tgt>` : 0 - headset, 1 - microphone.
|
||||
- `<vol>` : Integer among 0 - 15.
|
||||
|
||||
For example, `hf vu 0 9;` updates the volume of headset and the log on the AG side prints `Volume Update`, while on the HF Unit side the log prints:
|
||||
|
||||
```
|
||||
E (17087) BT_HF: APP HFP event: VOLUME_CONTROL_EVT
|
||||
E (17087) BT_HF: --volume_target: SPEAKER, volume 9
|
||||
```
|
||||
|
||||
And also, `hf vu 1 9;` updates the volume of a microphone and the log on the HF Unit side prints:
|
||||
|
||||
```
|
||||
E (32087) BT_HF: APP HFP event: VOLUME_CONTROL_EVT
|
||||
E (32087) BT_HF: --volume_target: MICROPHONE, volume 9
|
||||
```
|
||||
|
||||
#### Voice Recognition
|
||||
|
||||
You can type `hf vron;` to start the voice recognition and type `hf vroff;` to terminate this function in the AG device. Both commands will notify the HF Unit the status of voice recognition. For example, type `hf vron;` and the log will print:
|
||||
|
||||
```
|
||||
E (244131) CNSL: Command [hf vron;]
|
||||
Start Voice Recognition.
|
||||
I (244141) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (244141) BT_APP_HF: --Audio State connecting
|
||||
E (245301) BT_BTM: btm_sco_connected, handle 181
|
||||
I (245311) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (245311) BT_APP_HF: --Audio State connected
|
||||
```
|
||||
|
||||
#### Device Status Indication
|
||||
|
||||
You can type `hf ind <call> <ntk> <callsetup> <sig>` to send device status of AG to HF Unit. Log on AG prints such as: `Device Indicator Changed!` and on HF Unit side prints such as:
|
||||
|
||||
```
|
||||
E (293641) BT_HF: APP HFP event: CALL_IND_EVT
|
||||
E (293641) BT_HF: --Call indicator call in progress
|
||||
E (293641) BT_HF: APP HFP event: CALL_SETUP_IND_EVT
|
||||
E (293651) BT_HF: --Call setup indicator INCOMING
|
||||
E (293651) BT_HF: APP HFP event: SIGNAL_STRENGTH_IND_EVT
|
||||
E (293661) BT_HF: -- signal strength: 5
|
||||
```
|
||||
|
||||
**Note: The AG device sends only the changed status to the HF Unit.**
|
||||
|
||||
#### Send Extended AT Error Code
|
||||
|
||||
You can type `hf ate <rep> <err>` to send extended AT error code to HF Unit. The parameter should be set as follows:
|
||||
|
||||
- `<rep>` : integer among 0 - 7.
|
||||
- `<err>` : integer among 0 - 32.
|
||||
|
||||
When you type `hf ate 7 7;` the log on the AG side prints `Send CME Error.` while on the HF Unit side prints:
|
||||
|
||||
```
|
||||
E (448146) BT_HF: APP HFP event: AT_RESPONSE
|
||||
E (448146) BT_HF: --AT response event, code 7, cme 7
|
||||
```
|
||||
|
||||
#### In-Band Ring Tone Setting
|
||||
|
||||
You can type `hf iron;` to enable the in-band ring tone and type `hf iroff;` to disable it. The log on the AG side prints such as `Device Indicator Changed!` and on HF Unit side it prints such as:
|
||||
|
||||
```
|
||||
E (19546) BT_HF: APP HFP event: IN-BAND_RING_TONE_EVT
|
||||
E (19556) BT_HF: --in-band ring state Provided
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any problems, please check if the following rules are followed:
|
||||
|
||||
- You should type the command in the terminal according to the format described in the commands help table.
|
||||
- Not all commands in the table are supported by the HF Unit.
|
||||
- If you want to `hf con;` to establish a service level connection with a specific HF Unit, you should add the MAC address of the HF Unit in `app_hf_msg_set.c` for example: `esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};`
|
||||
- Use `esp_hf_client_register_callback()` and `esp_hf_client_init();` before establishing a service level connection.
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
Due to the complexity of the HFP, this example has more source files than other bluetooth examples. To show the functions of HFP in a simple way, we use the Commands and Effects scheme to illustrate APIs of the HFP in ESP-IDF.
|
||||
|
||||
- The example will respond to user command through the UART console. Please go to `console_uart.c` for the configuration details.
|
||||
- For the voice interface, ESP32 has provided PCM input/output signals which can be directed to GPIO pins. So, please go to `gpio_pcm_config.c` for the configuration details.
|
||||
- If you want to update the command table, please refer to `app_hf_msg_set.c`.
|
||||
- If you want to update the command parse rules, please refer to `app_hf_msg_prs.c`.
|
||||
- If you want to update the responses of the AG or want to update the log, please refer to `bt_app_hf.c`.
|
||||
- The task configuration part is in `bt_app_core.c`.
|
||||
@@ -0,0 +1,8 @@
|
||||
idf_component_register(SRCS "app_hf_msg_prs.c"
|
||||
"app_hf_msg_set.c"
|
||||
"bt_app_core.c"
|
||||
"bt_app_hf.c"
|
||||
"console_uart.c"
|
||||
"gpio_pcm_config.c"
|
||||
"main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "app_hf_msg_prs.h"
|
||||
#include "app_hf_msg_set.h"
|
||||
|
||||
// according to the design, message header length shall be no less than 2.
|
||||
#define HF_MSG_HDR_LEN (3)
|
||||
const static char hf_msg_hdr[HF_MSG_HDR_LEN] = {'h', 'f', ' '};
|
||||
|
||||
// according to the design, message header length shall be no less than 2.
|
||||
#define HF_MSG_TAIL_LEN (1)
|
||||
const static char hf_msg_tail[HF_MSG_TAIL_LEN] = {';'};
|
||||
|
||||
void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs)
|
||||
{
|
||||
prs->state = HF_MSG_PRS_IDLE;
|
||||
prs->cnt = 0;
|
||||
prs->h_idx = 0;
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
|
||||
void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb)
|
||||
{
|
||||
prs->callback = cb;
|
||||
}
|
||||
|
||||
hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs)
|
||||
{
|
||||
hf_msg_prs_err_t err = HF_MSG_PRS_ERR_IN_PROGRESS;
|
||||
switch (prs->state)
|
||||
{
|
||||
case HF_MSG_PRS_IDLE:
|
||||
{
|
||||
if (c == hf_msg_hdr[0]) {
|
||||
prs->state = HF_MSG_PRS_HDR;
|
||||
prs->buf[0] = c;
|
||||
prs->cnt = 1;
|
||||
prs->h_idx = 1;
|
||||
} else {
|
||||
err = HF_MSG_PRS_ERR_HDR_UNDETECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HF_MSG_PRS_HDR:
|
||||
{
|
||||
if (c == hf_msg_hdr[prs->h_idx]) {
|
||||
prs->buf[prs->cnt++] = c;
|
||||
if (++(prs->h_idx) == HF_MSG_HDR_LEN) {
|
||||
prs->state = HF_MSG_PRS_PAYL;
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
} else {
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_HDR_SYNC_FAILED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HF_MSG_PRS_PAYL:
|
||||
{
|
||||
prs->buf[prs->cnt++] = c;
|
||||
if (c == hf_msg_tail[prs->t_idx]) {
|
||||
if (++(prs->t_idx) == HF_MSG_TAIL_LEN) {
|
||||
prs->buf[prs->cnt] = '\0';
|
||||
prs->callback(prs->buf, prs->cnt);
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_OK;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
|
||||
if (prs->cnt >= HF_MSG_LEN_MAX) {
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_BUF_OVERFLOW;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
void hf_msg_split_args(char *start, char *end, char **argv, int *argn)
|
||||
{
|
||||
if (argn == NULL || *argn == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(argv, 0, (*argn) * sizeof(void *));
|
||||
|
||||
int max_argn = *argn;
|
||||
*argn = 0;
|
||||
|
||||
char *p = start;
|
||||
for (int i = 0; i < max_argn; ++i) {
|
||||
while (isspace((int)*p) && p != end) {
|
||||
++p;
|
||||
}
|
||||
if (p == end) {
|
||||
return;
|
||||
}
|
||||
|
||||
argv[i] = p++;
|
||||
++(*argn);
|
||||
|
||||
while (!isspace((int)*p) && p != end) {
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p == end) {
|
||||
return;
|
||||
} else {
|
||||
*p = '\0';
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hf_msg_args_parser(char *buf, int len)
|
||||
{
|
||||
char *argv[HF_MSG_ARGS_MAX];
|
||||
int argn = HF_MSG_ARGS_MAX;
|
||||
char *start = buf + HF_MSG_HDR_LEN;
|
||||
// set the command terminitor to '\0'
|
||||
char *end = buf + len - HF_MSG_TAIL_LEN;
|
||||
*end = '\0';
|
||||
|
||||
hf_msg_split_args(start, end, argv, &argn);
|
||||
|
||||
if (argn == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool cmd_supported = false;
|
||||
|
||||
hf_msg_hdl_t *cmd_tbl = hf_get_cmd_tbl();
|
||||
size_t cmd_tbl_size = hf_get_cmd_tbl_size();
|
||||
for (int i = 0; i < cmd_tbl_size; ++i) {
|
||||
hf_msg_hdl_t *hdl = &cmd_tbl[i];
|
||||
if (strcmp(argv[0], hdl->str) == 0) {
|
||||
if (hdl->handler) {
|
||||
hdl->handler(argn, argv);
|
||||
cmd_supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cmd_supported) {
|
||||
printf("unsupported command\n");
|
||||
hf_msg_show_usage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __APP_HF_MSG_PRS_H__
|
||||
#define __APP_HF_MSG_PRS_H__
|
||||
|
||||
typedef enum {
|
||||
HF_MSG_PRS_ERR_OK = 0, // a complete message is finished
|
||||
HF_MSG_PRS_ERR_IN_PROGRESS, // message parsing is in progress
|
||||
HF_MSG_PRS_ERR_HDR_UNDETECTED, // header not detected
|
||||
HF_MSG_PRS_ERR_HDR_SYNC_FAILED, // failed to sync header
|
||||
HF_MSG_PRS_ERR_BUF_OVERFLOW, // exceeds the buffer size: HF_MSG_LEN_MAX
|
||||
} hf_msg_prs_err_t;
|
||||
|
||||
typedef enum {
|
||||
HF_MSG_PRS_IDLE = 0,
|
||||
HF_MSG_PRS_HDR,
|
||||
HF_MSG_PRS_PAYL,
|
||||
} hf_msg_prs_state_t;
|
||||
|
||||
typedef void (*hf_msg_callback)(char *buf, int len);
|
||||
|
||||
#define HF_MSG_LEN_MAX (128)
|
||||
|
||||
typedef struct {
|
||||
hf_msg_prs_state_t state;
|
||||
char buf[HF_MSG_LEN_MAX + 1];
|
||||
int cnt;
|
||||
int h_idx;
|
||||
int t_idx;
|
||||
hf_msg_callback callback;
|
||||
} hf_msg_prs_cb_t;
|
||||
|
||||
void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs);
|
||||
|
||||
void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb);
|
||||
|
||||
hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs);
|
||||
|
||||
void hf_msg_show_usage(void);
|
||||
|
||||
#endif /* __APP_HF_MSG_PRS_H__*/
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "app_hf_msg_set.h"
|
||||
#include "bt_app_hf.h"
|
||||
|
||||
// if you want to connect a specific device, add it's bda here
|
||||
// esp_bd_addr_t hf_peer_addr = {0x70,0x26,0x05,0xca,0xeb,0x21};
|
||||
|
||||
void hf_msg_show_usage(void)
|
||||
{
|
||||
printf("########################################################################\n");
|
||||
printf("HFP AG command usage manual\n");
|
||||
printf("HFP AG commands begins with \"hf\" and end with \";\"\n");
|
||||
printf("Supported commands are as follows, arguments are embraced with < and >\n\n");
|
||||
printf("hf con; -- set up connection with peer device\n");
|
||||
printf("hf dis; -- disconnection with peer device\n");
|
||||
printf("hf cona; -- set up audio connection with peer device\n");
|
||||
printf("hf disa; -- release audio connection with peer device\n");
|
||||
printf("hf vron; -- start voice recognition\n");
|
||||
printf("hf vroff; -- stop voice recognition\n");
|
||||
printf("hf vu <tgt> <vol>; -- volume update\n");
|
||||
printf(" tgt: 0-speaker, 1-microphone\n");
|
||||
printf(" vol: volume gain ranges from 0 to 15\n");
|
||||
printf("hf ind <call> <ntk> <callsetup> <sig>; -- unsolicited indication device status to HF Client\n");
|
||||
printf(" call: call status [0,1]\n");
|
||||
printf(" callsetup: call setup status [0,3]\n");
|
||||
printf(" ntk: network status [0,1]\n");
|
||||
printf(" sig: signal strength value from 0~5\n");
|
||||
printf("hf ate <rep> <err>; -- send extended at error code\n");
|
||||
printf(" rep: response code from 0 to 7\n");
|
||||
printf(" err: error code from 0 to 32\n");
|
||||
printf("hf iron; -- in-band ring tone provided\n");
|
||||
printf("hf iroff; -- in-band ring tone not provided\n");
|
||||
printf("hf ac; -- Answer Incoming Call from AG\n");
|
||||
printf("hf rc; -- Reject Incoming Call from AG\n");
|
||||
printf("hf d <num>; -- Dial Number by AG, e.g. hf d 11223344\n");
|
||||
printf("hf end; -- End up a call by AG\n");
|
||||
printf("hf h; -- to see the command for HFP AG\n");
|
||||
printf("########################################################################\n");
|
||||
}
|
||||
|
||||
#define HF_CMD_HANDLER(cmd) static void hf_##cmd##_handler(int argn, char **argv)
|
||||
|
||||
HF_CMD_HANDLER(help)
|
||||
{
|
||||
hf_msg_show_usage();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(conn)
|
||||
{
|
||||
printf("Connect.\n");
|
||||
esp_bt_hf_connect(hf_peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(disc)
|
||||
{
|
||||
printf("Disconnect\n");
|
||||
esp_bt_hf_disconnect(hf_peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(conn_audio)
|
||||
{
|
||||
printf("Connect Audio\n");
|
||||
esp_bt_hf_connect_audio(hf_peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(disc_audio)
|
||||
{
|
||||
printf("Disconnect Audio\n");
|
||||
esp_bt_hf_disconnect_audio(hf_peer_addr);
|
||||
}
|
||||
|
||||
//AT+BVRA
|
||||
HF_CMD_HANDLER(vra_on)
|
||||
{
|
||||
printf("Start Voice Recognition.\n");
|
||||
esp_bt_hf_vra(hf_peer_addr,1);
|
||||
}
|
||||
//AT+BVRA
|
||||
HF_CMD_HANDLER(vra_off)
|
||||
{
|
||||
printf("Stop Voicer Recognition.\n");
|
||||
esp_bt_hf_vra(hf_peer_addr,0);
|
||||
}
|
||||
|
||||
//AT+VGS or AT+VGM
|
||||
HF_CMD_HANDLER(volume_control)
|
||||
{
|
||||
if (argn != 3) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
int target, volume;
|
||||
if (sscanf(argv[1], "%d", &target) != 1 ||
|
||||
(target != ESP_HF_VOLUME_CONTROL_TARGET_SPK &&
|
||||
target != ESP_HF_VOLUME_CONTROL_TARGET_MIC)) {
|
||||
printf("Invalid argument for target %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
if (sscanf(argv[2], "%d", &volume) != 1 ||
|
||||
(volume < 0 || volume > 15)) {
|
||||
printf("Invalid argument for volume %s\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
printf("Volume Update\n");
|
||||
esp_bt_hf_volume_control(hf_peer_addr, target, volume);
|
||||
}
|
||||
|
||||
//+CIEV
|
||||
HF_CMD_HANDLER(ind_change)
|
||||
{
|
||||
if (argn != 5) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int call_state, ntk_state, call_setup_state, signal;
|
||||
|
||||
if (sscanf(argv[1], "%d", &call_state) != 1 ||
|
||||
(call_state != ESP_HF_CALL_STATUS_NO_CALLS &&
|
||||
call_state != ESP_HF_CALL_STATUS_CALL_IN_PROGRESS)) {
|
||||
printf("Invalid argument for call state %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
if (sscanf(argv[2], "%d", &call_setup_state) != 1 ||
|
||||
(call_setup_state < ESP_HF_CALL_SETUP_STATUS_IDLE || call_setup_state > ESP_HF_CALL_SETUP_STATUS_OUTGOING_ALERTING)) {
|
||||
printf("Invalid argument for callsetup state %s\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
if (sscanf(argv[3], "%d", &ntk_state) != 1 ||
|
||||
(ntk_state != ESP_HF_NETWORK_STATE_NOT_AVAILABLE &&
|
||||
ntk_state != ESP_HF_NETWORK_STATE_AVAILABLE)) {
|
||||
printf("Invalid argument for netwrok state %s\n", argv[3]);
|
||||
return;
|
||||
}
|
||||
if (sscanf(argv[4], "%d", &signal) != 1 ||
|
||||
(signal < 0 || signal > 5)) {
|
||||
printf("Invalid argument for signal %s\n", argv[4]);
|
||||
return;
|
||||
}
|
||||
printf("Device Indicator Changed!\n");
|
||||
esp_bt_hf_indchange_notification(hf_peer_addr, call_state, call_setup_state, ntk_state, signal);
|
||||
}
|
||||
|
||||
//AT+CMEE
|
||||
HF_CMD_HANDLER(cme_err)
|
||||
{
|
||||
if (argn != 3) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int response_code, error_code;
|
||||
if (sscanf(argv[1], "%d", &response_code) != 1 ||
|
||||
(response_code < ESP_HF_AT_RESPONSE_CODE_OK && response_code > ESP_HF_AT_RESPONSE_CODE_CME)) {
|
||||
printf("Invalid argument for response_code %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sscanf(argv[2], "%d", &error_code) != 1 ||
|
||||
(error_code < ESP_HF_CME_AG_FAILURE || error_code > ESP_HF_CME_NETWORK_NOT_ALLOWED)) {
|
||||
printf("Invalid argument for volume %s\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Send CME Error.\n");
|
||||
esp_bt_hf_cmee_response(hf_peer_addr,response_code,error_code);
|
||||
}
|
||||
|
||||
//+BSIR:1
|
||||
HF_CMD_HANDLER(ir_on)
|
||||
{
|
||||
printf("Enable Voicer Recognition.\n");
|
||||
esp_bt_hf_bsir(hf_peer_addr,1);
|
||||
}
|
||||
|
||||
//+BSIR:0
|
||||
HF_CMD_HANDLER(ir_off)
|
||||
{
|
||||
printf("Disable Voicer Recognition.\n");
|
||||
esp_bt_hf_bsir(hf_peer_addr,0);
|
||||
}
|
||||
|
||||
//Answer Call from AG
|
||||
HF_CMD_HANDLER(ac)
|
||||
{
|
||||
printf("Answer Call from AG.\n");
|
||||
char *number = {"123456"};
|
||||
esp_bt_hf_answer_call(hf_peer_addr,1,0,1,1,number,0);
|
||||
}
|
||||
|
||||
//Reject Call from AG
|
||||
HF_CMD_HANDLER(rc)
|
||||
{
|
||||
printf("Reject Call from AG.\n");
|
||||
char *number = {"123456"};
|
||||
esp_bt_hf_reject_call(hf_peer_addr,0,0,0,0,number,0);
|
||||
}
|
||||
|
||||
//End Call from AG
|
||||
HF_CMD_HANDLER(end)
|
||||
{
|
||||
printf("End Call from AG.\n");
|
||||
char *number = {"123456"};
|
||||
esp_bt_hf_end_call(hf_peer_addr,0,0,0,0,number,0);
|
||||
}
|
||||
|
||||
//Dial Call from AG
|
||||
HF_CMD_HANDLER(d)
|
||||
{
|
||||
if (argn != 2) {
|
||||
printf("Insufficient number of arguments");
|
||||
} else {
|
||||
printf("Dial number %s\n", argv[1]);
|
||||
esp_bt_hf_out_call(hf_peer_addr,1,0,1,2,argv[1],0);
|
||||
}
|
||||
}
|
||||
|
||||
static hf_msg_hdl_t hf_cmd_tbl[] = {
|
||||
{0, "h", hf_help_handler},
|
||||
{5, "con", hf_conn_handler},
|
||||
{10, "dis", hf_disc_handler},
|
||||
{20, "cona", hf_conn_audio_handler},
|
||||
{30, "disa", hf_disc_audio_handler},
|
||||
{40, "vu", hf_volume_control_handler},
|
||||
{50, "ind", hf_ind_change_handler},
|
||||
{60, "vron", hf_vra_on_handler},
|
||||
{70, "vroff", hf_vra_off_handler},
|
||||
{80, "ate", hf_cme_err_handler},
|
||||
{90, "iron", hf_ir_on_handler},
|
||||
{100, "iroff", hf_ir_off_handler},
|
||||
{110, "ac", hf_ac_handler},
|
||||
{120, "rc", hf_rc_handler},
|
||||
{130, "end", hf_end_handler},
|
||||
{140, "d", hf_d_handler},
|
||||
};
|
||||
|
||||
hf_msg_hdl_t *hf_get_cmd_tbl(void)
|
||||
{
|
||||
return hf_cmd_tbl;
|
||||
}
|
||||
|
||||
size_t hf_get_cmd_tbl_size(void)
|
||||
{
|
||||
return sizeof(hf_cmd_tbl) / sizeof(hf_msg_hdl_t);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __APP_HF_MSG_SET_H__
|
||||
#define __APP_HF_MSG_SET_H__
|
||||
|
||||
#define HF_MSG_ARGS_MAX (8)
|
||||
|
||||
typedef void (* hf_cmd_handler)(int argn, char **argv);
|
||||
|
||||
typedef struct {
|
||||
uint16_t opcode;
|
||||
const char *str;
|
||||
hf_cmd_handler handler;
|
||||
} hf_msg_hdl_t;
|
||||
|
||||
extern hf_msg_hdl_t *hf_get_cmd_tbl(void);
|
||||
extern size_t hf_get_cmd_tbl_size(void);
|
||||
|
||||
void hf_msg_show_usage(void);
|
||||
#endif /* __APP_HF_MSG_SET_H__*/
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
|
||||
static void bt_app_task_handler(void *arg);
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg);
|
||||
|
||||
static xQueueHandle bt_app_task_queue = NULL;
|
||||
static xTaskHandle bt_app_task_handle = NULL;
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
bt_app_msg_t msg;
|
||||
memset(&msg, 0, sizeof(bt_app_msg_t));
|
||||
|
||||
msg.sig = BT_APP_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return bt_app_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_task_handler(void *arg)
|
||||
{
|
||||
bt_app_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case BT_APP_SIG_WORK_DISPATCH:
|
||||
bt_app_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
} // switch (msg.sig)
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
|
||||
xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &bt_app_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (bt_app_task_handle) {
|
||||
vTaskDelete(bt_app_task_handle);
|
||||
bt_app_task_handle = NULL;
|
||||
}
|
||||
if (bt_app_task_queue) {
|
||||
vQueueDelete(bt_app_task_queue);
|
||||
bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to bt_app_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
bt_app_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
|
||||
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "bt_app_hf.h"
|
||||
|
||||
const char *c_hf_evt_str[] = {
|
||||
"CONNECTION_STATE_EVT", /*!< SERVICE LEVEL CONNECTION STATE CONTROL */
|
||||
"AUDIO_STATE_EVT", /*!< AUDIO CONNECTION STATE CONTROL */
|
||||
"VR_STATE_CHANGE_EVT", /*!< VOICE RECOGNITION CHANGE */
|
||||
"VOLUME_CONTROL_EVT", /*!< AUDIO VOLUME CONTROL */
|
||||
"UNKNOW_AT_CMD", /*!< UNKNOW AT COMMAND RECIEVED */
|
||||
"IND_UPDATE", /*!< INDICATION UPDATE */
|
||||
"CIND_RESPONSE_EVT", /*!< CALL & DEVICE INDICATION */
|
||||
"COPS_RESPONSE_EVT", /*!< CURRENT OPERATOR EVENT */
|
||||
"CLCC_RESPONSE_EVT", /*!< LIST OF CURRENT CALL EVENT */
|
||||
"CNUM_RESPONSE_EVT", /*!< SUBSCRIBER INFORTMATION OF CALL EVENT */
|
||||
"DTMF_RESPONSE_EVT", /*!< DTMF TRANSFER EVT */
|
||||
"NREC_RESPONSE_EVT", /*!< NREC RESPONSE EVT */
|
||||
"ANSWER_INCOMING_EVT", /*!< ANSWER INCOMING EVT */
|
||||
"REJECT_INCOMING_EVT", /*!< AREJECT INCOMING EVT */
|
||||
"DIAL_EVT", /*!< DIAL INCOMING EVT */
|
||||
"WBS_EVT", /*!< CURRENT CODEC EVT */
|
||||
"BCS_EVT", /*!< CODEC NEGO EVT */
|
||||
};
|
||||
|
||||
//esp_hf_connection_state_t
|
||||
const char *c_connection_state_str[] = {
|
||||
"DISCONNECTED",
|
||||
"CONNECTING",
|
||||
"CONNECTED",
|
||||
"SLC_CONNECTED",
|
||||
"DISCONNECTING",
|
||||
};
|
||||
|
||||
// esp_hf_audio_state_t
|
||||
const char *c_audio_state_str[] = {
|
||||
"disconnected",
|
||||
"connecting",
|
||||
"connected",
|
||||
"connected_msbc",
|
||||
};
|
||||
|
||||
/// esp_hf_vr_state_t
|
||||
const char *c_vr_state_str[] = {
|
||||
"Disabled",
|
||||
"Enabled",
|
||||
};
|
||||
|
||||
// esp_hf_nrec_t
|
||||
const char *c_nrec_status_str[] = {
|
||||
"NREC DISABLE",
|
||||
"NREC ABLE",
|
||||
};
|
||||
|
||||
// esp_hf_control_target_t
|
||||
const char *c_volume_control_target_str[] = {
|
||||
"SPEAKER",
|
||||
"MICROPHONE",
|
||||
};
|
||||
|
||||
// esp_hf_subscriber_service_type_t
|
||||
char *c_operator_name_str[] = {
|
||||
"中国移动",
|
||||
"中国联通",
|
||||
"中国电信",
|
||||
};
|
||||
|
||||
// esp_hf_subscriber_service_type_t
|
||||
char *c_subscriber_service_type_str[] = {
|
||||
"UNKNOWN",
|
||||
"VOICE",
|
||||
"FAX",
|
||||
};
|
||||
|
||||
// esp_hf_nego_codec_status_t
|
||||
const char *c_codec_mode_str[] = {
|
||||
"CVSD Only",
|
||||
"Use CVSD",
|
||||
"Use MSBC",
|
||||
};
|
||||
|
||||
#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI
|
||||
// Produce a sine audio
|
||||
static const int16_t sine_int16[] = {
|
||||
0, 2057, 4107, 6140, 8149, 10126, 12062, 13952, 15786, 17557,
|
||||
19260, 20886, 22431, 23886, 25247, 26509, 27666, 28714, 29648, 30466,
|
||||
31163, 31738, 32187, 32509, 32702, 32767, 32702, 32509, 32187, 31738,
|
||||
31163, 30466, 29648, 28714, 27666, 26509, 25247, 23886, 22431, 20886,
|
||||
19260, 17557, 15786, 13952, 12062, 10126, 8149, 6140, 4107, 2057,
|
||||
0, -2057, -4107, -6140, -8149, -10126, -12062, -13952, -15786, -17557,
|
||||
-19260, -20886, -22431, -23886, -25247, -26509, -27666, -28714, -29648, -30466,
|
||||
-31163, -31738, -32187, -32509, -32702, -32767, -32702, -32509, -32187, -31738,
|
||||
-31163, -30466, -29648, -28714, -27666, -26509, -25247, -23886, -22431, -20886,
|
||||
-19260, -17557, -15786, -13952, -12062, -10126, -8149, -6140, -4107, -2057,
|
||||
};
|
||||
|
||||
#define TABLE_SIZE_CVSD 100
|
||||
static uint32_t bt_app_hf_outgoing_cb(uint8_t *p_buf, uint32_t sz)
|
||||
{
|
||||
static int sine_phase = 0;
|
||||
|
||||
for (int i = 0; i * 2 + 1 < sz; i++) {
|
||||
p_buf[i * 2] = sine_int16[sine_phase];
|
||||
p_buf[i * 2 + 1] = sine_int16[sine_phase];
|
||||
++sine_phase;
|
||||
if (sine_phase >= TABLE_SIZE_CVSD) {
|
||||
sine_phase -= TABLE_SIZE_CVSD;
|
||||
}
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
static void bt_app_hf_incoming_cb(const uint8_t *buf, uint32_t sz)
|
||||
{
|
||||
// direct to i2s
|
||||
esp_hf_outgoing_data_ready();
|
||||
}
|
||||
#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */
|
||||
|
||||
void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param)
|
||||
{
|
||||
if (event <= ESP_HF_BCS_RESPONSE_EVT) {
|
||||
ESP_LOGI(BT_HF_TAG, "APP HFP event: %s", c_hf_evt_str[event]);
|
||||
} else {
|
||||
ESP_LOGE(BT_HF_TAG, "APP HFP invalid event %d", event);
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_HF_CONNECTION_STATE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--connection state %s, peer feats 0x%x, chld_feats 0x%x",
|
||||
c_connection_state_str[param->conn_stat.state],
|
||||
param->conn_stat.peer_feat,
|
||||
param->conn_stat.chld_feat);
|
||||
memcpy(hf_peer_addr, param->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_AUDIO_STATE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Audio State %s", c_audio_state_str[param->audio_stat.state]);
|
||||
#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI
|
||||
if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED ||
|
||||
param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC)
|
||||
{
|
||||
esp_bt_hf_register_data_callback(bt_app_hf_incoming_cb, bt_app_hf_outgoing_cb);
|
||||
} else if (param->audio_stat.state == ESP_HF_AUDIO_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_HF_TAG, "--ESP AG Audio Connection Disconnected.");
|
||||
}
|
||||
#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_BVRA_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Voice Recognition is %s", c_vr_state_str[param->vra_rep.value]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_VOLUME_CONTROL_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Volume Target: %s, Volume %d", c_volume_control_target_str[param->volume_control.type], param->volume_control.volume);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_UNAT_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--UNKOW AT CMD: %s", param->unat_rep.unat);
|
||||
esp_hf_unat_response(hf_peer_addr, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_IND_UPDATE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--UPDATE INDCATOR!");
|
||||
esp_hf_call_status_t call_state = 1;
|
||||
esp_hf_call_setup_status_t call_setup_state = 2;
|
||||
esp_hf_network_state_t ntk_state = 1;
|
||||
int signal = 2;
|
||||
esp_bt_hf_indchange_notification(hf_peer_addr,call_state,call_setup_state,ntk_state,signal);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CIND_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--CIND Start.");
|
||||
esp_hf_call_status_t call_status = 0;
|
||||
esp_hf_call_setup_status_t call_setup_status = 0;
|
||||
esp_hf_network_state_t ntk_state = 1;
|
||||
int signal = 4;
|
||||
esp_hf_roaming_status_t roam = 0;
|
||||
int batt_lev = 3;
|
||||
esp_hf_call_held_status_t call_held_status = 0;
|
||||
esp_bt_hf_cind_response(hf_peer_addr,call_status,call_setup_status,ntk_state,signal,roam,batt_lev,call_held_status);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_COPS_RESPONSE_EVT:
|
||||
{
|
||||
const int svc_type = 1;
|
||||
esp_bt_hf_cops_response(hf_peer_addr, c_operator_name_str[svc_type]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLCC_RESPONSE_EVT:
|
||||
{
|
||||
int index = 1;
|
||||
//mandatory
|
||||
esp_hf_current_call_direction_t dir = 1;
|
||||
esp_hf_current_call_status_t current_call_status = 0;
|
||||
esp_hf_current_call_mode_t mode = 0;
|
||||
esp_hf_current_call_mpty_type_t mpty = 0;
|
||||
//option
|
||||
char *number = {"123456"};
|
||||
esp_hf_call_addr_type_t type = ESP_HF_CALL_ADDR_TYPE_UNKNOWN;
|
||||
|
||||
ESP_LOGI(BT_HF_TAG, "--Calling Line Identification.");
|
||||
esp_bt_hf_clcc_response(hf_peer_addr, index, dir, current_call_status, mode, mpty, number, type);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CNUM_RESPONSE_EVT:
|
||||
{
|
||||
char *number = {"123456"};
|
||||
esp_hf_subscriber_service_type_t type = 1;
|
||||
ESP_LOGI(BT_HF_TAG, "--Current Number is %s ,Type is %s.", number, c_subscriber_service_type_str[type]);
|
||||
esp_bt_hf_cnum_response(hf_peer_addr, number,type);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_VTS_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--DTMF code is: %s.", param->vts_rep.code);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_NREC_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--NREC status is: %s.", c_nrec_status_str[param->nrec.state]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_ATA_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Asnwer Incoming Call.");
|
||||
char *number = {"123456"};
|
||||
esp_bt_hf_answer_call(hf_peer_addr,1,0,1,0,number,0);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CHUP_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Reject Incoming Call.");
|
||||
char *number = {"123456"};
|
||||
esp_bt_hf_reject_call(hf_peer_addr,0,0,0,0,number,0);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_DIAL_EVT:
|
||||
{
|
||||
if (param->out_call.num_or_loc) {
|
||||
//dia_num_or_mem
|
||||
ESP_LOGI(BT_HF_TAG, "--Dial \"%s\".", param->out_call.num_or_loc);
|
||||
esp_bt_hf_out_call(hf_peer_addr,1,0,1,0,param->out_call.num_or_loc,0);
|
||||
} else {
|
||||
//dia_last
|
||||
ESP_LOGI(BT_HF_TAG, "--Dial last number.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
#if (BTM_WBS_INCLUDED == TRUE)
|
||||
case ESP_HF_WBS_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Current codec: %s",c_codec_mode_str[param->wbs_rep.codec]);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case ESP_HF_BCS_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Consequence of codec negotiation: %s",c_codec_mode_str[param->bcs_rep.mode]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGI(BT_HF_TAG, "Unsupported HF_AG EVT: %d.", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_HF_H__
|
||||
#define __BT_APP_HF_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
esp_bd_addr_t hf_peer_addr; // Declaration of peer device bdaddr
|
||||
|
||||
#define BT_HF_TAG "BT_APP_HF"
|
||||
|
||||
/**
|
||||
* @brief callback function for HF client
|
||||
*/
|
||||
void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param);
|
||||
|
||||
|
||||
#endif /* __BT_APP_HF_H__*/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "console_uart.h"
|
||||
#include "app_hf_msg_prs.h"
|
||||
|
||||
#define CONSOLE_UART_NUM UART_NUM_0
|
||||
|
||||
static QueueHandle_t uart_queue;
|
||||
static hf_msg_prs_cb_t hf_msg_parser;
|
||||
|
||||
static const uart_config_t uart_cfg = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 127,
|
||||
};
|
||||
|
||||
extern void hf_msg_args_parser(char *buf, int len);
|
||||
|
||||
void hf_msg_handler(char *buf, int len)
|
||||
{
|
||||
ESP_LOGE(TAG_CNSL, "Command [%s]", buf);
|
||||
hf_msg_args_parser(buf, len);
|
||||
}
|
||||
|
||||
static void console_uart_task(void *pvParameters)
|
||||
{
|
||||
int len;
|
||||
uart_event_t event;
|
||||
hf_msg_prs_cb_t *parser = &hf_msg_parser;
|
||||
hf_msg_parser_reset_state(parser);
|
||||
hf_msg_parser_register_callback(parser, hf_msg_handler);
|
||||
hf_msg_show_usage();
|
||||
#define TMP_BUF_LEN 128
|
||||
uint8_t tmp_buf[TMP_BUF_LEN] = {0};
|
||||
|
||||
for (;;) {
|
||||
//Waiting for UART event.
|
||||
if (xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||
switch (event.type) {
|
||||
//Event of UART receving data
|
||||
case UART_DATA: {
|
||||
len = uart_read_bytes(CONSOLE_UART_NUM, tmp_buf, TMP_BUF_LEN, 0);
|
||||
for (int i = 0; i < len; i++) {
|
||||
hf_msg_parse(tmp_buf[i], parser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
//Event of HW FIFO overflow detected
|
||||
case UART_FIFO_OVF:
|
||||
ESP_LOGI(TAG_CNSL, "hw fifo overflow");
|
||||
break;
|
||||
//Event of UART ring buffer full
|
||||
case UART_BUFFER_FULL:
|
||||
ESP_LOGI(TAG_CNSL, "ring buffer full");
|
||||
break;
|
||||
//Event of UART RX break detected
|
||||
case UART_BREAK:
|
||||
ESP_LOGI(TAG_CNSL, "uart rx break");
|
||||
break;
|
||||
//Event of UART parity check error
|
||||
case UART_PARITY_ERR:
|
||||
ESP_LOGI(TAG_CNSL, "uart parity error");
|
||||
break;
|
||||
//Event of UART frame error
|
||||
case UART_FRAME_ERR:
|
||||
ESP_LOGI(TAG_CNSL, "uart frame error");
|
||||
break;
|
||||
//Others
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
|
||||
esp_err_t console_uart_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
ret = uart_param_config(CONSOLE_UART_NUM, &uart_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG_CNSL, "Uart %d initialize err %04x", CONSOLE_UART_NUM, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uart_set_pin(CONSOLE_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
uart_driver_install(CONSOLE_UART_NUM, 1024, 1024, 8, &uart_queue, 0);
|
||||
xTaskCreate(console_uart_task, "uTask", 2048, NULL, 8, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __CONSOLE_UART_H__
|
||||
#define __CONSOLE_UART_H__
|
||||
|
||||
#define TAG_CNSL "CNSL"
|
||||
|
||||
/**
|
||||
* @brief configure uart console for command input and process
|
||||
*/
|
||||
esp_err_t console_uart_init(void);
|
||||
|
||||
#endif /* __BT_APP_HF_H__*/
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/gpio_reg.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "gpio_pcm_config.h"
|
||||
|
||||
#define GPIO_OUTPUT_PCM_FSYNC (25)
|
||||
#define GPIO_OUTPUT_PCM_CLK_OUT (5)
|
||||
#define GPIO_OUTPUT_PCM_DOUT (26)
|
||||
#define GPIO_INPUT_PCM_DIN (35)
|
||||
|
||||
#define GPIO_OUTPUT_PCM_PIN_SEL ((1ULL<<GPIO_OUTPUT_PCM_FSYNC) | (1ULL<<GPIO_OUTPUT_PCM_CLK_OUT) | (1ULL<<GPIO_OUTPUT_PCM_DOUT))
|
||||
|
||||
#define GPIO_INPUT_PCM_PIN_SEL (1ULL<<GPIO_INPUT_PCM_DIN)
|
||||
|
||||
void app_gpio_pcm_io_cfg(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
/// configure the PCM output pins
|
||||
//disable interrupt
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//set as output mode
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
//bit mask of the pins that you want to set,e.g.GPIO18/19
|
||||
io_conf.pin_bit_mask = GPIO_OUTPUT_PCM_PIN_SEL;
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
/// configure the PCM input pin
|
||||
//interrupt of rising edge
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//bit mask of the pins, use GPIO4/5 here
|
||||
io_conf.pin_bit_mask = GPIO_INPUT_PCM_PIN_SEL;
|
||||
//set as input mode
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
//enable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
io_conf.pull_down_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
/// matrix out | in the internal PCM signals to the GPIOs
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_FSYNC, PCMFSYNC_OUT_IDX, false, false);
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_CLK_OUT, PCMCLK_OUT_IDX, false, false);
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_DOUT, PCMDOUT_IDX, false, false);
|
||||
gpio_matrix_in(GPIO_INPUT_PCM_DIN, PCMDIN_IDX, false);
|
||||
}
|
||||
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
|
||||
#define GPIO_OUTPUT_AEC_1 (19)
|
||||
#define GPIO_OUTPUT_AEC_2 (21)
|
||||
#define GPIO_OUTPUT_AEC_3 (22)
|
||||
#define GPIO_OUTPUT_AEC_PIN_SEL ((1ULL<<GPIO_OUTPUT_AEC_1) | (1ULL<<GPIO_OUTPUT_AEC_2) | (1ULL<<GPIO_OUTPUT_AEC_3))
|
||||
|
||||
void app_gpio_aec_io_cfg(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
//disable interrupt
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//set as output mode
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
//bit mask of the pins that you want to set,e.g.GPIO18/19
|
||||
io_conf.pin_bit_mask = GPIO_OUTPUT_AEC_PIN_SEL;
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// set the output pins
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_2, 0);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_1, 0);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_1, 1);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_3, 1);
|
||||
}
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __GPIO_PCM_CONFIG_H__
|
||||
#define __GPIO_PCM_CONFIG_H__
|
||||
|
||||
#define ACOUSTIC_ECHO_CANCELLATION_ENABLE 1
|
||||
|
||||
void app_gpio_pcm_io_cfg(void);
|
||||
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
void app_gpio_aec_io_cfg(void);
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
|
||||
#endif /* #define __GPIO_PCM_CONFIG_H__ */
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "bt_app_hf.h"
|
||||
#include "gpio_pcm_config.h"
|
||||
#include "console_uart.h"
|
||||
|
||||
#define BT_HF_AG_TAG "HF_AG_DEMO_MAIN"
|
||||
|
||||
/* event for handler "hf_ag_hdl_stack_up */
|
||||
enum {
|
||||
BT_APP_EVT_STACK_UP = 0,
|
||||
};
|
||||
|
||||
/* handler for bluetooth stack enabled events */
|
||||
static void bt_hf_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_HF_TAG, "%s evt %d", __func__, event);
|
||||
switch (event)
|
||||
{
|
||||
case BT_APP_EVT_STACK_UP:
|
||||
{
|
||||
/* set up device name */
|
||||
char *dev_name = "ESP_HFP_AG";
|
||||
esp_bt_dev_set_device_name(dev_name);
|
||||
|
||||
esp_bt_hf_register_callback(bt_app_hf_cb);
|
||||
|
||||
// init and register for HFP_AG functions
|
||||
esp_bt_hf_init(hf_peer_addr);
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '0';
|
||||
pin_code[1] = '0';
|
||||
pin_code[2] = '0';
|
||||
pin_code[3] = '0';
|
||||
esp_bt_gap_set_pin(pin_type, 4, pin_code);
|
||||
|
||||
/* set discoverable and connectable mode, wait to be connected */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_HF_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_err_t err;
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
if ((err = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
if ((err = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
/* create application task */
|
||||
bt_app_task_start_up();
|
||||
|
||||
/* Bluetooth device name, connection mode and profile set up */
|
||||
bt_app_work_dispatch(bt_hf_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
|
||||
|
||||
/* initialize console via UART */
|
||||
console_uart_init();
|
||||
|
||||
/* configure the PCM interface and PINs used */
|
||||
app_gpio_pcm_io_cfg();
|
||||
|
||||
/* configure externel chip for acoustic echo cancellation */
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
app_gpio_aec_io_cfg();
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled and BT_DRAM_RELEASE is disabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_HFP_ENABLE=y
|
||||
CONFIG_BT_HFP_AG_ENABLE=y
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(hfp_hf)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := hfp_hf
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Hands-Free Unit
|
||||
|
||||
This example is to show how to use the APIs of Hands-Free (HF) Unit Component and the effects of them by providing a set of commands. You can use this example to communicate with an Audio Gateway (AG) device (e.g. a smart phone). This example uses UART for user commands.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example is designed to run on commonly available ESP32 development board, e.g. ESP32-DevKitC. To operate it should be connected to an AG running on a smartphone or on another ESP32 development board loaded with Hands Free Audio Gateway (hfp_ag) example from ESP-IDF.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
ESP32 supports two types of audio data path: PCM() and HCI(Host-Controller Interface) but the default sdkconfig of this example does not config the data path in a specific way. You should config the data path you want:
|
||||
|
||||
- PCM : When using PCM, audio data stream is mapped to GPIO pins and you should link these GPIO pins to a speaker via I2S port. And you should choose PCM in `menuconfig` path: `Component config --> Bluetooth controller --> BR/EDR Sync(SCO/eSCO) default data path --> PCM`and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> PCM`.
|
||||
- HCI : When using HCI, audio data stream will be transport to HF unit app via HCI. And you should choose HCI in `menuconfig` path: `Component config -->Bluetooth controller -->BR/EDR Sync(SCO/eSCO) default data path --> HCI` and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> HCI`.
|
||||
|
||||
**Note: Wide Band Speech is disabled by default, if you want to use it please select it in menuconfig path: `Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech`.**
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
When you run this example, the commands help table prints the following at the very begining:
|
||||
|
||||
```
|
||||
########################################################################
|
||||
HF client command usage manual
|
||||
HF client commands begin with "hf" and end with ";"
|
||||
Supported commands are as follows, arguments are embraced with < and >
|
||||
hf con; -- setup connection with peer device
|
||||
hf dis; -- release connection with peer device
|
||||
hf cona; -- setup audio connection with peer device
|
||||
hf disa; -- release connection with peer device
|
||||
hf qop; -- query current operator name
|
||||
hf qc; -- query current call status
|
||||
hf ac; -- answer incoming call
|
||||
hf rc; -- reject incoming call
|
||||
hf d <num>; -- dial <num>, e.g. hf d 11223344
|
||||
hf rd; -- redial
|
||||
hf dm <index>; -- dial memory
|
||||
hf vron; -- start voice recognition
|
||||
hf vroff; -- stop voice recognition
|
||||
hf vu <tgt> <vol>; -- volume update
|
||||
tgt: 0-speaker, 1-microphone
|
||||
vol: volume gain ranges from 0 to 15
|
||||
hf rs; -- retrieve subscriber information
|
||||
hf rv; -- retrieve last voice tag number
|
||||
hf rh <btrh>; -- response and hold
|
||||
btrh:
|
||||
0 - put call on hold,
|
||||
1 - accept the held call,
|
||||
2 -reject the held call
|
||||
hf k <dtmf>; -- send dtmf code.
|
||||
dtmf: single character in set 0-9, *, #, A-D
|
||||
hf h; -- show command manual
|
||||
########################################################################
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- This command help table will print out in monitor whenever you type `hf h;` or if you input a command that is not required by the command parse rule.
|
||||
- The command you type will not echo in monitor and your command should always start with `hf` and end with `;` or the example will not responds.
|
||||
- The command you typed will not echo in monitor.
|
||||
|
||||
### Service Level Connection and Disconnection
|
||||
|
||||
You can type `hf con;` to establish a service level connection with AG device and log prints such as:
|
||||
|
||||
```
|
||||
E (78502) CNSL: Command [hf con;]
|
||||
connect
|
||||
W (79632) BT_APPL: new conn_srvc id:27, app_id:1
|
||||
I (79642) BT_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (79642) BT_HF: --connection state connected, peer feats 0x0, chld_feats 0x0
|
||||
I (79792) BT_HF: APP HFP event: CALL_IND_EVT
|
||||
I (79792) BT_HF: --Call indicator NO call in progress
|
||||
I (79792) BT_HF: APP HFP event: CALL_SETUP_IND_EVT
|
||||
I (79802) BT_HF: --Call setup indicator NONE
|
||||
I (79802) BT_HF: APP HFP event: NETWORK_STATE_EVT
|
||||
I (79812) BT_HF: --NETWORK STATE available
|
||||
I (79812) BT_HF: APP HFP event: SIGNAL_STRENGTH_IND_EVT
|
||||
I (79822) BT_HF: -- signal strength: 4
|
||||
I (79822) BT_HF: APP HFP event: ROAMING_STATUS_IND_EVT
|
||||
I (79832) BT_HF: --ROAMING: inactive
|
||||
I (79832) BT_HF: APP HFP event: BATTERY_LEVEL_IND_EVT
|
||||
I (79842) BT_HF: --battery level 3
|
||||
I (79842) BT_HF: APP HFP event: CALL_HELD_IND_EVT
|
||||
I (79852) BT_HF: --Call held indicator NONE held
|
||||
I (79852) BT_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (79862) BT_HF: --connection state slc_connected, peer feats 0x16e, chld_feats 0x0
|
||||
I (79872) BT_HF: APP HFP event: INBAND_RING_TONE_EVT
|
||||
I (79872) BT_HF: --inband ring state Provided
|
||||
```
|
||||
|
||||
**Note: Only after Hands-Free Profile(HFP) service is initialized and a service level connection exists between an HF Unit and an AG device, could other commands be available.**
|
||||
|
||||
You can type `hf dis;` to disconnect with the connected AG device, and log prints such as:
|
||||
|
||||
```
|
||||
E (93382) CNSL: Command [hf dis;]
|
||||
disconnect
|
||||
W (93702) BT_RFCOMM: port_rfc_closed RFCOMM connection in state 3 closed: Closed (res: 19)
|
||||
W (93712) BT_APPL: BTA_HF_CLIENT_SCO_SHUTDOWN_ST: Ignoring event 3
|
||||
I (93712) BT_HF: APP HFP event: CONNECTION_STATE_EVT
|
||||
I (93712) BT_HF: --connection state disconnected, peer feats 0x0, chld_feats 0x0
|
||||
```
|
||||
|
||||
### Audio Connection and Disconnection
|
||||
|
||||
You can type `hf cona;` to establish the audio connection between HF Unit and AG device. Log prints such as:
|
||||
|
||||
```
|
||||
E (117232) CNSL: Command [hf cona;]
|
||||
connect audio
|
||||
I (117232) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (117232) BT_HF: --audio state connecting
|
||||
E (117262) BT_BTM: btm_sco_connected, handle 181
|
||||
I (117262) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (117262) BT_HF: --audio state connected
|
||||
```
|
||||
|
||||
Also, you can type `hf disa;` to close the audio data stream. Log prints such as:
|
||||
|
||||
```
|
||||
E (133002) CNSL: Command [hf disa;]
|
||||
disconnect audio
|
||||
I (133262) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (133262) BT_HF: --audio state disconnected
|
||||
```
|
||||
#### Scenarios for Audio Connection
|
||||
|
||||
- Answer an incoming call
|
||||
- Enable voice recognition
|
||||
- Dial an outgoing call
|
||||
|
||||
#### Scenarios for Audio Disconnection
|
||||
|
||||
- Reject an incoming call
|
||||
- Disable the voice recognition
|
||||
|
||||
#### Choise of Codec
|
||||
|
||||
ESP32 supports both CVSD and mSBC codec. HF Unit and AG device determine which codec to use by exchanging features during service level connection. The choice of codec also depends on the your configuration in `menuconfig`.
|
||||
|
||||
Since CVSD is the default codec in HFP, we just show the scenarios using mSBC:
|
||||
|
||||
- If you enable `BT_HFP_WBS_ENABLE` in `menuconfig`, mSBC will be available.
|
||||
- If both HF Unit and AG support mSBC and `BT_HFP_WBS_ENABLE` is enabled, ESP32 chooses mSBC.
|
||||
- If you use PCM data path, mSBC is not available.
|
||||
|
||||
### Answer or Reject an incoming call
|
||||
|
||||
#### Answer an incoming call
|
||||
|
||||
You can type `hf ac;` to answer an incoming call and log prints such as:
|
||||
|
||||
```
|
||||
E (196982) CNSL: Command [hf ac;]
|
||||
Answer call
|
||||
I (197102) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (197102) BT_HF: --AT response event, code 0, cme 0
|
||||
E (197232) BT_BTM: btm_sco_connected, handle 181
|
||||
I (197232) BT_HF: APP HFP event: CALL_IND_EVT
|
||||
I (197232) BT_HF: --Call indicator call in progress
|
||||
I (197232) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (197242) BT_HF: --audio state connected
|
||||
```
|
||||
|
||||
#### Reject an incoming call
|
||||
|
||||
You can type `hf rc;` to reject an incoming call and log prints such as:
|
||||
|
||||
```
|
||||
E (210112) CNSL: Command [hf rc;]
|
||||
Reject call
|
||||
I (210822) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (210822) BT_HF: --AT response event, code 0, cme 0
|
||||
I (210842) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (210842) BT_HF: --audio state disconnected
|
||||
I (210902) BT_HF: APP HFP event: CALL_IND_EVT
|
||||
I (210902) BT_HF: --Call indicator NO call in progress
|
||||
```
|
||||
|
||||
#### Dial Number
|
||||
|
||||
This example supports three dialing commands:
|
||||
|
||||
- `hf d <num>;` Dial the specific number.
|
||||
- `hf rd;` Redial the last number.
|
||||
- `hf dm <index>` Dial the specific indexed number in the AG memory.
|
||||
|
||||
For example, type `hf d 186xxxx5549;` to make an outgoing call to `186xxxx5549` and log prints such as:
|
||||
|
||||
```
|
||||
E (228882) CNSL: Command [hf d 186xxxx5549;]
|
||||
Dial number 186xxxx5549
|
||||
E (229702) BT_BTM: btm_sco_connected, handle 181
|
||||
I (229712) BT_HF: APP HFP event: CALL_SETUP_IND_EVT
|
||||
I (229712) BT_HF: --Call setup indicator OUTGOING_DIALING
|
||||
I (229712) BT_HF: APP HFP event: CALL_IND_EVT
|
||||
I (229712) BT_HF: --Call indicator call in progress
|
||||
I (229722) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (229722) BT_HF: --audio state connected
|
||||
I (229732) BT_HF: APP HFP event: CALL_SETUP_IND_EVT
|
||||
I (229732) BT_HF: --Call setup indicator NONE
|
||||
```
|
||||
|
||||
#### Respond and Hold
|
||||
|
||||
You can type `hf rh <btrh>;` to respond or hold the current call. The parameter should be set as follows:
|
||||
|
||||
- `<btrh>` : 0 - hold current call, 1 - answer held call, 2 - end held call.
|
||||
|
||||
#### Volume Control
|
||||
|
||||
You can type `hf vu <tgt> <vol>;` to update volume gain of speaker or microphone. The parameter should be set as follows:
|
||||
|
||||
- `<tgt>` : 0 - speaker, 1 - microphone.
|
||||
- `<vol>` : Integer among 0 - 15.
|
||||
|
||||
For example, type `hf vu 0 9;` to update the volume of speaker and log on AG prints:
|
||||
|
||||
```
|
||||
I (43684) BT_APP_HF: APP HFP event: VOLUME_CONTROL_EVT
|
||||
I (43684) BT_APP_HF: --Volume Target: SPEAKER, Volume 9
|
||||
```
|
||||
|
||||
And also, `hf vu 1 9;` update the volume gain of microphone and log on AG prints:
|
||||
|
||||
```
|
||||
I (177254) BT_APP_HF: APP HFP event: VOLUME_CONTROL_EVT
|
||||
I (177254) BT_APP_HF: --Volume Target: MICROPHONE, Volume 9
|
||||
```
|
||||
|
||||
#### Voice Recognition
|
||||
|
||||
You can type `hf vron;` to start the voice recognition of AG and type `hf vroff;` to terminate this function. For example, type `hf vron;` and log prints such as:
|
||||
|
||||
```
|
||||
E (292432) CNSL: Command [hf vron;]
|
||||
Start voice recognition
|
||||
I (293172) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (293172) BT_HF: --AT response event, code 0, cme 0
|
||||
E (293702) BT_BTM: btm_sco_connected, handle 181
|
||||
I (293702) BT_HF: APP HFP event: AUDIO_STATE_EVT
|
||||
I (293702) BT_HF: --audio state connecte
|
||||
```
|
||||
|
||||
#### Query Current Operator Name
|
||||
|
||||
You can type `hf qop;` to query the current operator name and log prints like:
|
||||
|
||||
```
|
||||
E (338322) CNSL: Command [hf qop;]
|
||||
Query operator
|
||||
I (339202) BT_HF: APP HFP event: CURRENT_OPERATOR_EVT
|
||||
I (339202) BT_HF: --operator name: 中国联通
|
||||
I (339202) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (339202) BT_HF: --AT response event, code 0, cme 0
|
||||
```
|
||||
|
||||
#### Retrieve Subscriber Information
|
||||
|
||||
You can type `hf rs;` to retrieve subscriber information and log prints such as:
|
||||
|
||||
```
|
||||
E (352902) CNSL: Command [hf rs;]
|
||||
Retrieve subscriber information
|
||||
I (353702) BT_HF: APP HFP event: SUBSCRIBER_INFO_EVT
|
||||
I (353702) BT_HF: --subscriber type unknown, number 186xxxx5549
|
||||
I (353702) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (353702) BT_HF: --AT response event, code 0, cme 0
|
||||
```
|
||||
|
||||
#### Query Current Call Status
|
||||
|
||||
You can type `hf qc;` to query current call status and log prints like:
|
||||
|
||||
```
|
||||
E (354522) CNSL: Command [hf qc;]
|
||||
Query current call status
|
||||
I (354582) BT_HF: APP HFP event: CLCC_EVT
|
||||
I (354582) BT_HF: --Current call: idx 1, dir incoming, state active, mpty single, number 186xxxx5549
|
||||
I (354582) BT_HF: APP HFP event: AT_RESPONSE
|
||||
I (354592) BT_HF: --AT response event, code 0, cme 0
|
||||
```
|
||||
|
||||
#### Transport DTMF Code
|
||||
|
||||
You can type `hf k <dtmf>;` to transport a DTMF code to AG. Log on HF unit side prints like:`send dtmf code: 9` and log on AG side prints such as:
|
||||
|
||||
```
|
||||
I (196284) BT_APP_HF: APP HFP event: DTMF_RESPONSE_EVT
|
||||
I (196284) BT_APP_HF: --DTMF code is: 9.
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any problems, please check if the following rules are followed:
|
||||
|
||||
- You should type the command in the terminal according to the format described in the commands help table.
|
||||
- Not all commands in the table are supported by AG device like _Hands Free Audio Gateway (hfp_ag)_ example from ESP-IDF.
|
||||
- If you want to use `hf con;` to establish a service level connection with specific AG device, you should add the MAC address of the AG device in `bt_app.c`, for example: `esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};`
|
||||
- Use `esp_hf_client_register_callback()` and `esp_hf_client_init();` before establishing a service level connection.
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
Due to the complexity of HFP, this example has more source files than other bluetooth examples. To show functions of HFP in a simple way, we use the Commands and Effects scheme to illustrate APIs of HFP in ESP-IDF.
|
||||
|
||||
- The example will respond to user command through UART console. Please go to `console_uart.c` for the configuration details.
|
||||
- For voice interface, ESP32 has provided PCM input/output signals which can be mapped to GPIO pins. So, please go to `gpio_pcm_config.c` for the configuration details.
|
||||
- If you want to update the command table, please refer to `app_hf_msg_set.c`.
|
||||
- If you want to update the command parse rules, please refer to `app_hf_msg_prs.c`.
|
||||
- If you want to update the responses of HF Unit or want to update the log, please refer to `bt_app_hf.c`.
|
||||
- Task configuration part is in `bt_app_core.c`.
|
||||
@@ -0,0 +1,8 @@
|
||||
idf_component_register(SRCS "app_hf_msg_prs.c"
|
||||
"app_hf_msg_set.c"
|
||||
"bt_app_core.c"
|
||||
"bt_app_hf.c"
|
||||
"console_uart.c"
|
||||
"gpio_pcm_config.c"
|
||||
"main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "app_hf_msg_prs.h"
|
||||
#include "app_hf_msg_set.h"
|
||||
|
||||
// according to the design, message header length shall be no less than 2.
|
||||
#define HF_MSG_HDR_LEN (3)
|
||||
const static char hf_msg_hdr[HF_MSG_HDR_LEN] = {'h', 'f', ' '};
|
||||
|
||||
// according to the design, message header length shall be no less than 2.
|
||||
#define HF_MSG_TAIL_LEN (1)
|
||||
const static char hf_msg_tail[HF_MSG_TAIL_LEN] = {';'};
|
||||
|
||||
void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs)
|
||||
{
|
||||
prs->state = HF_MSG_PRS_IDLE;
|
||||
prs->cnt = 0;
|
||||
prs->h_idx = 0;
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
|
||||
void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb)
|
||||
{
|
||||
prs->callback = cb;
|
||||
}
|
||||
|
||||
hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs)
|
||||
{
|
||||
hf_msg_prs_err_t err = HF_MSG_PRS_ERR_IN_PROGRESS;
|
||||
switch (prs->state) {
|
||||
case HF_MSG_PRS_IDLE:
|
||||
{
|
||||
if (c == hf_msg_hdr[0]) {
|
||||
prs->state = HF_MSG_PRS_HDR;
|
||||
prs->buf[0] = c;
|
||||
prs->cnt = 1;
|
||||
prs->h_idx = 1;
|
||||
} else {
|
||||
err = HF_MSG_PRS_ERR_HDR_UNDETECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HF_MSG_PRS_HDR:
|
||||
{
|
||||
if (c == hf_msg_hdr[prs->h_idx]) {
|
||||
prs->buf[prs->cnt++] = c;
|
||||
if (++(prs->h_idx) == HF_MSG_HDR_LEN) {
|
||||
prs->state = HF_MSG_PRS_PAYL;
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
} else {
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_HDR_SYNC_FAILED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HF_MSG_PRS_PAYL:
|
||||
{
|
||||
prs->buf[prs->cnt++] = c;
|
||||
if (c == hf_msg_tail[prs->t_idx]) {
|
||||
if (++(prs->t_idx) == HF_MSG_TAIL_LEN) {
|
||||
prs->buf[prs->cnt] = '\0';
|
||||
prs->callback(prs->buf, prs->cnt);
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_OK;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
prs->t_idx = 0;
|
||||
}
|
||||
|
||||
if (prs->cnt >= HF_MSG_LEN_MAX) {
|
||||
hf_msg_parser_reset_state(prs);
|
||||
err = HF_MSG_PRS_ERR_BUF_OVERFLOW;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
void hf_msg_split_args(char *start, char *end, char **argv, int *argn)
|
||||
{
|
||||
if (argn == NULL || *argn == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(argv, 0, (*argn) * sizeof(void *));
|
||||
|
||||
int max_argn = *argn;
|
||||
*argn = 0;
|
||||
|
||||
char *p = start;
|
||||
for (int i = 0; i < max_argn; ++i) {
|
||||
while (isspace((int)*p) && p != end) {
|
||||
++p;
|
||||
}
|
||||
if (p == end) {
|
||||
return;
|
||||
}
|
||||
|
||||
argv[i] = p++;
|
||||
++(*argn);
|
||||
|
||||
while (!isspace((int)*p) && p != end) {
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p == end) {
|
||||
return;
|
||||
} else {
|
||||
*p = '\0';
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hf_msg_args_parser(char *buf, int len)
|
||||
{
|
||||
char *argv[HF_MSG_ARGS_MAX];
|
||||
int argn = HF_MSG_ARGS_MAX;
|
||||
char *start = buf + HF_MSG_HDR_LEN;
|
||||
// set the command terminator to '\0'
|
||||
char *end = buf + len - HF_MSG_TAIL_LEN;
|
||||
*end = '\0';
|
||||
|
||||
hf_msg_split_args(start, end, argv, &argn);
|
||||
|
||||
if (argn == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool cmd_supported = false;
|
||||
|
||||
hf_msg_hdl_t *cmd_tbl = hf_get_cmd_tbl();
|
||||
size_t cmd_tbl_size = hf_get_cmd_tbl_size();
|
||||
for (int i = 0; i < cmd_tbl_size; ++i) {
|
||||
hf_msg_hdl_t *hdl = &cmd_tbl[i];
|
||||
if (strcmp(argv[0], hdl->str) == 0) {
|
||||
if (hdl->handler) {
|
||||
hdl->handler(argn, argv);
|
||||
cmd_supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cmd_supported) {
|
||||
printf("unsupported command\n");
|
||||
hf_msg_show_usage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __APP_HF_MSG_PRS_H__
|
||||
#define __APP_HF_MSG_PRS_H__
|
||||
|
||||
typedef enum {
|
||||
HF_MSG_PRS_ERR_OK = 0, // a complete message is finished
|
||||
HF_MSG_PRS_ERR_IN_PROGRESS, // message parsing is in progress
|
||||
HF_MSG_PRS_ERR_HDR_UNDETECTED, // header not detected
|
||||
HF_MSG_PRS_ERR_HDR_SYNC_FAILED, // failed to sync header
|
||||
HF_MSG_PRS_ERR_BUF_OVERFLOW, // exceeds the buffer size: HF_MSG_LEN_MAX
|
||||
} hf_msg_prs_err_t;
|
||||
|
||||
typedef enum {
|
||||
HF_MSG_PRS_IDLE = 0,
|
||||
HF_MSG_PRS_HDR,
|
||||
HF_MSG_PRS_PAYL,
|
||||
} hf_msg_prs_state_t;
|
||||
|
||||
typedef void (*hf_msg_callback)(char *buf, int len);
|
||||
|
||||
#define HF_MSG_LEN_MAX (128)
|
||||
|
||||
typedef struct {
|
||||
hf_msg_prs_state_t state;
|
||||
char buf[HF_MSG_LEN_MAX + 1];
|
||||
int cnt;
|
||||
int h_idx;
|
||||
int t_idx;
|
||||
hf_msg_callback callback;
|
||||
} hf_msg_prs_cb_t;
|
||||
|
||||
void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs);
|
||||
|
||||
void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb);
|
||||
|
||||
hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs);
|
||||
|
||||
void hf_msg_show_usage(void);
|
||||
|
||||
#endif /* __APP_HF_MSG_PRS_H__*/
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_hf_client_api.h"
|
||||
#include "app_hf_msg_set.h"
|
||||
|
||||
extern esp_bd_addr_t peer_addr;
|
||||
|
||||
void hf_msg_show_usage(void)
|
||||
{
|
||||
printf("########################################################################\n");
|
||||
printf("HF client command usage manual\n");
|
||||
printf("HF client commands begin with \"hf\" and end with \";\"\n");
|
||||
printf("Supported commands are as follows, arguments are embraced with < and >\n");
|
||||
printf("hf con; -- setup connection with peer device\n");
|
||||
printf("hf dis; -- release connection with peer device\n");
|
||||
printf("hf cona; -- setup audio connection with peer device\n");
|
||||
printf("hf disa; -- release connection with peer device\n");
|
||||
printf("hf qop; -- query current operator name\n");
|
||||
printf("hf qc; -- query current call status\n");
|
||||
printf("hf ac; -- answer incoming call\n");
|
||||
printf("hf rc; -- reject incoming call\n");
|
||||
printf("hf d <num>; -- dial <num>, e.g. hf d 11223344\n");
|
||||
printf("hf rd; -- redial\n");
|
||||
printf("hf dm <index>; -- dial memory\n");
|
||||
printf("hf vron; -- start voice recognition\n");
|
||||
printf("hf vroff; -- stop voice recognition\n");
|
||||
printf("hf vu <tgt> <vol>; -- volume update\n");
|
||||
printf(" tgt: 0-speaker, 1-microphone\n");
|
||||
printf(" vol: volume gain ranges from 0 to 15\n");
|
||||
printf("hf rs; -- retrieve subscriber information\n");
|
||||
printf("hf rv; -- retrieve last voice tag number\n");
|
||||
printf("hf rh <btrh>; -- response and hold\n");
|
||||
printf(" btrh:\n");
|
||||
printf(" 0 - put call on hold,\n");
|
||||
printf(" 1 - accept the held call,\n");
|
||||
printf(" 2 -reject the held call\n");
|
||||
printf("hf k <dtmf>; -- send dtmf code.\n");
|
||||
printf(" dtmf: single character in set 0-9, *, #, A-D\n");
|
||||
printf("hf h; -- show command manual\n");
|
||||
printf("########################################################################\n");
|
||||
}
|
||||
|
||||
#define HF_CMD_HANDLER(cmd) static void hf_##cmd##_handler(int argn, char **argv)
|
||||
|
||||
|
||||
HF_CMD_HANDLER(help)
|
||||
{
|
||||
hf_msg_show_usage();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(conn)
|
||||
{
|
||||
printf("connect\n");
|
||||
esp_hf_client_connect(peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(disc)
|
||||
{
|
||||
printf("disconnect\n");
|
||||
esp_hf_client_disconnect(peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(conn_audio)
|
||||
{
|
||||
printf("connect audio\n");
|
||||
esp_hf_client_connect_audio(peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(disc_audio)
|
||||
{
|
||||
printf("disconnect audio\n");
|
||||
esp_hf_client_disconnect_audio(peer_addr);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(query_op)
|
||||
{
|
||||
printf("Query operator\n");
|
||||
esp_hf_client_query_current_operator_name();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(answer)
|
||||
{
|
||||
printf("Answer call\n");
|
||||
esp_hf_client_answer_call();
|
||||
}
|
||||
HF_CMD_HANDLER(reject)
|
||||
{
|
||||
printf("Reject call\n");
|
||||
esp_hf_client_reject_call();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(dial)
|
||||
{
|
||||
if (argn != 2) {
|
||||
printf("Insufficient number of arguments");
|
||||
} else {
|
||||
printf("Dial number %s\n", argv[1]);
|
||||
esp_hf_client_dial(argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(redial)
|
||||
{
|
||||
printf("Dial number\n");
|
||||
esp_hf_client_dial(NULL);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(dial_mem)
|
||||
{
|
||||
if (argn != 2) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
int index;
|
||||
if (sscanf(argv[1], "%d", &index) != 1) {
|
||||
printf("Invalid argument %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Dial memory %d\n", index);
|
||||
esp_hf_client_dial_memory(index);
|
||||
}
|
||||
|
||||
|
||||
HF_CMD_HANDLER(start_vr)
|
||||
{
|
||||
printf("Start voice recognition\n");
|
||||
esp_hf_client_start_voice_recognition();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(stop_vr)
|
||||
{
|
||||
printf("Stop voice recognition\n");
|
||||
esp_hf_client_stop_voice_recognition();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(volume_update)
|
||||
{
|
||||
if (argn != 3) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
int target, volume;
|
||||
if (sscanf(argv[1], "%d", &target) != 1 ||
|
||||
(target != ESP_HF_VOLUME_CONTROL_TARGET_SPK &&
|
||||
target != ESP_HF_VOLUME_CONTROL_TARGET_MIC)) {
|
||||
printf("Invalid argument for target %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sscanf(argv[2], "%d", &volume) != 1 ||
|
||||
(volume < 0 || volume > 15)) {
|
||||
printf("Invalid argument for volume %s\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("volume update\n");
|
||||
esp_hf_client_volume_update(target, volume);
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(query_call)
|
||||
{
|
||||
printf("Query current call status\n");
|
||||
esp_hf_client_query_current_calls();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(retrieve_subscriber)
|
||||
{
|
||||
printf("Retrieve subscriber information\n");
|
||||
esp_hf_client_retrieve_subscriber_info();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(request_last_voice_tag)
|
||||
{
|
||||
printf("Request last voice tag\n");
|
||||
esp_hf_client_request_last_voice_tag_number();
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(btrh)
|
||||
{
|
||||
if (argn != 2) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int btrh;
|
||||
if (sscanf(argv[1], "%d", &btrh) != 1) {
|
||||
printf("Invalid argument %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
if (btrh < ESP_HF_BTRH_CMD_HOLD || btrh > ESP_HF_BTRH_CMD_REJECT) {
|
||||
printf("Invalid argument %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
printf("respond and hold command: %d\n", btrh);
|
||||
esp_hf_client_send_btrh_cmd(btrh);
|
||||
}
|
||||
|
||||
static bool is_dtmf_code(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return true;
|
||||
}
|
||||
if (c == '#' || c == '*') {
|
||||
return true;
|
||||
}
|
||||
if (c >= 'A' && c <= 'D') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HF_CMD_HANDLER(dtmf)
|
||||
{
|
||||
if (argn != 2) {
|
||||
printf("Insufficient number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(argv[1]) != 1 || !is_dtmf_code(argv[1][0])) {
|
||||
printf("Invalid argument %s\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("send dtmf code: %s\n", argv[1]);
|
||||
esp_hf_client_send_dtmf(argv[1][0]);
|
||||
}
|
||||
|
||||
static hf_msg_hdl_t hf_cmd_tbl[] = {
|
||||
{0, "h", hf_help_handler},
|
||||
{5, "con", hf_conn_handler},
|
||||
{10, "dis", hf_disc_handler},
|
||||
{20, "cona", hf_conn_audio_handler},
|
||||
{30, "disa", hf_disc_audio_handler},
|
||||
{40, "qop", hf_query_op_handler},
|
||||
{120, "qc", hf_query_call_handler},
|
||||
{50, "ac", hf_answer_handler},
|
||||
{60, "rc", hf_reject_handler},
|
||||
{70, "d", hf_dial_handler},
|
||||
{80, "rd", hf_redial_handler},
|
||||
{85, "dm", hf_dial_mem_handler},
|
||||
{90, "vron", hf_start_vr_handler},
|
||||
{100, "vroff", hf_stop_vr_handler},
|
||||
{110, "vu", hf_volume_update_handler},
|
||||
{130, "rs", hf_retrieve_subscriber_handler},
|
||||
{140, "rv", hf_request_last_voice_tag_handler},
|
||||
{150, "rh", hf_btrh_handler},
|
||||
{160, "k", hf_dtmf_handler},
|
||||
};
|
||||
|
||||
hf_msg_hdl_t *hf_get_cmd_tbl(void)
|
||||
{
|
||||
return hf_cmd_tbl;
|
||||
}
|
||||
|
||||
size_t hf_get_cmd_tbl_size(void)
|
||||
{
|
||||
return sizeof(hf_cmd_tbl) / sizeof(hf_msg_hdl_t);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __APP_HF_MSG_SET_H__
|
||||
#define __APP_HF_MSG_SET_H__
|
||||
|
||||
#define HF_MSG_ARGS_MAX (5)
|
||||
|
||||
typedef void (* hf_cmd_handler)(int argn, char **argv);
|
||||
|
||||
typedef struct {
|
||||
uint16_t opcode;
|
||||
const char *str;
|
||||
hf_cmd_handler handler;
|
||||
} hf_msg_hdl_t;
|
||||
|
||||
extern hf_msg_hdl_t *hf_get_cmd_tbl(void);
|
||||
extern size_t hf_get_cmd_tbl_size(void);
|
||||
|
||||
void hf_msg_show_usage(void);
|
||||
#endif /* __APP_HF_MSG_SET_H__*/
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
|
||||
static void bt_app_task_handler(void *arg);
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg);
|
||||
|
||||
static xQueueHandle bt_app_task_queue = NULL;
|
||||
static xTaskHandle bt_app_task_handle = NULL;
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
|
||||
|
||||
bt_app_msg_t msg;
|
||||
memset(&msg, 0, sizeof(bt_app_msg_t));
|
||||
|
||||
msg.sig = BT_APP_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return bt_app_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(&msg, msg.param, p_params);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
|
||||
ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_task_handler(void *arg)
|
||||
{
|
||||
bt_app_msg_t msg;
|
||||
for (;;) {
|
||||
if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
|
||||
switch (msg.sig) {
|
||||
case BT_APP_SIG_WORK_DISPATCH:
|
||||
{
|
||||
bt_app_work_dispatched(&msg);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig);
|
||||
break;
|
||||
} // switch (msg.sig)
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
|
||||
xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &bt_app_task_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (bt_app_task_handle) {
|
||||
vTaskDelete(bt_app_task_handle);
|
||||
bt_app_task_handle = NULL;
|
||||
}
|
||||
if (bt_app_task_queue) {
|
||||
vQueueDelete(bt_app_task_queue);
|
||||
bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to bt_app_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
bt_app_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be the last */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
|
||||
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express esp_hf_ag_apiied.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "bt_app_core.h"
|
||||
#include "bt_app_hf.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_hf_client_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "time.h"
|
||||
#include "sys/time.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
const char *c_hf_evt_str[] = {
|
||||
"CONNECTION_STATE_EVT", /*!< connection state changed event */
|
||||
"AUDIO_STATE_EVT", /*!< audio connection state change event */
|
||||
"VR_STATE_CHANGE_EVT", /*!< voice recognition state changed */
|
||||
"CALL_IND_EVT", /*!< call indication event */
|
||||
"CALL_SETUP_IND_EVT", /*!< call setup indication event */
|
||||
"CALL_HELD_IND_EVT", /*!< call held indicator event */
|
||||
"NETWORK_STATE_EVT", /*!< network state change event */
|
||||
"SIGNAL_STRENGTH_IND_EVT", /*!< signal strength indication event */
|
||||
"ROAMING_STATUS_IND_EVT", /*!< roaming status indication event */
|
||||
"BATTERY_LEVEL_IND_EVT", /*!< battery level indication event */
|
||||
"CURRENT_OPERATOR_EVT", /*!< current operator name event */
|
||||
"RESP_AND_HOLD_EVT", /*!< response and hold event */
|
||||
"CLIP_EVT", /*!< Calling Line Identification notification event */
|
||||
"CALL_WAITING_EVT", /*!< call waiting notification */
|
||||
"CLCC_EVT", /*!< listing current calls event */
|
||||
"VOLUME_CONTROL_EVT", /*!< audio volume control event */
|
||||
"AT_RESPONSE", /*!< audio volume control event */
|
||||
"SUBSCRIBER_INFO_EVT", /*!< subscriber information event */
|
||||
"INBAND_RING_TONE_EVT", /*!< in-band ring tone settings */
|
||||
"LAST_VOICE_TAG_NUMBER_EVT", /*!< requested number from AG event */
|
||||
"RING_IND_EVT", /*!< ring indication event */
|
||||
};
|
||||
|
||||
// esp_hf_client_connection_state_t
|
||||
const char *c_connection_state_str[] = {
|
||||
"disconnected",
|
||||
"connecting",
|
||||
"connected",
|
||||
"slc_connected",
|
||||
"disconnecting",
|
||||
};
|
||||
|
||||
// esp_hf_client_audio_state_t
|
||||
const char *c_audio_state_str[] = {
|
||||
"disconnected",
|
||||
"connecting",
|
||||
"connected",
|
||||
"connected_msbc",
|
||||
};
|
||||
|
||||
/// esp_hf_vr_state_t
|
||||
const char *c_vr_state_str[] = {
|
||||
"disabled",
|
||||
"enabled",
|
||||
};
|
||||
|
||||
// esp_hf_service_availability_status_t
|
||||
const char *c_service_availability_status_str[] = {
|
||||
"unavailable",
|
||||
"available",
|
||||
};
|
||||
|
||||
// esp_hf_roaming_status_t
|
||||
const char *c_roaming_status_str[] = {
|
||||
"inactive",
|
||||
"active",
|
||||
};
|
||||
|
||||
// esp_hf_client_call_state_t
|
||||
const char *c_call_str[] = {
|
||||
"NO call in progress",
|
||||
"call in progress",
|
||||
};
|
||||
|
||||
// esp_hf_client_callsetup_t
|
||||
const char *c_call_setup_str[] = {
|
||||
"NONE",
|
||||
"INCOMING",
|
||||
"OUTGOING_DIALING",
|
||||
"OUTGOING_ALERTING"
|
||||
};
|
||||
|
||||
// esp_hf_client_callheld_t
|
||||
const char *c_call_held_str[] = {
|
||||
"NONE held",
|
||||
"Held and Active",
|
||||
"Held",
|
||||
};
|
||||
|
||||
// esp_hf_response_and_hold_status_t
|
||||
const char *c_resp_and_hold_str[] = {
|
||||
"HELD",
|
||||
"HELD ACCEPTED",
|
||||
"HELD REJECTED",
|
||||
};
|
||||
|
||||
// esp_hf_client_call_direction_t
|
||||
const char *c_call_dir_str[] = {
|
||||
"outgoing",
|
||||
"incoming",
|
||||
};
|
||||
|
||||
// esp_hf_client_call_state_t
|
||||
const char *c_call_state_str[] = {
|
||||
"active",
|
||||
"held",
|
||||
"dialing",
|
||||
"alerting",
|
||||
"incoming",
|
||||
"waiting",
|
||||
"held_by_resp_hold",
|
||||
};
|
||||
|
||||
// esp_hf_current_call_mpty_type_t
|
||||
const char *c_call_mpty_type_str[] = {
|
||||
"single",
|
||||
"multi",
|
||||
};
|
||||
|
||||
// esp_hf_volume_control_target_t
|
||||
const char *c_volume_control_target_str[] = {
|
||||
"SPEAKER",
|
||||
"MICROPHONE"
|
||||
};
|
||||
|
||||
// esp_hf_at_response_code_t
|
||||
const char *c_at_response_code_str[] = {
|
||||
"OK",
|
||||
"ERROR"
|
||||
"ERR_NO_CARRIER",
|
||||
"ERR_BUSY",
|
||||
"ERR_NO_ANSWER",
|
||||
"ERR_DELAYED",
|
||||
"ERR_BLACKLILSTED",
|
||||
"ERR_CME",
|
||||
};
|
||||
|
||||
// esp_hf_subscriber_service_type_t
|
||||
const char *c_subscriber_service_type_str[] = {
|
||||
"unknown",
|
||||
"voice",
|
||||
"fax",
|
||||
};
|
||||
|
||||
// esp_hf_client_in_band_ring_state_t
|
||||
const char *c_inband_ring_state_str[] = {
|
||||
"NOT provided",
|
||||
"Provided",
|
||||
};
|
||||
|
||||
// esp_bd_addr_t peer_addr;
|
||||
// If you want to connect a specific device, add it's address here
|
||||
esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};
|
||||
|
||||
#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI
|
||||
|
||||
#define ESP_HFP_RINGBUF_SIZE 3600
|
||||
static RingbufHandle_t m_rb = NULL;
|
||||
|
||||
static void bt_app_hf_client_audio_open(void)
|
||||
{
|
||||
m_rb = xRingbufferCreate(ESP_HFP_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
}
|
||||
|
||||
static void bt_app_hf_client_audio_close(void)
|
||||
{
|
||||
if (!m_rb) {
|
||||
return ;
|
||||
}
|
||||
|
||||
vRingbufferDelete(m_rb);
|
||||
}
|
||||
|
||||
static uint32_t bt_app_hf_client_outgoing_cb(uint8_t *p_buf, uint32_t sz)
|
||||
{
|
||||
if (!m_rb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t item_size = 0;
|
||||
uint8_t *data = xRingbufferReceiveUpTo(m_rb, &item_size, 0, sz);
|
||||
if (item_size == sz) {
|
||||
memcpy(p_buf, data, item_size);
|
||||
vRingbufferReturnItem(m_rb, data);
|
||||
return sz;
|
||||
} else if (0 < item_size) {
|
||||
vRingbufferReturnItem(m_rb, data);
|
||||
return 0;
|
||||
} else {
|
||||
// data not enough, do not read
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_hf_client_incoming_cb(const uint8_t *buf, uint32_t sz)
|
||||
{
|
||||
if (! m_rb) {
|
||||
return;
|
||||
}
|
||||
BaseType_t done = xRingbufferSend(m_rb, (uint8_t *)buf, sz, 0);
|
||||
if (! done) {
|
||||
ESP_LOGE(BT_HF_TAG, "rb send fail");
|
||||
}
|
||||
|
||||
esp_hf_client_outgoing_data_ready();
|
||||
}
|
||||
#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */
|
||||
|
||||
/* callback for HF_CLIENT */
|
||||
void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param)
|
||||
{
|
||||
if (event <= ESP_HF_CLIENT_RING_IND_EVT) {
|
||||
ESP_LOGI(BT_HF_TAG, "APP HFP event: %s", c_hf_evt_str[event]);
|
||||
} else {
|
||||
ESP_LOGE(BT_HF_TAG, "APP HFP invalid event %d", event);
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_HF_CLIENT_CONNECTION_STATE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--connection state %s, peer feats 0x%x, chld_feats 0x%x",
|
||||
c_connection_state_str[param->conn_stat.state],
|
||||
param->conn_stat.peer_feat,
|
||||
param->conn_stat.chld_feat);
|
||||
memcpy(peer_addr,param->conn_stat.remote_bda,ESP_BD_ADDR_LEN);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_AUDIO_STATE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--audio state %s",
|
||||
c_audio_state_str[param->audio_stat.state]);
|
||||
#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI
|
||||
if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED ||
|
||||
param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) {
|
||||
esp_hf_client_register_data_callback(bt_app_hf_client_incoming_cb,
|
||||
bt_app_hf_client_outgoing_cb);
|
||||
bt_app_hf_client_audio_open();
|
||||
} else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED) {
|
||||
bt_app_hf_client_audio_close();
|
||||
}
|
||||
#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_BVRA_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--VR state %s",
|
||||
c_vr_state_str[param->bvra.value]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_SERVICE_AVAILABILITY_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--NETWORK STATE %s",
|
||||
c_service_availability_status_str[param->service_availability.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_ROAMING_STATUS_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--ROAMING: %s",
|
||||
c_roaming_status_str[param->roaming.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_SIGNAL_STRENGTH_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "-- signal strength: %d",
|
||||
param->signal_strength.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_BATTERY_LEVEL_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--battery level %d",
|
||||
param->battery_level.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_COPS_CURRENT_OPERATOR_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--operator name: %s",
|
||||
param->cops.name);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_CALL_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Call indicator %s",
|
||||
c_call_str[param->call.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_CALL_SETUP_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Call setup indicator %s",
|
||||
c_call_setup_str[param->call_setup.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CIND_CALL_HELD_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Call held indicator %s",
|
||||
c_call_held_str[param->call_held.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_BTRH_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--response and hold %s",
|
||||
c_resp_and_hold_str[param->btrh.status]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CLIP_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--clip number %s",
|
||||
(param->clip.number == NULL) ? "NULL" : (param->clip.number));
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CCWA_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--call_waiting %s",
|
||||
(param->ccwa.number == NULL) ? "NULL" : (param->ccwa.number));
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CLCC_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--Current call: idx %d, dir %s, state %s, mpty %s, number %s",
|
||||
param->clcc.idx,
|
||||
c_call_dir_str[param->clcc.dir],
|
||||
c_call_state_str[param->clcc.status],
|
||||
c_call_mpty_type_str[param->clcc.mpty],
|
||||
(param->clcc.number == NULL) ? "NULL" : (param->clcc.number));
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_VOLUME_CONTROL_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--volume_target: %s, volume %d",
|
||||
c_volume_control_target_str[param->volume_control.type],
|
||||
param->volume_control.volume);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_AT_RESPONSE_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--AT response event, code %d, cme %d",
|
||||
param->at_response.code, param->at_response.cme);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_CNUM_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--subscriber type %s, number %s",
|
||||
c_subscriber_service_type_str[param->cnum.type],
|
||||
(param->cnum.number == NULL) ? "NULL" : param->cnum.number);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_BSIR_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--inband ring state %s",
|
||||
c_inband_ring_state_str[param->bsir.state]);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLIENT_BINP_EVT:
|
||||
{
|
||||
ESP_LOGI(BT_HF_TAG, "--last voice tag number: %s",
|
||||
(param->binp.number == NULL) ? "NULL" : param->binp.number);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGE(BT_HF_TAG, "HF_CLIENT EVT: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_HF_H__
|
||||
#define __BT_APP_HF_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_hf_client_api.h"
|
||||
|
||||
|
||||
#define BT_HF_TAG "BT_HF"
|
||||
|
||||
/**
|
||||
* @brief callback function for HF client
|
||||
*/
|
||||
void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param);
|
||||
|
||||
|
||||
#endif /* __BT_APP_HF_H__*/
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding include to include path.)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "console_uart.h"
|
||||
#include "app_hf_msg_prs.h"
|
||||
|
||||
#define CONSOLE_UART_NUM UART_NUM_0
|
||||
|
||||
static QueueHandle_t uart_queue;
|
||||
static hf_msg_prs_cb_t hf_msg_parser;
|
||||
|
||||
static const uart_config_t uart_cfg = {
|
||||
.baud_rate = 115200, //1.5M
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 127,
|
||||
};
|
||||
|
||||
extern void hf_msg_args_parser(char *buf, int len);
|
||||
|
||||
void hf_msg_handler(char *buf, int len)
|
||||
{
|
||||
ESP_LOGE(TAG_CNSL, "Command [%s]", buf);
|
||||
hf_msg_args_parser(buf, len);
|
||||
}
|
||||
|
||||
static void console_uart_task(void *pvParameters)
|
||||
{
|
||||
int len;
|
||||
uart_event_t event;
|
||||
hf_msg_prs_cb_t *parser = &hf_msg_parser;
|
||||
hf_msg_parser_reset_state(parser);
|
||||
hf_msg_parser_register_callback(parser, hf_msg_handler);
|
||||
hf_msg_show_usage();
|
||||
#define TMP_BUF_LEN 128
|
||||
uint8_t tmp_buf[128] = {0};
|
||||
|
||||
for (;;) {
|
||||
//Waiting for UART event.
|
||||
if (xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||
switch (event.type) {
|
||||
//Event of UART receving data
|
||||
case UART_DATA:
|
||||
{
|
||||
len = uart_read_bytes(CONSOLE_UART_NUM, tmp_buf, TMP_BUF_LEN, 0);
|
||||
for (int i = 0; i < len; i++) {
|
||||
hf_msg_parse(tmp_buf[i], parser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
//Event of HW FIFO overflow detected
|
||||
case UART_FIFO_OVF:
|
||||
{
|
||||
ESP_LOGI(TAG_CNSL, "hw fifo overflow");
|
||||
break;
|
||||
}
|
||||
//Event of UART ring buffer full
|
||||
case UART_BUFFER_FULL:
|
||||
{
|
||||
ESP_LOGI(TAG_CNSL, "ring buffer full");
|
||||
break;
|
||||
}
|
||||
//Event of UART RX break detected
|
||||
case UART_BREAK:
|
||||
{
|
||||
ESP_LOGI(TAG_CNSL, "uart rx break");
|
||||
break;
|
||||
}
|
||||
//Event of UART parity check error
|
||||
case UART_PARITY_ERR:
|
||||
{
|
||||
ESP_LOGI(TAG_CNSL, "uart parity error");
|
||||
break;
|
||||
}
|
||||
//Event of UART frame error
|
||||
case UART_FRAME_ERR:
|
||||
{
|
||||
ESP_LOGI(TAG_CNSL, "uart frame error");
|
||||
break;
|
||||
}
|
||||
//Others
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
|
||||
esp_err_t console_uart_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
ret = uart_param_config(CONSOLE_UART_NUM, &uart_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG_CNSL, "Uart %d initialize err %04x", CONSOLE_UART_NUM, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uart_set_pin(CONSOLE_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
uart_driver_install(CONSOLE_UART_NUM, 1024, 1024, 8, &uart_queue, 0);
|
||||
xTaskCreate(console_uart_task, "uTask", 2048, NULL, 8, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __CONSOLE_UART_H__
|
||||
#define __CONSOLE_UART_H__
|
||||
|
||||
#define TAG_CNSL "CNSL"
|
||||
|
||||
/**
|
||||
* @brief configure uart console for command input and process
|
||||
*/
|
||||
esp_err_t console_uart_init(void);
|
||||
|
||||
#endif /* __BT_APP_HF_H__*/
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/gpio_reg.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "gpio_pcm_config.h"
|
||||
|
||||
#define GPIO_OUTPUT_PCM_FSYNC (25)
|
||||
#define GPIO_OUTPUT_PCM_CLK_OUT (5)
|
||||
#define GPIO_OUTPUT_PCM_DOUT (26)
|
||||
#define GPIO_INPUT_PCM_DIN (35)
|
||||
|
||||
#define GPIO_OUTPUT_PCM_PIN_SEL ((1ULL<<GPIO_OUTPUT_PCM_FSYNC) | (1ULL<<GPIO_OUTPUT_PCM_CLK_OUT) | (1ULL<<GPIO_OUTPUT_PCM_DOUT))
|
||||
|
||||
#define GPIO_INPUT_PCM_PIN_SEL (1ULL<<GPIO_INPUT_PCM_DIN)
|
||||
|
||||
void app_gpio_pcm_io_cfg(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
/// configure the PCM output pins
|
||||
//disable interrupt
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//set as output mode
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
//bit mask of the pins that you want to set,e.g.GPIO18/19
|
||||
io_conf.pin_bit_mask = GPIO_OUTPUT_PCM_PIN_SEL;
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
/// configure the PCM input pin
|
||||
//interrupt of rising edge
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//bit mask of the pins, use GPIO4/5 here
|
||||
io_conf.pin_bit_mask = GPIO_INPUT_PCM_PIN_SEL;
|
||||
//set as input mode
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
//enable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
io_conf.pull_down_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
/// matrix out | in the internal PCM signals to the GPIOs
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_FSYNC, PCMFSYNC_OUT_IDX, false, false);
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_CLK_OUT, PCMCLK_OUT_IDX, false, false);
|
||||
gpio_matrix_out(GPIO_OUTPUT_PCM_DOUT, PCMDOUT_IDX, false, false);
|
||||
gpio_matrix_in(GPIO_INPUT_PCM_DIN, PCMDIN_IDX, false);
|
||||
}
|
||||
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
|
||||
#define GPIO_OUTPUT_AEC_1 (19)
|
||||
#define GPIO_OUTPUT_AEC_2 (21)
|
||||
#define GPIO_OUTPUT_AEC_3 (22)
|
||||
#define GPIO_OUTPUT_AEC_PIN_SEL ((1ULL<<GPIO_OUTPUT_AEC_1) | (1ULL<<GPIO_OUTPUT_AEC_2) | (1ULL<<GPIO_OUTPUT_AEC_3))
|
||||
|
||||
void app_gpio_aec_io_cfg(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
//disable interrupt
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
//set as output mode
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
//bit mask of the pins that you want to set,e.g.GPIO18/19
|
||||
io_conf.pin_bit_mask = GPIO_OUTPUT_AEC_PIN_SEL;
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// set the output pins
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_2, 0);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_1, 0);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_1, 1);
|
||||
|
||||
gpio_set_level(GPIO_OUTPUT_AEC_3, 1);
|
||||
}
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __GPIO_PCM_CONFIG_H__
|
||||
#define __GPIO_PCM_CONFIG_H__
|
||||
|
||||
#define ACOUSTIC_ECHO_CANCELLATION_ENABLE 1
|
||||
|
||||
void app_gpio_pcm_io_cfg(void);
|
||||
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
void app_gpio_aec_io_cfg(void);
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
|
||||
#endif /* #define __GPIO_PCM_CONFIG_H__ */
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_hf_client_api.h"
|
||||
#include "bt_app_hf.h"
|
||||
#include "gpio_pcm_config.h"
|
||||
#include "console_uart.h"
|
||||
|
||||
/* event for handler "bt_av_hdl_stack_up */
|
||||
enum {
|
||||
BT_APP_EVT_STACK_UP = 0,
|
||||
};
|
||||
|
||||
/* handler for bluetooth stack enabled events */
|
||||
static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_err_t err;
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((err = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(BT_HF_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
/* create application task */
|
||||
bt_app_task_start_up();
|
||||
|
||||
/* Bluetooth device name, connection mode and profile set up */
|
||||
bt_app_work_dispatch(bt_hf_client_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
|
||||
|
||||
/* initialize console via UART */
|
||||
console_uart_init();
|
||||
|
||||
/* configure the PCM interface and PINs used */
|
||||
app_gpio_pcm_io_cfg();
|
||||
|
||||
/* configure externel chip for acoustic echo cancellation */
|
||||
#if ACOUSTIC_ECHO_CANCELLATION_ENABLE
|
||||
app_gpio_aec_io_cfg();
|
||||
#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */
|
||||
}
|
||||
|
||||
|
||||
static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_HF_TAG, "%s evt %d", __func__, event);
|
||||
switch (event) {
|
||||
case BT_APP_EVT_STACK_UP: {
|
||||
/* set up device name */
|
||||
char *dev_name = "ESP_HFP_HF";
|
||||
esp_bt_dev_set_device_name(dev_name);
|
||||
|
||||
esp_hf_client_register_callback(bt_app_hf_client_cb);
|
||||
esp_hf_client_init();
|
||||
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '0';
|
||||
pin_code[1] = '0';
|
||||
pin_code[2] = '0';
|
||||
pin_code[3] = '0';
|
||||
esp_bt_gap_set_pin(pin_type, 4, pin_code);
|
||||
|
||||
/* set discoverable and connectable mode, wait to be connected */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_HF_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled and BT_DRAM_RELEASE is disabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_HFP_ENABLE=y
|
||||
CONFIG_BT_HFP_CLIENT_ENABLE=y
|
||||
Reference in New Issue
Block a user