AIvoices/firmware-arduino/src/main.cpp
2025-10-23 00:57:10 +01:00

253 lines
No EOL
7.4 KiB
C++

#include "FactoryReset.h"
#include "LEDHandler.h"
#include "OTA.h"
#include "WifiManager.h"
#include <driver/touch_sensor.h>
#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();
}
}