Initial sanitized SmartSpeaker snapshot
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Parse wifi-credentials from the project root directory at configure-time
|
||||
set(PASSWORD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../wifi-credentials")
|
||||
if(EXISTS "${PASSWORD_FILE}")
|
||||
file(READ "${PASSWORD_FILE}" PASSWORD_CONTENT)
|
||||
string(REGEX MATCH "SSID=([^\r\n]*)" _ "${PASSWORD_CONTENT}")
|
||||
set(WIFI_SSID "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "PASSWORD=([^\r\n]*)" _ "${PASSWORD_CONTENT}")
|
||||
set(WIFI_PASSWORD "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
set(WIFI_SSID "YOUR_SSID")
|
||||
set(WIFI_PASSWORD "YOUR_PASSWORD")
|
||||
endif()
|
||||
|
||||
# Generate wifi_credentials.h inside the build directory so credentials aren't checked into Git
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/wifi_credentials.h"
|
||||
"// Generated by CMake. Do not edit manually.\n\
|
||||
#define WIFI_SSID \"${WIFI_SSID}\"\n\
|
||||
#define WIFI_PASSWORD \"${WIFI_PASSWORD}\"\n"
|
||||
)
|
||||
|
||||
# Parse device model configuration from the project root directory at configure-time
|
||||
set(CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../device-config")
|
||||
if(EXISTS "${CONFIG_FILE}")
|
||||
file(READ "${CONFIG_FILE}" CONFIG_CONTENT)
|
||||
string(REGEX MATCH "MODEL=([^\r\n]*)" _ "${CONFIG_CONTENT}")
|
||||
set(DEVICE_MODEL "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
set(DEVICE_MODEL "DEV-1")
|
||||
endif()
|
||||
|
||||
# Generate device_config.h inside the build directory
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/device_config.h"
|
||||
"// Generated by CMake. Do not edit manually.\n\
|
||||
#define DEVICE_MODEL \"${DEVICE_MODEL}\"\n"
|
||||
)
|
||||
|
||||
# Register all project source files
|
||||
set(SOURCES
|
||||
"main.c"
|
||||
"wifi_connect.c"
|
||||
"socket_client.c"
|
||||
"state_manager.c"
|
||||
"ota_manager.c"
|
||||
"hardeware_driver/bsp_board.c"
|
||||
"audio_play_driver/audio_driver.c"
|
||||
"tca9555_driver/tca9555_driver.c"
|
||||
)
|
||||
|
||||
# Set include directories, including the generated binary directory
|
||||
set(INCLUDE_DIRS
|
||||
"."
|
||||
"hardeware_driver"
|
||||
"audio_play_driver"
|
||||
"tca9555_driver"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
# Register the main component with dependencies
|
||||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS ${INCLUDE_DIRS}
|
||||
PRIV_REQUIRES esp_driver_i2s esp_driver_i2c spi_flash esp_audio_simple_player esp_io_expander_tca95xx_16bit fatfs sdmmc spiffs led_strip esp_wifi nvs_flash esp_event esp_websocket_client esp-tls esp_https_ota app_update esp_driver_ledc esp_adc
|
||||
)
|
||||
|
||||
# Add compile definition based on model to the component library target
|
||||
if(DEVICE_MODEL STREQUAL "PROTO-1")
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE MODEL_PROTO_1=1)
|
||||
else()
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE MODEL_DEV_1=1)
|
||||
endif()
|
||||
|
||||
# Package and build the spiffs partition containing startup.mp3
|
||||
spiffs_create_partition_image(model spiffs FLASH_IN_PROJECT)
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "audio_driver.h"
|
||||
|
||||
#include "tca9555_driver.h"
|
||||
#include "bsp_board.h"
|
||||
|
||||
static const char *TAG = "audio play";
|
||||
|
||||
|
||||
static uint8_t Volume = PLAYER_VOLUME;
|
||||
static esp_asp_handle_t handle = NULL;
|
||||
|
||||
static void Audio_PA_EN(void)
|
||||
{
|
||||
Set_EXIO(IO_EXPANDER_PIN_NUM_8,true);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
static void Audio_PA_DIS(void)
|
||||
{
|
||||
Set_EXIO(IO_EXPANDER_PIN_NUM_8,false);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
static int out_data_callback(uint8_t *data, int data_size, void *ctx)
|
||||
{
|
||||
esp_audio_play((int16_t*)data,data_size,500 / portTICK_PERIOD_MS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int in_data_callback(uint8_t *data, int data_size, void *ctx)
|
||||
{
|
||||
int ret = fread(data, 1, data_size, ctx);
|
||||
ESP_LOGD(TAG, "%s-%d,rd size:%d", __func__, __LINE__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mock_event_callback(esp_asp_event_pkt_t *event, void *ctx)
|
||||
{
|
||||
if (event->type == ESP_ASP_EVENT_TYPE_MUSIC_INFO)
|
||||
{
|
||||
esp_asp_music_info_t info = {0};
|
||||
memcpy(&info, event->payload, event->payload_size);
|
||||
ESP_LOGI(TAG, "Get info, rate:%d, channels:%d, bits:%d ,bitrate = %d", info.sample_rate, info.channels, info.bits,info.bitrate);
|
||||
}
|
||||
else if (event->type == ESP_ASP_EVENT_TYPE_STATE)
|
||||
{
|
||||
esp_asp_state_t st = 0;
|
||||
memcpy(&st, event->payload, event->payload_size);
|
||||
|
||||
ESP_LOGI(TAG, "Get State, %d,%s", st, esp_audio_simple_player_state_to_str(st));
|
||||
/*if(st == ESP_ASP_STATE_FINISHED)
|
||||
{
|
||||
|
||||
}*/
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void pipeline_init(void)
|
||||
{
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
|
||||
esp_asp_cfg_t cfg = {
|
||||
.in.cb = NULL,
|
||||
.in.user_ctx = NULL,
|
||||
.out.cb = out_data_callback,
|
||||
.out.user_ctx = NULL,
|
||||
};
|
||||
|
||||
esp_gmf_err_t err = esp_audio_simple_player_new(&cfg, &handle);
|
||||
err = esp_audio_simple_player_set_event(handle, mock_event_callback, NULL);
|
||||
}
|
||||
|
||||
|
||||
esp_gmf_err_t Audio_Play_Music(const char* url)
|
||||
{
|
||||
esp_audio_simple_player_stop(handle);
|
||||
esp_gmf_err_t err = esp_audio_simple_player_run(handle, url, NULL);
|
||||
Audio_PA_EN();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_gmf_err_t Audio_Play_Music_To_End(const char* url)
|
||||
{
|
||||
esp_audio_simple_player_stop(handle);
|
||||
Audio_PA_EN();
|
||||
esp_gmf_err_t err = esp_audio_simple_player_run_to_end(handle, url, NULL);
|
||||
Audio_PA_DIS();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_gmf_err_t Audio_Stop_Play(void)
|
||||
{
|
||||
esp_gmf_err_t err = esp_audio_simple_player_stop(handle);
|
||||
Audio_PA_DIS();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_gmf_err_t Audio_Resume_Play(void)
|
||||
{
|
||||
esp_gmf_err_t err = esp_audio_simple_player_resume(handle);
|
||||
Audio_PA_EN();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_gmf_err_t Audio_Pause_Play(void)
|
||||
{
|
||||
esp_gmf_err_t err = esp_audio_simple_player_pause(handle);
|
||||
Audio_PA_DIS();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_asp_state_t Audio_Get_Current_State(void)
|
||||
{
|
||||
esp_asp_state_t state;
|
||||
esp_gmf_err_t err = esp_audio_simple_player_get_state(handle, &state);
|
||||
if (err != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE("AUDIO", "Get state failed: %d", err);
|
||||
return ESP_ASP_STATE_ERROR;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
void Audio_Play_Init(void)
|
||||
{
|
||||
pipeline_init();
|
||||
}
|
||||
|
||||
void Volume_Adjustment(uint8_t Vol)
|
||||
{
|
||||
if(Vol > Volume_MAX )
|
||||
{
|
||||
printf("Audio : The volume value is incorrect. Please enter 0 to 21\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
esp_audio_set_play_vol(Vol);
|
||||
Volume = Vol;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_audio_volume(void)
|
||||
{
|
||||
return Volume;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_gmf_element.h"
|
||||
#include "esp_gmf_pipeline.h"
|
||||
#include "esp_gmf_pool.h"
|
||||
#include "esp_gmf_alc.h"
|
||||
#include "esp_audio_simple_player.h"
|
||||
#include "esp_audio_simple_player_advance.h"
|
||||
#include "esp_codec_dev.h"
|
||||
#include "esp_gmf_io.h"
|
||||
#include "esp_gmf_io_embed_flash.h"
|
||||
#include "esp_gmf_audio_dec.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define Volume_MAX 100
|
||||
|
||||
void Audio_Play_Init(void);
|
||||
void Volume_Adjustment(uint8_t Vol);
|
||||
uint8_t get_audio_volume(void);
|
||||
|
||||
esp_gmf_err_t Audio_Play_Music(const char* url);
|
||||
esp_gmf_err_t Audio_Play_Music_To_End(const char* url);
|
||||
esp_gmf_err_t Audio_Stop_Play(void);
|
||||
esp_gmf_err_t Audio_Resume_Play(void);
|
||||
esp_gmf_err_t Audio_Pause_Play(void);
|
||||
esp_asp_state_t Audio_Get_Current_State(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,684 @@
|
||||
#include "bsp_board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "string.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
|
||||
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "dirent.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include <errno.h>
|
||||
|
||||
#if ((SOC_SDMMC_HOST_SUPPORTED) && (FUNC_SDMMC_EN))
|
||||
#include "driver/sdmmc_host.h"
|
||||
#endif /* ((SOC_SDMMC_HOST_SUPPORTED) && (FUNC_SDMMC_EN)) */
|
||||
|
||||
|
||||
#define ADC_I2S_CHANNEL 4
|
||||
|
||||
static sdmmc_card_t *card;
|
||||
static const char *TAG = "board";
|
||||
static int s_play_sample_rate = 16000;
|
||||
static int s_play_channel_format = 1;
|
||||
static int s_bits_per_chan = 16;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
static i2s_chan_handle_t tx_handle = NULL; // I2S tx channel handler
|
||||
static i2s_chan_handle_t rx_handle = NULL; // I2S rx channel handler
|
||||
#endif
|
||||
static audio_codec_data_if_t *record_data_if = NULL;
|
||||
static audio_codec_ctrl_if_t *record_ctrl_if = NULL;
|
||||
static audio_codec_if_t *record_codec_if = NULL;
|
||||
static esp_codec_dev_handle_t record_dev = NULL;
|
||||
|
||||
static audio_codec_data_if_t *play_data_if = NULL;
|
||||
static audio_codec_ctrl_if_t *play_ctrl_if = NULL;
|
||||
static audio_codec_gpio_if_t *play_gpio_if = NULL;
|
||||
static audio_codec_if_t *play_codec_if = NULL;
|
||||
static esp_codec_dev_handle_t play_dev = NULL;
|
||||
static i2c_master_bus_handle_t i2c_bus_handle = NULL;
|
||||
static uint32_t SDCard_Size = 0;
|
||||
|
||||
|
||||
esp_codec_dev_handle_t esp_ret_play_dev(void)
|
||||
{
|
||||
return play_dev;
|
||||
}
|
||||
|
||||
i2c_master_bus_handle_t esp_ret_i2c_handle(void)
|
||||
{
|
||||
return i2c_bus_handle;
|
||||
}
|
||||
|
||||
uint32_t Get_SD_Size(void)
|
||||
{
|
||||
return SDCard_Size;
|
||||
}
|
||||
|
||||
static esp_err_t i2c_master_init(void)
|
||||
{
|
||||
const i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM,
|
||||
.sda_io_num = GPIO_I2C_SDA,
|
||||
.scl_io_num = GPIO_I2C_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t ret = i2c_new_master_bus(&bus_config, &i2c_bus_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize I2C bus: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "I2C bus initialized successfully");
|
||||
return ESP_OK; // 返回 ESP_OK 表示成功
|
||||
}
|
||||
|
||||
|
||||
esp_err_t bsp_codec_adc_init(int sample_rate)
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
// Do initialize of related interface: data_if, ctrl_if and gpio_if
|
||||
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||
.port = I2S_NUM_1,
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
.rx_handle = rx_handle,
|
||||
.tx_handle = NULL,
|
||||
#endif
|
||||
};
|
||||
record_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {.addr = ES7210_CODEC_DEFAULT_ADDR,.bus_handle = i2c_bus_handle};
|
||||
record_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
// New input codec interface
|
||||
es7210_codec_cfg_t es7210_cfg = {
|
||||
.ctrl_if = record_ctrl_if,
|
||||
.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4,
|
||||
};
|
||||
record_codec_if = es7210_codec_new(&es7210_cfg);
|
||||
// New input codec device
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.codec_if = record_codec_if,
|
||||
.data_if = record_data_if,
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
};
|
||||
record_dev = esp_codec_dev_new(&dev_cfg);
|
||||
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.sample_rate = 16000,
|
||||
.channel = 2,
|
||||
.bits_per_sample = 32,
|
||||
};
|
||||
esp_codec_dev_open(record_dev, &fs);
|
||||
// esp_codec_dev_set_in_gain(record_dev, RECORD_VOLUME);
|
||||
esp_codec_dev_set_in_channel_gain(record_dev, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), RECORD_VOLUME);
|
||||
esp_codec_dev_set_in_channel_gain(record_dev, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1), RECORD_VOLUME);
|
||||
esp_codec_dev_set_in_channel_gain(record_dev, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(2), RECORD_VOLUME);
|
||||
esp_codec_dev_set_in_channel_gain(record_dev, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(3), RECORD_VOLUME);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
esp_err_t bsp_codec_dac_init(int sample_rate, int channel_format, int bits_per_chan)
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
// Do initialize of related interface: data_if, ctrl_if and gpio_if
|
||||
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||
.port = I2S_NUM_1,
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
.rx_handle = NULL,
|
||||
.tx_handle = tx_handle,
|
||||
#endif
|
||||
};
|
||||
play_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {.addr = ES8311_CODEC_DEFAULT_ADDR,.bus_handle = i2c_bus_handle};
|
||||
play_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
play_gpio_if = audio_codec_new_gpio();
|
||||
// New output codec interface
|
||||
es8311_codec_cfg_t es8311_cfg = {
|
||||
.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
|
||||
.ctrl_if = play_ctrl_if,
|
||||
.gpio_if = play_gpio_if,
|
||||
.pa_pin = GPIO_PWR_CTRL,
|
||||
.use_mclk = false,
|
||||
};
|
||||
play_codec_if = es8311_codec_new(&es8311_cfg);
|
||||
// New output codec device
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.codec_if = play_codec_if,
|
||||
.data_if = play_data_if,
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
};
|
||||
play_dev = esp_codec_dev_new(&dev_cfg);
|
||||
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = bits_per_chan,
|
||||
.sample_rate = sample_rate,
|
||||
.channel = channel_format,
|
||||
};
|
||||
esp_codec_dev_set_out_vol(play_dev, PLAYER_VOLUME);
|
||||
esp_codec_dev_open(play_dev, &fs);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static esp_err_t bsp_codec_adc_deinit()
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
if (record_dev) {
|
||||
esp_codec_dev_close(record_dev);
|
||||
esp_codec_dev_delete(record_dev);
|
||||
record_dev = NULL;
|
||||
}
|
||||
|
||||
// Delete codec interface
|
||||
if (record_codec_if) {
|
||||
audio_codec_delete_codec_if(record_codec_if);
|
||||
record_codec_if = NULL;
|
||||
}
|
||||
|
||||
// Delete codec control interface
|
||||
if (record_ctrl_if) {
|
||||
audio_codec_delete_ctrl_if(record_ctrl_if);
|
||||
record_ctrl_if = NULL;
|
||||
}
|
||||
|
||||
// Delete codec data interface
|
||||
if (record_data_if) {
|
||||
audio_codec_delete_data_if(record_data_if);
|
||||
record_data_if = NULL;
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static esp_err_t bsp_codec_dac_deinit()
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
if (play_dev) {
|
||||
esp_codec_dev_close(play_dev);
|
||||
esp_codec_dev_delete(play_dev);
|
||||
play_dev = NULL;
|
||||
}
|
||||
|
||||
// Delete codec interface
|
||||
if (play_codec_if) {
|
||||
audio_codec_delete_codec_if(play_codec_if);
|
||||
play_codec_if = NULL;
|
||||
}
|
||||
|
||||
// Delete codec control interface
|
||||
if (play_ctrl_if) {
|
||||
audio_codec_delete_ctrl_if(play_ctrl_if);
|
||||
play_ctrl_if = NULL;
|
||||
}
|
||||
|
||||
if (play_gpio_if) {
|
||||
audio_codec_delete_gpio_if(play_gpio_if);
|
||||
play_gpio_if = NULL;
|
||||
}
|
||||
|
||||
// Delete codec data interface
|
||||
if (play_data_if) {
|
||||
audio_codec_delete_data_if(play_data_if);
|
||||
play_data_if = NULL;
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
esp_err_t esp_audio_set_play_vol(int volume)
|
||||
{
|
||||
if (!play_dev) {
|
||||
ESP_LOGE(TAG, "DAC codec init fail");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
esp_codec_dev_set_out_vol(play_dev, volume);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_audio_get_play_vol(int *volume)
|
||||
{
|
||||
if (!play_dev) {
|
||||
ESP_LOGE(TAG, "DAC codec init fail");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
esp_codec_dev_get_out_vol(play_dev, volume);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// static esp_err_t bsp_i2s_init(i2s_port_t i2s_num, uint32_t sample_rate, i2s_channel_fmt_t channel_format, i2s_bits_per_chan_t bits_per_chan)
|
||||
static esp_err_t bsp_i2s_init(int i2s_num, uint32_t sample_rate, int channel_format, int bits_per_chan)
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
i2s_slot_mode_t channel_fmt = I2S_SLOT_MODE_STEREO;
|
||||
if (channel_format == 1) {
|
||||
channel_fmt = I2S_SLOT_MODE_MONO;
|
||||
} else if (channel_format == 2) {
|
||||
channel_fmt = I2S_SLOT_MODE_STEREO;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unable to configure channel_format %d", channel_format);
|
||||
channel_format = 1;
|
||||
channel_fmt = I2S_SLOT_MODE_MONO;
|
||||
}
|
||||
|
||||
if (bits_per_chan != 16 && bits_per_chan != 32) {
|
||||
ESP_LOGE(TAG, "Unable to configure bits_per_chan %d", bits_per_chan);
|
||||
bits_per_chan = 32;
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(i2s_num, I2S_ROLE_MASTER);
|
||||
ret_val |= i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
|
||||
i2s_std_config_t std_cfg = I2S_CONFIG_DEFAULT(sample_rate, channel_fmt, bits_per_chan);
|
||||
ret_val |= i2s_channel_init_std_mode(tx_handle, &std_cfg);
|
||||
ret_val |= i2s_channel_init_std_mode(rx_handle, &std_cfg);
|
||||
ret_val |= i2s_channel_enable(tx_handle);
|
||||
ret_val |= i2s_channel_enable(rx_handle);
|
||||
#else
|
||||
i2s_channel_fmt_t channel_fmt = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
if (channel_format == 1) {
|
||||
channel_fmt = I2S_CHANNEL_FMT_ONLY_LEFT;
|
||||
} else if (channel_format == 2) {
|
||||
channel_fmt = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unable to configure channel_format %d", channel_format);
|
||||
channel_format = 1;
|
||||
channel_fmt = I2S_CHANNEL_FMT_ONLY_LEFT;
|
||||
}
|
||||
|
||||
if (bits_per_chan != 16 && bits_per_chan != 32) {
|
||||
ESP_LOGE(TAG, "Unable to configure bits_per_chan %d", bits_per_chan);
|
||||
bits_per_chan = 16;
|
||||
}
|
||||
|
||||
i2s_config_t i2s_config = I2S_CONFIG_DEFAULT(sample_rate, channel_fmt, bits_per_chan);
|
||||
|
||||
i2s_pin_config_t pin_config = {
|
||||
.bck_io_num = GPIO_I2S_SCLK,
|
||||
.ws_io_num = GPIO_I2S_LRCK,
|
||||
.data_out_num = GPIO_I2S_DOUT,
|
||||
.data_in_num = GPIO_I2S_SDIN,
|
||||
.mck_io_num = GPIO_I2S_MCLK,
|
||||
};
|
||||
|
||||
ret_val |= i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
|
||||
ret_val |= i2s_set_pin(i2s_num, &pin_config);
|
||||
#endif
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static esp_err_t bsp_i2s_deinit(int i2s_num)
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
if (i2s_num == I2S_NUM_1 && rx_handle) {
|
||||
ret_val |= i2s_channel_disable(rx_handle);
|
||||
ret_val |= i2s_del_channel(rx_handle);
|
||||
rx_handle = NULL;
|
||||
} else if (i2s_num == I2S_NUM_0 && tx_handle) {
|
||||
ret_val |= i2s_channel_disable(tx_handle);
|
||||
ret_val |= i2s_del_channel(tx_handle);
|
||||
tx_handle = NULL;
|
||||
}
|
||||
#else
|
||||
ret_val |= i2s_stop(i2s_num);
|
||||
ret_val |= i2s_driver_uninstall(i2s_num);
|
||||
#endif
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static esp_err_t bsp_codec_init(int adc_sample_rate, int dac_sample_rate, int dac_channel_format, int dac_bits_per_chan)
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
ret_val |= bsp_codec_adc_init(adc_sample_rate);
|
||||
ret_val |= bsp_codec_dac_init(dac_sample_rate, dac_channel_format, dac_bits_per_chan);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static esp_err_t bsp_codec_deinit()
|
||||
{
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
ret_val |= bsp_codec_adc_deinit();
|
||||
ret_val |= bsp_codec_dac_deinit();
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
esp_err_t esp_audio_play(const int16_t* data, int length, uint32_t ticks_to_wait)
|
||||
{
|
||||
size_t bytes_write = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (!play_dev) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int out_length= length;
|
||||
int audio_time = 1;
|
||||
audio_time *= (16000 / s_play_sample_rate);
|
||||
audio_time *= (2 / s_play_channel_format);
|
||||
|
||||
int *data_out = NULL;
|
||||
if (s_bits_per_chan != 32) {
|
||||
out_length = length * 2;
|
||||
data_out = malloc(out_length);
|
||||
for (int i = 0; i < length / sizeof(int16_t); i++) {
|
||||
int ret = data[i];
|
||||
data_out[i] = ret << 16;
|
||||
}
|
||||
}
|
||||
|
||||
int *data_out_1 = NULL;
|
||||
if (s_play_channel_format != 2 || s_play_sample_rate != 16000) {
|
||||
out_length *= audio_time;
|
||||
data_out_1 = malloc(out_length);
|
||||
int *tmp_data = NULL;
|
||||
if (data_out != NULL) {
|
||||
tmp_data = data_out;
|
||||
} else {
|
||||
tmp_data = (int *)data;
|
||||
}
|
||||
|
||||
for (int i = 0; i < out_length / (audio_time * sizeof(int)); i++) {
|
||||
for (int j = 0; j < audio_time; j++) {
|
||||
data_out_1[audio_time * i + j] = tmp_data[i];
|
||||
}
|
||||
}
|
||||
if (data_out != NULL) {
|
||||
free(data_out);
|
||||
data_out = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (data_out != NULL) {
|
||||
ret = esp_codec_dev_write(play_dev, (void *)data_out, out_length);
|
||||
free(data_out);
|
||||
} else if (data_out_1 != NULL) {
|
||||
ret = esp_codec_dev_write(play_dev, (void *)data_out_1, out_length);
|
||||
free(data_out_1);
|
||||
} else {
|
||||
ret = esp_codec_dev_write(play_dev, (void *)data, length);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_get_feed_data(bool is_get_raw_channel, int16_t *buffer, int buffer_len)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
size_t bytes_read;
|
||||
int audio_chunksize = buffer_len / (sizeof(int16_t) * ADC_I2S_CHANNEL);
|
||||
|
||||
ret = esp_codec_dev_read(record_dev, (void *)buffer, buffer_len);
|
||||
if (!is_get_raw_channel) {
|
||||
for (int i = 0; i < audio_chunksize; i++) {
|
||||
int16_t ref = buffer[4 * i + 0];
|
||||
buffer[3 * i + 0] = buffer[4 * i + 1];
|
||||
buffer[3 * i + 1] = buffer[4 * i + 3];
|
||||
buffer[3 * i + 2] = ref;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int esp_get_feed_channel(void)
|
||||
{
|
||||
return ADC_I2S_CHANNEL;
|
||||
}
|
||||
|
||||
char* esp_get_input_format(void)
|
||||
{
|
||||
return "RMNM";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
esp_err_t esp_board_init(uint32_t sample_rate, int channel_format, int bits_per_chan)
|
||||
{
|
||||
/*!< Initialize I2C bus, used for audio codec*/
|
||||
|
||||
i2c_master_init();
|
||||
s_play_sample_rate = sample_rate;
|
||||
|
||||
if (channel_format != 2 && channel_format != 1) {
|
||||
ESP_LOGE(TAG, "Unable to configure channel_format");
|
||||
channel_format = 2;
|
||||
}
|
||||
s_play_channel_format = channel_format;
|
||||
|
||||
if (bits_per_chan != 32 && bits_per_chan != 16) {
|
||||
ESP_LOGE(TAG, "Unable to configure bits_per_chan");
|
||||
bits_per_chan = 32;
|
||||
}
|
||||
s_bits_per_chan = bits_per_chan;
|
||||
|
||||
bsp_i2s_init(I2S_NUM_1, 16000, 2, 32);
|
||||
// Because record and play use the same i2s.
|
||||
bsp_codec_init(16000, 16000, 2, 32);
|
||||
|
||||
/* Initialize PA */
|
||||
/*gpio_config_t io_conf;
|
||||
memset(&io_conf, 0, sizeof(io_conf));
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = ((1ULL << GPIO_PWR_CTRL));
|
||||
io_conf.pull_down_en = 0;
|
||||
io_conf.pull_up_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(GPIO_PWR_CTRL, 1);*/
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_sdcard_init(char *mount_point, size_t max_files)
|
||||
{
|
||||
if (NULL != card) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Check if SD crad is supported */
|
||||
if (!FUNC_SDMMC_EN && !FUNC_SDSPI_EN) {
|
||||
ESP_LOGE(TAG, "SDMMC and SDSPI not supported on this board!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t ret_val = ESP_OK;
|
||||
|
||||
/**
|
||||
* @brief Options for mounting the filesystem.
|
||||
* If format_if_mount_failed is set to true, SD card will be partitioned and
|
||||
* formatted in case when mounting fails.
|
||||
*
|
||||
*/
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = max_files,
|
||||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Use settings defined above to initialize SD card and mount FAT filesystem.
|
||||
* Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
|
||||
* Please check its source code and implement error recovery when developing
|
||||
* production applications.
|
||||
*
|
||||
*/
|
||||
sdmmc_host_t host =
|
||||
#if FUNC_SDMMC_EN
|
||||
SDMMC_HOST_DEFAULT();
|
||||
#else
|
||||
SDSPI_HOST_DEFAULT();
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = GPIO_SDSPI_MOSI,
|
||||
.miso_io_num = GPIO_SDSPI_MISO,
|
||||
.sclk_io_num = GPIO_SDSPI_SCLK,
|
||||
.quadwp_io_num = GPIO_NUM_NC,
|
||||
.quadhd_io_num = GPIO_NUM_NC,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
ret_val = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CH_AUTO);
|
||||
if (ret_val != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize bus.");
|
||||
return ret_val;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief This initializes the slot without card detect (CD) and write protect (WP) signals.
|
||||
* Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
|
||||
*
|
||||
*/
|
||||
#if FUNC_SDMMC_EN
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
#else
|
||||
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
#endif
|
||||
|
||||
#if FUNC_SDMMC_EN
|
||||
/* Config SD data width. 0, 4 or 8. Currently for SD card, 8 bit is not supported. */
|
||||
slot_config.width = SDMMC_BUS_WIDTH;
|
||||
|
||||
/**
|
||||
* @brief On chips where the GPIOs used for SD card can be configured, set them in
|
||||
* the slot_config structure.
|
||||
*
|
||||
*/
|
||||
#if SOC_SDMMC_USE_GPIO_MATRIX
|
||||
slot_config.clk = GPIO_SDMMC_CLK;
|
||||
slot_config.cmd = GPIO_SDMMC_CMD;
|
||||
slot_config.d0 = GPIO_SDMMC_D0;
|
||||
slot_config.d1 = GPIO_SDMMC_D1;
|
||||
slot_config.d2 = GPIO_SDMMC_D2;
|
||||
slot_config.d3 = GPIO_SDMMC_D3;
|
||||
#endif
|
||||
slot_config.cd = GPIO_SDMMC_DET;
|
||||
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
|
||||
#else
|
||||
slot_config.gpio_cs = GPIO_SDSPI_CS;
|
||||
slot_config.host_id = host.slot;
|
||||
#endif
|
||||
/**
|
||||
* @brief Enable internal pullups on enabled pins. The internal pullups
|
||||
* are insufficient however, please make sure 10k external pullups are
|
||||
* connected on the bus. This is for debug / example purpose only.
|
||||
*/
|
||||
|
||||
/* get FAT filesystem on SD card registered in VFS. */
|
||||
ret_val =
|
||||
#if FUNC_SDMMC_EN
|
||||
esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
||||
#else
|
||||
esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
||||
#endif
|
||||
|
||||
/* Check for SDMMC mount result. */
|
||||
if (ret_val != ESP_OK) {
|
||||
if (ret_val == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount filesystem. "
|
||||
"If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
|
||||
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret_val));
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
/* Card has been initialized, print its properties. */
|
||||
sdmmc_card_print_info(stdout, card);
|
||||
SDCard_Size = ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024);
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
esp_err_t esp_sdcard_deinit(char *mount_point)
|
||||
{
|
||||
if (NULL == mount_point) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Unmount an SD card from the FAT filesystem and release resources acquired */
|
||||
esp_err_t ret_val = esp_vfs_fat_sdcard_unmount(mount_point, card);
|
||||
|
||||
/* Make SD/MMC card information structure pointer NULL */
|
||||
card = NULL;
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
#define MOUNT_POINT "/sdcard"
|
||||
|
||||
static const char *SD_TAG = "SD";
|
||||
|
||||
|
||||
|
||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][MAX_FILE_NAME_SIZE], uint16_t maxFiles)
|
||||
{
|
||||
DIR *dir = opendir(directory);
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(SD_TAG, "Path: <%s> does not exist", directory);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t fileCount = 0;
|
||||
struct dirent *entry;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL && fileCount < maxFiles) {
|
||||
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *dot = strrchr(entry->d_name, '.');
|
||||
if (dot != NULL && dot != entry->d_name) {
|
||||
|
||||
if (strcasecmp(dot, fileExtension) == 0) {
|
||||
strncpy(File_Name[fileCount], entry->d_name, MAX_FILE_NAME_SIZE - 1);
|
||||
File_Name[fileCount][MAX_FILE_NAME_SIZE - 1] = '\0';
|
||||
|
||||
char filePath[MAX_PATH_SIZE];
|
||||
snprintf(filePath, MAX_PATH_SIZE, "%s/%s", directory, entry->d_name);
|
||||
|
||||
printf("File found: %s\r\n", filePath);
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
else{
|
||||
|
||||
// printf("No extension found for file: %s\r\n", entry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (fileCount > 0) {
|
||||
ESP_LOGI(SD_TAG, "Retrieved %d files with extension '%s'", fileCount, fileExtension);
|
||||
} else {
|
||||
ESP_LOGW(SD_TAG, "No files with extension '%s' found in directory: %s", fileExtension, directory);
|
||||
}
|
||||
|
||||
return fileCount;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "driver/i2s_tdm.h"
|
||||
#include "driver/i2c_master.h"
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include "esp_codec_dev.h"
|
||||
#include "esp_codec_dev_defaults.h"
|
||||
#include "esp_codec_dev_os.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "device_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_FILE_NAME_SIZE 100 // Define maximum file name size
|
||||
#define MAX_PATH_SIZE 512 // Define a larger size for the full path
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-CAM-OVxxxx I2C GPIO defineation
|
||||
*
|
||||
*/
|
||||
#define I2C_NUM (0)
|
||||
#define GPIO_I2C_SCL (GPIO_NUM_10)
|
||||
#define GPIO_I2C_SDA (GPIO_NUM_11)
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-CAM-OVxxxx SDMMC GPIO defination
|
||||
*
|
||||
* @note Only avaliable when PMOD connected
|
||||
*/
|
||||
#define FUNC_SDMMC_EN (1)
|
||||
#define SDMMC_BUS_WIDTH (1)
|
||||
#define GPIO_SDMMC_CLK (GPIO_NUM_40)
|
||||
#define GPIO_SDMMC_CMD (GPIO_NUM_42)
|
||||
#define GPIO_SDMMC_D0 (GPIO_NUM_41)
|
||||
#define GPIO_SDMMC_D1 (GPIO_NUM_NC)
|
||||
#define GPIO_SDMMC_D2 (GPIO_NUM_NC)
|
||||
#define GPIO_SDMMC_D3 (GPIO_NUM_NC)
|
||||
#define GPIO_SDMMC_DET (GPIO_NUM_NC)
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-CAM-OVxxxx SDSPI GPIO definationv
|
||||
*
|
||||
*/
|
||||
#define FUNC_SDSPI_EN (0)
|
||||
#define SDSPI_HOST (SPI2_HOST)
|
||||
#define GPIO_SDSPI_CS (GPIO_NUM_NC)
|
||||
#define GPIO_SDSPI_SCLK (GPIO_NUM_NC)
|
||||
#define GPIO_SDSPI_MISO (GPIO_NUM_NC)
|
||||
#define GPIO_SDSPI_MOSI (GPIO_NUM_NC)
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-CAM-OVxxxx I2S GPIO defination
|
||||
*
|
||||
*/
|
||||
#define FUNC_I2S_EN (1)
|
||||
#define GPIO_I2S_LRCK (GPIO_NUM_14)
|
||||
#define GPIO_I2S_MCLK (GPIO_NUM_12)
|
||||
#define GPIO_I2S_SCLK (GPIO_NUM_13)
|
||||
|
||||
#ifdef MODEL_PROTO_1
|
||||
#define GPIO_I2S_SDIN (GPIO_NUM_16) // Swapped for PROTO-1
|
||||
#define GPIO_I2S_DOUT (GPIO_NUM_15) // Swapped for PROTO-1
|
||||
#else
|
||||
#define GPIO_I2S_SDIN (GPIO_NUM_15)
|
||||
#define GPIO_I2S_DOUT (GPIO_NUM_16)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-CAM-OVxxxx I2S GPIO defination
|
||||
*
|
||||
*/
|
||||
#define FUNC_I2S0_EN (0)
|
||||
#define GPIO_I2S0_LRCK (GPIO_NUM_NC)
|
||||
#define GPIO_I2S0_MCLK (GPIO_NUM_NC)
|
||||
#define GPIO_I2S0_SCLK (GPIO_NUM_NC)
|
||||
#define GPIO_I2S0_SDIN (GPIO_NUM_NC)
|
||||
#define GPIO_I2S0_DOUT (GPIO_NUM_NC)
|
||||
|
||||
/**
|
||||
* @brief record configurations
|
||||
*
|
||||
*/
|
||||
#define RECORD_VOLUME (30.0)
|
||||
|
||||
/**
|
||||
* @brief player configurations
|
||||
*
|
||||
*/
|
||||
#define PLAYER_VOLUME (60)
|
||||
|
||||
/**
|
||||
* @brief ESP32-S3-HMI-DevKit power control IO
|
||||
*
|
||||
* @note Some power control pins might not be listed yet
|
||||
*
|
||||
*/
|
||||
//#define FUNC_PWR_CTRL (1)
|
||||
#define GPIO_PWR_CTRL (-1)
|
||||
#define GPIO_PWR_ON_LEVEL (1)
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
|
||||
#define I2S_CONFIG_DEFAULT(sample_rate, channel_fmt, bits_per_chan) { \
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), \
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(32, I2S_SLOT_MODE_STEREO), \
|
||||
.gpio_cfg = { \
|
||||
.mclk = GPIO_I2S_MCLK, \
|
||||
.bclk = GPIO_I2S_SCLK, \
|
||||
.ws = GPIO_I2S_LRCK, \
|
||||
.dout = GPIO_I2S_DOUT, \
|
||||
.din = GPIO_I2S_SDIN, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define I2S_CONFIG_DEFAULT(sample_rate, channel_fmt, bits_per_chan) { \
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX, \
|
||||
.sample_rate = 16000, \
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, \
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, \
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S, \
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, \
|
||||
.dma_buf_count = 6, \
|
||||
.dma_buf_len = 160, \
|
||||
.use_apll = false, \
|
||||
.tx_desc_auto_clear = true, \
|
||||
.fixed_mclk = 0, \
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, \
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_32BIT, \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* LCD settings */
|
||||
#define EXAMPLE_LCD_SPI_NUM (SPI3_HOST)
|
||||
#define EXAMPLE_LCD_PIXEL_CLK_HZ (80 * 1000 * 1000)
|
||||
#define EXAMPLE_LCD_CMD_BITS (8)
|
||||
#define EXAMPLE_LCD_PARAM_BITS (8)
|
||||
#define EXAMPLE_LCD_BITS_PER_PIXEL (16)
|
||||
#define EXAMPLE_LCD_DRAW_BUFF_DOUBLE (1)
|
||||
#define EXAMPLE_LCD_DRAW_BUFF_HEIGHT (50)
|
||||
#define EXAMPLE_LCD_BL_ON_LEVEL (1)
|
||||
#define Backlight_MAX 100
|
||||
#define DEFAULT_BACKLIGHT 80
|
||||
|
||||
/* LCD pins */
|
||||
#define EXAMPLE_LCD_GPIO_SCLK (GPIO_NUM_5)
|
||||
#define EXAMPLE_LCD_GPIO_MOSI (GPIO_NUM_1)
|
||||
#define EXAMPLE_LCD_GPIO_RST (GPIO_NUM_NC)
|
||||
#define EXAMPLE_LCD_GPIO_DC (GPIO_NUM_3)
|
||||
#define EXAMPLE_LCD_GPIO_CS (GPIO_NUM_6)
|
||||
#define EXAMPLE_LCD_GPIO_BL (GPIO_NUM_NC)
|
||||
|
||||
/* LCD touch pins */
|
||||
#define EXAMPLE_TOUCH_GPIO_RST (GPIO_NUM_NC)
|
||||
|
||||
#ifdef CONFIG_WAVESHARE_1_47INCH_TOUCH_LCD
|
||||
#define EXAMPLE_LCD_H_RES (172)
|
||||
#define EXAMPLE_LCD_V_RES (320)
|
||||
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_9)
|
||||
#elif defined(CONFIG_WAVESHARE_2INCH_TOUCH_LCD)
|
||||
#define EXAMPLE_LCD_H_RES (240)
|
||||
#define EXAMPLE_LCD_V_RES (320)
|
||||
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_9)
|
||||
#elif defined(CONFIG_WAVESHARE_2_8INCH_TOUCH_LCD)
|
||||
#define EXAMPLE_LCD_H_RES (240)
|
||||
#define EXAMPLE_LCD_V_RES (320)
|
||||
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_NC)
|
||||
#elif defined(CONFIG_WAVESHARE_3_5INCH_TOUCH_LCD)
|
||||
#define EXAMPLE_LCD_H_RES (320)
|
||||
#define EXAMPLE_LCD_V_RES (480)
|
||||
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_9)
|
||||
#endif
|
||||
|
||||
// GPIO assignment
|
||||
#ifdef MODEL_PROTO_1
|
||||
#define LED_STRIP_GPIO_PIN 9
|
||||
#else
|
||||
#define LED_STRIP_GPIO_PIN 38
|
||||
#endif
|
||||
#define LED_STRIP_LED_COUNT 7
|
||||
|
||||
|
||||
esp_err_t esp_board_init(uint32_t sample_rate, int channel_format, int bits_per_chan);
|
||||
|
||||
esp_err_t esp_sdcard_init(char *mount_point, size_t max_files);
|
||||
esp_err_t esp_sdcard_deinit(char *mount_point);
|
||||
|
||||
esp_err_t esp_audio_play(const int16_t* data, int length, uint32_t ticks_to_wait);
|
||||
|
||||
esp_err_t esp_get_feed_data(bool is_get_raw_channel, int16_t *buffer, int buffer_len);
|
||||
int esp_get_feed_channel(void);
|
||||
char* esp_get_input_format(void);
|
||||
esp_err_t esp_audio_set_play_vol(int volume);
|
||||
esp_err_t esp_audio_get_play_vol(int *volume);
|
||||
|
||||
i2c_master_bus_handle_t esp_ret_i2c_handle(void);
|
||||
esp_codec_dev_handle_t esp_ret_play_dev();
|
||||
uint32_t Get_SD_Size(void);
|
||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][MAX_FILE_NAME_SIZE], uint16_t maxFiles) ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies:
|
||||
espressif/esp_audio_simple_player: ^0.9.4~1
|
||||
espressif/esp_io_expander_tca95xx_16bit: ^2.0.1
|
||||
espressif/led_strip: ^3.0.1~1
|
||||
espressif/esp_websocket_client: "^1.7.0"
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "led_strip.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "bsp_board.h"
|
||||
#include "tca9555_driver.h"
|
||||
#include "audio_driver.h"
|
||||
#include "wifi_connect.h"
|
||||
#include "socket_client.h"
|
||||
#include "state_manager.h"
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
// Converts HSV color space (Hue 0-359, Saturation 0-100, Value 0-100) to RGB (0-255)
|
||||
static void hsv_to_rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
|
||||
{
|
||||
h %= 360;
|
||||
uint32_t rgb_max = (v * 255) / 100;
|
||||
uint32_t rgb_min = (rgb_max * (100 - s)) / 100;
|
||||
uint32_t diff = h % 60;
|
||||
uint32_t rgb_adj = ((rgb_max - rgb_min) * diff) / 60;
|
||||
|
||||
switch (h / 60) {
|
||||
case 0:
|
||||
*r = rgb_max;
|
||||
*g = rgb_min + rgb_adj;
|
||||
*b = rgb_min;
|
||||
break;
|
||||
case 1:
|
||||
*r = rgb_max - rgb_adj;
|
||||
*g = rgb_max;
|
||||
*b = rgb_min;
|
||||
break;
|
||||
case 2:
|
||||
*r = rgb_min;
|
||||
*g = rgb_max;
|
||||
*b = rgb_min + rgb_adj;
|
||||
break;
|
||||
case 3:
|
||||
*r = rgb_min;
|
||||
*g = rgb_max - rgb_adj;
|
||||
*b = rgb_max;
|
||||
break;
|
||||
case 4:
|
||||
*r = rgb_min + rgb_adj;
|
||||
*g = rgb_min;
|
||||
*b = rgb_max;
|
||||
break;
|
||||
default:
|
||||
*r = rgb_max;
|
||||
*g = rgb_min;
|
||||
*b = rgb_max - rgb_adj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static volatile led_state_t g_led_state = LED_STATE_OFF;
|
||||
|
||||
void set_led_state(led_state_t state)
|
||||
{
|
||||
g_led_state = state;
|
||||
ESP_LOGI(TAG, "LED state changed to: %d", state);
|
||||
}
|
||||
|
||||
led_state_t get_led_state(void)
|
||||
{
|
||||
return g_led_state;
|
||||
}
|
||||
|
||||
|
||||
// Background task to slowly fade the 7-LED ring in a smooth rainbow pattern or show status colors
|
||||
static void led_rainbow_task(void *pvParameters)
|
||||
{
|
||||
// LED strip general initialization
|
||||
led_strip_config_t strip_config = {
|
||||
.strip_gpio_num = LED_STRIP_GPIO_PIN, // Use configured model pin (38 on DEV-1, 9 on PROTO-1)
|
||||
.max_leds = 7, // 7 LEDs in the ring
|
||||
.led_model = LED_MODEL_WS2812, // WS2812 model type
|
||||
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_RGB,
|
||||
.flags = {
|
||||
.invert_out = false,
|
||||
}
|
||||
};
|
||||
|
||||
// LED strip backend configuration (RMT)
|
||||
led_strip_rmt_config_t rmt_config = {
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 10 * 1000 * 1000, // 10MHz
|
||||
.mem_block_symbols = 0,
|
||||
.flags = {
|
||||
.with_dma = 0,
|
||||
}
|
||||
};
|
||||
|
||||
led_strip_handle_t led_strip;
|
||||
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
|
||||
ESP_LOGI(TAG, "LED Strip initialized successfully.");
|
||||
|
||||
uint32_t hue = 0;
|
||||
bool blink_toggle = false;
|
||||
|
||||
while (1) {
|
||||
device_state_t st;
|
||||
state_get_current(&st);
|
||||
float br = st.led_brightness; // 0.0 to 1.0 coefficient
|
||||
|
||||
if (g_led_state == LED_STATE_OFF || br <= 0.001f) {
|
||||
// Turn off LEDs
|
||||
led_strip_clear(led_strip);
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // check back in 100ms
|
||||
}
|
||||
else if (g_led_state == LED_STATE_WIFI_CONNECTING) {
|
||||
// Flash red
|
||||
uint8_t r = (uint8_t)(255.0f * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (blink_toggle) {
|
||||
led_strip_set_pixel(led_strip, i, r, 0, 0);
|
||||
} else {
|
||||
led_strip_set_pixel(led_strip, i, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
blink_toggle = !blink_toggle;
|
||||
vTaskDelay(pdMS_TO_TICKS(500)); // flash every 500ms
|
||||
}
|
||||
else if (g_led_state == LED_STATE_WIFI_FAILED) {
|
||||
// Solid red
|
||||
uint8_t r = (uint8_t)(255.0f * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
led_strip_set_pixel(led_strip, i, r, 0, 0);
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // check back in 100ms
|
||||
}
|
||||
else if (g_led_state == LED_STATE_SERVER_CONNECTING) {
|
||||
// Flash orange: RGB (255, 128, 0)
|
||||
uint8_t r = (uint8_t)(255.0f * br);
|
||||
uint8_t g = (uint8_t)(128.0f * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (blink_toggle) {
|
||||
led_strip_set_pixel(led_strip, i, r, g, 0);
|
||||
} else {
|
||||
led_strip_set_pixel(led_strip, i, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
blink_toggle = !blink_toggle;
|
||||
vTaskDelay(pdMS_TO_TICKS(500)); // flash every 500ms
|
||||
}
|
||||
else if (g_led_state == LED_STATE_SERVER_FAILED) {
|
||||
// Solid red
|
||||
uint8_t r = (uint8_t)(255.0f * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
led_strip_set_pixel(led_strip, i, r, 0, 0);
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // check back in 100ms
|
||||
}
|
||||
else if (g_led_state == LED_STATE_UPDATING) {
|
||||
// Pulse blue slowly (triangle wave breathing animation)
|
||||
static int pulse_dir = 1;
|
||||
static int pulse_level = 20;
|
||||
|
||||
pulse_level += (pulse_dir * 8);
|
||||
if (pulse_level >= 255) {
|
||||
pulse_level = 255;
|
||||
pulse_dir = -1;
|
||||
} else if (pulse_level <= 20) {
|
||||
pulse_level = 20;
|
||||
pulse_dir = 1;
|
||||
}
|
||||
|
||||
uint8_t b_val = (uint8_t)(pulse_level * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
led_strip_set_pixel(led_strip, i, 0, 0, b_val);
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
vTaskDelay(pdMS_TO_TICKS(40)); // ~25 steps per second
|
||||
}
|
||||
else {
|
||||
// LED_STATE_CONNECTED: Rainbow rotate
|
||||
uint32_t val = (uint32_t)(100.0f * br);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
uint32_t r = 0, g = 0, b = 0;
|
||||
// Shift the hue slightly for each consecutive LED on the ring to create a rotating color wheel
|
||||
uint32_t shifted_hue = (hue + (i * 360 / 7)) % 360;
|
||||
|
||||
hsv_to_rgb(shifted_hue, 100, val, &r, &g, &b);
|
||||
led_strip_set_pixel(led_strip, i, r, g, b);
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
|
||||
// Advance the base hue color for the fade effect
|
||||
hue = (hue + 4) % 360;
|
||||
|
||||
// Delay of 25ms controls the speed of the rainbow rotation
|
||||
vTaskDelay(pdMS_TO_TICKS(25));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mount_spiffs(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing SPIFFS partition 'model'...");
|
||||
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = "model",
|
||||
.max_files = 5,
|
||||
.format_if_mount_failed = true // format if it fails to mount
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition in partition table");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info("model", &total, &used);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MODEL_PROTO_1
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
|
||||
#define HAPTIC_PWM_GPIO GPIO_NUM_5
|
||||
#define HAPTIC_LEDC_CHANNEL LEDC_CHANNEL_0
|
||||
#define HAPTIC_LEDC_TIMER LEDC_TIMER_0
|
||||
#define HAPTIC_LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||
|
||||
#define TOUCH_SENSOR_GPIO GPIO_NUM_7
|
||||
|
||||
static adc_oneshot_unit_handle_t s_adc_handle = NULL;
|
||||
|
||||
void proto1_haptics_init(void)
|
||||
{
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.speed_mode = HAPTIC_LEDC_MODE,
|
||||
.timer_num = HAPTIC_LEDC_TIMER,
|
||||
.duty_resolution = LEDC_TIMER_10_BIT,
|
||||
.freq_hz = 1000,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
};
|
||||
ledc_timer_config(&ledc_timer);
|
||||
|
||||
ledc_channel_config_t ledc_channel = {
|
||||
.speed_mode = HAPTIC_LEDC_MODE,
|
||||
.channel = HAPTIC_LEDC_CHANNEL,
|
||||
.timer_sel = HAPTIC_LEDC_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = HAPTIC_PWM_GPIO,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
};
|
||||
ledc_channel_config(&ledc_channel);
|
||||
ESP_LOGI(TAG, "PROTO-1 haptic driver initialized on GPIO 5.");
|
||||
}
|
||||
|
||||
void proto1_haptics_set_intensity(uint8_t intensity)
|
||||
{
|
||||
if (intensity > 100) intensity = 100;
|
||||
uint32_t duty = (uint32_t)((intensity / 100.0f) * 1023.0f);
|
||||
ledc_set_duty(HAPTIC_LEDC_MODE, HAPTIC_LEDC_CHANNEL, duty);
|
||||
ledc_update_duty(HAPTIC_LEDC_MODE, HAPTIC_LEDC_CHANNEL);
|
||||
}
|
||||
|
||||
void proto1_adc_init(void)
|
||||
{
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1, // GPIO 4 is ADC1 Channel 3
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
if (adc_oneshot_new_unit(&init_config, &s_adc_handle) == ESP_OK) {
|
||||
adc_oneshot_chan_cfg_t config = {
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
};
|
||||
adc_oneshot_config_channel(s_adc_handle, ADC_CHANNEL_3, &config);
|
||||
ESP_LOGI(TAG, "PROTO-1 ADC light sensor driver initialized on GPIO 4 (ADC1_CH3).");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t proto1_adc_read(void)
|
||||
{
|
||||
int raw_val = 0;
|
||||
if (s_adc_handle != NULL) {
|
||||
adc_oneshot_read(s_adc_handle, ADC_CHANNEL_3, &raw_val);
|
||||
}
|
||||
return (uint16_t)raw_val;
|
||||
}
|
||||
|
||||
void proto1_touch_init(void)
|
||||
{
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << TOUCH_SENSOR_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
ESP_LOGI(TAG, "PROTO-1 digital touch sensor driver initialized on GPIO 7.");
|
||||
}
|
||||
|
||||
static void proto1_sensor_task(void *pvParameters)
|
||||
{
|
||||
proto1_haptics_init();
|
||||
proto1_adc_init();
|
||||
proto1_touch_init();
|
||||
|
||||
while (1) {
|
||||
uint16_t light = proto1_adc_read();
|
||||
uint8_t touch = (uint8_t)(gpio_get_level(TOUCH_SENSOR_GPIO) == 0); // active low capacitive touch
|
||||
|
||||
state_set_sensors(light, touch);
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // poll every 100ms
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Ding Dong Audio Project Starting Up...");
|
||||
|
||||
// Initialize the centralized Unified State Manager (USM)
|
||||
state_manager_init();
|
||||
|
||||
// Initialize NVS storage (required for storing Wi-Fi configurations)
|
||||
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);
|
||||
|
||||
// 1. Initialize board support package (audio codec, I2C master, and I2S)
|
||||
ESP_ERROR_CHECK(esp_board_init(16000, 2, 16));
|
||||
|
||||
// 2. Initialize TCA9555 IO expander driver (expands GPIO pins for key buttons/amplifiers)
|
||||
tca9555_driver_init();
|
||||
|
||||
// 3. Mount SPIFFS containing startup.mp3
|
||||
mount_spiffs();
|
||||
|
||||
// 4. Initialize simple audio player pipeline
|
||||
Audio_Play_Init();
|
||||
|
||||
// Adjust volume (0 to 100)
|
||||
Volume_Adjustment(85);
|
||||
|
||||
// 5. Start background task for the smooth rainbow LED fade
|
||||
xTaskCreate(led_rainbow_task, "led_rainbow_task", 4096, NULL, 5, NULL);
|
||||
|
||||
#ifdef MODEL_PROTO_1
|
||||
// 5b. Start background task to poll PROTO-1 hardware sensors (light & touch)
|
||||
xTaskCreate(proto1_sensor_task, "proto1_sensor_task", 4096, NULL, 4, NULL);
|
||||
#endif
|
||||
|
||||
// 6. Play startup.mp3 from SPIFFS partition (blocking until it finishes)
|
||||
ESP_LOGI(TAG, "Playing startup audio: 'Ding Dong, I'm turned on!'");
|
||||
esp_gmf_err_t err = Audio_Play_Music_To_End("file://spiffs/startup.mp3");
|
||||
if (err != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start audio playback: %d", err);
|
||||
}
|
||||
|
||||
// 7. Play connecting chime (blocking until it finishes)
|
||||
ESP_LOGI(TAG, "Playing connecting audio...");
|
||||
err = Audio_Play_Music_To_End("file://spiffs/connecting_wifi.mp3");
|
||||
if (err != ESP_GMF_ERR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to play connecting audio: %d", err);
|
||||
}
|
||||
|
||||
// 8. Connect to Wi-Fi using credentials in wifi-credentials
|
||||
ESP_LOGI(TAG, "Starting Wi-Fi connection...");
|
||||
if (wifi_init_sta() == ESP_OK) {
|
||||
// 9. Attempt to connect to dev server
|
||||
ESP_LOGI(TAG, "Wi-Fi connected. Connecting to dev server...");
|
||||
socket_client_init();
|
||||
}
|
||||
|
||||
// Keep main task alive and monitor state
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
esp_asp_state_t state = Audio_Get_Current_State();
|
||||
ESP_LOGD(TAG, "Audio player state: %s", esp_audio_simple_player_state_to_str(state));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_https_ota.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
|
||||
#include "ota_manager.h"
|
||||
#include "socket_client.h" // for socket_send_raw()
|
||||
#include "state_manager.h" // for state_set_led()
|
||||
#include "esp_crt_bundle.h" // for esp_crt_bundle_attach()
|
||||
|
||||
static const char *TAG = "ota_manager";
|
||||
|
||||
// Guard against concurrent OTA runs
|
||||
static volatile bool s_ota_running = false;
|
||||
|
||||
// URL is copied here so the task owns its memory
|
||||
#define OTA_URL_MAX_LEN 256
|
||||
static char s_ota_url[OTA_URL_MAX_LEN];
|
||||
|
||||
|
||||
static esp_err_t ota_http_event(esp_http_client_event_t *evt)
|
||||
{
|
||||
if (evt->event_id == HTTP_EVENT_ERROR ||
|
||||
evt->event_id == HTTP_EVENT_DISCONNECTED) {
|
||||
|
||||
int tls_error = 0;
|
||||
int tls_flags = 0;
|
||||
|
||||
esp_err_t tls_result =
|
||||
esp_http_client_get_and_clear_last_tls_error(
|
||||
evt->client,
|
||||
&tls_error,
|
||||
&tls_flags
|
||||
);
|
||||
|
||||
ESP_LOGE(
|
||||
"OTA",
|
||||
"HTTP failure: tls_result=%s (0x%x), "
|
||||
"tls_error=0x%x, tls_flags=0x%x, errno=%d",
|
||||
esp_err_to_name(tls_result),
|
||||
tls_result,
|
||||
tls_error,
|
||||
tls_flags,
|
||||
esp_http_client_get_errno(evt->client)
|
||||
);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* OTA task
|
||||
* ------------------------------------------------------------------ */
|
||||
static void ota_task(void *pvParam)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
ESP_LOGI(TAG, "OTA task started. Firmware URL: %s", s_ota_url);
|
||||
|
||||
// Pulse blue slowly during update
|
||||
state_set_led(LED_STATE_UPDATING);
|
||||
|
||||
// ---- Configure HTTP client ----
|
||||
esp_http_client_config_t http_cfg = {
|
||||
.url = s_ota_url,
|
||||
.event_handler = ota_http_event,
|
||||
.timeout_ms = 30000,
|
||||
.keep_alive_enable = true,
|
||||
.max_redirection_count = 5,
|
||||
.skip_cert_common_name_check = false, // Keep false to ensure SNI remains enabled for Cloudflare Host mapping
|
||||
.crt_bundle_attach = esp_crt_bundle_attach, // Use system root CAs to validate Cloudflare certificates
|
||||
};
|
||||
|
||||
esp_https_ota_config_t ota_cfg = {
|
||||
.http_config = &http_cfg,
|
||||
};
|
||||
|
||||
// Gate HTTPS OTA until system clock is synchronized (prevents TLS certificate validity window checks from failing)
|
||||
bool is_https = (strncmp(s_ota_url, "https", 5) == 0);
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (is_https) {
|
||||
if (now < 1704067200) {
|
||||
ESP_LOGI(TAG, "Waiting for system clock synchronization (up to 15s)...");
|
||||
esp_err_t sync_err = esp_netif_sntp_sync_wait(pdMS_TO_TICKS(15000));
|
||||
time(&now);
|
||||
if (sync_err != ESP_OK || now < 1704067200) {
|
||||
ESP_LOGE(TAG, "HTTPS OTA blocked: system clock not synchronized");
|
||||
socket_send_ota_error("System clock not synchronized");
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log diagnostics: exact URL, current Unix time, total heap, internal heap
|
||||
ESP_LOGI(TAG, "Diagnostics before OTA begin: URL=%s, unix_time=%ld, free_heap_total=%d, free_heap_internal=%d",
|
||||
s_ota_url, (long)now, (int)esp_get_free_heap_size(), (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
|
||||
// Enable verbose TLS logging to pinpoint exactly where the handshake fails
|
||||
esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("mbedtls", ESP_LOG_VERBOSE);
|
||||
|
||||
// ---- Begin OTA handle ----
|
||||
esp_https_ota_handle_t ota_handle = NULL;
|
||||
err = esp_https_ota_begin(&ota_cfg, &ota_handle);
|
||||
if (err != ESP_OK || ota_handle == NULL) {
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_begin failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Get image size for progress reporting ----
|
||||
int image_size = esp_https_ota_get_image_size(ota_handle);
|
||||
if (image_size <= 0) {
|
||||
// Some servers don't send Content-Length; treat as unknown
|
||||
image_size = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Firmware image size: %d bytes", image_size);
|
||||
|
||||
// ---- Stream and flash ----
|
||||
int bytes_written = 0;
|
||||
int last_reported_pct = -1;
|
||||
|
||||
while (1) {
|
||||
err = esp_https_ota_perform(ota_handle);
|
||||
if (err == ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
|
||||
bytes_written = esp_https_ota_get_image_len_read(ota_handle);
|
||||
|
||||
// Report progress every 5% (or every ~32KB if size unknown)
|
||||
int pct = (image_size > 0)
|
||||
? (int)((bytes_written * 100LL) / image_size)
|
||||
: -1;
|
||||
|
||||
if (pct >= 0 && pct != last_reported_pct && (pct % 5 == 0)) {
|
||||
socket_send_ota_progress(pct, bytes_written, image_size);
|
||||
last_reported_pct = pct;
|
||||
ESP_LOGI(TAG, "OTA progress: %d%% (%d / %d bytes)", pct, bytes_written, image_size);
|
||||
} else if (pct < 0 && (bytes_written - (last_reported_pct * 32768)) >= 32768) {
|
||||
socket_send_ota_progress(0, bytes_written, 0);
|
||||
last_reported_pct = bytes_written / 32768;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == ESP_OK) {
|
||||
// Download complete
|
||||
break;
|
||||
}
|
||||
|
||||
// Any other error is fatal
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_perform failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
esp_https_ota_abort(ota_handle);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
bytes_written = esp_https_ota_get_image_len_read(ota_handle);
|
||||
|
||||
// ---- Validate and commit ----
|
||||
if (!esp_https_ota_is_complete_data_received(ota_handle)) {
|
||||
const char *reason = "Incomplete image received";
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
esp_https_ota_abort(ota_handle);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
err = esp_https_ota_finish(ota_handle);
|
||||
if (err != ESP_OK) {
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_finish failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Success ----
|
||||
socket_send_ota_progress(100, bytes_written, bytes_written);
|
||||
ESP_LOGI(TAG, "OTA complete! %d bytes written. Rebooting in 500ms...", bytes_written);
|
||||
socket_send_ota_complete();
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Public API
|
||||
* ------------------------------------------------------------------ */
|
||||
void ota_start(const char *url)
|
||||
{
|
||||
if (s_ota_running) {
|
||||
ESP_LOGW(TAG, "OTA already in progress, ignoring request.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (url == NULL || strlen(url) == 0) {
|
||||
ESP_LOGE(TAG, "ota_start called with empty URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy URL before spawning task
|
||||
strlcpy(s_ota_url, url, sizeof(s_ota_url));
|
||||
|
||||
s_ota_running = true;
|
||||
|
||||
ESP_LOGI(TAG, "Spawning OTA task for URL: %s", s_ota_url);
|
||||
|
||||
BaseType_t ret = xTaskCreate(
|
||||
ota_task,
|
||||
"ota_task",
|
||||
8192, // 8KB stack — enough for HTTP + flash ops
|
||||
NULL,
|
||||
5, // Priority: slightly above normal tasks
|
||||
NULL
|
||||
);
|
||||
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create OTA task");
|
||||
s_ota_running = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Start an OTA update from the given HTTP/HTTPS URL.
|
||||
*
|
||||
* Spawns a background FreeRTOS task that:
|
||||
* 1. Downloads the firmware binary from `url` using esp_http_client
|
||||
* 2. Writes it to the inactive OTA partition via esp_ota_ops
|
||||
* 3. Reports progress back to the server via WebSocket ota_progress messages
|
||||
* 4. On success: sends ota_complete, waits 500ms, then reboots
|
||||
* 5. On failure: sends ota_error with reason string
|
||||
*
|
||||
* HTTPS connections use the system certificate bundle
|
||||
* (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL) which includes all
|
||||
* standard root CAs and works with Cloudflare, AWS, etc.
|
||||
*
|
||||
* Only one OTA task may run at a time. Subsequent calls while one is
|
||||
* already running are silently ignored.
|
||||
*
|
||||
* @param url Null-terminated HTTP/HTTPS URL string for the firmware binary.
|
||||
* The string is copied internally; the caller may free it.
|
||||
*/
|
||||
void ota_start(const char *url);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,384 @@
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_crt_bundle.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#include "socket_client.h"
|
||||
#include "audio_driver.h"
|
||||
#include "wifi_connect.h"
|
||||
#include "state_manager.h"
|
||||
#include "device_config.h"
|
||||
#include "ota_manager.h"
|
||||
|
||||
static const char *TAG = "socket_client";
|
||||
|
||||
static EventGroupHandle_t s_ws_event_group;
|
||||
#define WS_CONNECTED_BIT BIT0
|
||||
|
||||
static esp_websocket_client_handle_t s_client = NULL;
|
||||
|
||||
static void send_telemetry(void)
|
||||
{
|
||||
if (s_client == NULL || !(xEventGroupGetBits(s_ws_event_group) & WS_CONNECTED_BIT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
device_state_t st;
|
||||
state_get_current(&st);
|
||||
|
||||
int free_heap = esp_get_free_heap_size();
|
||||
int uptime = esp_timer_get_time() / 1000000; // seconds
|
||||
int rssi = 0;
|
||||
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
rssi = ap_info.rssi;
|
||||
}
|
||||
|
||||
char payload[512];
|
||||
int len = snprintf(payload, sizeof(payload),
|
||||
"{\"type\":\"state_report\",\"system\":{\"model\":\"%s\"},\"health\":{\"free_heap_bytes\":%d,\"uptime_seconds\":%d},\"network\":{\"wifi_rssi\":%d},\"peripherals\":{\"audio\":{\"volume\":%d},\"led_ring\":{\"state_id\":%d,\"brightness_coef\":%.2f},\"haptic_intensity\":%d,\"ambient_light\":%d,\"touch_active\":%s}}",
|
||||
DEVICE_MODEL, free_heap, uptime, rssi, st.volume, st.led_state, st.led_brightness,
|
||||
st.haptic_intensity, st.ambient_light, st.touch_active ? "true" : "false");
|
||||
|
||||
if (len > 0 && len < sizeof(payload)) {
|
||||
ESP_LOGI(TAG, "Sending state report: %s", payload);
|
||||
esp_websocket_client_send_text(s_client, payload, len, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_telemetry_broadcast(void)
|
||||
{
|
||||
send_telemetry();
|
||||
}
|
||||
|
||||
static char *extract_json_string(const char *json, const char *key)
|
||||
{
|
||||
char *key_pos = strstr(json, key);
|
||||
if (!key_pos) return NULL;
|
||||
|
||||
// Find the colon after key
|
||||
char *colon = strchr(key_pos + strlen(key), ':');
|
||||
if (!colon) return NULL;
|
||||
|
||||
// Find the opening quote of the string value
|
||||
char *start_quote = strchr(colon, '"');
|
||||
if (!start_quote) return NULL;
|
||||
|
||||
// Find the closing quote, making sure we handle escaped quotes \"
|
||||
char *end_ptr = start_quote + 1;
|
||||
while (*end_ptr) {
|
||||
if (*end_ptr == '"' && *(end_ptr - 1) != '\\') {
|
||||
break;
|
||||
}
|
||||
end_ptr++;
|
||||
}
|
||||
if (*end_ptr != '"') return NULL;
|
||||
|
||||
size_t raw_len = end_ptr - start_quote - 1;
|
||||
char *out = malloc(raw_len + 1);
|
||||
if (!out) return NULL;
|
||||
|
||||
// Unescape the string while copying
|
||||
size_t out_idx = 0;
|
||||
for (size_t i = 0; i < raw_len; i++) {
|
||||
if (start_quote[1 + i] == '\\' && i + 1 < raw_len) {
|
||||
char next_char = start_quote[1 + i + 1];
|
||||
if (next_char == 'n') {
|
||||
out[out_idx++] = '\n';
|
||||
i++;
|
||||
} else if (next_char == 'r') {
|
||||
out[out_idx++] = '\r';
|
||||
i++;
|
||||
} else if (next_char == 't') {
|
||||
out[out_idx++] = '\t';
|
||||
i++;
|
||||
} else if (next_char == '\\' || next_char == '"' || next_char == '/') {
|
||||
out[out_idx++] = next_char;
|
||||
i++;
|
||||
} else {
|
||||
out[out_idx++] = '\\';
|
||||
}
|
||||
} else {
|
||||
out[out_idx++] = start_quote[1 + i];
|
||||
}
|
||||
}
|
||||
out[out_idx] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
|
||||
switch (event_id) {
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Websocket connected to server!");
|
||||
xEventGroupSetBits(s_ws_event_group, WS_CONNECTED_BIT);
|
||||
state_set_websocket_connected(true);
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Websocket disconnected!");
|
||||
xEventGroupClearBits(s_ws_event_group, WS_CONNECTED_BIT);
|
||||
state_set_websocket_connected(false);
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DATA:
|
||||
if (data->data_ptr != NULL && data->data_len > 0) {
|
||||
ESP_LOGI(TAG, "Received message: %.*s", data->data_len, (char *)data->data_ptr);
|
||||
|
||||
char *msg_str = malloc(data->data_len + 1);
|
||||
if (msg_str != NULL) {
|
||||
memcpy(msg_str, data->data_ptr, data->data_len);
|
||||
msg_str[data->data_len] = '\0';
|
||||
|
||||
if (strstr(msg_str, "\"set_volume\"") != NULL) {
|
||||
char *val_ptr = strstr(msg_str, "\"value\"");
|
||||
if (val_ptr != NULL) {
|
||||
int level = -1;
|
||||
if (sscanf(val_ptr, "\"value\"%*[^0-9]%d", &level) == 1) {
|
||||
if (level >= 0 && level <= 100) {
|
||||
ESP_LOGI(TAG, "Parsed set_volume command. Mutating state to: %d", level);
|
||||
state_set_volume((uint8_t)level);
|
||||
}
|
||||
}
|
||||
}
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"set_led\"") != NULL) {
|
||||
char *val_ptr = strstr(msg_str, "\"value\"");
|
||||
if (val_ptr != NULL) {
|
||||
if (strstr(val_ptr, "\"rainbow\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed set_led command: rainbow");
|
||||
state_set_led(LED_STATE_CONNECTED);
|
||||
} else if (strstr(val_ptr, "\"connecting\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed set_led command: connecting");
|
||||
state_set_led(LED_STATE_SERVER_CONNECTING);
|
||||
} else if (strstr(val_ptr, "\"failed\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed set_led command: failed");
|
||||
state_set_led(LED_STATE_SERVER_FAILED);
|
||||
} else if (strstr(val_ptr, "\"idle\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed set_led command: idle");
|
||||
state_set_led(LED_STATE_OFF);
|
||||
} else if (strstr(val_ptr, "\"updating\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed set_led command: updating");
|
||||
state_set_led(LED_STATE_UPDATING);
|
||||
}
|
||||
}
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"play_audio\"") != NULL) {
|
||||
char *val_ptr = strstr(msg_str, "\"value\"");
|
||||
if (val_ptr != NULL) {
|
||||
if (strstr(val_ptr, "\"startup\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed play_audio command: startup");
|
||||
Audio_Play_Music_To_End("file://spiffs/startup.mp3");
|
||||
} else if (strstr(val_ptr, "\"wifi_connecting\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed play_audio command: wifi_connecting");
|
||||
Audio_Play_Music_To_End("file://spiffs/connecting_wifi.mp3");
|
||||
} else if (strstr(val_ptr, "\"wifi_connected\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed play_audio command: wifi_connected");
|
||||
Audio_Play_Music_To_End("file://spiffs/connected_wifi.mp3");
|
||||
}
|
||||
}
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"set_brightness\"") != NULL) {
|
||||
char *val_ptr = strstr(msg_str, "\"value\"");
|
||||
if (val_ptr != NULL) {
|
||||
float brightness = -1.0f;
|
||||
if (sscanf(val_ptr, "\"value\"%*[^0-9.-]%f", &brightness) == 1) {
|
||||
if (brightness >= 0.0f && brightness <= 1.0f) {
|
||||
ESP_LOGI(TAG, "Parsed set_brightness command. Mutating state to: %f", brightness);
|
||||
state_set_brightness(brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"set_haptics\"") != NULL) {
|
||||
char *val_ptr = strstr(msg_str, "\"value\"");
|
||||
if (val_ptr != NULL) {
|
||||
int intensity = -1;
|
||||
if (sscanf(val_ptr, "\"value\"%*[^0-9]%d", &intensity) == 1) {
|
||||
if (intensity >= 0 && intensity <= 100) {
|
||||
ESP_LOGI(TAG, "Parsed set_haptics command. Mutating state to: %d", intensity);
|
||||
state_set_haptic_intensity((uint8_t)intensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"query_state\"") != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed query_state command. Dispatching telemetry update...");
|
||||
send_telemetry();
|
||||
}
|
||||
else if (strstr(msg_str, "\"start_ota\"") != NULL) {
|
||||
char *url = extract_json_string(msg_str, "\"url\"");
|
||||
|
||||
if (url != NULL) {
|
||||
ESP_LOGI(TAG, "Parsed start_ota command. URL: %s", url);
|
||||
ota_start(url);
|
||||
free(url);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "start_ota: missing url field");
|
||||
}
|
||||
}
|
||||
free(msg_str);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "Websocket error occurred!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void socket_client_task(void *pvParameters)
|
||||
{
|
||||
bool was_connected = false;
|
||||
|
||||
while (1) {
|
||||
// Wait until Wi-Fi is connected
|
||||
while (!wifi_is_connected()) {
|
||||
state_set_wifi_connected(false);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
state_set_wifi_connected(true);
|
||||
|
||||
EventBits_t bits = xEventGroupGetBits(s_ws_event_group);
|
||||
if (!(bits & WS_CONNECTED_BIT)) {
|
||||
// We are not connected!
|
||||
if (was_connected) {
|
||||
// We lost connection!
|
||||
ESP_LOGW(TAG, "WebSocket connection lost! Playing alert...");
|
||||
state_set_led(LED_STATE_SERVER_CONNECTING);
|
||||
Audio_Play_Music_To_End("file://spiffs/disconnected_server.mp3");
|
||||
was_connected = false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Attempting WebSocket connection...");
|
||||
state_set_led(LED_STATE_SERVER_CONNECTING);
|
||||
|
||||
// Play connecting audio: "Connecting to the dev server"
|
||||
Audio_Play_Music_To_End("file://spiffs/connecting_server.mp3");
|
||||
|
||||
// Stop (if running) and start connection
|
||||
esp_websocket_client_stop(s_client);
|
||||
esp_websocket_client_start(s_client);
|
||||
|
||||
// Wait up to 10 seconds for connection
|
||||
ESP_LOGI(TAG, "Waiting for websocket connection...");
|
||||
bits = xEventGroupWaitBits(s_ws_event_group, WS_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
|
||||
if (bits & WS_CONNECTED_BIT) {
|
||||
ESP_LOGI(TAG, "WebSocket connected successfully.");
|
||||
was_connected = true;
|
||||
state_set_led(LED_STATE_CONNECTED);
|
||||
Audio_Play_Music_To_End("file://spiffs/connected_server.mp3");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to connect to dev server within 10 seconds!");
|
||||
state_set_led(LED_STATE_SERVER_FAILED);
|
||||
esp_websocket_client_stop(s_client);
|
||||
Audio_Play_Music_To_End("file://spiffs/connect_server_fail.mp3");
|
||||
|
||||
// Play "Still not working, let me try again in a moment"
|
||||
Audio_Play_Music_To_End("file://spiffs/retry_later.mp3");
|
||||
|
||||
// Wait 10 seconds before next connection attempt
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
} else {
|
||||
// Already connected! Keep monitoring
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void socket_client_init(void)
|
||||
{
|
||||
s_ws_event_group = xEventGroupCreate();
|
||||
|
||||
// Load WebSocket configuration from SPIFFS
|
||||
char url_buf[128] = {0};
|
||||
FILE *f = fopen("/spiffs/server_url.txt", "r");
|
||||
if (f != NULL) {
|
||||
if (fgets(url_buf, sizeof(url_buf), f) != NULL) {
|
||||
// Strip trailing whitespace/newlines
|
||||
size_t len = strlen(url_buf);
|
||||
while (len > 0 && (url_buf[len - 1] == '\n' || url_buf[len - 1] == '\r' || url_buf[len - 1] == ' ')) {
|
||||
url_buf[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
ESP_LOGI(TAG, "Loaded WebSocket URI from SPIFFS: %s", url_buf);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
const char *ws_uri = "wss://example.com"; // Replace with your deployment endpoint
|
||||
if (strlen(url_buf) > 0) {
|
||||
ws_uri = url_buf;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "No server_url.txt configuration found or empty, using default: %s", ws_uri);
|
||||
}
|
||||
|
||||
esp_websocket_client_config_t websocket_cfg = {
|
||||
.uri = ws_uri,
|
||||
.buffer_size = 4096,
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Initializing WebSocket client...");
|
||||
s_client = esp_websocket_client_init(&websocket_cfg);
|
||||
|
||||
// Register event handler
|
||||
ESP_ERROR_CHECK(esp_websocket_register_events(s_client, WEBSOCKET_EVENT_ANY, websocket_event_handler, NULL));
|
||||
|
||||
// Create the background connection task
|
||||
ESP_LOGI(TAG, "Starting WebSocket monitor task...");
|
||||
xTaskCreate(socket_client_task, "socket_client_task", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* OTA WebSocket message helpers
|
||||
* Called from ota_manager.c to report OTA state back to the server.
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
void socket_send_ota_progress(int percent, int bytes_written, int bytes_total)
|
||||
{
|
||||
if (s_client == NULL || !(xEventGroupGetBits(s_ws_event_group) & WS_CONNECTED_BIT)) {
|
||||
return;
|
||||
}
|
||||
char buf[128];
|
||||
int len = snprintf(buf, sizeof(buf),
|
||||
"{\"type\":\"ota_progress\",\"percent\":%d,\"bytes_written\":%d,\"bytes_total\":%d}",
|
||||
percent, bytes_written, bytes_total);
|
||||
if (len > 0 && len < (int)sizeof(buf)) {
|
||||
esp_websocket_client_send_text(s_client, buf, len, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void socket_send_ota_complete(void)
|
||||
{
|
||||
if (s_client == NULL || !(xEventGroupGetBits(s_ws_event_group) & WS_CONNECTED_BIT)) {
|
||||
return;
|
||||
}
|
||||
const char *msg = "{\"type\":\"ota_complete\"}";
|
||||
esp_websocket_client_send_text(s_client, msg, strlen(msg), portMAX_DELAY);
|
||||
}
|
||||
|
||||
void socket_send_ota_error(const char *reason)
|
||||
{
|
||||
if (s_client == NULL || !(xEventGroupGetBits(s_ws_event_group) & WS_CONNECTED_BIT)) {
|
||||
return;
|
||||
}
|
||||
char buf[192];
|
||||
int len = snprintf(buf, sizeof(buf),
|
||||
"{\"type\":\"ota_error\",\"reason\":\"%s\"}", reason ? reason : "unknown");
|
||||
if (len > 0 && len < (int)sizeof(buf)) {
|
||||
esp_websocket_client_send_text(s_client, buf, len, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initializes the websocket client and starts the connection process
|
||||
void socket_client_init(void);
|
||||
|
||||
// Triggers an immediate WebSocket telemetry report push
|
||||
void trigger_telemetry_broadcast(void);
|
||||
|
||||
// OTA progress/result messages (called from ota_manager.c)
|
||||
void socket_send_ota_progress(int percent, int bytes_written, int bytes_total);
|
||||
void socket_send_ota_complete(void);
|
||||
void socket_send_ota_error(const char *reason);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
wss://example.com
|
||||
Binary file not shown.
@@ -0,0 +1,167 @@
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "state_manager.h"
|
||||
#include "audio_driver.h"
|
||||
#include "wifi_connect.h"
|
||||
#include "socket_client.h"
|
||||
|
||||
static const char *TAG = "state_manager";
|
||||
|
||||
// Centralized state container protected by a mutex
|
||||
static device_state_t s_device_state;
|
||||
static SemaphoreHandle_t s_state_mutex = NULL;
|
||||
|
||||
void state_manager_init(void)
|
||||
{
|
||||
s_state_mutex = xSemaphoreCreateMutex();
|
||||
|
||||
// Set initial boot default states
|
||||
s_device_state.volume = 85;
|
||||
s_device_state.led_state = LED_STATE_OFF;
|
||||
s_device_state.led_brightness = 0.2f;
|
||||
s_device_state.wifi_connected = false;
|
||||
s_device_state.websocket_connected = false;
|
||||
s_device_state.haptic_intensity = 0;
|
||||
s_device_state.ambient_light = 0;
|
||||
s_device_state.touch_active = 0;
|
||||
|
||||
ESP_LOGI(TAG, "State Manager initialized successfully.");
|
||||
}
|
||||
|
||||
void state_get_current(device_state_t *out_state)
|
||||
{
|
||||
if (s_state_mutex == NULL) {
|
||||
return;
|
||||
}
|
||||
if (xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
memcpy(out_state, &s_device_state, sizeof(device_state_t));
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_volume(uint8_t volume)
|
||||
{
|
||||
if (volume > 100) volume = 100;
|
||||
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.volume != volume) {
|
||||
s_device_state.volume = volume;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "Volume state changed to %d. Updating DAC codec driver...", volume);
|
||||
Volume_Adjustment(volume);
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_led(led_state_t state)
|
||||
{
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.led_state != state) {
|
||||
s_device_state.led_state = state;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "LED state changed to %d. Updating visual animation thread...", state);
|
||||
set_led_state(state);
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_brightness(float brightness)
|
||||
{
|
||||
if (brightness < 0.0f) brightness = 0.0f;
|
||||
if (brightness > 1.0f) brightness = 1.0f;
|
||||
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.led_brightness != brightness) {
|
||||
s_device_state.led_brightness = brightness;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "LED brightness state changed to %f", brightness);
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_wifi_connected(bool connected)
|
||||
{
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.wifi_connected != connected) {
|
||||
s_device_state.wifi_connected = connected;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "Wi-Fi connection state changed to: %s", connected ? "CONNECTED" : "DISCONNECTED");
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_websocket_connected(bool connected)
|
||||
{
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.websocket_connected != connected) {
|
||||
s_device_state.websocket_connected = connected;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "WebSocket server state changed to: %s", connected ? "CONNECTED" : "DISCONNECTED");
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_haptic_intensity(uint8_t intensity)
|
||||
{
|
||||
if (intensity > 100) intensity = 100;
|
||||
|
||||
bool changed = false;
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_device_state.haptic_intensity != intensity) {
|
||||
s_device_state.haptic_intensity = intensity;
|
||||
changed = true;
|
||||
}
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "Haptic intensity changed to %d", intensity);
|
||||
#ifdef MODEL_PROTO_1
|
||||
extern void proto1_haptics_set_intensity(uint8_t intensity);
|
||||
proto1_haptics_set_intensity(intensity);
|
||||
#endif
|
||||
trigger_telemetry_broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void state_set_sensors(uint16_t ambient_light, uint8_t touch_active)
|
||||
{
|
||||
if (s_state_mutex != NULL && xSemaphoreTake(s_state_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
s_device_state.ambient_light = ambient_light;
|
||||
s_device_state.touch_active = touch_active;
|
||||
xSemaphoreGive(s_state_mutex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "wifi_connect.h" // for led_state_t definition
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t volume;
|
||||
uint8_t led_state; // Explicit size to avoid enum size mismatch
|
||||
float led_brightness;
|
||||
uint8_t wifi_connected; // Explicit size to avoid bool size mismatch
|
||||
uint8_t websocket_connected; // Explicit size to avoid bool size mismatch
|
||||
uint8_t haptic_intensity; // Haptic PWM intensity (0-100)
|
||||
uint16_t ambient_light; // Analog light value (0-4095)
|
||||
uint8_t touch_active; // Touch sensor input state (0 or 1)
|
||||
} device_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the state manager, locks, and default values.
|
||||
*/
|
||||
void state_manager_init(void);
|
||||
|
||||
/**
|
||||
* @brief Thread-safe query to get a copy of the current system states.
|
||||
*/
|
||||
void state_get_current(device_state_t *out_state);
|
||||
|
||||
/* --- Setter Mutators (Trigger Hooks & Hardware Actions) --- */
|
||||
void state_set_volume(uint8_t volume);
|
||||
void state_set_led(led_state_t state);
|
||||
void state_set_brightness(float brightness);
|
||||
void state_set_wifi_connected(bool connected);
|
||||
void state_set_websocket_connected(bool connected);
|
||||
void state_set_haptic_intensity(uint8_t intensity);
|
||||
void state_set_sensors(uint16_t ambient_light, uint8_t touch_active);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "tca9555_driver.h"
|
||||
#include "esp_log.h"
|
||||
#include "bsp_board.h"
|
||||
|
||||
|
||||
esp_io_expander_handle_t io_expander = NULL;
|
||||
|
||||
|
||||
void tca9555_driver_init(void)
|
||||
{
|
||||
|
||||
i2c_master_bus_handle_t i2c_bus = esp_ret_i2c_handle();
|
||||
esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_expander);
|
||||
|
||||
/* Test output level function */
|
||||
ret = esp_io_expander_set_dir(io_expander, (IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_5 | IO_EXPANDER_PIN_NUM_6 | IO_EXPANDER_PIN_NUM_8), IO_EXPANDER_OUTPUT);
|
||||
if(ret != ESP_OK){
|
||||
printf("EXIO Mode setting failure!!\r\n");
|
||||
}
|
||||
|
||||
ret = esp_io_expander_set_dir(io_expander, (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_9 | IO_EXPANDER_PIN_NUM_10 | IO_EXPANDER_PIN_NUM_11), IO_EXPANDER_INPUT);
|
||||
if(ret != ESP_OK){
|
||||
printf("EXIO Mode setting failure!!\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************************************************** Set the EXIO output status **********************************************************/
|
||||
void Set_EXIO(uint32_t Pin,uint8_t State) // Sets the level state of the Pin without affecting the other pins(PIN:1~8)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = esp_io_expander_set_level(io_expander, Pin, State);
|
||||
if(ret != ESP_OK){
|
||||
printf("EXIO level setting failure!!\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
/********************************************************** Read EXIO status **********************************************************/
|
||||
bool Read_EXIO(uint32_t Pin) // Read the level of the TCA9555PWR Pin
|
||||
{
|
||||
|
||||
esp_err_t ret;
|
||||
uint32_t input_level_mask = 0;
|
||||
ret = esp_io_expander_get_level(io_expander, Pin, &input_level_mask);
|
||||
if(ret != ESP_OK){
|
||||
printf("EXIO level reading failure!!\r\n");
|
||||
}
|
||||
bool bitStatus = input_level_mask & Pin;
|
||||
return bitStatus;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern esp_io_expander_handle_t io_expander;
|
||||
void tca9555_driver_init(void);
|
||||
void Set_EXIO(uint32_t Pin,uint8_t State);
|
||||
bool Read_EXIO(uint32_t Pin);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,174 @@
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
|
||||
#include "wifi_connect.h"
|
||||
#include "wifi_credentials.h"
|
||||
#include "audio_driver.h"
|
||||
|
||||
#define ESP_MAXIMUM_RETRY 5
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected */
|
||||
static EventGroupHandle_t s_wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event, but we only care about two events:
|
||||
* - we are connected to the AP with an IP
|
||||
* - we failed to connect after the maximum amount of retries */
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
static const char *TAG = "wifi station";
|
||||
|
||||
static bool s_was_connected = false;
|
||||
static bool s_first_time_connect = true;
|
||||
|
||||
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) {
|
||||
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
ESP_LOGI(TAG, "Disconnected from AP");
|
||||
} 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:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
static void init_sntp(void)
|
||||
{
|
||||
ESP_LOGI("SNTP", "Initializing SNTP...");
|
||||
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
|
||||
ESP_ERROR_CHECK(esp_netif_sntp_init(&config));
|
||||
}
|
||||
|
||||
static void wifi_manager_task(void *pvParameters)
|
||||
{
|
||||
while (1) {
|
||||
EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
|
||||
if (!(bits & WIFI_CONNECTED_BIT)) {
|
||||
// We are not connected!
|
||||
if (s_was_connected) {
|
||||
// We lost connection!
|
||||
ESP_LOGW(TAG, "Wi-Fi connection lost! Playing alert...");
|
||||
set_led_state(LED_STATE_WIFI_CONNECTING);
|
||||
Audio_Play_Music_To_End("file://spiffs/disconnected_wifi.mp3");
|
||||
s_was_connected = false;
|
||||
}
|
||||
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
while (retry_count < ESP_MAXIMUM_RETRY) {
|
||||
ESP_LOGI(TAG, "Attempting Wi-Fi connection (try %d/%d)...", retry_count + 1, ESP_MAXIMUM_RETRY);
|
||||
set_led_state(LED_STATE_WIFI_CONNECTING);
|
||||
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
esp_wifi_connect();
|
||||
|
||||
// Wait up to 6 seconds for connection bit
|
||||
bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(6000));
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
retry_count++;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
ESP_LOGI(TAG, "Wi-Fi Connected successfully.");
|
||||
s_was_connected = true;
|
||||
set_led_state(LED_STATE_SERVER_CONNECTING);
|
||||
|
||||
// Play connected audio prompt
|
||||
Audio_Play_Music_To_End("file://spiffs/connected_wifi.mp3");
|
||||
s_first_time_connect = false;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Wi-Fi connection failed. Retrying in 10 seconds...");
|
||||
set_led_state(LED_STATE_WIFI_FAILED);
|
||||
|
||||
// Play "Still not working, let me try again in a moment"
|
||||
Audio_Play_Music_To_End("file://spiffs/retry_later.mp3");
|
||||
|
||||
// Wait 10 seconds before next connection attempt
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
|
||||
// Play "Connecting to the Wi-Fi" chime before retrying
|
||||
Audio_Play_Music_To_End("file://spiffs/connecting_wifi.mp3");
|
||||
}
|
||||
} else {
|
||||
// Already connected! Keep monitoring
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool wifi_is_connected(void)
|
||||
{
|
||||
if (s_wifi_event_group == NULL) return false;
|
||||
EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
|
||||
return (bits & WIFI_CONNECTED_BIT) != 0;
|
||||
}
|
||||
|
||||
esp_err_t wifi_init_sta(void)
|
||||
{
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// Only create event loop if it hasn't been created yet
|
||||
esp_err_t err = esp_event_loop_create_default();
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
// Initialize SNTP exactly once during startup
|
||||
init_sntp();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
esp_event_handler_instance_t instance_any_id;
|
||||
esp_event_handler_instance_t instance_got_ip;
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&event_handler,
|
||||
NULL,
|
||||
&instance_any_id));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
||||
IP_EVENT_STA_GOT_IP,
|
||||
&event_handler,
|
||||
NULL,
|
||||
&instance_got_ip));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = WIFI_SSID,
|
||||
.password = WIFI_PASSWORD,
|
||||
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
|
||||
ESP_ERROR_CHECK(esp_wifi_start() );
|
||||
|
||||
ESP_LOGI(TAG, "Starting Wi-Fi manager task...");
|
||||
xTaskCreate(wifi_manager_task, "wifi_manager_task", 4096, NULL, 5, NULL);
|
||||
|
||||
// Wait blockingly on first boot for initial connection
|
||||
ESP_LOGI(TAG, "Waiting for initial Wi-Fi connection...");
|
||||
xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LED_STATE_OFF = 0,
|
||||
LED_STATE_WIFI_CONNECTING, // Flashing Red
|
||||
LED_STATE_WIFI_FAILED, // Solid Red
|
||||
LED_STATE_SERVER_CONNECTING, // Flashing Orange
|
||||
LED_STATE_SERVER_FAILED, // Solid Orange
|
||||
LED_STATE_CONNECTED, // Rainbow Rotate
|
||||
LED_STATE_UPDATING // Pulse Blue slowly (OTA flashing)
|
||||
} led_state_t;
|
||||
|
||||
// Set the current LED state to update visual indicators
|
||||
void set_led_state(led_state_t state);
|
||||
|
||||
// Get the current LED state
|
||||
led_state_t get_led_state(void);
|
||||
|
||||
|
||||
// Initializes Wi-Fi station mode and starts the connection process
|
||||
esp_err_t wifi_init_sta(void);
|
||||
|
||||
// Returns true if Wi-Fi is currently connected to the AP with an IP
|
||||
bool wifi_is_connected(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user