#include "FactoryReset.h" #include "LEDHandler.h" #include "OTA.h" #include "WifiManager.h" #include #define TOUCH_THRESHOLD 28000 #define REQUIRED_RELEASE_CHECKS \ 100 // how many consecutive times we need "below threshold" to confirm release #define TOUCH_DEBOUNCE_DELAY 500 // milliseconds AsyncWebServer webServer(80); WIFIMANAGER WifiManager; esp_err_t getErr = ESP_OK; // Main Thread -> onButtonLongPressUpEventCb -> enterSleep() // Main Thread -> onButtonDoubleClickCb -> enterSleep() // Touch Task -> touchTask -> enterSleep() // Main Thread -> loop() (inactivity timeout) -> enterSleep() void enterSleep() { Serial.println("Going to sleep..."); // First, change device state to prevent any new data processing deviceState = SLEEP; scheduleListeningRestart = false; i2sOutputFlushScheduled = true; i2sInputFlushScheduled = true; vTaskDelay(10); // let all tasks accept state xSemaphoreTake(wsMutex, portMAX_DELAY); // Stop audio tasks first i2s_stop(I2S_PORT_IN); i2s_stop(I2S_PORT_OUT); // Properly disconnect WebSocket and wait for it to complete if (webSocket.isConnected()) { webSocket.disconnect(); // Give some time for the disconnect to process } xSemaphoreGive(wsMutex); delay(100); // Stop all tasks that might be using I2S or other peripherals i2s_driver_uninstall(I2S_PORT_IN); i2s_driver_uninstall(I2S_PORT_OUT); // Flush any remaining serial output Serial.flush(); #ifdef TOUCH_MODE touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ALL); while (touchRead(TOUCH_PAD_NUM2) > TOUCH_THRESHOLD) { delay(50); } delay(500); touchSleepWakeUpEnable(TOUCH_PAD_NUM2, TOUCH_THRESHOLD); #endif esp_deep_sleep_start(); delay(1000); } void processSleepRequest() { if (sleepRequested) { sleepRequested = false; enterSleep(); // Just call it directly - no state checking needed } } void printOutESP32Error(esp_err_t err) { switch (err) { case ESP_OK: Serial.println("ESP_OK no errors"); break; case ESP_ERR_INVALID_ARG: Serial.println("ESP_ERR_INVALID_ARG if the selected GPIO is not an RTC " "GPIO, or the mode is invalid"); break; case ESP_ERR_INVALID_STATE: Serial.println("ESP_ERR_INVALID_STATE if wakeup triggers conflict or " "wireless not stopped"); break; default: Serial.printf("Unknown error code: %d\n", err); break; } } static void onButtonLongPressUpEventCb(void *button_handle, void *usr_data) { Serial.println("Button long press end"); delay(10); sleepRequested = true; } static void onButtonDoubleClickCb(void *button_handle, void *usr_data) { Serial.println("Button double click"); delay(10); sleepRequested = true; } void getAuthTokenFromNVS() { preferences.begin("auth", false); authTokenGlobal = preferences.getString("auth_token", ""); preferences.end(); } void setupWiFi() { WifiManager.startBackgroundTask( "ELATO-DEVICE"); // Run the background task to take care of our Wifi WifiManager.fallbackToSoftAp( true); // Run a SoftAP if no known AP can be reached WifiManager.attachWebServer(&webServer); // Attach our API to the Webserver WifiManager.attachUI(); // Attach the UI to the Webserver // Run the Webserver and add your webpages to it webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->redirect("/wifi"); }); webServer.onNotFound([](AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); }); webServer.begin(); } void touchTask(void *parameter) { touch_pad_init(); touch_pad_config(TOUCH_PAD_NUM2); bool touched = false; unsigned long pressStartTime = 0; unsigned long lastTouchTime = 0; const unsigned long LONG_PRESS_DURATION = 500; // 500ms for long press while (1) { // Read the touch sensor uint32_t touchValue = touchRead(TOUCH_PAD_NUM2); bool isTouched = (touchValue > TOUCH_THRESHOLD); unsigned long currentTime = millis(); // Initial touch detection if (isTouched && !touched && (currentTime - lastTouchTime > TOUCH_DEBOUNCE_DELAY)) { touched = true; pressStartTime = currentTime; // Start timing the press lastTouchTime = currentTime; } // Check for long press while touched if (touched && isTouched) { if (currentTime - pressStartTime >= LONG_PRESS_DURATION) { sleepRequested = true; // Only enter sleep after 500ms of continuous touch } } // Release detection if (!isTouched && touched) { touched = false; pressStartTime = 0; // Reset the press timer } vTaskDelay(20); // Reduced from 50ms to 20ms for better responsiveness } vTaskDelete(NULL); } void setupDeviceMetadata() { // factoryResetDevice(); // resetAuth(); deviceState = IDLE; getAuthTokenFromNVS(); getOTAStatusFromNVS(); if (otaState == OTA_IN_PROGRESS || otaState == OTA_COMPLETE) { deviceState = OTA; } if (factory_reset_status) { deviceState = FACTORY_RESET; } } void setup() { Serial.begin(115200); delay(500); // SETUP setupDeviceMetadata(); wsMutex = xSemaphoreCreateMutex(); // INTERRUPT #ifdef TOUCH_MODE xTaskCreate(touchTask, "Touch Task", 4096, NULL, configMAX_PRIORITIES - 2, NULL); #else getErr = esp_sleep_enable_ext0_wakeup(BUTTON_PIN, LOW); printOutESP32Error(getErr); Button *btn = new Button(BUTTON_PIN, false); btn->attachLongPressUpEventCb(&onButtonLongPressUpEventCb, NULL); btn->attachDoubleClickEventCb(&onButtonDoubleClickCb, NULL); btn->detachSingleClickEvent(); #endif // Pin audio tasks to Core 1 (application core) xTaskCreatePinnedToCore(ledTask, // Function "LED Task", // Name 4096, // Stack size NULL, // Parameters 5, // Priority NULL, // Handle 1 // Core 1 (application core) ); xTaskCreatePinnedToCore(audioStreamTask, // Function "Speaker Task", // Name 4096, // Stack size NULL, // Parameters 3, // Priority NULL, // Handle 1 // Core 1 (application core) ); xTaskCreatePinnedToCore(micTask, // Function "Microphone Task", // Name 4096, // Stack size NULL, // Parameters 4, // Priority NULL, // Handle 1 // Core 1 (application core) ); // Pin network task to Core 0 (protocol core) xTaskCreatePinnedToCore(networkTask, // Function "Websocket Task", // Name 8192, // Stack size NULL, // Parameters configMAX_PRIORITIES - 1, // Highest priority &networkTaskHandle, // Handle 0 // Core 0 (protocol core) ); // WIFI setupWiFi(); } void loop() { processSleepRequest(); if (otaState == OTA_IN_PROGRESS) { loopOTA(); } }