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