Console support WIP!

Some instabilities to tackle. BT Ring buffer were taken out.  DAC is
crashing with stack overflow.  So does A2DP after playing for a little
while.  This needs to be investigated.
This commit is contained in:
Sebastien Leclerc
2019-06-14 16:38:48 -04:00
parent 97144f7f5b
commit 8500b2180d
41 changed files with 3420 additions and 658 deletions

View File

@@ -0,0 +1,7 @@
set(COMPONENT_ADD_INCLUDEDIRS . )
set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "bt_app_core.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c")
set(REQUIRES esp_common)
set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console )
register_component()

View File

@@ -0,0 +1,122 @@
/*
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 "bt_app_core.h"
#include "esp_system.h"
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
static const char * TAG = "platform_esp32";
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_LOGV(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(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_LOGV(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(TAG,"%s, unhandled sig: %d", __func__, msg.sig);
break;
} // switch (msg.sig)
if (msg.param) {
free(msg.param);
}
}
else
{
ESP_LOGW(TAG,"No messaged received from queue.");
}
}
}
void bt_app_task_start_up(void)
{
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
assert(s_bt_app_task_queue!=NULL);
assert(xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle)==pdPASS);
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;
}
}

View File

@@ -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 __BT_APP_CORE_H__
#define __BT_APP_CORE_H__
#include "esp_log.h"
#include "time.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__ */

View File

@@ -0,0 +1,21 @@
/* Console example — declarations of command registration functions.
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.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "cmd_system.h"
#include "cmd_wifi.h"
#include "cmd_nvs.h"
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,173 @@
//size_t esp_console_split_argv(char *line, char **argv, size_t argv_size);
#include "cmd_squeezelite.h"
#include <stdio.h>
#include <string.h>
#include "cmd_decl.h"
#include "esp_log.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "pthread.h"
#include "platform_esp32.h"
#include "nvs.h"
#include "nvs_flash.h"
//extern char current_namespace[];
static const char * TAG = "squeezelite_cmd";
#define SQUEEZELITE_THREAD_STACK_SIZE 20480
extern int main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);
pthread_t thread_squeezelite;
pthread_t thread_squeezelite_runner;
/** Arguments used by 'squeezelite' function */
static struct {
struct arg_str *parameters;
struct arg_end *end;
} squeezelite_args;
static struct {
int argc;
char ** argv;
} thread_parms ;
static void * squeezelite_runner_thread(){
ESP_LOGI(TAG ,"Calling squeezelite");
main(thread_parms.argc,thread_parms.argv);
return NULL;
}
#define ADDITIONAL_SQUEEZELILTE_ARGS 5
static void * squeezelite_thread(){
int * exit_code;
static bool isRunning=false;
if(isRunning) {
ESP_LOGE(TAG,"Squeezelite already running. Exiting!");
return NULL;
}
isRunning=true;
ESP_LOGI(TAG,"Waiting for WiFi.");
while(!wait_for_wifi()){};
ESP_LOGD(TAG ,"Number of args received: %u",thread_parms.argc );
ESP_LOGV(TAG ,"Values:");
for(int i = 0;i<thread_parms.argc; i++){
ESP_LOGV(TAG ," %s",thread_parms.argv[i]);
}
ESP_LOGD(TAG,"Starting Squeezelite runner Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite-run";
cfg.inherit_cfg = true;
cfg.stack_size = SQUEEZELITE_THREAD_STACK_SIZE ;
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_squeezelite_runner, &attr, squeezelite_runner_thread,NULL);
pthread_attr_destroy(&attr);
// Wait for thread completion so we can free up memory.
pthread_join(thread_squeezelite_runner,(void **)&exit_code);
ESP_LOGV(TAG ,"Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++){
ESP_LOGV(TAG ,"Freeing char buffer for parameter %u", i+1);
free(thread_parms.argv[i]);
}
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
isRunning=false;
return NULL;
}
//static int launchsqueezelite_dft(int _argc, char **_argv){
// nvs_handle nvs;
// esp_err_t err;
// optListStruct * curOpt=&optList[0];
// ESP_LOGV(TAG ,"preparing to allocate memory ");
// int argc =_argc+50; // todo: max number of parms?
// char ** argv = malloc(sizeof(char**)*argc);
// memset(argv,'\0',sizeof(char**)*argc);
// int curOptNum=0;
// argv[curOptNum++]=strdup(_argv[0]);
// ESP_LOGV(TAG ,"nvs_open\n");
// err = nvs_open(current_namespace, NVS_READONLY, &nvs);
// if (err != ESP_OK) {
// return err;
// }
//
// while(curOpt->optName!=NULL){
// ESP_LOGV(TAG ,"Checking option %s with default value %s",curOpt->optName, curOpt->defaultValue);
// if(!strcmp(curOpt->relatedcommand,"squeezelite"))
// {
// ESP_LOGV(TAG ,"option is for Squeezelite command, processing it");
// // this is a squeezelite option
// if(curOpt->cmdLinePrefix!=NULL){
// ESP_LOGV(TAG ,"adding prefix %s",curOpt->cmdLinePrefix);
// argv[curOptNum++]=strdup(curOpt->cmdLinePrefix);
// }
// size_t len;
// if ( (nvs_get_str(nvs, curOpt->optName, NULL, &len)) == ESP_OK) {
// char *str = (char *)malloc(len);
// nvs_get_str(nvs, curOpt->optName, str, &len);
// ESP_LOGV(TAG ,"assigning retrieved value %s",str);
// argv[curOptNum++]=str;
//
// }
// }
// curOpt++;
// }
// nvs_close(nvs);
// ESP_LOGV(TAG ,"calling launchsqueezelite with parameters");
// launchsqueezelite(argc, argv);
// ESP_LOGV(TAG ,"back from calling launchsqueezelite");
// return 0;
//}
static int launchsqueezelite(int argc, char **argv)
{
ESP_LOGV(TAG ,"Begin");
ESP_LOGV(TAG, "Parameters:");
for(int i = 0;i<argc; i++){
ESP_LOGV(TAG, " %s",argv[i]);
}
ESP_LOGV(TAG,"Saving args in thread structure");
thread_parms.argc=0;
thread_parms.argv = malloc(sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
memset(thread_parms.argv,'\0',sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
for(int i=0;i<argc;i++){
ESP_LOGD(TAG ,"assigning parm %u : %s",i,argv[i]);
thread_parms.argv[thread_parms.argc++]=strdup(argv[i]);
}
if(argc==1){
// There isn't a default configuration that would actually work
// if no parameter is passed.
ESP_LOGV(TAG ,"Adding argv value at %u. Prev value: %s",thread_parms.argc,thread_parms.argv[thread_parms.argc-1]);
thread_parms.argv[thread_parms.argc++]=strdup("-?");
}
ESP_LOGD(TAG,"Starting Squeezelite Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite";
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_squeezelite, &attr, squeezelite_thread,NULL);
pthread_attr_destroy(&attr);
ESP_LOGD(TAG ,"Back to console thread!");
return 0;
}
void register_squeezelite(){
squeezelite_args.parameters = arg_str0(NULL, NULL, "<parms>", "command line for squeezelite. -h for help, --defaults to launch with default values.");
squeezelite_args.end = arg_end(1);
const esp_console_cmd_t launch_squeezelite = {
.command = "squeezelite",
.help = "Starts squeezelite",
.hint = NULL,
.func = &launchsqueezelite,
.argtable = &squeezelite_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&launch_squeezelite) );
}

View File

@@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Register WiFi functions
void register_squeezelite();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,185 @@
/* Console example — WiFi commands
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 "cmd_wifi.h"
#include <stdio.h>
#include <string.h>
#include "cmd_decl.h"
#include "esp_log.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "tcpip_adapter.h"
#include "esp_event.h"
#define JOIN_TIMEOUT_MS (10000)
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
static const char * TAG = "cmd_wifi";
/** Arguments used by 'join' function */
static struct {
struct arg_int *timeout;
struct arg_str *ssid;
struct arg_str *password;
struct arg_end *end;
} join_args;
///** Arguments used by 'join' function */
//static struct {
// struct arg_int *autoconnect;
// struct arg_end *end;
//} auto_connect_args;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
}
}
bool wait_for_wifi(){
bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
if(!connected){
ESP_LOGD(TAG,"Waiting for WiFi...");
connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
if(!connected){
ESP_LOGD(TAG,"wifi timeout.");
}
else
{
ESP_LOGI(TAG,"WiFi Connected!");
}
}
return connected;
}
static void initialise_wifi(void)
{
static bool initialized = false;
if (initialized) {
return;
}
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
ESP_ERROR_CHECK( esp_wifi_start() );
initialized = true;
}
static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
{
initialise_wifi();
wifi_config_t wifi_config = { 0 };
strncpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
if (pass) {
strncpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
}
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_connect() );
int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
return (bits & CONNECTED_BIT) != 0;
}
static int set_auto_connect(int argc, char **argv)
{
// int nerrors = arg_parse(argc, argv, (void **) &join_args);
// if (nerrors != 0) {
// arg_print_errors(stderr, join_args.end, argv[0]);
// return 1;
// }
// ESP_LOGI(__func__, "Connecting to '%s'",
// join_args.ssid->sval[0]);
//
// /* set default value*/
// if (join_args.timeout->count == 0) {
// join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
// }
//
// bool connected = wifi_join(join_args.ssid->sval[0],
// join_args.password->sval[0],
// join_args.timeout->ival[0]);
// if (!connected) {
// ESP_LOGW(__func__, "Connection timed out");
// return 1;
// }
// ESP_LOGI(__func__, "Connected");
return 0;
}
static int connect(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &join_args);
if (nerrors != 0) {
arg_print_errors(stderr, join_args.end, argv[0]);
return 1;
}
ESP_LOGI(__func__, "Connecting to '%s'",
join_args.ssid->sval[0]);
/* set default value*/
if (join_args.timeout->count == 0) {
join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
}
bool connected = wifi_join(join_args.ssid->sval[0],
join_args.password->sval[0],
join_args.timeout->ival[0]);
if (!connected) {
ESP_LOGW(__func__, "Connection timed out");
return 1;
}
ESP_LOGI(__func__, "Connected");
return 0;
}
void register_wifi_join()
{
join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
join_args.end = arg_end(2);
const esp_console_cmd_t join_cmd = {
.command = "join",
.help = "Join WiFi AP as a station",
.hint = NULL,
.func = &connect,
.argtable = &join_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
}
void register_wifi()
{
register_wifi_join();
initialise_wifi();
}

View File

@@ -0,0 +1,21 @@
/* Console example — declarations of command registration functions.
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.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Register WiFi functions
void register_wifi();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,10 @@
#
# Component Makefile
#
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
# this will take the sources in the src/ directory, compile them and link them into
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the SDK documents if you need to do this.
#
CFLAGS += -Os -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4
CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG

View File

@@ -0,0 +1,316 @@
/* Console example
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 <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "pthread.h"
#include "platform_esp32.h"
#include "cmd_decl.h"
#include "console.h"
#include "cmd_squeezelite.h"
#include "nvs_utilities.h"
pthread_t thread_console;
static void * console_thread();
void console_start();
static const char * TAG = "console";
extern char current_namespace[];
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
const char* prompt = LOG_COLOR_I "squeezelite-esp32> " LOG_RESET_COLOR;
/* Console command history can be stored to and loaded from a file.
* The easiest way to do this is to use FATFS filesystem on top of
* wear_levelling library.
*/
#define MOUNT_PATH "/data"
#define HISTORY_PATH MOUNT_PATH "/history.txt"
void run_command(char * line);
//optListStruct * getOptionByName(char * option){
// optListStruct * curOpt=&optList[0];
// while(curOpt->optName !=NULL){
// if(!strcmp(curOpt->optName, option)){
// return curOpt;
// }
// curOpt++;
// }
// return NULL;
//}
//
//static int list_options(int argc, char **argv)
//{
// nvs_handle nvs;
// esp_err_t err;
//
// err = nvs_open(current_namespace, NVS_READONLY, &nvs);
// if (err != ESP_OK) {
// return err;
// }
// //
// optListStruct * curOpt=&optList[0];
// printf("System Configuration Options.\n");
// while(curOpt->optName!=NULL){
// printf("Option: %s\n"
// " Description: %20s\n"
// " Default Value: %20s\n"
// " Current Value: ",curOpt->optName, curOpt->description, curOpt->defaultValue);
// size_t len;
// if ( (nvs_get_str(nvs, curOpt->optName, NULL, &len)) == ESP_OK) {
// char *str = (char *)malloc(len);
// if ( (nvs_get_str(nvs, curOpt->optName, str, &len)) == ESP_OK) {
// printf("%20s\n", str);
// }
// free(str);
// }
// else
// {
// if(store_nvs_value(NVS_TYPE_STR, curOpt->optName,curOpt->defaultValue, strlen(curOpt->defaultValue))==ESP_OK)
// {
// printf("%20s\n", curOpt->defaultValue);
// }
// else
// {
// printf("Error. Invalid key\n");
// }
// }
// curOpt++;
// }
// printf("\n");
// nvs_close(nvs);
// return 0;
//}
//void register_list_options(){
// const esp_console_cmd_t config_list = {
// .command = "config-list",
// .help = "Lists available configuration options.",
// .hint = NULL,
// .func = &list_options,
// .argtable = NULL
// };
//
// ESP_ERROR_CHECK( esp_console_cmd_register(&config_list) );
//
//}
void process_autoexec(){
int i=1;
char autoexec_name[21]={0};
char * autoexec_value=NULL;
int * autoexec_flag = get_nvs_value_alloc(NVS_TYPE_U8, "autoexec");
if(autoexec_flag!=NULL)
{
ESP_LOGI(TAG,"autoexec flag value found with value %d", *autoexec_flag);
printf("printf -- autoexec flag value found with value %d", *autoexec_flag);
if(*autoexec_flag == 1)
{
do {
snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i++);
ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
autoexec_value= get_nvs_value_alloc(NVS_TYPE_STR, autoexec_name);
if(autoexec_value!=NULL){
ESP_LOGI(TAG,"Running command %s = %s", autoexec_name, autoexec_value);
run_command(autoexec_value);
free(autoexec_value);
}
else
{
ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
break;
}
} while(1);
}
free(autoexec_flag);
}
else
{
ESP_LOGD(TAG,"No matching command found for name autoexec. Adding default entries");
uint8_t autoexec_dft=0;
char autoexec1_dft[]="join " CONFIG_WIFI_SSID CONFIG_WIFI_PASSWORD;
char autoexec2_dft[]="squeezelite -o \"DAC\" -b 500:2000 -d all=debug -M esp32 -r \"44100,4800\" -N \"playername.txt\"";
store_nvs_value(NVS_TYPE_U8,"autoexec",&autoexec_dft);
store_nvs_value(NVS_TYPE_U8,"autoexec1",autoexec1_dft);
store_nvs_value(NVS_TYPE_U8,"autoexec2",autoexec2_dft);
}
}
static void initialize_filesystem() {
static wl_handle_t wl_handle;
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 10,
.format_if_mount_failed = true,
.allocation_unit_size = 4096
};
esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage",
&mount_config, &wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return;
}
}
static void initialize_nvs() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
}
void initialize_console() {
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = { .baud_rate =
CONFIG_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true };
ESP_ERROR_CHECK(uart_param_config(CONFIG_CONSOLE_UART_NUM, &uart_config));
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(
uart_driver_install(CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM);
/* Initialize the console */
esp_console_config_t console_config = { .max_cmdline_args = 8,
.max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
/* Configure linenoise line completion library */
/* Enable multiline editing. If not set, long commands will scroll within
* single line.
*/
linenoiseSetMultiLine(1);
/* Tell linenoise where to get command completions and hints */
linenoiseSetCompletionCallback(&esp_console_get_completion);
linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
/* Set command history size */
linenoiseHistorySetMaxLen(100);
/* Load command history from filesystem */
linenoiseHistoryLoad(HISTORY_PATH);
}
void console_start() {
initialize_nvs();
initialize_filesystem();
initialize_console();
/* Register commands */
esp_console_register_help_command();
register_system();
register_wifi();
register_nvs();
register_squeezelite();
printf("\n"
"Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n"
"\n"
"To automatically execute lines at startup:\n"
"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n"
"\n"
"\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n****************************\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n"
"****************************\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
prompt = "squeezelite-esp32> ";
#endif //CONFIG_LOG_COLORS
}
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "console";
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_console, &attr, console_thread, NULL);
pthread_attr_destroy(&attr);
}
void run_command(char * line){
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND) {
printf("Unrecognized command\n");
} else if (err == ESP_ERR_INVALID_ARG) {
// command was empty
} else if (err == ESP_OK && ret != ESP_OK) {
printf("Command returned non-zero error code: 0x%x (%s)\n", ret,
esp_err_to_name(err));
} else if (err != ESP_OK) {
printf("Internal error: %s\n", esp_err_to_name(err));
}
}
static void * console_thread() {
process_autoexec();
/* Main loop */
while (1) {
/* Get a line using linenoise.
* The line is returned when ENTER is pressed.
*/
char* line = linenoise(prompt);
if (line == NULL) { /* Ignore empty lines */
continue;
}
/* Add the command to the history */
linenoiseHistoryAdd(line);
/* Save command history to filesystem */
linenoiseHistorySave(HISTORY_PATH);
printf("\n");
run_command(line);
/* linenoise allocates line buffer on the heap, so need to free it */
linenoiseFree(line);
}
return NULL;
}

View File

@@ -0,0 +1,18 @@
/* Console example — declarations of command registration functions.
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.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,147 @@
/* Scan Example
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 example shows how to use the All Channel Scan or Fast Scan to connect
to a Wi-Fi network.
In the Fast Scan mode, the scan will stop as soon as the first network matching
the SSID is found. In this mode, an application can set threshold for the
authentication mode and the Signal strength. Networks that do not meet the
threshold requirements will be ignored.
In the All Channel Scan mode, the scan will end only after all the channels
are scanned, and connection will start with the best network. The networks
can be sorted based on Authentication Mode or Signal Strength. The priority
for the Authentication mode is: WPA2 > WPA > WEP > Open
*/
#include "platform_esp32.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "esp_bt.h"
#include "esp_bt_device.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_log.h"
#include "esp_pthread.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "nvs_utilities.h"
#include "pthread.h"
#include "string.h"
#include "sys/socket.h"
#include <signal.h>
#include "esp_system.h"
#include <signal.h>
/*Set the SSID and Password via "make menuconfig"*/
#define DEFAULT_SSID CONFIG_WIFI_SSID
#define DEFAULT_PWD CONFIG_WIFI_PASSWORD
#if CONFIG_WIFI_ALL_CHANNEL_SCAN
#define DEFAULT_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
#elif CONFIG_WIFI_FAST_SCAN
#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
#else
#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
#endif /*CONFIG_SCAN_METHOD*/
#if CONFIG_WIFI_CONNECT_AP_BY_SIGNAL
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
#elif CONFIG_WIFI_CONNECT_AP_BY_SECURITY
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
#else
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
#endif /*CONFIG_SORT_METHOD*/
#if CONFIG_FAST_SCAN_THRESHOLD
#define DEFAULT_RSSI CONFIG_FAST_SCAN_MINIMUM_SIGNAL
#if CONFIG_EXAMPLE_OPEN
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#elif CONFIG_EXAMPLE_WEP
#define DEFAULT_AUTHMODE WIFI_AUTH_WEP
#elif CONFIG_EXAMPLE_WPA
#define DEFAULT_AUTHMODE WIFI_AUTH_WPA_PSK
#elif CONFIG_EXAMPLE_WPA2
#define DEFAULT_AUTHMODE WIFI_AUTH_WPA2_PSK
#else
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#endif
#else
#define DEFAULT_RSSI -127
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#endif /*CONFIG_FAST_SCAN_THRESHOLD*/
extern char current_namespace[];
static const char * TAG = "platform_esp32";
//static void event_handler(void* arg, esp_event_base_t event_base,
// int32_t event_id, void* event_data)
//{
// if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
// esp_wifi_connect();
// } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
// esp_wifi_connect();
// } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
// ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
// ESP_LOGI(TAG, "got ip: %s.", ip4addr_ntoa(&event->ip_info.ip));
// ESP_LOGD(TAG,"Signaling wifi connected. Locking.\n");
// pthread_mutex_lock(&wifi_connect_suspend_mutex);
// ESP_LOGD(TAG,"Signaling wifi connected. Broadcasting.\n");
// pthread_cond_broadcast(&wifi_connect_suspend_cond);
// ESP_LOGD(TAG,"Signaling wifi connected. Unlocking.\n");
// pthread_mutex_unlock(&wifi_connect_suspend_mutex);
// }
//}
//
///* Initialize Wi-Fi as sta and set scan method */
//static void wifi_scan(void)
//{
//
// tcpip_adapter_init();
// ESP_ERROR_CHECK(esp_event_loop_create_default());
//
// wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
// ESP_ERROR_CHECK(esp_wifi_init(&cfg));
//
// ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
//
// wifi_config_t wifi_config = {
// .sta = {
// .ssid = DEFAULT_SSID,
// .password = DEFAULT_PWD,
// .scan_method = DEFAULT_SCAN_METHOD,
// .sort_method = DEFAULT_SORT_METHOD,
// .threshold.rssi = DEFAULT_RSSI,
// .threshold.authmode = DEFAULT_AUTHMODE,
// },
// };
// ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
// ESP_ERROR_CHECK(esp_wifi_start());
//}
void app_main()
{
console_start();
}

View File

@@ -0,0 +1,179 @@
#include "nvs_utilities.h"
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "cmd_decl.h"
#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
extern char current_namespace[];
static const char * TAG = "platform_esp32";
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data) {
if (type == NVS_TYPE_BLOB)
return ESP_ERR_NVS_TYPE_MISMATCH;
return store_nvs_value_len(type, key, data,0);
}
esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data,
size_t data_len) {
esp_err_t err;
nvs_handle nvs;
if (type == NVS_TYPE_ANY) {
return ESP_ERR_NVS_TYPE_MISMATCH;
}
err = nvs_open(current_namespace, NVS_READWRITE, &nvs);
if (err != ESP_OK) {
return err;
}
if (type == NVS_TYPE_I8) {
err = nvs_set_i8(nvs, key, *(int8_t *) data);
} else if (type == NVS_TYPE_U8) {
err = nvs_set_u8(nvs, key, *(uint8_t *) data);
} else if (type == NVS_TYPE_I16) {
err = nvs_set_i16(nvs, key, *(int16_t *) data);
} else if (type == NVS_TYPE_U16) {
err = nvs_set_u16(nvs, key, *(uint16_t *) data);
} else if (type == NVS_TYPE_I32) {
err = nvs_set_i32(nvs, key, *(int32_t *) data);
} else if (type == NVS_TYPE_U32) {
err = nvs_set_u32(nvs, key, *(uint32_t *) data);
} else if (type == NVS_TYPE_I64) {
err = nvs_set_i64(nvs, key, *(int64_t *) data);
} else if (type == NVS_TYPE_U64) {
err = nvs_set_u64(nvs, key, *(uint64_t *) data);
} else if (type == NVS_TYPE_STR) {
err = nvs_set_str(nvs, key, data);
} else if (type == NVS_TYPE_BLOB) {
err = nvs_set_blob(nvs, key, (void *) data, data_len);
}
if (err == ESP_OK) {
err = nvs_commit(nvs);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Value stored under key '%s'", key);
}
}
nvs_close(nvs);
return err;
}
void * get_nvs_value_alloc(nvs_type_t type, const char *key) {
nvs_handle nvs;
esp_err_t err;
void * value=NULL;
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
if (err != ESP_OK) {
return value;
}
if (type == NVS_TYPE_I8) {
value=malloc(sizeof(int8_t));
err = nvs_get_i8(nvs, key, (int8_t *) value);
printf("value found = %d\n",*(int8_t *)value);
} else if (type == NVS_TYPE_U8) {
value=malloc(sizeof(uint8_t));
err = nvs_get_u8(nvs, key, (uint8_t *) value);
printf("value found = %u\n",*(uint8_t *)value);
} else if (type == NVS_TYPE_I16) {
value=malloc(sizeof(int16_t));
err = nvs_get_i16(nvs, key, (int16_t *) value);
printf("value found = %d\n",*(int16_t *)value);
} else if (type == NVS_TYPE_U16) {
value=malloc(sizeof(uint16_t));
err = nvs_get_u16(nvs, key, (uint16_t *) value);
printf("value found = %u\n",*(uint16_t *)value);
} else if (type == NVS_TYPE_I32) {
value=malloc(sizeof(int32_t));
err = nvs_get_i32(nvs, key, (int32_t *) value);
} else if (type == NVS_TYPE_U32) {
value=malloc(sizeof(uint32_t));
err = nvs_get_u32(nvs, key, (uint32_t *) value);
} else if (type == NVS_TYPE_I64) {
value=malloc(sizeof(int64_t));
err = nvs_get_i64(nvs, key, (int64_t *) value);
} else if (type == NVS_TYPE_U64) {
value=malloc(sizeof(uint64_t));
err = nvs_get_u64(nvs, key, (uint64_t *) value);
} else if (type == NVS_TYPE_STR) {
size_t len;
if ((err = nvs_get_str(nvs, key, NULL, &len)) == ESP_OK) {
value=malloc(sizeof(len+1));
err = nvs_get_str(nvs, key, value, &len);
}
} else if (type == NVS_TYPE_BLOB) {
size_t len;
if ((err = nvs_get_blob(nvs, key, NULL, &len)) == ESP_OK) {
value=malloc(sizeof(len+1));
err = nvs_get_blob(nvs, key, value, &len);
}
}
if(err!=ESP_OK){
free(value);
value=NULL;
}
nvs_close(nvs);
return value;
}
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size) {
nvs_handle nvs;
esp_err_t err;
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
if (err != ESP_OK) {
return err;
}
if (type == NVS_TYPE_I8) {
err = nvs_get_i8(nvs, key, (int8_t *) value);
} else if (type == NVS_TYPE_U8) {
err = nvs_get_u8(nvs, key, (uint8_t *) value);
} else if (type == NVS_TYPE_I16) {
err = nvs_get_i16(nvs, key, (int16_t *) value);
} else if (type == NVS_TYPE_U16) {
err = nvs_get_u16(nvs, key, (uint16_t *) value);
} else if (type == NVS_TYPE_I32) {
err = nvs_get_i32(nvs, key, (int32_t *) value);
} else if (type == NVS_TYPE_U32) {
err = nvs_get_u32(nvs, key, (uint32_t *) value);
} else if (type == NVS_TYPE_I64) {
err = nvs_get_i64(nvs, key, (int64_t *) value);
} else if (type == NVS_TYPE_U64) {
err = nvs_get_u64(nvs, key, (uint64_t *) value);
} else if (type == NVS_TYPE_STR) {
size_t len;
if ((err = nvs_get_str(nvs, key, NULL, &len)) == ESP_OK) {
if (len > buf_size) {
//ESP_LOGE("Error reading value for %s. Buffer size: %d, Value Length: %d", key, buf_size, len);
err = ESP_FAIL;
} else {
err = nvs_get_str(nvs, key, value, &len);
}
}
} else if (type == NVS_TYPE_BLOB) {
size_t len;
if ((err = nvs_get_blob(nvs, key, NULL, &len)) == ESP_OK) {
if (len > buf_size) {
//ESP_LOGE("Error reading value for %s. Buffer size: %d, Value Length: %d",
// key, buf_size, len);
err = ESP_FAIL;
} else {
err = nvs_get_blob(nvs, key, value, &len);
}
}
}
nvs_close(nvs);
return err;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "esp_err.h"
#include "nvs.h"
#ifdef __cplusplus
extern "C" {
#endif
esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, size_t data_len);
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
void * get_nvs_value_alloc(nvs_type_t type, const char *key);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,68 @@
#pragma once
#include "time.h"
#include "sys/time.h"
#include "esp_system.h"
#define PERF_MAX LONG_MAX
#define MIN_MAX_VAL(x) x==PERF_MAX?0:x
#define CURR_SAMPLE_RATE output.current_sample_rate>0?output.current_sample_rate:1
#define FRAMES_TO_MS(f) (uint32_t)f*(uint32_t)1000/(uint32_t)(CURR_SAMPLE_RATE)
#ifdef BYTES_TO_FRAME
#define BYTES_TO_MS(b) FRAMES_TO_MS(BYTES_TO_FRAME(b))
#else
#define BYTES_TO_MS(b) FRAMES_TO_MS(b/BYTES_PER_FRAME)
#endif
#define SET_MIN_MAX(val,var) var=val; if(var<min_##var) min_##var=var; if(var>max_##var) max_##var=var; count_##var++; avgtot_##var+= var
#define SET_MIN_MAX_SIZED(val,var,siz) var=val; if(var<min_##var) min_##var=var; if(var>max_##var) max_##var=var; count_##var++; avgtot_##var+= var;size_##var=siz
#define RESET_MIN_MAX(var) min_##var=PERF_MAX; max_##var=0; avgtot_##var=0;count_##var=0;var=0;size_##var=0
#define RESET_MIN_MAX_DURATION(var) min_##var=PERF_MAX; max_##var=0; avgtot_##var=0;count_##var=0;var=0
#define DECLARE_MIN_MAX(var) static uint32_t min_##var = PERF_MAX, max_##var = 0, size_##var = 0,avgtot_##var = 0, count_##var=0; uint32_t var=0
#define DECLARE_MIN_MAX_DURATION(var) static uint32_t min_##var = PERF_MAX, max_##var = 0, avgtot_##var = 0, count_##var=0; uint32_t var=0
#define LINE_MIN_MAX_AVG(var) (count_##var>0?avgtot_##var/count_##var:0)
#define LINE_MIN_MAX_FORMAT_HEAD1 " +----------+----------+----------------+-----+----------------+"
#define LINE_MIN_MAX_FORMAT_HEAD2 " | max | min | average | | count |"
#define LINE_MIN_MAX_FORMAT_HEAD3 " | (bytes) | (bytes) | (bytes) | | |"
#define LINE_MIN_MAX_FORMAT_HEAD4 " +----------+----------+----------------+-----+----------------+"
#define LINE_MIN_MAX_FORMAT_FOOTER " +----------+----------+----------------+-----+----------------+"
#define LINE_MIN_MAX_FORMAT "%14s|%10u|%10u|%16u|%5u|%16u|"
#define LINE_MIN_MAX(name,var) name,\
MIN_MAX_VAL(max_##var),\
MIN_MAX_VAL(min_##var),\
LINE_MIN_MAX_AVG(var),\
size_##var!=0?100*LINE_MIN_MAX_AVG(var)/size_##var:0,\
count_##var
#define LINE_MIN_MAX_FORMAT_STREAM "%14s|%10u|%10u|%16u|%5u|%16u|"
#define LINE_MIN_MAX_STREAM(name,var) name,\
MIN_MAX_VAL(max_##var),\
MIN_MAX_VAL(min_##var),\
LINE_MIN_MAX_AVG(var),\
size_##var!=0?100*LINE_MIN_MAX_AVG(var)/size_##var:0,\
count_##var
#define LINE_MIN_MAX_DURATION_FORMAT "%14s%10u|%10u|%11u|%11u|"
#define LINE_MIN_MAX_DURATION(name,var) name,MIN_MAX_VAL(max_##var),MIN_MAX_VAL(min_##var), LINE_MIN_MAX_AVG(var), count_##var
#define TIME_MEASUREMENT_START(x) x=esp_timer_get_time()
#define TIME_MEASUREMENT_GET(x) (esp_timer_get_time()-x)
#define TIMED_SECTION_START_MS_FORCE(x,force) if(hasTimeElapsed(x,force)) {
#define TIMED_SECTION_START_MS(x) if(hasTimeElapsed(x,false)){
#define TIMED_SECTION_START_FORCE(x,force) TIMED_SECTION_START_MS(x * 1000UL,force)
#define TIMED_SECTION_START(x) TIMED_SECTION_START_MS(x * 1000UL)
#define TIMED_SECTION_END }
static inline bool hasTimeElapsed(time_t delayMS, bool bforce)
{
static time_t lastTime=0;
struct timeval tv;
gettimeofday(&tv, NULL);
if (lastTime <= tv.tv_sec * 1000 + tv.tv_usec / 1000 ||bforce)
{
lastTime = tv.tv_sec * 1000 + tv.tv_usec / 1000 + delayMS;
return true;
}
else
return false;
}

View File

@@ -0,0 +1,954 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "esp_log.h"
#include "esp_system.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "esp_bt.h"
#include "esp_bt_device.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_console.h"
#include "esp_pthread.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "nvs_utilities.h"
#include "pthread.h"
#include "string.h"
//#include "esp_event.h"
#include "sys/socket.h"
#include <signal.h>
#include <signal.h>
#include "platform_esp32.h"
#include "../../main/squeezelite.h"
#include "argtable3/argtable3.h"
#define STATS_REPORT_DELAY_MS 15000
static const char * TAG = "platform_esp32";
extern char * get_output_state_desc(output_state state);
extern struct outputstate output;
extern struct buffer *outputbuf;
extern struct buffer *streambuf;
extern uint8_t * btout;
time_t disconnect_time=0;
#define LOCK_S pthread_mutex_lock(&(streambuf->mutex))
#define UNLOCK_S pthread_mutex_unlock(&(streambuf->mutex))
#define LOCK pthread_mutex_lock(&(outputbuf->mutex))
#define UNLOCK pthread_mutex_unlock(&(outputbuf->mutex))
int64_t connecting_timeout = 0;
static const char * art_a2dp_connected[]={"\n",
" ___ _____ _____ _____ _ _ _ ",
" /\\ |__ \\| __ \\| __ \\ / ____| | | | | |",
" / \\ ) | | | | |__) | | | ___ _ __ _ __ ___ ___| |_ ___ __| | |",
" / /\\ \\ / /| | | | ___/ | | / _ \\| '_ \\| '_ \\ / _ \\/ __| __/ _ \\/ _` | |",
" / ____ \\ / /_| |__| | | | |___| (_) | | | | | | | __/ (__| || __/ (_| |_|",
" /_/ \\_\\____|_____/|_| \\_____\\___/|_| |_|_| |_|\\___|\\___|\\__\\___|\\__,_(_)\n",
"\0"};
static const char * art_a2dp_connecting[]= {"\n",
" ___ _____ _____ _____ _ _ ",
" /\\ |__ \\| __ \\| __ \\ / ____| | | (_) ",
" / \\ ) | | | | |__) | | | ___ _ __ _ __ ___ ___| |_ _ _ __ __ _ ",
" / /\\ \\ / /| | | | ___/ | | / _ \\| '_ \\| '_ \\ / _ \\/ __| __| | '_ \\ / _` | ",
" / ____ \\ / /_| |__| | | | |___| (_) | | | | | | | __/ (__| |_| | | | | (_| |_ _ _ ",
" /_/ \\_\\____|_____/|_| \\_____\\___/|_| |_|_| |_|\\___|\\___|\\__|_|_| |_|\\__, (_|_|_)",
" __/ | ",
" |___/ \n",
"\0"};
static void bt_app_av_state_connecting(uint16_t event, void *param);
#define A2DP_TIMER_INIT connecting_timeout = esp_timer_get_time() +(CONFIG_A2DP_CONNECT_TIMEOUT_MS * 1000)
#define IS_A2DP_TIMER_OVER esp_timer_get_time() >= connecting_timeout
#define FRAME_TO_BYTES(f) f*BYTES_PER_FRAME
#define BYTES_TO_FRAME(b) b/BYTES_PER_FRAME
#define RESET_ALL_MIN_MAX RESET_MIN_MAX(req); RESET_MIN_MAX(rec); RESET_MIN_MAX(bt);RESET_MIN_MAX(under); RESET_MIN_MAX_DURATION(stream_buf); RESET_MIN_MAX_DURATION(lock_out_time)
DECLARE_MIN_MAX(stream_buf);
DECLARE_MIN_MAX(req);
DECLARE_MIN_MAX(rec);
DECLARE_MIN_MAX(bt);
DECLARE_MIN_MAX(under);
DECLARE_MIN_MAX_DURATION(lock_out_time);
static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param);
void get_mac(u8_t mac[]) {
esp_read_mac(mac, ESP_MAC_WIFI_STA);
}
_sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}
void *audio_calloc(size_t nmemb, size_t size) {
return calloc(nmemb, size);
}
/* 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,
};
char * APP_AV_STATE_DESC[] = {
"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_BUFFERING,
APP_AV_MEDIA_STATE_STARTED,
APP_AV_MEDIA_STATE_STOPPING,
APP_AV_MEDIA_STATE_WAIT_DISCONNECT
};
#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 void a2d_app_heart_beat(void *arg);
/// A2DP application state machine
static void bt_app_av_sm_hdlr(uint16_t event, void *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 uint32_t s_pkt_cnt = 0;
static TimerHandle_t s_tmr;
static struct {
struct arg_str *sink_name;
struct arg_int *control_delay;
struct arg_int *connect_timeout_delay;
struct arg_end *end;
} squeezelite_args;
void hal_bluetooth_init(const char * options)
{
ESP_LOGD(TAG,"Initializing Bluetooth HAL");
//CONFIG_A2DP_SINK_NAME
//CONFIG_A2DP_CONTROL_DELAY_MS
//CONFIG_A2DP_CONNECT_TIMEOUT_MS
squeezelite_args.sink_name = arg_str1("n", "name", "<sink name>", "the name of the bluetooth to connect to");
squeezelite_args.control_delay = arg_int0("d", "delay", "<control delay>", "the delay between each pass at the A2DP control loop");
squeezelite_args.connect_timeout_delay = arg_int0("t","timeout", "<timeout>", "the timeout duration for connecting to the A2DP sink");
squeezelite_args.end = arg_end(2);
ESP_LOGD(TAG,"Copying parameters");
char * opts = strdup(options);
char **argv = malloc(sizeof(char**)*15);
size_t argv_size=15;
// change parms so ' appear as " for parsing the options
for (char* p = opts; (p = strchr(p, '\'')); ++p) *p = '"';
ESP_LOGD(TAG,"Splitting arg line: %s", opts);
argv_size = esp_console_split_argv(opts, argv, argv_size);
ESP_LOGD(TAG,"Parsing parameters");
int nerrors = arg_parse(argv_size , argv, (void **) &squeezelite_args);
if (nerrors != 0) {
ESP_LOGD(TAG,"Parsing Errors");
arg_print_errors(stdout, squeezelite_args.end, "BT");
arg_print_glossary_gnu(stdout, (void **) &squeezelite_args);
free(opts);
free(argv);
return;
}
if(squeezelite_args.sink_name->count == 0)
{
ESP_LOGD(TAG,"Using default sink name : %s",CONFIG_A2DP_SINK_NAME);
squeezelite_args.sink_name->sval[0] = CONFIG_A2DP_SINK_NAME;
}
if(squeezelite_args.connect_timeout_delay->count == 0)
{
ESP_LOGD(TAG,"Using default connect timeout");
squeezelite_args.connect_timeout_delay->ival[0]=CONFIG_A2DP_CONNECT_TIMEOUT_MS;
}
if(squeezelite_args.control_delay->count == 0)
{
ESP_LOGD(TAG,"Using default control delay");
squeezelite_args.control_delay->ival[0]=CONFIG_A2DP_CONTROL_DELAY_MS;
}
ESP_LOGD(TAG,"Freeing options");
free(argv);
free(opts);
/*
* Bluetooth audio source init Start
*/
//running_test = false;
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(TAG,"%s initialize controller failed\n", __func__);
return;
}
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
ESP_LOGE(TAG,"%s enable controller failed\n", __func__);
return;
}
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(TAG,"%s initialize bluedroid failed\n", __func__);
return;
}
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(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 int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
{
int32_t avail_data=0,wanted_len=0, start_timer=0;
if (len < 0 || data == NULL ) {
return 0;
}
btout=data;
// This is how the BTC layer calculates the number of bytes to
// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
wanted_len=len;
SET_MIN_MAX(len,req);
TIME_MEASUREMENT_START(start_timer);
LOCK;
SET_MIN_MAX_SIZED(_buf_used(outputbuf),bt,outputbuf->size);
do {
avail_data = _output_frames( wanted_len/BYTES_PER_FRAME )*BYTES_PER_FRAME; // Keep the transfer buffer full
wanted_len-=avail_data;
} while (wanted_len > 0 && avail_data != 0);
if(wanted_len>0)
{
SET_MIN_MAX(wanted_len, under);
}
output.device_frames = 0; // todo: check if this is the right way do to this.
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
UNLOCK;
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
SET_MIN_MAX((len-wanted_len), rec);
TIME_MEASUREMENT_START(start_timer);
output_bt_check_buffer();
return len-wanted_len;
}
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);
}
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)
{
ESP_LOGI(TAG,"Discovery completed. Ready to start connecting to %s. ",s_peer_bdname);
s_a2d_state = APP_AV_STATE_UNCONNECTED;
}
else
{
// not discovered, continue to discover
ESP_LOGI(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(TAG,"Discovery started.");
}
else
{
ESP_LOGD(TAG,"This shouldn't happen. Discovery has only 2 states (for now).");
}
break;
}
case ESP_BT_GAP_RMT_SRVCS_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_BT_GAP_RMT_SRVCS_EVT);
break;
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
ESP_LOG_DEBUG_EVENT(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(TAG,"authentication success: %s", param->auth_cmpl.device_name);
//esp_log_buffer_hex(param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
} else {
ESP_LOGE(TAG,"authentication failed, status:%d", param->auth_cmpl.stat);
}
break;
}
case ESP_BT_GAP_PIN_REQ_EVT: {
ESP_LOGI(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(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(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(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(TAG,"ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
break;
ESP_LOGI(TAG,"ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
break;
#endif
default: {
ESP_LOGI(TAG,"event: %d", event);
break;
}
}
return;
}
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_LOGD(TAG,"%s state %s, evt 0x%x, output state: %d", __func__, APP_AV_STATE_DESC[s_a2d_state], event, output.state);
switch (s_a2d_state) {
case APP_AV_STATE_DISCOVERING:
ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", APP_AV_STATE_DESC[s_a2d_state], event, get_output_state_desc(output.state));
break;
case APP_AV_STATE_DISCOVERED:
ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", APP_AV_STATE_DESC[s_a2d_state], event, get_output_state_desc(output.state));
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(TAG,"%s invalid state %d", __func__, s_a2d_state);
break;
}
}
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 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;
uint8_t nameLen = 0;
esp_bt_gap_dev_prop_t *p;
memset(s_peer_bdname, 0x00,sizeof(s_peer_bdname));
ESP_LOGI(TAG,"\n=======================\nScanned 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(TAG,"-- Class of Device: 0x%x", cod);
break;
case ESP_BT_GAP_DEV_PROP_RSSI:
rssi = *(int8_t *)(p->val);
ESP_LOGI(TAG,"-- RSSI: %d", rssi);
break;
case ESP_BT_GAP_DEV_PROP_EIR:
eir = (uint8_t *)(p->val);
ESP_LOGI(TAG,"-- EIR: %u", *eir);
break;
case ESP_BT_GAP_DEV_PROP_BDNAME:
nameLen = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN : (uint8_t)p->len;
memcpy(s_peer_bdname, (uint8_t *)(p->val), nameLen);
s_peer_bdname[nameLen] = '\0';
ESP_LOGI(TAG,"-- Name: %s", s_peer_bdname);
break;
default:
break;
}
}
if (!esp_bt_gap_is_valid_cod(cod)){
/* search for device with MAJOR service class as "rendering" in COD */
ESP_LOGI(TAG,"--Invalid class of device. Skipping.\n");
return;
}
else if (!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING))
{
ESP_LOGI(TAG,"--Not a rendering device. Skipping.\n");
return;
}
/* search for device named "ESP_SPEAKER" in its extended inqury response */
if (eir) {
ESP_LOGI(TAG,"--Getting details from eir.\n");
get_name_from_eir(eir, s_peer_bdname, NULL);
ESP_LOGI(TAG,"--Device name is %s\n",s_peer_bdname);
}
if (strcmp((char *)s_peer_bdname, CONFIG_A2DP_SINK_NAME) == 0) {
ESP_LOGI(TAG,"Found a target device! address %s, name %s", bda_str, s_peer_bdname);
ESP_LOGI(TAG,"=======================\n");
if(esp_bt_gap_cancel_discovery()!=ESP_ERR_INVALID_STATE)
{
ESP_LOGI(TAG,"Cancel device discovery ...");
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
s_a2d_state = APP_AV_STATE_DISCOVERED;
}
else
{
ESP_LOGE(TAG,"Cancel device discovery failed...");
}
}
else
{
ESP_LOGI(TAG,"Not the device we are looking for. Continuing scan.");
}
}
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
{
switch (event) {
case BT_APP_EVT_STACK_UP: {
ESP_LOGI(TAG,"BT Stack going up.");
/* set up device name */
char *dev_name = CONFIG_A2DP_DEV_NAME;
esp_bt_dev_set_device_name(dev_name);
ESP_LOGI(TAG,"Preparing to connect to device: %s",CONFIG_A2DP_SINK_NAME);
/* register GAP callback function */
esp_bt_gap_register_callback(bt_app_gap_cb);
/* 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(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", (CONFIG_A2DP_CONTROL_DELAY_MS / portTICK_RATE_MS),
pdTRUE, (void *)tmr_id, a2d_app_heart_beat);
xTimerStart(s_tmr, portMAX_DELAY);
} while (0);
break;
}
default:
ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event);
break;
}
}
#ifdef BTAUDIO
bool test_open(const char *device, unsigned rates[], bool userdef_rates) {
// running_test = true;
// while(running_test)
// {
// // wait until BT playback has started
// // this will allow querying the sample rate
// usleep(100000);
// }
memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
if (!strcmp(device, "BT")) {
rates[0] = 44100;
} else {
unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 };
memcpy(rates, _rates, sizeof(_rates));
}
return true;
}
#endif
static void bt_app_av_media_proc(uint16_t event, void *param)
{
esp_a2d_cb_param_t *a2d = NULL;
LOCK;
output_state out_state=output.state;
UNLOCK;
switch (s_media_state) {
case APP_AV_MEDIA_STATE_IDLE: {
if (event == BT_APP_HEART_BEAT_EVT) {
if(out_state > OUTPUT_STOPPED)
{
ESP_LOGI(TAG,"Output state is %s, a2dp media ready and connected. Checking if A2DP is ready.", get_output_state_desc(out_state));
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(TAG,"a2dp media ready, waiting for media buffering ...");
s_media_state = APP_AV_MEDIA_STATE_BUFFERING;
}
}
break;
}
case APP_AV_MEDIA_STATE_BUFFERING: {
if (event == BT_APP_HEART_BEAT_EVT) {
switch (out_state) {
case OUTPUT_RUNNING :
case OUTPUT_PAUSE_FRAMES :
case OUTPUT_SKIP_FRAMES:
case OUTPUT_START_AT:
case OUTPUT_BUFFER:
// Buffer is ready, local buffer has some data, start playback!
ESP_LOGI(TAG,"Out state is %s, a2dp media ready and connected. Starting playback! ", get_output_state_desc(out_state));
s_media_state = APP_AV_MEDIA_STATE_STARTING;
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
break;
case OUTPUT_STOPPED:
case OUTPUT_OFF:
ESP_LOGD(TAG,"Output state is %s. Changing app status to ",get_output_state_desc(out_state));
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
break;
default:
ESP_LOGE(TAG,"Unknown output status while waiting for buffering to complete %d",out_state);
break;
}
}
else{
ESP_LOGW(TAG,"Received unknown event while in state APP_AV_MEDIA_STATE_BUFFERING");
}
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(TAG,"a2dp media started successfully.");
s_media_state = APP_AV_MEDIA_STATE_STARTED;
} else {
// not started succesfully, transfer to idle state
ESP_LOGI(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(out_state <= OUTPUT_STOPPED) {
ESP_LOGI(TAG,"Output state is stopped. Stopping a2dp media ...");
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
}
else
{
LOCK_S;
SET_MIN_MAX_SIZED(_buf_used(streambuf),stream_buf,streambuf->size);
UNLOCK_S;
static time_t lastTime=0;
if (lastTime <= gettime_ms() )
{
lastTime = gettime_ms() + 15000;
ESP_LOGD(TAG, "Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);
ESP_LOGD(TAG, " +==========+==========+================+=====+================+");
ESP_LOGD(TAG, " | max | min | average | avg | count |");
ESP_LOGD(TAG, " | (bytes) | (bytes) | (bytes) | pct | |");
ESP_LOGD(TAG, " +==========+==========+================+=====+================+");
ESP_LOGD(TAG,LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("stream avl",stream_buf));
ESP_LOGD(TAG,LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output avl",bt));
ESP_LOGD(TAG,LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
ESP_LOGD(TAG,LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
ESP_LOGD(TAG,LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("underrun",under));
ESP_LOGD(TAG, " +==========+==========+================+=====+================+");
ESP_LOGD(TAG,"\n");
ESP_LOGD(TAG," ==========+==========+===========+===========+ ");
ESP_LOGD(TAG," max (us) | min (us) | avg(us) | count | ");
ESP_LOGD(TAG," ==========+==========+===========+===========+ ");
ESP_LOGD(TAG,LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Out Buf Lock",lock_out_time));
ESP_LOGD(TAG," ==========+==========+===========+===========+");
RESET_ALL_MIN_MAX;
}
}
}
break;
}
case APP_AV_MEDIA_STATE_STOPPING: {
ESP_LOG_DEBUG_EVENT(TAG,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(TAG,"a2dp media stopped successfully...");
//s_media_state = APP_AV_MEDIA_STATE_WAIT_DISCONNECT;
// if(CONFIG_A2DP_DISCONNECT_MS==0){
// we're not going to disconnect.
s_media_state = APP_AV_MEDIA_STATE_IDLE;
// }
// else
// {
// disconnect_time = gettime_ms()+CONFIG_A2DP_DISCONNECT_MS;
// s_media_state = APP_AV_MEDIA_STATE_WAIT_DISCONNECT;
// }
} else {
ESP_LOGI(TAG,"a2dp media stopping...");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
}
}
break;
}
case APP_AV_MEDIA_STATE_WAIT_DISCONNECT:{
if(gettime_ms()>disconnect_time){
// we've reached timeout
esp_a2d_source_disconnect(s_peer_bda);
s_a2d_state = APP_AV_STATE_DISCONNECTING;
}
}
}
}
static void bt_app_av_state_unconnected(uint16_t event, void *param)
{
// LOCK;
// output_state out_state= output.state;
// UNLOCK;
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_CONNECTION_STATE_EVT);
// this could happen if connection was established
// right after we timed out. Pass the call down to the connecting
// handler.
esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
bt_app_av_state_connecting(event, param);
}
break;
case ESP_A2D_AUDIO_STATE_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_STATE_EVT);
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_CFG_EVT);
break;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_MEDIA_CTRL_ACK_EVT);
break;
case BT_APP_HEART_BEAT_EVT: {
// uint8_t *p = s_peer_bda;
// ESP_LOGI(TAG,"BT_APP_HEART_BEAT_EVT a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",p[0], p[1], p[2], p[3], p[4], p[5]);
switch (esp_bluedroid_get_status()) {
case ESP_BLUEDROID_STATUS_UNINITIALIZED:
ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_UNINITIALIZED.");
break;
case ESP_BLUEDROID_STATUS_INITIALIZED:
ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_INITIALIZED.");
break;
case ESP_BLUEDROID_STATUS_ENABLED:
ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_ENABLED.");
break;
default:
break;
}
// if(out_state > OUTPUT_STOPPED){
// only attempt a connect when playback isn't stopped
if(esp_a2d_source_connect(s_peer_bda)==ESP_OK) {
s_a2d_state = APP_AV_STATE_CONNECTING;
for(uint8_t l=0;art_a2dp_connecting[l][0]!='\0';l++){
ESP_LOGI(TAG,"%s",art_a2dp_connecting[l]);
}
ESP_LOGI(TAG,"********** A2DP CONNECTING TO %s", s_peer_bdname);
A2DP_TIMER_INIT;
}
else {
// there was an issue connecting... continue to discover
ESP_LOGE(TAG,"Attempt at connecting failed, restart at discover...");
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
// }
}
break;
}
default:
ESP_LOGE(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) {
s_a2d_state = APP_AV_STATE_CONNECTED;
s_media_state = APP_AV_MEDIA_STATE_IDLE;
for(uint8_t l=0;art_a2dp_connected[l][0]!='\0';l++){
ESP_LOGI(TAG,"%s",art_a2dp_connected[l]);
}
ESP_LOGD(TAG,"Setting scan mode to ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE");
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
ESP_LOGD(TAG,"Done setting scan mode. App state is now CONNECTED and media state IDLE.");
} 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:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_STATE_EVT);
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_CFG_EVT);
break;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_MEDIA_CTRL_ACK_EVT);
break;
case BT_APP_HEART_BEAT_EVT:
if (IS_A2DP_TIMER_OVER)
{
s_a2d_state = APP_AV_STATE_UNCONNECTED;
ESP_LOGE(TAG,"A2DP Connect time out! Setting state to Unconnected. ");
A2DP_TIMER_INIT;
}
ESP_LOGV(TAG,"BT_APP_HEART_BEAT_EVT");
break;
default:
ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event);
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(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: {
ESP_LOG_DEBUG_EVENT(TAG,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
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_CFG_EVT);
break;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:{
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_MEDIA_CTRL_ACK_EVT);
bt_app_av_media_proc(event, param);
break;
}
case BT_APP_HEART_BEAT_EVT: {
ESP_LOG_DEBUG_EVENT(TAG,BT_APP_HEART_BEAT_EVT);
bt_app_av_media_proc(event, param);
break;
}
default:
ESP_LOGE(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: {
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_CONNECTION_STATE_EVT);
a2d = (esp_a2d_cb_param_t *)(param);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(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:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_STATE_EVT);
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_AUDIO_CFG_EVT);
break;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
ESP_LOG_DEBUG_EVENT(TAG,ESP_A2D_MEDIA_CTRL_ACK_EVT);
break;
case BT_APP_HEART_BEAT_EVT:
ESP_LOG_DEBUG_EVENT(TAG,BT_APP_HEART_BEAT_EVT);
break;
default:
ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event);
break;
}
}

View File

@@ -0,0 +1,165 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "bt_app_core.h"
#include "perf_trace.h"
#include "esp_pthread.h"
#ifndef QUOTE
#define QUOTE(name) #name
#define STR(macro) QUOTE(macro)
#endif
extern void run_command(char * line);
extern bool wait_for_wifi();
//typedef struct {
// char opt_slimproto_logging[11];
// char opt_stream_logging[11];
// char opt_decode_logging[11];
// char opt_output_logging[11];
// char opt_player_name[11];
// char opt_output_rates[21];
// char opt_buffer[11];
//} str_squeezelite_options ;
extern void console_start();
extern pthread_cond_t wifi_connect_suspend_cond;
extern pthread_t wifi_connect_suspend_mutex;
//static const char * art_wifi[]={
// "\n",
// "o `O ooOoOOo OOooOoO ooOoOOo\n",
// "O o O o O \n",
// "o O o O o \n",
// "O O O oOooO O \n",
// "o o o o O o \n",
// "O O O O o O \n",
// "`o O o O' O o O \n",
// " `OoO' `OoO' ooOOoOo O' ooOOoOo\n",
// "\n",
// ""
//};
//static const char * art_wifi_connecting[]={
// " .oOOOo.",
// ".O o o \n",
// "o O \n",
// "o oOo \n",
// "o .oOo. 'OoOo. 'OoOo. .oOo. .oOo o O 'OoOo. .oOoO \n",
// "O O o o O o O OooO' O O o o O o O \n",
// "`o .o o O O o O o O o o O O o O o \n",
// " `OoooO' `OoO' o O o O `OoO' `OoO' `oO o' o O `OoOo \n",
// " O \n",
// " OoO' \n",
// "\n",
// ""
//};
//static const char * art_wifi_connected[]={
// " .oOOOo. o oO\n",
// ".O o O OO\n",
// "o O o oO\n",
// "o oOo o Oo\n",
// "o .oOo. 'OoOo. 'OoOo. .oOo. .oOo o .oOo. .oOoO oO\n",
// "O O o o O o O OooO' O O OooO' o O \n",
// "`o .o o O O o O o O o o O O o Oo\n",
// " `OoooO' `OoO' o O o O `OoO' `OoO' `oO `OoO' `OoO'o oO\n",
// "\n",
// ""
//};
#define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " QUOTE(e))
typedef struct {
char * optName;
char * cmdLinePrefix;
char * description;
char * defaultValue;
char * relatedcommand;
} optListStruct;
optListStruct * getOptionByName(char * option);
//static optListStruct optList[] = {
// {
// .optName= "log_slimproto",
// .cmdLinePrefix="-d slimproto=",
// .description="Slimproto Logging Level info|debug|sdebug",
// .defaultValue=(CONFIG_LOGGING_SLIMPROTO),
// .relatedcommand="squeezelite"
// },
// {
// .optName="log_stream",
// .cmdLinePrefix="-d stream=",
// .description="Stream Logging Level info|debug|sdebug",
// .defaultValue=(CONFIG_LOGGING_STREAM),
// .relatedcommand="squeezelite"
// },
// {
// .optName="log_decode",
// .cmdLinePrefix="-d decode=",
// .description="Decode Logging Level info|debug|sdebug",
// .defaultValue=(CONFIG_LOGGING_DECODE),
// .relatedcommand="squeezelite"
// },
// {
// .optName="log_output",
// .cmdLinePrefix="-d output=",
// .description="Output Logging Level info|debug|sdebug",
// .defaultValue=(CONFIG_LOGGING_OUTPUT),
// .relatedcommand="squeezelite"
// },
// {
// .optName="output_rates",
// .cmdLinePrefix="-r ",
// .description="Supported rates",
// .defaultValue=(CONFIG_OUTPUT_RATES),
// .relatedcommand="squeezelite"
// },
// {
// .optName="output_dev",
// .cmdLinePrefix="-O",
// .description="Output device to use. BT for Bluetooth, DAC for i2s DAC.",
// .defaultValue=(CONFIG_A2DP_SINK_NAME),
// .relatedcommand=""
// },
// {
// .optName="a2dp_sink_name",
// .cmdLinePrefix="",
// .description="Bluetooth sink name to connect to.",
// .defaultValue=(CONFIG_A2DP_SINK_NAME),
// .relatedcommand=""
// },
// {
// .optName="a2dp_dev_name",
// .cmdLinePrefix="",
// .description="A2DP Device name to use when connecting to audio sink.",
// .defaultValue=(CONFIG_A2DP_DEV_NAME),
// .relatedcommand=""
// },
// {
// .optName="a2dp_cntrldelay",
// .cmdLinePrefix="",
// .description="Delay (ms) for each pass of the A2DP control loop.",
// .defaultValue=STR(CONFIG_A2DP_CONTROL_DELAY_MS),
// .relatedcommand=""
// },
// {
// .optName="a2dp_timeout",
// .cmdLinePrefix="",
// .description="Delay (ms) for A2DP timeout on connect.",
// .defaultValue=STR(CONFIG_A2DP_CONNECT_TIMEOUT_MS),
// .relatedcommand=""
// },
// {
// .optName="wifi_ssid",
// .cmdLinePrefix="",
// .description="WiFi access point name to connect to.",
// .defaultValue= (CONFIG_WIFI_SSID),
// .relatedcommand=""
// },
// {
// .optName="wifi_password",
// .cmdLinePrefix= "",
// .description="WiFi access point password.",
// .defaultValue=(CONFIG_WIFI_PASSWORD),
// .relatedcommand=""
// },
// {}
//};
#ifdef __cplusplus
}
#endif