pleasedontcode

**Digital Emotions** rev_06

Mar 9th, 2026
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 34.84 KB | None | 0 0
  1. /********* Pleasedontcode.com **********
  2.  
  3.     Pleasedontcode thanks you for automatic code generation! Enjoy your code!
  4.  
  5.     - Terms and Conditions:
  6.     You have a non-exclusive, revocable, worldwide, royalty-free license
  7.     for personal and commercial use. Attribution is optional; modifications
  8.     are allowed, but you're responsible for code maintenance. We're not
  9.     liable for any loss or damage. For full terms,
  10.     please visit pleasedontcode.com/termsandconditions.
  11.  
  12.     - Project: **Digital Emotions**
  13.     - Version: 006
  14.     - Source Code NOT compiled for: ESP32S3 Dev Module
  15.     - Source Code created on: 2026-03-09 21:35:24
  16.  
  17. ********* Pleasedontcode.com **********/
  18.  
  19. /****** SYSTEM REQUIREMENTS *****/
  20. /****** SYSTEM REQUIREMENT 1 *****/
  21.     /* SH1106 1.3" OLED display (128x64) with U8g2 */
  22.     /* library, I2C address 0x3C. Procedural animated */
  23.     /* face: eyes with physics-based pupils, blinking, */
  24.     /* iris glow, eyelids, eyebrows, mouth, cheeks, nose. */
  25.     /* 45 FPS rendering. */
  26. /****** SYSTEM REQUIREMENT 2 *****/
  27.     /* 8 emotion states (neutral, happy, sad, excited, */
  28.     /* sleepy, curious, shy, playful) with unique */
  29.     /* animations. Touch button (D1) cycles emotions. LED */
  30.     /* (D2) indicates state. Idle timeout triggers */
  31.     /* autonomous baby behaviors with random emotion */
  32.     /* changes. */
  33. /****** SYSTEM REQUIREMENT 3 *****/
  34.     /* Modular architecture: physics engine, animation */
  35.     /* state machine, emotion config presets. Fully */
  36.     /* commented code, optimized for ESP32-S3, minimal */
  37.     /* memory footprint, frame rate limited to 45 FPS. */
  38. /****** SYSTEM REQUIREMENT 4 *****/
  39.     /* [POTA] Include POTA.h and secrets.h. Init POTA in */
  40.     /* setup() with WiFi creds from secrets.h. Call */
  41.     /* pota.loop() in loop(). */
  42. /****** SYSTEM REQUIREMENT 5 *****/
  43.     /* [POTA] MANDATORY OTA: call */
  44.     /* pota.checkAndPerformOTA() in setup() after */
  45.     /* begin(). Register pota.onOTAAvailable(cb) where cb */
  46.     /* sets a bool flag. In loop() when flag is true call */
  47.     /* pota.restart(). Without OTA the device cannot */
  48.     /* update remotely. */
  49. /****** END SYSTEM REQUIREMENTS *****/
  50.  
  51.  
  52. /* START CODE */
  53.  
  54. /****** DEFINITION OF LIBRARIES *****/
  55. #include <U8g2lib.h>           // https://github.com/olikraus/u8g2
  56. #include <EasyButton.h>        // https://github.com/evert-arias/EasyButton
  57. #include "secrets.h"           // WiFi and authentication credentials
  58. #include "POTA.h"              // POTA OTA update management
  59.  
  60. /****** SYSTEM REQUIREMENTS *****/
  61. // SR1: SH1106 1.3" OLED display (128x64) with U8g2 library, I2C address 0x3C.
  62. //      Procedural animated face: eyes with physics-based pupils, blinking, iris glow,
  63. //      eyelids, eyebrows, mouth, cheeks, nose. 45 FPS rendering.
  64. // SR2: 8 emotion states (neutral, happy, sad, excited, sleepy, curious, shy, playful)
  65. //      with unique animations. Touch button (D1) cycles emotions. LED (D2) indicates
  66. //      state. Idle timeout triggers autonomous baby behaviors with random emotion
  67. //      changes.
  68. // SR3: Modular architecture: physics engine, animation state machine, emotion config
  69. //      presets. Fully commented code, optimized for ESP32-S3, minimal memory footprint,
  70. //      frame rate limited to 45 FPS.
  71. // SR4: [POTA] Include POTA.h and secrets.h. Init POTA in setup() with WiFi creds from
  72. //      secrets.h. Call pota.loop() in loop(). Dashboard widgets configured.
  73. // SR5: [POTA] MANDATORY OTA: call pota.checkAndPerformOTA() in setup() after begin().
  74. //      Register pota.onOTAAvailable(cb) where cb sets a bool flag. In loop() when
  75. //      flag is true call pota.restart(). Without OTA the device cannot update remotely.
  76.  
  77. /****** FUNCTION PROTOTYPES *****/\nvoid setup(void);
  78. void loop(void);
  79. void onOTAAvailable();  // OTA availability callback
  80.  
  81. /***** DEFINITION OF DIGITAL INPUT PINS *****/
  82. const uint8_t faceDisplay_PushButton_PIN_D1 = 1;
  83.  
  84. /***** DEFINITION OF DIGITAL OUTPUT PINS *****/
  85. const uint8_t emotionButton_LED_PIN_D2 = 2;
  86.  
  87. /****** OLED DISPLAY CONFIGURATION *****/
  88. // U8g2 display: SH1106 1.3" OLED 128x64 with I2C at address 0x3C
  89. U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 21, 22);
  90.  
  91. // Display dimensions
  92. #define SCREEN_WIDTH 128
  93. #define SCREEN_HEIGHT 64
  94.  
  95. /****** EASY BUTTON CONFIGURATION *****/
  96. EasyButton button(faceDisplay_PushButton_PIN_D1);
  97.  
  98. /****** POTA OTA MANAGEMENT *****/
  99. // Global POTA instance for OTA update management
  100. POTA pota;
  101.  
  102. // OTA update flag - set by callback, triggers restart in loop()
  103. volatile bool otaUpdateAvailable = false;
  104.  
  105. /****** EMOTION ENUMERATION *****/
  106. enum Emotion {
  107.   NEUTRAL = 0,
  108.   HAPPY = 1,
  109.   SAD = 2,
  110.   EXCITED = 3,
  111.   SLEEPY = 4,
  112.   CURIOUS = 5,
  113.   SHY = 6,
  114.   PLAYFUL = 7
  115. };
  116.  
  117. /****** PHYSICS ENGINE STRUCTURE *****/
  118. struct PhysicsState {
  119.   // Eye pupil tracking physics
  120.   float pupilX;           // Current pupil X position (-1.0 to 1.0)
  121.   float pupilY;           // Current pupil Y position (-1.0 to 1.0)
  122.   float pupilVelX;        // Pupil velocity X (inertia)
  123.   float pupilVelY;        // Pupil velocity Y (inertia)
  124.   float targetPupilX;     // Target pupil X position
  125.   float targetPupilY;     // Target pupil Y position
  126.  
  127.   // Blinking physics
  128.   float blinkValue;       // 0.0 (open) to 1.0 (closed)
  129.   float blinkVel;         // Blinking velocity for smooth animation
  130.   bool isBlinking;        // Currently blinking
  131.   unsigned long blinkStartTime;
  132.  
  133.   // Eyelid animation
  134.   float eyelidOffset;     // Additional eyelid movement beyond blink
  135.  
  136.   // Eyebrow animation
  137.   float eyebrowAngle;     // Eyebrow rotation in degrees
  138.   float eyebrowTargetAngle;
  139.  
  140.   // Iris glow
  141.   float irisGlow;         // 0.0 to 1.0 glow intensity
  142.  
  143.   // Mouth expression
  144.   float mouthHeight;      // Mouth curvature
  145.   float mouthAngle;       // Mouth angle/tilt
  146.  
  147.   // Cheek blush
  148.   float cheekBlush;       // Cheek color intensity
  149.  
  150.   // Nose animation
  151.   float noseWiggle;       // Nose movement micro-animation
  152. };
  153.  
  154. /****** ANIMATION STATE STRUCTURE *****/
  155. struct AnimationState {
  156.   Emotion currentEmotion;
  157.   Emotion targetEmotion;
  158.  
  159.   // Autonomous behavior
  160.   bool autonomousMode;
  161.   unsigned long lastInputTime;
  162.   unsigned long idleThreshold;  // 5000ms = 5 seconds
  163.  
  164.   // Animation timing
  165.   unsigned long frameTime;      // Current frame timestamp
  166.   unsigned long lastFrameTime;
  167.   float deltaTime;              // Time since last frame in seconds
  168.   uint32_t frameCount;          // Total frames rendered
  169.  
  170.   // Animation phase for cyclic behaviors
  171.   float animationPhase;         // 0.0 to 1.0, wraps around
  172.   float phaseVelocity;          // Controls animation speed based on emotion
  173.  
  174.   // Micro-movement
  175.   float microMoveX;             // Subtle position jitter
  176.   float microMoveY;             // Subtle position jitter
  177.  
  178.   // Gaze direction
  179.   float gazeX;                  // Where the eyes are looking
  180.   float gazeY;
  181. };
  182.  
  183. /****** EMOTION CONFIGURATION STRUCTURE *****/
  184. struct EmotionConfig {
  185.   float animationSpeed;         // How fast animations play
  186.   float blinkFrequency;         // How often to blink (blinks per second)
  187.   float pupilTrackingIntensity; // How responsive pupil is to gaze
  188.   float microMoveAmount;        // Amount of micro-movement jitter
  189.   int8_t eyebrowAngle;          // Base eyebrow angle in degrees
  190.   float mouthHeight;            // Base mouth curve height
  191.   float cheekColor;             // Cheek blush intensity (0.0-1.0)
  192.   float irisGlowIntensity;      // Iris glow brightness
  193. };
  194.  
  195. /****** GLOBAL STATE VARIABLES *****/
  196. PhysicsState physics;
  197. AnimationState animation;
  198. EmotionConfig emotionConfigs[8];
  199.  
  200. /****** FRAME RATE LIMITING VARIABLES *****/
  201. unsigned long lastFrameTimeMs = 0;
  202. const unsigned long FRAME_TIME_MS = 22;  // 22ms per frame for 45 FPS
  203.  
  204. /****** INITIALIZE EMOTION CONFIGURATIONS *****/
  205. void initializeEmotionConfigs() {
  206.   // NEUTRAL: calm, baseline state
  207.   emotionConfigs[NEUTRAL] = {
  208.     1.0f,      // animationSpeed
  209.     1.5f,      // blinkFrequency (blinks per second)
  210.     0.7f,      // pupilTrackingIntensity
  211.     0.5f,      // microMoveAmount
  212.     0,         // eyebrowAngle
  213.     0.3f,      // mouthHeight
  214.     0.1f,      // cheekColor
  215.     0.3f       // irisGlowIntensity
  216.   };
  217.  
  218.   // HAPPY: cheerful, animated
  219.   emotionConfigs[HAPPY] = {
  220.     1.5f,      // animationSpeed (faster)
  221.     2.0f,      // blinkFrequency
  222.     0.8f,      // pupilTrackingIntensity
  223.     1.0f,      // microMoveAmount (more movement)
  224.     15,        // eyebrowAngle (raised)
  225.     0.7f,      // mouthHeight (big smile)
  226.     0.6f,      // cheekColor (strong blush)
  227.     0.5f       // irisGlowIntensity
  228.   };
  229.  
  230.   // SAD: depressed, slower movements
  231.   emotionConfigs[SAD] = {
  232.     0.5f,      // animationSpeed (slower)
  233.     0.8f,      // blinkFrequency
  234.     0.5f,      // pupilTrackingIntensity
  235.     0.2f,      // microMoveAmount
  236.     -15,       // eyebrowAngle (lowered)
  237.     -0.5f,     // mouthHeight (frown)
  238.     0.2f,      // cheekColor
  239.     0.1f       // irisGlowIntensity
  240.   };
  241.  
  242.   // EXCITED: very animated, rapid blinks
  243.   emotionConfigs[EXCITED] = {
  244.     2.0f,      // animationSpeed (very fast)
  245.     3.0f,      // blinkFrequency
  246.     0.9f,      // pupilTrackingIntensity
  247.     1.5f,      // microMoveAmount
  248.     20,        // eyebrowAngle
  249.     0.8f,      // mouthHeight
  250.     0.7f,      // cheekColor
  251.     0.7f       // irisGlowIntensity
  252.   };
  253.  
  254.   // SLEEPY: slow, droopy eyes
  255.   emotionConfigs[SLEEPY] = {
  256.     0.3f,      // animationSpeed
  257.     0.3f,      // blinkFrequency (frequent half-blinks)
  258.     0.3f,      // pupilTrackingIntensity
  259.     0.1f,      // microMoveAmount
  260.     -20,       // eyebrowAngle (relaxed)
  261.     0.2f,      // mouthHeight
  262.     0.05f,     // cheekColor
  263.     0.05f      // irisGlowIntensity
  264.   };
  265.  
  266.   // CURIOUS: searching movements, alert
  267.   emotionConfigs[CURIOUS] = {
  268.     1.3f,      // animationSpeed
  269.     1.2f,      // blinkFrequency
  270.     1.0f,      // pupilTrackingIntensity (maximum)
  271.     0.8f,      // microMoveAmount
  272.     10,        // eyebrowAngle
  273.     0.4f,      // mouthHeight
  274.     0.2f,      // cheekColor
  275.     0.6f       // irisGlowIntensity
  276.   };
  277.  
  278.   // SHY: minimal movements, down-looking
  279.   emotionConfigs[SHY] = {
  280.     0.6f,      // animationSpeed
  281.     1.0f,      // blinkFrequency
  282.     0.4f,      // pupilTrackingIntensity
  283.     0.3f,      // microMoveAmount
  284.     -10,       // eyebrowAngle
  285.     0.1f,      // mouthHeight
  286.     0.4f,      // cheekColor (blushing)
  287.     0.2f       // irisGlowIntensity
  288.   };
  289.  
  290.   // PLAYFUL: bouncy, dynamic movements
  291.   emotionConfigs[PLAYFUL] = {
  292.     1.8f,      // animationSpeed
  293.     2.5f,      // blinkFrequency
  294.     0.85f,     // pupilTrackingIntensity
  295.     1.3f,      // microMoveAmount
  296.     12,        // eyebrowAngle
  297.     0.6f,      // mouthHeight
  298.     0.5f,      // cheekColor
  299.     0.6f       // irisGlowIntensity
  300.   };
  301. }
  302.  
  303. /****** PHYSICS ENGINE: UPDATE PUPIL TRACKING *****/
  304. void updatePupilTracking(float deltaTime) {
  305.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  306.  
  307.   // Calculate target pupil position based on gaze
  308.   physics.targetPupilX = animation.gazeX * config.pupilTrackingIntensity;
  309.   physics.targetPupilY = animation.gazeY * config.pupilTrackingIntensity;
  310.  
  311.   // Inertia damping: smooth acceleration towards target
  312.   float inertiaFactor = 5.0f * config.pupilTrackingIntensity;
  313.   float maxVelocity = 2.0f;
  314.  
  315.   // Update velocity with acceleration towards target
  316.   physics.pupilVelX += (physics.targetPupilX - physics.pupilX) * inertiaFactor * deltaTime;
  317.   physics.pupilVelY += (physics.targetPupilY - physics.pupilY) * inertiaFactor * deltaTime;
  318.  
  319.   // Clamp velocity
  320.   physics.pupilVelX = constrain(physics.pupilVelX, -maxVelocity, maxVelocity);
  321.   physics.pupilVelY = constrain(physics.pupilVelY, -maxVelocity, maxVelocity);
  322.  
  323.   // Apply damping (friction)
  324.   float damping = 0.92f;
  325.   physics.pupilVelX *= damping;
  326.   physics.pupilVelY *= damping;
  327.  
  328.   // Update position
  329.   physics.pupilX += physics.pupilVelX * deltaTime;
  330.   physics.pupilY += physics.pupilVelY * deltaTime;
  331.  
  332.   // Clamp pupil position within iris circle, not extending beyond
  333.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  334.   if (pupilDist > 0.65f) {
  335.     float scale = 0.65f / pupilDist;
  336.     physics.pupilX *= scale;
  337.     physics.pupilY *= scale;
  338.   }
  339. }
  340.  
  341. /****** PHYSICS ENGINE: UPDATE BLINKING *****/
  342. void updateBlinking(float deltaTime) {
  343.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  344.  
  345.   // Calculate blink duration based on emotion
  346.   float blinkDuration = 0.15f;  // 150ms standard blink
  347.   float blinkInterval = 1.0f / config.blinkFrequency;
  348.  
  349.   if (!physics.isBlinking) {
  350.     // Check if it's time to blink
  351.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  352.    
  353.     // Add some randomness to blink timing
  354.     float randomBlink = 0.8f + (random(40) / 100.0f);  // 0.8 to 1.2 variation
  355.    
  356.     if (timeSinceBlink > blinkInterval * randomBlink) {
  357.       physics.isBlinking = true;
  358.       physics.blinkStartTime = millis();
  359.       physics.blinkVel = 4.0f / blinkDuration;  // Velocity to close eyes
  360.     }
  361.   } else {
  362.     // Animate blinking
  363.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  364.    
  365.     if (timeSinceBlink < 0.075f) {
  366.       // Closing phase (first 75ms)
  367.       physics.blinkValue += physics.blinkVel * deltaTime;
  368.       physics.blinkValue = min(physics.blinkValue, 1.0f);
  369.     } else if (timeSinceBlink < 0.15f) {
  370.       // Opening phase (next 75ms)
  371.       physics.blinkValue -= physics.blinkVel * deltaTime;
  372.       physics.blinkValue = max(physics.blinkValue, 0.0f);
  373.     } else {
  374.       // Blink complete
  375.       physics.isBlinking = false;
  376.       physics.blinkValue = 0.0f;
  377.       physics.blinkStartTime = millis();
  378.     }
  379.   }
  380. }
  381.  
  382. /****** PHYSICS ENGINE: UPDATE EYEBROW ANIMATION *****/
  383. void updateEyebrows(float deltaTime) {
  384.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  385.  
  386.   // Base eyebrow angle from emotion config
  387.   physics.eyebrowTargetAngle = config.eyebrowAngle;
  388.  
  389.   // Add micro-animations based on emotion phase
  390.   float emotionInfluence = sin(animation.animationPhase * M_PI * 2.0f);
  391.  
  392.   switch(animation.currentEmotion) {
  393.     case HAPPY:
  394.     case EXCITED:
  395.       physics.eyebrowTargetAngle += emotionInfluence * 5.0f;
  396.       break;
  397.     case CURIOUS:
  398.       physics.eyebrowTargetAngle += abs(emotionInfluence) * 3.0f;
  399.       break;
  400.     case SHY:
  401.       physics.eyebrowTargetAngle -= abs(emotionInfluence) * 2.0f;
  402.       break;
  403.     default:
  404.       break;
  405.   }
  406.  
  407.   // Smoothly interpolate to target angle
  408.   float lerpFactor = 8.0f * deltaTime;
  409.   physics.eyebrowAngle += (physics.eyebrowTargetAngle - physics.eyebrowAngle) * lerpFactor;
  410. }
  411.  
  412. /****** PHYSICS ENGINE: UPDATE MOUTH EXPRESSION *****/
  413. void updateMouthExpression(float deltaTime) {
  414.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  415.  
  416.   // Base mouth height from emotion
  417.   float targetMouthHeight = config.mouthHeight;
  418.  
  419.   // Add animation variation
  420.   float animVariation = sin(animation.animationPhase * M_PI * 2.0f);
  421.   targetMouthHeight += animVariation * 0.1f * config.animationSpeed;
  422.  
  423.   // Add mouth angle (smile tilt)
  424.   physics.mouthAngle = config.eyebrowAngle * 0.3f;
  425.  
  426.   // Smooth interpolation
  427.   float lerpFactor = 6.0f * deltaTime;
  428.   physics.mouthHeight += (targetMouthHeight - physics.mouthHeight) * lerpFactor;
  429. }
  430.  
  431. /****** PHYSICS ENGINE: UPDATE CHEEK BLUSH *****/
  432. void updateCheeks(float deltaTime) {
  433.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  434.  
  435.   // Base cheek blush from emotion
  436.   float targetCheekBlush = config.cheekColor;
  437.  
  438.   // Pulse the blush slightly based on animation phase
  439.   float pulseInfluence = (sin(animation.animationPhase * M_PI * 2.0f) + 1.0f) * 0.5f;
  440.   targetCheekBlush += pulseInfluence * 0.15f * config.animationSpeed;
  441.  
  442.   // Smooth interpolation
  443.   float lerpFactor = 3.0f * deltaTime;
  444.   physics.cheekBlush += (targetCheekBlush - physics.cheekBlush) * lerpFactor;
  445.   physics.cheekBlush = constrain(physics.cheekBlush, 0.0f, 1.0f);
  446. }
  447.  
  448. /****** PHYSICS ENGINE: UPDATE IRIS GLOW *****/
  449. void updateIrisGlow(float deltaTime) {
  450.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  451.  
  452.   // Base glow intensity from emotion
  453.   float targetGlow = config.irisGlowIntensity;
  454.  
  455.   // Pulse glow subtly
  456.   float glowPulse = (sin(animation.animationPhase * M_PI * 4.0f) + 1.0f) * 0.5f;
  457.   targetGlow += glowPulse * 0.1f;
  458.  
  459.   // Smooth interpolation
  460.   float lerpFactor = 4.0f * deltaTime;
  461.   physics.irisGlow += (targetGlow - physics.irisGlow) * lerpFactor;
  462.   physics.irisGlow = constrain(physics.irisGlow, 0.0f, 1.0f);
  463. }
  464.  
  465. /****** PHYSICS ENGINE: UPDATE NOSE WIGGLE *****/
  466. void updateNose(float deltaTime) {
  467.   // Nose wiggles subtly with high-frequency animation
  468.   physics.noseWiggle = sin(animation.animationPhase * M_PI * 8.0f) * 0.5f;
  469. }
  470.  
  471. /****** PHYSICS ENGINE: UPDATE MICRO-MOVEMENTS *****/
  472. void updateMicroMovements(float deltaTime) {
  473.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  474.  
  475.   // Generate micro-movement using noise-like pattern
  476.   float time = millis() / 1000.0f;
  477.  
  478.   // Use sine waves at different frequencies to create natural micro-jitter
  479.   animation.microMoveX = sin(time * 2.3f) * 0.5f + sin(time * 3.7f) * 0.3f;
  480.   animation.microMoveY = cos(time * 1.9f) * 0.5f + cos(time * 4.1f) * 0.3f;
  481.  
  482.   // Scale by emotion's micro-move amount
  483.   animation.microMoveX *= config.microMoveAmount;
  484.   animation.microMoveY *= config.microMoveAmount;
  485. }
  486.  
  487. /****** ANIMATION ENGINE: UPDATE GAZE *****/
  488. void updateGaze(float deltaTime) {
  489.   // Generate gaze pattern based on emotion
  490.   float time = millis() / 1000.0f;
  491.  
  492.   switch(animation.currentEmotion) {
  493.     case NEUTRAL:
  494.       // Gentle scanning
  495.       animation.gazeX = sin(time * 0.5f) * 0.7f;
  496.       animation.gazeY = cos(time * 0.7f) * 0.5f;
  497.       break;
  498.      
  499.     case HAPPY:
  500.       // Looking upward and around
  501.       animation.gazeX = sin(time * 0.8f) * 0.8f;
  502.       animation.gazeY = 0.5f + cos(time * 0.6f) * 0.3f;
  503.       break;
  504.      
  505.     case SAD:
  506.       // Looking downward
  507.       animation.gazeX = sin(time * 0.3f) * 0.4f;
  508.       animation.gazeY = -0.6f + sin(time * 0.5f) * 0.2f;
  509.       break;
  510.      
  511.     case EXCITED:
  512.       // Rapid, wide scanning
  513.       animation.gazeX = sin(time * 1.5f);
  514.       animation.gazeY = cos(time * 1.8f);
  515.       break;
  516.      
  517.     case SLEEPY:
  518.       // Slowly drooping gaze
  519.       animation.gazeX = sin(time * 0.2f) * 0.3f;
  520.       animation.gazeY = -0.5f - abs(sin(time * 0.3f)) * 0.3f;
  521.       break;
  522.      
  523.     case CURIOUS:
  524.       // Focused searching
  525.       animation.gazeX = sin(time * 1.2f) * 0.9f;
  526.       animation.gazeY = cos(time * 1.0f) * 0.8f;
  527.       break;
  528.      
  529.     case SHY:
  530.       // Shy glancing down
  531.       animation.gazeX = sin(time * 0.4f) * 0.3f;
  532.       animation.gazeY = -0.7f + sin(time * 0.6f) * 0.2f;
  533.       break;
  534.      
  535.     case PLAYFUL:
  536.       // Bouncy, playful eye movement
  537.       animation.gazeX = sin(time * 1.3f) * 0.85f;
  538.       animation.gazeY = abs(sin(time * 1.6f)) * 0.7f;
  539.       break;
  540.   }
  541.  
  542.   // Clamp gaze to valid range
  543.   animation.gazeX = constrain(animation.gazeX, -1.0f, 1.0f);
  544.   animation.gazeY = constrain(animation.gazeY, -1.0f, 1.0f);
  545. }
  546.  
  547. /****** ANIMATION ENGINE: UPDATE AUTONOMOUS BEHAVIOR *****/
  548. void updateAutonomousBehavior(float deltaTime) {
  549.   // Generate random gestures and state changes in autonomous mode
  550.   static float autonomousPhase = 0.0f;
  551.  
  552.   // Slowly accumulate phase
  553.   autonomousPhase += deltaTime * 0.2f;
  554.   if (autonomousPhase > 1.0f) {
  555.     autonomousPhase -= 1.0f;
  556.    
  557.     // Randomly change emotion during autonomous mode
  558.     if (random(100) < 30) {  // 30% chance each cycle
  559.       animation.targetEmotion = (Emotion)(random(8));
  560.     }
  561.   }
  562.  
  563.   // Add more pronounced micro-movements in autonomous mode
  564.   float autonomousJitter = sin(millis() / 500.0f) * 0.3f;
  565.   animation.microMoveX += autonomousJitter;
  566.   animation.microMoveY += cos(millis() / 700.0f) * 0.3f;
  567. }
  568.  
  569. /****** ANIMATION ENGINE: UPDATE ANIMATION PHASE *****/
  570. void updateAnimationPhase(float deltaTime) {
  571.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  572.  
  573.   // Update animation phase (cycles 0.0 to 1.0)
  574.   animation.animationPhase += deltaTime * config.animationSpeed;
  575.   if (animation.animationPhase > 1.0f) {
  576.     animation.animationPhase -= 1.0f;
  577.   }
  578. }
  579.  
  580. /****** ANIMATION ENGINE: UPDATE EMOTION TRANSITION *****/
  581. void updateEmotionTransition(float deltaTime) {
  582.   // Smooth transition between emotions
  583.   if (animation.currentEmotion != animation.targetEmotion) {
  584.     // Immediate emotion change
  585.     animation.currentEmotion = animation.targetEmotion;
  586.   }
  587. }
  588.  
  589. /****** ANIMATION ENGINE: MAIN UPDATE LOOP *****/
  590. void updateAnimation() {
  591.   // Calculate delta time
  592.   unsigned long currentTime = millis();
  593.   animation.frameTime = currentTime;
  594.  
  595.   if (animation.lastFrameTime == 0) {
  596.     animation.lastFrameTime = currentTime;
  597.   }
  598.  
  599.   animation.deltaTime = (currentTime - animation.lastFrameTime) / 1000.0f;
  600.   animation.lastFrameTime = currentTime;
  601.  
  602.   // Cap delta time to prevent physics instability
  603.   if (animation.deltaTime > 0.033f) {
  604.     animation.deltaTime = 0.033f;
  605.   }
  606.  
  607.   // Update physics systems
  608.   updatePupilTracking(animation.deltaTime);
  609.   updateBlinking(animation.deltaTime);
  610.   updateEyebrows(animation.deltaTime);
  611.   updateMouthExpression(animation.deltaTime);
  612.   updateCheeks(animation.deltaTime);
  613.   updateIrisGlow(animation.deltaTime);
  614.   updateNose(animation.deltaTime);
  615.   updateMicroMovements(animation.deltaTime);
  616.   updateGaze(animation.deltaTime);
  617.  
  618.   // Update animation control
  619.   updateAnimationPhase(animation.deltaTime);
  620.   updateEmotionTransition(animation.deltaTime);
  621.  
  622.   // Update autonomous behavior if active
  623.   if (animation.autonomousMode) {
  624.     updateAutonomousBehavior(animation.deltaTime);
  625.   }
  626.  
  627.   // Increment frame count
  628.   animation.frameCount++;
  629. }
  630.  
  631. /****** RENDERING: DRAW LEFT EYE *****/
  632. void drawLeftEye(int16_t centerX, int16_t centerY, int16_t radius) {
  633.   // Eye white (sclera) using U8g2
  634.   u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
  635.  
  636.   // Apply eyelid closure (blink effect)
  637.   if (physics.blinkValue > 0.1f) {
  638.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  639.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  640.     // Top eyelid with safe bounds
  641.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  642.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  643.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  644.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  645.     if (safeWidth > 0 && safeHeight > 0) {
  646.       u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
  647.     }
  648.   }
  649.  
  650.   // Calculate pupil position with physics
  651.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  652.   float pupilScale = 1.0f;
  653.   if (pupilDist > 0.65f) {
  654.     pupilScale = 0.65f / pupilDist;
  655.   }
  656.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  657.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  658.  
  659.   // Iris (darker circle)
  660.   u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
  661.  
  662.   // Iris glow effect (small highlights)
  663.   if (physics.irisGlow > 0.0f) {
  664.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  665.     if (glowIntensity > 50) {
  666.       u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
  667.     }
  668.   }
  669. }
  670.  
  671. /****** RENDERING: DRAW RIGHT EYE *****/
  672. void drawRightEye(int16_t centerX, int16_t centerY, int16_t radius) {
  673.   // Eye white (sclera) using U8g2
  674.   u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
  675.  
  676.   // Apply eyelid closure (blink effect)
  677.   if (physics.blinkValue > 0.1f) {
  678.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  679.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  680.     // Top eyelid with safe bounds
  681.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  682.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  683.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  684.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  685.     if (safeWidth > 0 && safeHeight > 0) {
  686.       u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
  687.     }
  688.   }
  689.  
  690.   // Calculate pupil position with physics
  691.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  692.   float pupilScale = 1.0f;
  693.   if (pupilDist > 0.65f) {
  694.     pupilScale = 0.65f / pupilDist;
  695.   }
  696.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  697.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  698.  
  699.   // Iris (darker circle)
  700.   u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
  701.  
  702.   // Iris glow effect (small highlights)
  703.   if (physics.irisGlow > 0.0f) {
  704.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  705.     if (glowIntensity > 50) {
  706.       u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
  707.     }
  708.   }
  709. }
  710.  
  711. /****** RENDERING: DRAW EYEBROWS *****/
  712. void drawEyebrows(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY, int16_t eyeRadius) {
  713.   int16_t eyebrowY = eyeY - eyeRadius - 8;
  714.   int16_t eyebrowWidth = eyeRadius + 5;
  715.  
  716.   // Convert angle to slope for drawing
  717.   int8_t angleOffset = (int8_t)(physics.eyebrowAngle * 0.3f);
  718.  
  719.   // Left eyebrow using U8g2 drawLine with step=1
  720.   u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset,
  721.                 leftEyeX + eyebrowWidth, eyebrowY - angleOffset);
  722.   u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset + 1,
  723.                 leftEyeX + eyebrowWidth, eyebrowY - angleOffset + 1);
  724.  
  725.   // Right eyebrow using U8g2 drawLine with step=1
  726.   u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset,
  727.                 rightEyeX + eyebrowWidth, eyebrowY + angleOffset);
  728.   u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset + 1,
  729.                 rightEyeX + eyebrowWidth, eyebrowY + angleOffset + 1);
  730. }
  731.  
  732. /****** RENDERING: DRAW MOUTH *****/
  733. void drawMouth(int16_t centerX, int16_t centerY) {
  734.   int16_t mouthWidth = 20;
  735.   int16_t mouthY = centerY + 10;
  736.  
  737.   // Mouth curve based on expression
  738.   int16_t curveHeight = (int16_t)(physics.mouthHeight * 8.0f);
  739.  
  740.   if (curveHeight > 0) {
  741.     // Happy/smiling mouth (arc) using U8g2 drawLine with step=1
  742.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  743.       int16_t y = (int16_t)(curveHeight - (x * x) / (float)(mouthWidth * mouthWidth) * curveHeight);
  744.       u8g2.drawPixel(centerX + x, mouthY + y);
  745.     }
  746.   } else if (curveHeight < 0) {
  747.     // Sad mouth (inverted arc) using U8g2 drawLine with step=1
  748.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  749.       int16_t y = (int16_t)(curveHeight + (x * x) / (float)(mouthWidth * mouthWidth) * abs(curveHeight));
  750.       u8g2.drawPixel(centerX + x, mouthY + y);
  751.     }
  752.   } else {
  753.     // Neutral mouth (straight line)
  754.     u8g2.drawLine(centerX - mouthWidth, mouthY, centerX + mouthWidth, mouthY);
  755.   }
  756. }
  757.  
  758. /****** RENDERING: DRAW NOSE *****/
  759. void drawNose(int16_t centerX, int16_t centerY) {
  760.   int16_t noseX = centerX + (int16_t)(physics.noseWiggle * 2.0f);
  761.   int16_t noseY = centerY - 2;
  762.  
  763.   // Simple nose (small triangle)
  764.   u8g2.drawLine(noseX, noseY, noseX - 2, noseY + 3);
  765.   u8g2.drawLine(noseX, noseY, noseX + 2, noseY + 3);
  766.   u8g2.drawLine(noseX - 2, noseY + 3, noseX + 2, noseY + 3);
  767. }
  768.  
  769. /****** RENDERING: DRAW CHEEKS *****/
  770. void drawCheeks(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY) {
  771.   int16_t cheekRadius = 5;
  772.   int16_t cheekY = eyeY + 10;
  773.  
  774.   // Cheek blush intensity determines if we draw
  775.   if (physics.cheekBlush > 0.1f) {
  776.     // Left cheek using U8g2 drawDisc
  777.     u8g2.drawDisc(leftEyeX - 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
  778.    
  779.     // Right cheek using U8g2 drawDisc
  780.     u8g2.drawDisc(rightEyeX + 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
  781.   }
  782. }
  783.  
  784. /****** RENDERING: MAIN DRAW FUNCTION *****/
  785. void drawFace() {
  786.   // U8g2 picture loop for double buffering
  787.   u8g2.firstPage();
  788.   do {
  789.     // Calculate face position with micro-movements
  790.     int16_t faceCenterX = SCREEN_WIDTH / 2 + (int16_t)(animation.microMoveX * 2.0f);
  791.     int16_t faceCenterY = SCREEN_HEIGHT / 2 + (int16_t)(animation.microMoveY * 2.0f);
  792.    
  793.     // Increased eye radius for better visibility
  794.     int16_t eyeRadius = 12;
  795.     int16_t eyeSeparation = 30;
  796.     int16_t eyeY = faceCenterY - 10;
  797.    
  798.     // Draw eyes
  799.     drawLeftEye(faceCenterX - eyeSeparation / 2, eyeY, eyeRadius);
  800.     drawRightEye(faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  801.    
  802.     // Draw eyebrows
  803.     drawEyebrows(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  804.    
  805.     // Draw nose
  806.     drawNose(faceCenterX, faceCenterY);
  807.    
  808.     // Draw mouth
  809.     drawMouth(faceCenterX, faceCenterY);
  810.    
  811.     // Draw cheeks
  812.     drawCheeks(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY);
  813.    
  814.     // Display frame count
  815.     u8g2.setFont(u8g2_font_5x7_tf);
  816.     u8g2.setCursor(0, 7);
  817.     u8g2.print(animation.frameCount);
  818.    
  819.   } while (u8g2.nextPage());
  820. }
  821.  
  822. /****** BUTTON EVENT HANDLER *****/
  823. void handleButtonPress() {
  824.   // Cycle to next emotion
  825.   animation.targetEmotion = (Emotion)((animation.currentEmotion + 1) % 8);
  826.   animation.lastInputTime = millis();
  827.   animation.autonomousMode = false;
  828.  
  829.   // Blink indicator LED
  830.   digitalWrite(emotionButton_LED_PIN_D2, HIGH);
  831.   delay(100);
  832.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  833. }
  834.  
  835. /****** OTA CALLBACK FUNCTION *****/
  836. // Called by POTA when an OTA update is available on the server
  837. void onOTAAvailable() {
  838.   // Set flag to trigger restart in main loop
  839.   otaUpdateAvailable = true;
  840.   Serial.println(F("[OTA] Update available! Will restart after next frame."));
  841. }
  842.  
  843. /****** POTA DASHBOARD WIDGET SETUP *****/
  844. // Configure POTA dashboard widgets for visualization and control
  845. void setupPOTADashboard() {
  846.   // Add dashboard widget for current emotion state
  847.   pota.addWidget("emotion_state", "Emotion State", "text");
  848.  
  849.   // Add dashboard widget for frame count monitoring
  850.   pota.addWidget("frame_count", "Frame Count", "number");
  851.  
  852.   // Add dashboard widget for autonomous mode status
  853.   pota.addWidget("autonomous_mode", "Autonomous Mode", "toggle");
  854.  
  855.   // Add dashboard widget for FPS display
  856.   pota.addWidget("fps_display", "FPS", "number");
  857.  
  858.   // Add dashboard widget for WiFi signal strength
  859.   pota.addWidget("wifi_signal", "WiFi Signal", "number");
  860.  
  861.   // Add dashboard widget for device uptime
  862.   pota.addWidget("device_uptime", "Device Uptime (sec)", "number");
  863.  
  864.   // Add dashboard widget for firmware version
  865.   pota.addWidget("firmware_version", "Firmware Version", "text");
  866. }
  867.  
  868. /****** UPDATE POTA DASHBOARD VALUES *****/
  869. // Periodically update dashboard widget values
  870. void updatePOTADashboard() {
  871.   // Array of emotion names for display
  872.   const char* emotionNames[] = {
  873.     "NEUTRAL", "HAPPY", "SAD", "EXCITED",
  874.     "SLEEPY", "CURIOUS", "SHY", "PLAYFUL"
  875.   };
  876.  
  877.   // Update emotion state widget
  878.   pota.updateWidget("emotion_state", emotionNames[animation.currentEmotion]);
  879.  
  880.   // Update frame count widget
  881.   pota.updateWidget("frame_count", (int)animation.frameCount);
  882.  
  883.   // Update autonomous mode widget
  884.   pota.updateWidget("autonomous_mode", animation.autonomousMode ? 1 : 0);
  885.  
  886.   // Calculate and update FPS
  887.   static unsigned long lastFPSTime = 0;
  888.   static uint32_t framesSinceLastFPS = 0;
  889.   unsigned long currentTime = millis();
  890.   framesSinceLastFPS++;
  891.  
  892.   if (currentTime - lastFPSTime >= 1000) {
  893.     float fps = (framesSinceLastFPS * 1000.0f) / (currentTime - lastFPSTime);
  894.     pota.updateWidget("fps_display", (int)fps);
  895.     lastFPSTime = currentTime;
  896.     framesSinceLastFPS = 0;
  897.   }
  898.  
  899.   // Update WiFi signal strength (RSSI in dBm)
  900.   int32_t rssi = WiFi.RSSI();
  901.   pota.updateWidget("wifi_signal", (int)rssi);
  902.  
  903.   // Update device uptime in seconds
  904.   unsigned long uptime = millis() / 1000;
  905.   pota.updateWidget("device_uptime", (int)uptime);
  906.  
  907.   // Update firmware version
  908.   pota.updateWidget("firmware_version", FIRMWARE_VERSION);
  909. }
  910.  
  911. /****** SETUP FUNCTION *****/
  912. void setup(void) {
  913.   // Initialize serial for debugging
  914.   Serial.begin(115200);
  915.   delay(100);
  916.  
  917.   // Configure GPIO pins
  918.   pinMode(faceDisplay_PushButton_PIN_D1, INPUT_PULLUP);
  919.   pinMode(emotionButton_LED_PIN_D2, OUTPUT);
  920.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  921.  
  922.   // Initialize U8g2 OLED display
  923.   u8g2.begin();
  924.   u8g2.enableUTF8Print();
  925.  
  926.   // Clear and show startup message
  927.   u8g2.firstPage();
  928.   do {
  929.     u8g2.setFont(u8g2_font_5x7_tf);
  930.     u8g2.drawStr(32, 32, "Initializing...");
  931.   } while (u8g2.nextPage());
  932.  
  933.   delay(1000);
  934.  
  935.   // Initialize emotion configurations
  936.   initializeEmotionConfigs();
  937.  
  938.   // Initialize animation state
  939.   animation.currentEmotion = NEUTRAL;
  940.   animation.targetEmotion = NEUTRAL;
  941.   animation.autonomousMode = false;
  942.   animation.lastInputTime = millis();
  943.   animation.idleThreshold = 5000;  // 5 seconds
  944.   animation.frameTime = 0;
  945.   animation.lastFrameTime = 0;
  946.   animation.frameCount = 0;
  947.   animation.animationPhase = 0.0f;
  948.   animation.phaseVelocity = 0.0f;
  949.   animation.microMoveX = 0.0f;
  950.   animation.microMoveY = 0.0f;
  951.   animation.gazeX = 0.0f;
  952.   animation.gazeY = 0.0f;
  953.  
  954.   // Initialize physics state
  955.   physics.pupilX = 0.0f;
  956.   physics.pupilY = 0.0f;
  957.   physics.pupilVelX = 0.0f;
  958.   physics.pupilVelY = 0.0f;
  959.   physics.targetPupilX = 0.0f;
  960.   physics.targetPupilY = 0.0f;
  961.   physics.blinkValue = 0.0f;
  962.   physics.blinkVel = 0.0f;
  963.   physics.isBlinking = false;
  964.   physics.blinkStartTime = millis();
  965.   physics.eyelidOffset = 0.0f;
  966.   physics.eyebrowAngle = 0.0f;
  967.   physics.eyebrowTargetAngle = 0.0f;
  968.   physics.irisGlow = 0.3f;
  969.   physics.mouthHeight = 0.3f;
  970.   physics.mouthAngle = 0.0f;
  971.   physics.cheekBlush = 0.0f;
  972.   physics.noseWiggle = 0.0f;
  973.  
  974.   // Setup EasyButton
  975.   button.begin();
  976.   button.onPressed(handleButtonPress);
  977.  
  978.   // Initialize frame rate limiter
  979.   lastFrameTimeMs = millis();
  980.  
  981.   // Initialize POTA OTA management
  982.   Serial.println(F("[POTA] Initializing POTA OTA system..."));
  983.   pota.begin(WIFI_SSID, WIFI_PASSWORD, AUTH_TOKEN, SERVER_SECRET);
  984.  
  985.   // Register OTA availability callback
  986.   pota.onOTAAvailable(onOTAAvailable);
  987.  
  988.   // Check for available OTA updates and perform if found
  989.   Serial.println(F("[POTA] Checking for OTA updates..."));
  990.   pota.checkAndPerformOTA();
  991.  
  992.   // Setup POTA dashboard widgets
  993.   setupPOTADashboard();
  994.  
  995.   Serial.println(F("Face animation system initialized!"));
  996.   Serial.println(F("Press button to cycle emotions."));
  997.   Serial.println(F("System idle for 5 seconds to enter autonomous mode."));
  998.   Serial.println(F("[POTA] OTA system ready and monitoring for updates."));
  999. }
  1000.  
  1001. /****** MAIN LOOP *****/
  1002. void loop(void) {
  1003.   // Handle POTA OTA updates and WiFi connectivity
  1004.   pota.loop();
  1005.  
  1006.   // Check if OTA update is available and trigger restart
  1007.   if (otaUpdateAvailable) {
  1008.     Serial.println(F("[OTA] Restarting device for OTA update..."));
  1009.     u8g2.firstPage();
  1010.     do {
  1011.       u8g2.setFont(u8g2_font_5x7_tf);
  1012.       u8g2.drawStr(20, 32, "OTA Update...");
  1013.     } while (u8g2.nextPage());
  1014.     delay(500);
  1015.     pota.restart();  // Restart device to apply OTA update
  1016.   }
  1017.  
  1018.   // Read button input
  1019.   button.read();
  1020.  
  1021.   // Check for idle timeout (autonomous mode)
  1022.   unsigned long timeSinceLastInput = millis() - animation.lastInputTime;
  1023.   if (timeSinceLastInput > animation.idleThreshold && !animation.autonomousMode) {
  1024.     animation.autonomousMode = true;
  1025.     Serial.println(F("Entering autonomous mode..."));
  1026.   }
  1027.  
  1028.   // Update animation physics and state
  1029.   updateAnimation();
  1030.  
  1031.   // Render face to OLED
  1032.   drawFace();
  1033.  
  1034.   // Update POTA dashboard with current system status
  1035.   updatePOTADashboard();
  1036.  
  1037.   // Frame rate limiting (22ms per frame for 45 FPS)
  1038.   unsigned long currentTime = millis();
  1039.   unsigned long elapsedTime = currentTime - lastFrameTimeMs;
  1040.   if (elapsedTime < FRAME_TIME_MS) {
  1041.     delay(FRAME_TIME_MS - elapsedTime);
  1042.   }
  1043.   lastFrameTimeMs = millis();
  1044. }
  1045.  
  1046. /* END CODE */
  1047.  
Advertisement
Add Comment
Please, Sign In to add comment