Initial sanitized SmartSpeaker snapshot

This commit is contained in:
ben
2026-07-13 21:13:19 -07:00
commit ac26dcc238
66 changed files with 11438 additions and 0 deletions
+73
View File
@@ -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)
+146
View File
@@ -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;
}
+36
View File
@@ -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
+684
View File
@@ -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;
}
+213
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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));
}
}
+248
View File
@@ -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;
}
}
+31
View File
@@ -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
+384
View File
@@ -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);
}
}
+22
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
wss://example.com
Binary file not shown.
+167
View File
@@ -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);
}
}
+43
View File
@@ -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
+50
View File
@@ -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(PIN1~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;
}
+19
View File
@@ -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
+174
View File
@@ -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;
}
+34
View File
@@ -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