From 0d43c5f892831340e6a4940461dd78125cb8f51c Mon Sep 17 00:00:00 2001 From: David Montero Date: Sat, 23 May 2026 16:19:48 +0200 Subject: [PATCH] fix: relax -Werror for user sketches + un-nest /* */ in robot-desktop-eyes Two related fixes for the ESP32 Arduino-compat compile path: (a) backend/app/services/esp-idf-template/main/CMakeLists.txt: Demote -Werror=comment / =parentheses / =sign-compare / =narrowing / =write-strings / =missing-field-initializers / =reorder back to plain warnings. ESP-IDF's project defaults are stricter than what Arduino/arduino-cli users expect, so common Arduino idioms (nested /* */, missing field initializers in struct literals, etc.) were failing builds that compile fine in the Arduino IDE. -Wall stays on; we just stop the abort. (b) examples-robot-desktop.ts (robot-desktop-eyes example): Replace the nested /* xTaskCreatePinnedToCore( ... /* Task function. */ ... */ block with `#if 0 / #endif` so the inner block comments don't terminate the outer one. Even with -Wno-error=comment the real-syntax-level issue (the first inner `*/` closes the outer comment, leaving the rest of the lines as bare code) would still bite, so this needs an actual code fix. --- .../esp-idf-template/main/CMakeLists.txt | 17 +++++++++++++++++ frontend/src/data/examples-robot-desktop.ts | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/app/services/esp-idf-template/main/CMakeLists.txt b/backend/app/services/esp-idf-template/main/CMakeLists.txt index 7319be84..47251ec9 100644 --- a/backend/app/services/esp-idf-template/main/CMakeLists.txt +++ b/backend/app/services/esp-idf-template/main/CMakeLists.txt @@ -67,6 +67,23 @@ if(DEFINED ENV{ARDUINO_ESP32_PATH}) target_compile_definitions(${COMPONENT_LIB} PRIVATE "LED_BUILTIN=2") endif() + + # Relax `-Werror=` for user sketches. ESP-IDF defaults treat several + # warning categories as errors (-Werror=comment, -Werror=missing-field- + # initializers, etc.) which is great for IDF itself but ruthless for + # user Arduino code that often has nested /* */ comments, + # parenthesized macros, or other idioms that compile fine under + # arduino-cli's permissive defaults. Demoting these to plain warnings + # gives users the same "it just compiles" experience they expect from + # the Arduino IDE without disabling -Wall entirely. + target_compile_options(${COMPONENT_LIB} PRIVATE + -Wno-error=comment + -Wno-error=parentheses + -Wno-error=sign-compare + -Wno-error=narrowing + -Wno-error=write-strings + -Wno-error=missing-field-initializers + -Wno-error=reorder) else() # Pure ESP-IDF mode: main.c #includes sketch_translated.c idf_component_register( diff --git a/frontend/src/data/examples-robot-desktop.ts b/frontend/src/data/examples-robot-desktop.ts index 4c6ec752..6a18e469 100644 --- a/frontend/src/data/examples-robot-desktop.ts +++ b/frontend/src/data/examples-robot-desktop.ts @@ -82,7 +82,7 @@ export const robotDesktopExamples: ExampleProject[] = [ // the file that matches the directory name), then headers // alphabetical, then .hpp, then .cpp. Tabs left untouched so // the editor matches the upstream source byte-for-byte. - { name: "esp32-eyes.ino", content: "#include \n#include \n#include \n#include \n\n#include \"Face.h\"\n#include \"Weather.h\"\n#include \"Common.h\"\n#include \"SensorDriver.h\"\n#include \"ServoMotorDriver.h\"\n// GLOBALS\nFace *face;\n\nunsigned long lastMovementTime = 0; // Tiempo del \u00faltimo movimiento detectado\nconst unsigned long WEATHER_TIMEOUT = 600000; // 10 minutos en milisegundos\nconst unsigned long SLEEP_TIMEOUT = 3600000; // 1 hora en milisegundos\n\nint progress = 0;\n\n// Flags para controlar el estado\nbool weatherTimeoutReached = false;\nbool sleepTimeoutReached = false;\n\nunsigned long lastTriggerTime = 0;\nbool lastStateMovement = false;\nbool movementDetected = false;\n\nRobotState lastInsertedState = SLEEP;\n// Estado actual\nRobotState currentState = SLEEP;\n\nTaskHandle_t faceHandle;\nServo ServoDerecha;\nServo ServoIzquierda;\n\nTaskHandle_t servoHandle;\n// GLOBALS\n\neEmotions previousEmotion;\n\nTaskHandle_t soundHandle;\n\nTaskHandle_t sensorHandle;\n\nvoid vUpdateFaceTask(void *pvParameters)\n{\n while (1)\n {\n\n face->RandomBehavior = false;\n face->RandomBlink = true;\n face->RandomLook = true;\n\n if (currentState == NORMAL)\n {\n Serial.println(\"STATE Normal 224****************************************\");\n face->RandomBehavior = true;\n }\n else if (currentState == SOUND)\n {\n // Acciones para el estado NERVIOSO\n eEmotions cEmotion = face->Behavior.CurrentEmotion;\n if (cEmotion != eEmotions::Surprised)\n {\n face->Expression.GoTo_Surprised();\n }\n Serial.println(\"STATE = SONIDO ..... *************************************************\");\n }\n else if (currentState == MOVE)\n {\n // Acciones para el estado MOVE\n Serial.println(\"STATE = MOVE ...... *************************************************\");\n eEmotions cEmotion = face->Behavior.CurrentEmotion;\n if (cEmotion != eEmotions::Focused)\n {\n face->Expression.GoTo_Focused();\n }\n }\n else if (currentState == SLEEP)\n {\n Serial.println(\"STATE = SLEEP ..... **************************************************\");\n face->RandomBehavior = false;\n face->RandomBlink = false;\n face->RandomLook = false;\n Serial.println(face->Behavior.CurrentEmotion);\n Serial.println(eEmotions::Sleepy);\n if (face->Behavior.CurrentEmotion != eEmotions::Sleepy)\n {\n face->Behavior.CurrentEmotion = eEmotions::Sleepy;\n face->Expression.GoTo_Sleepy();\n Serial.println(\"GoTo_Sleepy\");\n }\n else\n {\n Serial.println(\"Distinto ******------------------------------------------------------------------------------************\");\n face->Update();\n }\n }\n else if (currentState == WEATHER)\n {\n\n Serial.println(\"STATE = Weather ..... ************************************************\");\n Serial.println(analogRead(LDR_PIN));\n face->Expression.GoTo_Normal();\n updateWEATHER();\n vTaskDelay(pdMS_TO_TICKS(100));\n continue;\n }\n else if (currentState == INICIO)\n {\n currentState = SLEEP;\n }\n\n face->Update();\n vTaskDelay(pdMS_TO_TICKS(100));\n }\n}\n\nvoid setup(void)\n{\n // Create a serial connection\n Serial.begin(115200);\n Serial.println(\"Iniciando................\");\n ServoDerecha.write(0);\n ServoDerecha.write(90);\n\n pinMode(PIR_PIN, INPUT);\n // pinMode(SOUND_PIN, INPUT);\n pinMode(LDR_PIN, INPUT);\n // insertState(INICIO);\n currentState = SLEEP;\n lastStateMovement = digitalRead(PIR_PIN);\n // Configure input pins\n pinMode(SOUND_PIN, OUTPUT);\n randomSeed(analogRead(0));\n\n // StarWarsPlay();\n // Create a new face\n\n face = new Face(/* screenWidth = */ 128, /* screenHeight = */ 64, /* eyeSize = */ 40);\n\n initDht();\n\n delay(3000);\n // Assign the current expression\n face->Expression.GoTo_Sleepy();\n Serial.println(\"GoTo_Sleepy\");\n\n // Assign a weight to each emotion\n face->Behavior.SetEmotion(eEmotions::Normal, 1.0);\n face->Behavior.SetEmotion(eEmotions::Angry, 1.0);\n face->Behavior.SetEmotion(eEmotions::Glee, 1.0);\n face->Behavior.SetEmotion(eEmotions::Happy, 1.0);\n face->Behavior.SetEmotion(eEmotions::Sad, 1.0);\n face->Behavior.SetEmotion(eEmotions::Worried, 1.0);\n face->Behavior.SetEmotion(eEmotions::Focused, 1.0);\n face->Behavior.SetEmotion(eEmotions::Annoyed, 1.0);\n face->Behavior.SetEmotion(eEmotions::Surprised, 1.0);\n face->Behavior.SetEmotion(eEmotions::Skeptic, 1.0);\n face->Behavior.SetEmotion(eEmotions::Frustrated, 1.0);\n face->Behavior.SetEmotion(eEmotions::Unimpressed, 1.0);\n face->Behavior.SetEmotion(eEmotions::Sleepy, 1.0);\n face->Behavior.SetEmotion(eEmotions::Suspicious, 1.0);\n face->Behavior.SetEmotion(eEmotions::Squint, 1.0);\n face->Behavior.SetEmotion(eEmotions::Furious, 1.0);\n face->Behavior.SetEmotion(eEmotions::Scared, 1.0);\n face->Behavior.SetEmotion(eEmotions::Awe, 1.0);\n\n face->RandomBehavior = false;\n\n // Automatically blink\n face->RandomBlink = true;\n // Set blink rate\n face->Blink.Timer.SetIntervalMillis(8000);\n face->Behavior.Timer.SetIntervalMillis(15000);\n\n // Automatically choose a new random direction to look\n face->RandomLook = true;\n ServoDerecha.attach(SERVO1_Derecha);\n ServoIzquierda.attach(SERVO2_Izquierda);\n ServoIzquierda.write(0);\n ServoDerecha.write(170);\n\n delay(3000);\n\n xTaskCreatePinnedToCore(\n vUpdateFaceTask, /* Task function. */\n \"Update face\", /* name of task. */\n 10000, /* Stack size of task */\n NULL, /* parameter of the task */\n 1, /* priority of the task */\n &faceHandle, /* Task handle to keep track of created task */\n 0); /* pin task to core 0 */\n\n xTaskCreatePinnedToCore(\n vMoveServoTask, /* Task function. */\n \"Update Servo\", /* name of task. */\n 10000, /* Stack size of task */\n face, /* parameter of the task */\n 5, /* priority of the task */\n &servoHandle, /* Task handle to keep track of created task */\n 1); /* pin task to core 0 */\n\n xTaskCreatePinnedToCore(\n vReadSensorTask, /* Task function. */\n \"Read Sensor\", /* name of task. */\n 10000, /* Stack size of task */\n NULL, /* parameter of the task */\n 10, /* priority of the task */\n &sensorHandle, /* Task handle to keep track of created task */\n 1); /* pin task to core 0 */\n\n /* xTaskCreatePinnedToCore(\n vSoundTask, /* Task function. */\n // \"Sound\", /* name of task. */\n // 10000, /* Stack size of task */\n // NULL, /* parameter of the task */\n // 10, /* priority of the task */\n // &sensorHandle, /* Task handle to keep track of created task */\n // 1); /* pin task to core 0 */\n\n Serial.println(\"Finalizando MAIN\");\n}\n\nvoid loop()\n{\n // vUpdateFaceTask();\n}\n" }, + { name: "esp32-eyes.ino", content: "#include \n#include \n#include \n#include \n\n#include \"Face.h\"\n#include \"Weather.h\"\n#include \"Common.h\"\n#include \"SensorDriver.h\"\n#include \"ServoMotorDriver.h\"\n// GLOBALS\nFace *face;\n\nunsigned long lastMovementTime = 0; // Tiempo del \u00faltimo movimiento detectado\nconst unsigned long WEATHER_TIMEOUT = 600000; // 10 minutos en milisegundos\nconst unsigned long SLEEP_TIMEOUT = 3600000; // 1 hora en milisegundos\n\nint progress = 0;\n\n// Flags para controlar el estado\nbool weatherTimeoutReached = false;\nbool sleepTimeoutReached = false;\n\nunsigned long lastTriggerTime = 0;\nbool lastStateMovement = false;\nbool movementDetected = false;\n\nRobotState lastInsertedState = SLEEP;\n// Estado actual\nRobotState currentState = SLEEP;\n\nTaskHandle_t faceHandle;\nServo ServoDerecha;\nServo ServoIzquierda;\n\nTaskHandle_t servoHandle;\n// GLOBALS\n\neEmotions previousEmotion;\n\nTaskHandle_t soundHandle;\n\nTaskHandle_t sensorHandle;\n\nvoid vUpdateFaceTask(void *pvParameters)\n{\n while (1)\n {\n\n face->RandomBehavior = false;\n face->RandomBlink = true;\n face->RandomLook = true;\n\n if (currentState == NORMAL)\n {\n Serial.println(\"STATE Normal 224****************************************\");\n face->RandomBehavior = true;\n }\n else if (currentState == SOUND)\n {\n // Acciones para el estado NERVIOSO\n eEmotions cEmotion = face->Behavior.CurrentEmotion;\n if (cEmotion != eEmotions::Surprised)\n {\n face->Expression.GoTo_Surprised();\n }\n Serial.println(\"STATE = SONIDO ..... *************************************************\");\n }\n else if (currentState == MOVE)\n {\n // Acciones para el estado MOVE\n Serial.println(\"STATE = MOVE ...... *************************************************\");\n eEmotions cEmotion = face->Behavior.CurrentEmotion;\n if (cEmotion != eEmotions::Focused)\n {\n face->Expression.GoTo_Focused();\n }\n }\n else if (currentState == SLEEP)\n {\n Serial.println(\"STATE = SLEEP ..... **************************************************\");\n face->RandomBehavior = false;\n face->RandomBlink = false;\n face->RandomLook = false;\n Serial.println(face->Behavior.CurrentEmotion);\n Serial.println(eEmotions::Sleepy);\n if (face->Behavior.CurrentEmotion != eEmotions::Sleepy)\n {\n face->Behavior.CurrentEmotion = eEmotions::Sleepy;\n face->Expression.GoTo_Sleepy();\n Serial.println(\"GoTo_Sleepy\");\n }\n else\n {\n Serial.println(\"Distinto ******------------------------------------------------------------------------------************\");\n face->Update();\n }\n }\n else if (currentState == WEATHER)\n {\n\n Serial.println(\"STATE = Weather ..... ************************************************\");\n Serial.println(analogRead(LDR_PIN));\n face->Expression.GoTo_Normal();\n updateWEATHER();\n vTaskDelay(pdMS_TO_TICKS(100));\n continue;\n }\n else if (currentState == INICIO)\n {\n currentState = SLEEP;\n }\n\n face->Update();\n vTaskDelay(pdMS_TO_TICKS(100));\n }\n}\n\nvoid setup(void)\n{\n // Create a serial connection\n Serial.begin(115200);\n Serial.println(\"Iniciando................\");\n ServoDerecha.write(0);\n ServoDerecha.write(90);\n\n pinMode(PIR_PIN, INPUT);\n // pinMode(SOUND_PIN, INPUT);\n pinMode(LDR_PIN, INPUT);\n // insertState(INICIO);\n currentState = SLEEP;\n lastStateMovement = digitalRead(PIR_PIN);\n // Configure input pins\n pinMode(SOUND_PIN, OUTPUT);\n randomSeed(analogRead(0));\n\n // StarWarsPlay();\n // Create a new face\n\n face = new Face(/* screenWidth = */ 128, /* screenHeight = */ 64, /* eyeSize = */ 40);\n\n initDht();\n\n delay(3000);\n // Assign the current expression\n face->Expression.GoTo_Sleepy();\n Serial.println(\"GoTo_Sleepy\");\n\n // Assign a weight to each emotion\n face->Behavior.SetEmotion(eEmotions::Normal, 1.0);\n face->Behavior.SetEmotion(eEmotions::Angry, 1.0);\n face->Behavior.SetEmotion(eEmotions::Glee, 1.0);\n face->Behavior.SetEmotion(eEmotions::Happy, 1.0);\n face->Behavior.SetEmotion(eEmotions::Sad, 1.0);\n face->Behavior.SetEmotion(eEmotions::Worried, 1.0);\n face->Behavior.SetEmotion(eEmotions::Focused, 1.0);\n face->Behavior.SetEmotion(eEmotions::Annoyed, 1.0);\n face->Behavior.SetEmotion(eEmotions::Surprised, 1.0);\n face->Behavior.SetEmotion(eEmotions::Skeptic, 1.0);\n face->Behavior.SetEmotion(eEmotions::Frustrated, 1.0);\n face->Behavior.SetEmotion(eEmotions::Unimpressed, 1.0);\n face->Behavior.SetEmotion(eEmotions::Sleepy, 1.0);\n face->Behavior.SetEmotion(eEmotions::Suspicious, 1.0);\n face->Behavior.SetEmotion(eEmotions::Squint, 1.0);\n face->Behavior.SetEmotion(eEmotions::Furious, 1.0);\n face->Behavior.SetEmotion(eEmotions::Scared, 1.0);\n face->Behavior.SetEmotion(eEmotions::Awe, 1.0);\n\n face->RandomBehavior = false;\n\n // Automatically blink\n face->RandomBlink = true;\n // Set blink rate\n face->Blink.Timer.SetIntervalMillis(8000);\n face->Behavior.Timer.SetIntervalMillis(15000);\n\n // Automatically choose a new random direction to look\n face->RandomLook = true;\n ServoDerecha.attach(SERVO1_Derecha);\n ServoIzquierda.attach(SERVO2_Izquierda);\n ServoIzquierda.write(0);\n ServoDerecha.write(170);\n\n delay(3000);\n\n xTaskCreatePinnedToCore(\n vUpdateFaceTask, /* Task function. */\n \"Update face\", /* name of task. */\n 10000, /* Stack size of task */\n NULL, /* parameter of the task */\n 1, /* priority of the task */\n &faceHandle, /* Task handle to keep track of created task */\n 0); /* pin task to core 0 */\n\n xTaskCreatePinnedToCore(\n vMoveServoTask, /* Task function. */\n \"Update Servo\", /* name of task. */\n 10000, /* Stack size of task */\n face, /* parameter of the task */\n 5, /* priority of the task */\n &servoHandle, /* Task handle to keep track of created task */\n 1); /* pin task to core 0 */\n\n xTaskCreatePinnedToCore(\n vReadSensorTask, /* Task function. */\n \"Read Sensor\", /* name of task. */\n 10000, /* Stack size of task */\n NULL, /* parameter of the task */\n 10, /* priority of the task */\n &sensorHandle, /* Task handle to keep track of created task */\n 1); /* pin task to core 0 */\n\n // Sound task is currently disabled. Kept here for reference\n // (use #if 0 / #endif instead of /* */ so the inner block\n // comments don't terminate the outer one).\n #if 0\n xTaskCreatePinnedToCore(\n vSoundTask, // Task function.\n \"Sound\", // name of task.\n 10000, // Stack size of task\n NULL, // parameter of the task\n 10, // priority of the task\n &sensorHandle, // Task handle to keep track of created task\n 1); // pin task to core 0\n #endif\n\n Serial.println(\"Finalizando MAIN\");\n}\n\nvoid loop()\n{\n // vUpdateFaceTask();\n}\n" }, { name: "Animations.h", content: "/***************************************************\nCopyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas\n(www.luisllamas.es)\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License along with this program. If not, see \n\nclass IAnimation {\npublic:\n\tvirtual float GetValue() = 0;\n\tvirtual float GetValue(unsigned long overWriteMillis) = 0;\n\tvirtual unsigned long GetElapsed() = 0;\n\nprivate:\n\tvirtual float Calculate(unsigned long elapsedMillis) = 0;\n};\n\nclass AnimationBase : IAnimation {\n public:\n\t AnimationBase(unsigned long interval) : Interval(interval), StarTime(millis()) {}\n\n\t unsigned long Interval;\n\t unsigned long StarTime;\n\n\t virtual void Restart() {\n\t\t StarTime = millis();\n\t }\n\t float GetValue() override final {\n\t\t return GetValue(GetElapsed());\n\t }\n\t float GetValue(unsigned long elapsedMillis) override final {\n\t\t return Calculate(elapsedMillis);\n\t }\n\t unsigned long GetElapsed() override {\n\t\t return static_cast (millis() - StarTime);\n\t }\n\n protected:\n\t float Calculate(unsigned long elapsedMillis) override { return 0.0; }\n};\n\nclass DeltaAnimation : public AnimationBase {\n public:\n\t unsigned long StarTime;\n\t DeltaAnimation(unsigned long interval) : AnimationBase(interval) {};\n\t float Calculate(unsigned long elapsedMillis) {\n\t\t if (elapsedMillis < Interval)\t{\n\t\t\t return 0.0f;\n\t\t }\n\t\t else {\n\t\t\t return 1.0f;\n\t\t }\n\t };\n};\n\n\nclass StepAnimation : public AnimationBase {\n public:\n\t unsigned long Interval;\n\t unsigned long StarTime;\n\t bool IsActive = true;\n \tStepAnimation(unsigned long interval) : AnimationBase(interval) {};\n\n\t float Calculate(unsigned long elapsedMillis) {\n\t\t if (elapsedMillis < Interval)\t{\n\t\t\t return 0.0f;\n\t\t }\n\t\t return 1.0f;\n\t };\n};\n\n\nclass RampAnimation : public AnimationBase {\npublic:\n\n\tunsigned long StarTime;\n\tbool IsActive = true;\n\n\tRampAnimation(unsigned long interval) : AnimationBase(interval) {};\n\n\tfloat Calculate(unsigned long elapsedMillis) {\n\t\tif (elapsedMillis < Interval)\t{\n\t\t\treturn static_cast(elapsedMillis) / Interval;\n\t\t}\n\t\treturn 1.0f;\n\t};\n};\n\nclass TriangleAnimation : public AnimationBase {\npublic:\n\n\tTriangleAnimation(unsigned long interval) : AnimationBase(interval) {\n\t\t_t0 = interval / 2;\n\t\t_t1 = interval - _t0;\n\t}\n\tTriangleAnimation(unsigned long t0, unsigned long t1) : AnimationBase(t0 + t1) {\n\t\t_t0 = t0;\n\t\t_t1 = t1;\n\t};\n\tfloat Calculate(unsigned long elapsedMillis) {\n\t\tif (elapsedMillis % Interval < _t0) {\n\t\t\treturn static_cast(elapsedMillis % Interval) / _t0;\n\t\t}\n\t\treturn 1.0f - (static_cast(elapsedMillis % Interval) - _t0) / _t1;\n\t};\n\tunsigned long _t0;\n\tunsigned long _t1;\n};\n\n\nclass TrapeziumAnimation : public AnimationBase {\npublic:\n\tTrapeziumAnimation(unsigned long t) : AnimationBase(t) {\n\t\t_t0 = t / 3;\n\t\t_t1 = _t0;\n\t\t_t2 = t - _t0 - _t1;\n\t};\n\tTrapeziumAnimation(unsigned long t0, unsigned long t1, unsigned long t2) : AnimationBase(t0 + t1 + t2) {\n\t\t_t0 = t0;\n\t\t_t1 = t1;\n\t\t_t2 = t2;\n\t}; \n\tfloat Calculate(unsigned long elapsedMillis) override {\n\t\tif (elapsedMillis > Interval) return 0.0;\n\t\tif (elapsedMillis < _t0) {\n\t\t\treturn static_cast(elapsedMillis) / _t0;\n\t\t}\n\t\telse if (elapsedMillis < _t0 + _t1) {\n\t\t\treturn 1.0f;\n\t\t}\n\t\telse {\n\t\t\treturn 1.0f - (static_cast(elapsedMillis) - _t1 - _t0) / _t2;\n\t\t}\n\t};\n\n\tunsigned long _t0;\n\tunsigned long _t1;\n\tunsigned long _t2;\n};\n\n\nclass TrapeziumPulseAnimation : public AnimationBase {\npublic:\n\tTrapeziumPulseAnimation(unsigned long t) : AnimationBase(t) {\n\t\t_t0 = 0;\n\t\t_t1 = t / 3;\n\t\t_t2 = t - _t0 - _t0;\n\t\t_t3 = _t1;\n\t\t_t4 = 0;\n\t};\n\n\tTrapeziumPulseAnimation(unsigned long t0, unsigned long t1, unsigned long t2) : AnimationBase(t0 + t1 + t2) {\n\t\t_t0 = 0;\n\t\t_t1 = t0;\n\t\t_t2 = t1;\n\t\t_t3 = t2;\n\t\t_t4 = 0;\n\t};\n\n\tTrapeziumPulseAnimation(unsigned long t0, unsigned long t1, unsigned long t2, unsigned long t3, unsigned long t4) : AnimationBase(t0 + t1 + t2 + t3 + t4) {\n\t\t_t0 = t0;\n\t\t_t1 = t1;\n\t\t_t2 = t2;\n\t\t_t3 = t3;\n\t\t_t4 = t4;\n\t};\n\n\tfloat Calculate(unsigned long elapsedMillis) override {\n\t\tunsigned long elapsed = elapsedMillis % Interval;\n\n\t\tif (elapsed < _t0) {\n\t\t\treturn 0.0;\n\t\t}\n\t\tif (elapsed < _t0 + _t1) {\n\t\t\treturn static_cast(elapsed - _t0) / _t1;\n\t\t}\n\t\telse if (elapsed < _t0 + _t1 + _t2)\t{\n\t\t\treturn 1.0f;\n\t\t}\n\t\telse if (elapsed < _t0 + _t1 + _t2 + _t3)\t{\n\t\t\treturn 1.0f - (static_cast(elapsed) - _t2 - _t1 - _t0) / _t3;\n\t\t}\n\t\treturn 0.0;\n\t};\n\n\tvoid SetInterval(uint16_t t) {\n\t\t_t0 = 0;\n\t\t_t1 = t / 3;\n\t\t_t2 = t - _t0 - _t0;\n\t\t_t3 = _t1;\n\t\t_t4 = 0;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\n\tvoid SetTriangle(uint16_t t, uint16_t delay) {\n\t\t_t0 = 0;\n\t\t_t1 = t / 2;\n\t\t_t2 = 0;\n\t\t_t3 = _t1;\n\t\t_t4 = delay;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\n\tvoid SetTriangleCuadrature(uint16_t t, uint16_t delay) {\n\t\t_t0 = delay;\n\t\t_t1 = t / 2;\n\t\t_t2 = 0;\n\t\t_t3 = _t1;\n\t\t_t4 = 0;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\t\n\tvoid SetPulse(uint16_t t, uint16_t delay) {\n\t\t_t0 = 0;\n\t\t_t1 = t / 3;\n\t\t_t2 = t - _t0 - _t0;\n\t\t_t3 = _t1;\n\t\t_t4 = delay;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\n\tvoid SetPulseCuadrature(uint16_t t, uint16_t delay) {\n\t\t_t0 = delay;\n\t\t_t1 = t / 3;\n\t\t_t2 = t - _t0 - _t0;\n\t\t_t3 = _t1;\n\t\t_t4 = 0;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\n\tvoid SetInterval(uint16_t t0, uint16_t t1, uint16_t t2, uint16_t t3, uint16_t t4) {\n\t\t_t0 = t0;\n\t\t_t1 = t1;\n\t\t_t2 = t2;\n\t\t_t3 = t3;\n\t\t_t4 = t4;\n\t\tInterval = _t0 + _t1 + _t2 + _t3 + _t4;\n\t}\n\n\tunsigned long _t0;\n\tunsigned long _t1;\n\tunsigned long _t2;\n\tunsigned long _t3;\n\tunsigned long _t4;\n};\n\n#endif" }, { name: "AsyncTimer.h", content: "/***************************************************\nCopyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas\n(www.luisllamas.es)\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License along with this program. If not, see \n\ntypedef void(*AsyncTimerCallback)();\n\nclass AsyncTimer {\n public:\n\tAsyncTimer(unsigned long millisInterval);\n\tAsyncTimer(unsigned long millisInterval, AsyncTimerCallback OnFinish);\n\n\tvoid Start();\n\tvoid Reset();\n\tvoid Stop();\n\tbool Update();\n\n\tvoid SetIntervalMillis(unsigned long interval);\n\t\n\tunsigned long GetStartTime();\n\tunsigned long GetElapsedTime();\n\tunsigned long GetRemainingTime();\n\n\tbool IsActive() const;\n\tbool IsExpired() const;\n\t\n\tunsigned long Interval;\n\t\n\tAsyncTimerCallback OnFinish;\n\nprivate:\n\tbool _isActive;\n\tbool _isExpired;\n\tunsigned long _startTime;\n};\n#endif" }, { name: "BlinkAssistant.h", content: "/***************************************************\nCopyright (c) 2020 Luis Llamas\n(www.luisllamas.es)\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or (at your option) any later version. \n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License along with this program. If not, see \n#include \"Animations.h\"\n#include \"AsyncTimer.h\"\n\nclass Face;\n\nclass BlinkAssistant {\n protected:\n\tFace& _face;\n\n public:\n\tBlinkAssistant(Face& face);\n\n\tAsyncTimer Timer;\n\n\tvoid Update();\n\tvoid Blink();\n};\n\n#endif" },