Files
SmartSpeaker/main/main.c
T

408 lines
13 KiB
C

#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));
}
}