pleasedontcode

# Digital Emotion rev_09

Mar 9th, 2026
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 32.61 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 Emotion
  13.     - Version: 009
  14.     - Source Code NOT compiled for: ESP32S3 Dev Module
  15.     - Source Code created on: 2026-03-09 21:46:05
  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 <POTA.h>              // https://github.com/pleasedontcode/POTA
  58. #include "secrets.h"           // Wi-Fi credentials and authentication tokens
  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
  72. //      from secrets.h. Call pota.loop() in loop(). Implement mandatory OTA:
  73. //      call pota.checkAndPerformOTA() in setup() after begin(). Register
  74. //      pota.onOTAAvailable(cb) callback that sets bool flag. In loop() when flag
  75. //      is true call pota.restart().
  76.  
  77. /****** FUNCTION PROTOTYPES *****/
  78. void setup(void);
  79. void loop(void);
  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 OVER-THE-AIR UPDATE CONFIGURATION *****/
  99. // POTA instance for wireless firmware updates
  100. POTA pota;
  101.  
  102. // Flag to track when OTA update is available
  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. /****** OTA CALLBACK HANDLER *****/
  205. // Called by POTA when firmware update is available
  206. void onOTAAvailable(void) {
  207.   Serial.println(F("[OTA] Update available! Setting restart flag..."));
  208.   otaUpdateAvailable = true;
  209. }
  210.  
  211. /****** INITIALIZE EMOTION CONFIGURATIONS *****/
  212. void initializeEmotionConfigs() {
  213.   // NEUTRAL: calm, baseline state
  214.   emotionConfigs[NEUTRAL] = {
  215.     1.0f,      // animationSpeed
  216.     1.5f,      // blinkFrequency (blinks per second)
  217.     0.7f,      // pupilTrackingIntensity
  218.     0.5f,      // microMoveAmount
  219.     0,         // eyebrowAngle
  220.     0.3f,      // mouthHeight
  221.     0.1f,      // cheekColor
  222.     0.3f       // irisGlowIntensity
  223.   };
  224.  
  225.   // HAPPY: cheerful, animated
  226.   emotionConfigs[HAPPY] = {
  227.     1.5f,      // animationSpeed (faster)
  228.     2.0f,      // blinkFrequency
  229.     0.8f,      // pupilTrackingIntensity
  230.     1.0f,      // microMoveAmount (more movement)
  231.     15,        // eyebrowAngle (raised)
  232.     0.7f,      // mouthHeight (big smile)
  233.     0.6f,      // cheekColor (strong blush)
  234.     0.5f       // irisGlowIntensity
  235.   };
  236.  
  237.   // SAD: depressed, slower movements
  238.   emotionConfigs[SAD] = {
  239.     0.5f,      // animationSpeed (slower)
  240.     0.8f,      // blinkFrequency
  241.     0.5f,      // pupilTrackingIntensity
  242.     0.2f,      // microMoveAmount
  243.     -15,       // eyebrowAngle (lowered)
  244.     -0.5f,     // mouthHeight (frown)
  245.     0.2f,      // cheekColor
  246.     0.1f       // irisGlowIntensity
  247.   };
  248.  
  249.   // EXCITED: very animated, rapid blinks
  250.   emotionConfigs[EXCITED] = {
  251.     2.0f,      // animationSpeed (very fast)
  252.     3.0f,      // blinkFrequency
  253.     0.9f,      // pupilTrackingIntensity
  254.     1.5f,      // microMoveAmount
  255.     20,        // eyebrowAngle
  256.     0.8f,      // mouthHeight
  257.     0.7f,      // cheekColor
  258.     0.7f       // irisGlowIntensity
  259.   };
  260.  
  261.   // SLEEPY: slow, droopy eyes
  262.   emotionConfigs[SLEEPY] = {
  263.     0.3f,      // animationSpeed
  264.     0.3f,      // blinkFrequency (frequent half-blinks)
  265.     0.3f,      // pupilTrackingIntensity
  266.     0.1f,      // microMoveAmount
  267.     -20,       // eyebrowAngle (relaxed)
  268.     0.2f,      // mouthHeight
  269.     0.05f,     // cheekColor
  270.     0.05f      // irisGlowIntensity
  271.   };
  272.  
  273.   // CURIOUS: searching movements, alert
  274.   emotionConfigs[CURIOUS] = {
  275.     1.3f,      // animationSpeed
  276.     1.2f,      // blinkFrequency
  277.     1.0f,      // pupilTrackingIntensity (maximum)
  278.     0.8f,      // microMoveAmount
  279.     10,        // eyebrowAngle
  280.     0.4f,      // mouthHeight
  281.     0.2f,      // cheekColor
  282.     0.6f       // irisGlowIntensity
  283.   };
  284.  
  285.   // SHY: minimal movements, down-looking
  286.   emotionConfigs[SHY] = {
  287.     0.6f,      // animationSpeed
  288.     1.0f,      // blinkFrequency
  289.     0.4f,      // pupilTrackingIntensity
  290.     0.3f,      // microMoveAmount
  291.     -10,       // eyebrowAngle
  292.     0.1f,      // mouthHeight
  293.     0.4f,      // cheekColor (blushing)
  294.     0.2f       // irisGlowIntensity
  295.   };
  296.  
  297.   // PLAYFUL: bouncy, dynamic movements
  298.   emotionConfigs[PLAYFUL] = {
  299.     1.8f,      // animationSpeed
  300.     2.5f,      // blinkFrequency
  301.     0.85f,     // pupilTrackingIntensity
  302.     1.3f,      // microMoveAmount
  303.     12,        // eyebrowAngle
  304.     0.6f,      // mouthHeight
  305.     0.5f,      // cheekColor
  306.     0.6f       // irisGlowIntensity
  307.   };
  308. }
  309.  
  310. /****** PHYSICS ENGINE: UPDATE PUPIL TRACKING *****/
  311. void updatePupilTracking(float deltaTime) {
  312.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  313.  
  314.   // Calculate target pupil position based on gaze
  315.   physics.targetPupilX = animation.gazeX * config.pupilTrackingIntensity;
  316.   physics.targetPupilY = animation.gazeY * config.pupilTrackingIntensity;
  317.  
  318.   // Inertia damping: smooth acceleration towards target
  319.   float inertiaFactor = 5.0f * config.pupilTrackingIntensity;
  320.   float maxVelocity = 2.0f;
  321.  
  322.   // Update velocity with acceleration towards target
  323.   physics.pupilVelX += (physics.targetPupilX - physics.pupilX) * inertiaFactor * deltaTime;
  324.   physics.pupilVelY += (physics.targetPupilY - physics.pupilY) * inertiaFactor * deltaTime;
  325.  
  326.   // Clamp velocity
  327.   physics.pupilVelX = constrain(physics.pupilVelX, -maxVelocity, maxVelocity);
  328.   physics.pupilVelY = constrain(physics.pupilVelY, -maxVelocity, maxVelocity);
  329.  
  330.   // Apply damping (friction)
  331.   float damping = 0.92f;
  332.   physics.pupilVelX *= damping;
  333.   physics.pupilVelY *= damping;
  334.  
  335.   // Update position
  336.   physics.pupilX += physics.pupilVelX * deltaTime;
  337.   physics.pupilY += physics.pupilVelY * deltaTime;
  338.  
  339.   // Clamp pupil position within iris circle, not extending beyond
  340.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  341.   if (pupilDist > 0.65f) {
  342.     float scale = 0.65f / pupilDist;
  343.     physics.pupilX *= scale;
  344.     physics.pupilY *= scale;
  345.   }
  346. }
  347.  
  348. /****** PHYSICS ENGINE: UPDATE BLINKING *****/
  349. void updateBlinking(float deltaTime) {
  350.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  351.  
  352.   // Calculate blink duration based on emotion
  353.   float blinkDuration = 0.15f;  // 150ms standard blink
  354.   float blinkInterval = 1.0f / config.blinkFrequency;
  355.  
  356.   if (!physics.isBlinking) {
  357.     // Check if it's time to blink
  358.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  359.    
  360.     // Add some randomness to blink timing
  361.     float randomBlink = 0.8f + (random(40) / 100.0f);  // 0.8 to 1.2 variation
  362.    
  363.     if (timeSinceBlink > blinkInterval * randomBlink) {
  364.       physics.isBlinking = true;
  365.       physics.blinkStartTime = millis();
  366.       physics.blinkVel = 4.0f / blinkDuration;  // Velocity to close eyes
  367.     }
  368.   } else {
  369.     // Animate blinking
  370.     float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
  371.    
  372.     if (timeSinceBlink < 0.075f) {
  373.       // Closing phase (first 75ms)
  374.       physics.blinkValue += physics.blinkVel * deltaTime;
  375.       physics.blinkValue = min(physics.blinkValue, 1.0f);
  376.     } else if (timeSinceBlink < 0.15f) {
  377.       // Opening phase (next 75ms)
  378.       physics.blinkValue -= physics.blinkVel * deltaTime;
  379.       physics.blinkValue = max(physics.blinkValue, 0.0f);
  380.     } else {
  381.       // Blink complete
  382.       physics.isBlinking = false;
  383.       physics.blinkValue = 0.0f;
  384.       physics.blinkStartTime = millis();
  385.     }
  386.   }
  387. }
  388.  
  389. /****** PHYSICS ENGINE: UPDATE EYEBROW ANIMATION *****/
  390. void updateEyebrows(float deltaTime) {
  391.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  392.  
  393.   // Base eyebrow angle from emotion config
  394.   physics.eyebrowTargetAngle = config.eyebrowAngle;
  395.  
  396.   // Add micro-animations based on emotion phase
  397.   float emotionInfluence = sin(animation.animationPhase * M_PI * 2.0f);
  398.  
  399.   switch(animation.currentEmotion) {
  400.     case HAPPY:
  401.     case EXCITED:
  402.       physics.eyebrowTargetAngle += emotionInfluence * 5.0f;
  403.       break;
  404.     case CURIOUS:
  405.       physics.eyebrowTargetAngle += abs(emotionInfluence) * 3.0f;
  406.       break;
  407.     case SHY:
  408.       physics.eyebrowTargetAngle -= abs(emotionInfluence) * 2.0f;
  409.       break;
  410.     default:
  411.       break;
  412.   }
  413.  
  414.   // Smoothly interpolate to target angle
  415.   float lerpFactor = 8.0f * deltaTime;
  416.   physics.eyebrowAngle += (physics.eyebrowTargetAngle - physics.eyebrowAngle) * lerpFactor;
  417. }
  418.  
  419. /****** PHYSICS ENGINE: UPDATE MOUTH EXPRESSION *****/
  420. void updateMouthExpression(float deltaTime) {
  421.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  422.  
  423.   // Base mouth height from emotion
  424.   float targetMouthHeight = config.mouthHeight;
  425.  
  426.   // Add animation variation
  427.   float animVariation = sin(animation.animationPhase * M_PI * 2.0f);
  428.   targetMouthHeight += animVariation * 0.1f * config.animationSpeed;
  429.  
  430.   // Add mouth angle (smile tilt)
  431.   physics.mouthAngle = config.eyebrowAngle * 0.3f;
  432.  
  433.   // Smooth interpolation
  434.   float lerpFactor = 6.0f * deltaTime;
  435.   physics.mouthHeight += (targetMouthHeight - physics.mouthHeight) * lerpFactor;
  436. }
  437.  
  438. /****** PHYSICS ENGINE: UPDATE CHEEK BLUSH *****/
  439. void updateCheeks(float deltaTime) {
  440.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  441.  
  442.   // Base cheek blush from emotion
  443.   float targetCheekBlush = config.cheekColor;
  444.  
  445.   // Pulse the blush slightly based on animation phase
  446.   float pulseInfluence = (sin(animation.animationPhase * M_PI * 2.0f) + 1.0f) * 0.5f;
  447.   targetCheekBlush += pulseInfluence * 0.15f * config.animationSpeed;
  448.  
  449.   // Smooth interpolation
  450.   float lerpFactor = 3.0f * deltaTime;
  451.   physics.cheekBlush += (targetCheekBlush - physics.cheekBlush) * lerpFactor;
  452.   physics.cheekBlush = constrain(physics.cheekBlush, 0.0f, 1.0f);
  453. }
  454.  
  455. /****** PHYSICS ENGINE: UPDATE IRIS GLOW *****/
  456. void updateIrisGlow(float deltaTime) {
  457.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  458.  
  459.   // Base glow intensity from emotion
  460.   float targetGlow = config.irisGlowIntensity;
  461.  
  462.   // Pulse glow subtly
  463.   float glowPulse = (sin(animation.animationPhase * M_PI * 4.0f) + 1.0f) * 0.5f;
  464.   targetGlow += glowPulse * 0.1f;
  465.  
  466.   // Smooth interpolation
  467.   float lerpFactor = 4.0f * deltaTime;
  468.   physics.irisGlow += (targetGlow - physics.irisGlow) * lerpFactor;
  469.   physics.irisGlow = constrain(physics.irisGlow, 0.0f, 1.0f);
  470. }
  471.  
  472. /****** PHYSICS ENGINE: UPDATE NOSE WIGGLE *****/
  473. void updateNose(float deltaTime) {
  474.   // Nose wiggles subtly with high-frequency animation
  475.   physics.noseWiggle = sin(animation.animationPhase * M_PI * 8.0f) * 0.5f;
  476. }
  477.  
  478. /****** PHYSICS ENGINE: UPDATE MICRO-MOVEMENTS *****/
  479. void updateMicroMovements(float deltaTime) {
  480.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  481.  
  482.   // Generate micro-movement using noise-like pattern
  483.   float time = millis() / 1000.0f;
  484.  
  485.   // Use sine waves at different frequencies to create natural micro-jitter
  486.   animation.microMoveX = sin(time * 2.3f) * 0.5f + sin(time * 3.7f) * 0.3f;
  487.   animation.microMoveY = cos(time * 1.9f) * 0.5f + cos(time * 4.1f) * 0.3f;
  488.  
  489.   // Scale by emotion's micro-move amount
  490.   animation.microMoveX *= config.microMoveAmount;
  491.   animation.microMoveY *= config.microMoveAmount;
  492. }
  493.  
  494. /****** ANIMATION ENGINE: UPDATE GAZE *****/
  495. void updateGaze(float deltaTime) {
  496.   // Generate gaze pattern based on emotion
  497.   float time = millis() / 1000.0f;
  498.  
  499.   switch(animation.currentEmotion) {
  500.     case NEUTRAL:
  501.       // Gentle scanning
  502.       animation.gazeX = sin(time * 0.5f) * 0.7f;
  503.       animation.gazeY = cos(time * 0.7f) * 0.5f;
  504.       break;
  505.      
  506.     case HAPPY:
  507.       // Looking upward and around
  508.       animation.gazeX = sin(time * 0.8f) * 0.8f;
  509.       animation.gazeY = 0.5f + cos(time * 0.6f) * 0.3f;
  510.       break;
  511.      
  512.     case SAD:
  513.       // Looking downward
  514.       animation.gazeX = sin(time * 0.3f) * 0.4f;
  515.       animation.gazeY = -0.6f + sin(time * 0.5f) * 0.2f;
  516.       break;
  517.      
  518.     case EXCITED:
  519.       // Rapid, wide scanning
  520.       animation.gazeX = sin(time * 1.5f);
  521.       animation.gazeY = cos(time * 1.8f);
  522.       break;
  523.      
  524.     case SLEEPY:
  525.       // Slowly drooping gaze
  526.       animation.gazeX = sin(time * 0.2f) * 0.3f;
  527.       animation.gazeY = -0.5f - abs(sin(time * 0.3f)) * 0.3f;
  528.       break;
  529.      
  530.     case CURIOUS:
  531.       // Focused searching
  532.       animation.gazeX = sin(time * 1.2f) * 0.9f;
  533.       animation.gazeY = cos(time * 1.0f) * 0.8f;
  534.       break;
  535.      
  536.     case SHY:
  537.       // Shy glancing down
  538.       animation.gazeX = sin(time * 0.4f) * 0.3f;
  539.       animation.gazeY = -0.7f + sin(time * 0.6f) * 0.2f;
  540.       break;
  541.      
  542.     case PLAYFUL:
  543.       // Bouncy, playful eye movement
  544.       animation.gazeX = sin(time * 1.3f) * 0.85f;
  545.       animation.gazeY = abs(sin(time * 1.6f)) * 0.7f;
  546.       break;
  547.   }
  548.  
  549.   // Clamp gaze to valid range
  550.   animation.gazeX = constrain(animation.gazeX, -1.0f, 1.0f);
  551.   animation.gazeY = constrain(animation.gazeY, -1.0f, 1.0f);
  552. }
  553.  
  554. /****** ANIMATION ENGINE: UPDATE AUTONOMOUS BEHAVIOR *****/
  555. void updateAutonomousBehavior(float deltaTime) {
  556.   // Generate random gestures and state changes in autonomous mode
  557.   static float autonomousPhase = 0.0f;
  558.  
  559.   // Slowly accumulate phase
  560.   autonomousPhase += deltaTime * 0.2f;
  561.   if (autonomousPhase > 1.0f) {
  562.     autonomousPhase -= 1.0f;
  563.    
  564.     // Randomly change emotion during autonomous mode
  565.     if (random(100) < 30) {  // 30% chance each cycle
  566.       animation.targetEmotion = (Emotion)(random(8));
  567.     }
  568.   }
  569.  
  570.   // Add more pronounced micro-movements in autonomous mode
  571.   float autonomousJitter = sin(millis() / 500.0f) * 0.3f;
  572.   animation.microMoveX += autonomousJitter;
  573.   animation.microMoveY += cos(millis() / 700.0f) * 0.3f;
  574. }
  575.  
  576. /****** ANIMATION ENGINE: UPDATE ANIMATION PHASE *****/
  577. void updateAnimationPhase(float deltaTime) {
  578.   EmotionConfig& config = emotionConfigs[animation.currentEmotion];
  579.  
  580.   // Update animation phase (cycles 0.0 to 1.0)
  581.   animation.animationPhase += deltaTime * config.animationSpeed;
  582.   if (animation.animationPhase > 1.0f) {
  583.     animation.animationPhase -= 1.0f;
  584.   }
  585. }
  586.  
  587. /****** ANIMATION ENGINE: UPDATE EMOTION TRANSITION *****/
  588. void updateEmotionTransition(float deltaTime) {
  589.   // Smooth transition between emotions
  590.   if (animation.currentEmotion != animation.targetEmotion) {
  591.     // Immediate emotion change
  592.     animation.currentEmotion = animation.targetEmotion;
  593.   }
  594. }
  595.  
  596. /****** ANIMATION ENGINE: MAIN UPDATE LOOP *****/
  597. void updateAnimation() {
  598.   // Calculate delta time
  599.   unsigned long currentTime = millis();
  600.   animation.frameTime = currentTime;
  601.  
  602.   if (animation.lastFrameTime == 0) {
  603.     animation.lastFrameTime = currentTime;
  604.   }
  605.  
  606.   animation.deltaTime = (currentTime - animation.lastFrameTime) / 1000.0f;
  607.   animation.lastFrameTime = currentTime;
  608.  
  609.   // Cap delta time to prevent physics instability
  610.   if (animation.deltaTime > 0.033f) {
  611.     animation.deltaTime = 0.033f;
  612.   }
  613.  
  614.   // Update physics systems
  615.   updatePupilTracking(animation.deltaTime);
  616.   updateBlinking(animation.deltaTime);
  617.   updateEyebrows(animation.deltaTime);
  618.   updateMouthExpression(animation.deltaTime);
  619.   updateCheeks(animation.deltaTime);
  620.   updateIrisGlow(animation.deltaTime);
  621.   updateNose(animation.deltaTime);
  622.   updateMicroMovements(animation.deltaTime);
  623.   updateGaze(animation.deltaTime);
  624.  
  625.   // Update animation control
  626.   updateAnimationPhase(animation.deltaTime);
  627.   updateEmotionTransition(animation.deltaTime);
  628.  
  629.   // Update autonomous behavior if active
  630.   if (animation.autonomousMode) {
  631.     updateAutonomousBehavior(animation.deltaTime);
  632.   }
  633.  
  634.   // Increment frame count
  635.   animation.frameCount++;
  636. }
  637.  
  638. /****** RENDERING: DRAW LEFT EYE *****/
  639. void drawLeftEye(int16_t centerX, int16_t centerY, int16_t radius) {
  640.   // Eye white (sclera) using U8g2
  641.   u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
  642.  
  643.   // Apply eyelid closure (blink effect)
  644.   if (physics.blinkValue > 0.1f) {
  645.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  646.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  647.     // Top eyelid with safe bounds
  648.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  649.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  650.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  651.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  652.     if (safeWidth > 0 && safeHeight > 0) {
  653.       u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
  654.     }
  655.   }
  656.  
  657.   // Calculate pupil position with physics
  658.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  659.   float pupilScale = 1.0f;
  660.   if (pupilDist > 0.65f) {
  661.     pupilScale = 0.65f / pupilDist;
  662.   }
  663.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  664.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  665.  
  666.   // Iris (darker circle)
  667.   u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
  668.  
  669.   // Iris glow effect (small highlights)
  670.   if (physics.irisGlow > 0.0f) {
  671.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  672.     if (glowIntensity > 50) {
  673.       u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
  674.     }
  675.   }
  676. }
  677.  
  678. /****** RENDERING: DRAW RIGHT EYE *****/
  679. void drawRightEye(int16_t centerX, int16_t centerY, int16_t radius) {
  680.   // Eye white (sclera) using U8g2
  681.   u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
  682.  
  683.   // Apply eyelid closure (blink effect)
  684.   if (physics.blinkValue > 0.1f) {
  685.     int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
  686.     eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
  687.     // Top eyelid with safe bounds
  688.     int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
  689.     int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
  690.     int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
  691.     int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
  692.     if (safeWidth > 0 && safeHeight > 0) {
  693.       u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
  694.     }
  695.   }
  696.  
  697.   // Calculate pupil position with physics
  698.   float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
  699.   float pupilScale = 1.0f;
  700.   if (pupilDist > 0.65f) {
  701.     pupilScale = 0.65f / pupilDist;
  702.   }
  703.   int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
  704.   int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
  705.  
  706.   // Iris (darker circle)
  707.   u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
  708.  
  709.   // Iris glow effect (small highlights)
  710.   if (physics.irisGlow > 0.0f) {
  711.     int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
  712.     if (glowIntensity > 50) {
  713.       u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
  714.     }
  715.   }
  716. }
  717.  
  718. /****** RENDERING: DRAW EYEBROWS *****/
  719. void drawEyebrows(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY, int16_t eyeRadius) {
  720.   int16_t eyebrowY = eyeY - eyeRadius - 8;
  721.   int16_t eyebrowWidth = eyeRadius + 5;
  722.  
  723.   // Convert angle to slope for drawing
  724.   int8_t angleOffset = (int8_t)(physics.eyebrowAngle * 0.3f);
  725.  
  726.   // Left eyebrow using U8g2 drawLine with step=1
  727.   u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset,
  728.                 leftEyeX + eyebrowWidth, eyebrowY - angleOffset);
  729.   u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset + 1,
  730.                 leftEyeX + eyebrowWidth, eyebrowY - angleOffset + 1);
  731.  
  732.   // Right eyebrow using U8g2 drawLine with step=1
  733.   u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset,
  734.                 rightEyeX + eyebrowWidth, eyebrowY + angleOffset);
  735.   u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset + 1,
  736.                 rightEyeX + eyebrowWidth, eyebrowY + angleOffset + 1);
  737. }
  738.  
  739. /****** RENDERING: DRAW MOUTH *****/
  740. void drawMouth(int16_t centerX, int16_t centerY) {
  741.   int16_t mouthWidth = 20;
  742.   int16_t mouthY = centerY + 10;
  743.  
  744.   // Mouth curve based on expression
  745.   int16_t curveHeight = (int16_t)(physics.mouthHeight * 8.0f);
  746.  
  747.   if (curveHeight > 0) {
  748.     // Happy/smiling mouth (arc) using U8g2 drawLine with step=1
  749.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  750.       int16_t y = (int16_t)(curveHeight - (x * x) / (float)(mouthWidth * mouthWidth) * curveHeight);
  751.       u8g2.drawPixel(centerX + x, mouthY + y);
  752.     }
  753.   } else if (curveHeight < 0) {
  754.     // Sad mouth (inverted arc) using U8g2 drawLine with step=1
  755.     for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
  756.       int16_t y = (int16_t)(curveHeight + (x * x) / (float)(mouthWidth * mouthWidth) * abs(curveHeight));
  757.       u8g2.drawPixel(centerX + x, mouthY + y);
  758.     }
  759.   } else {
  760.     // Neutral mouth (straight line)
  761.     u8g2.drawLine(centerX - mouthWidth, mouthY, centerX + mouthWidth, mouthY);
  762.   }
  763. }
  764.  
  765. /****** RENDERING: DRAW NOSE *****/
  766. void drawNose(int16_t centerX, int16_t centerY) {
  767.   int16_t noseX = centerX + (int16_t)(physics.noseWiggle * 2.0f);
  768.   int16_t noseY = centerY - 2;
  769.  
  770.   // Simple nose (small triangle)
  771.   u8g2.drawLine(noseX, noseY, noseX - 2, noseY + 3);
  772.   u8g2.drawLine(noseX, noseY, noseX + 2, noseY + 3);
  773.   u8g2.drawLine(noseX - 2, noseY + 3, noseX + 2, noseY + 3);
  774. }
  775.  
  776. /****** RENDERING: DRAW CHEEKS *****/
  777. void drawCheeks(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY) {
  778.   int16_t cheekRadius = 5;
  779.   int16_t cheekY = eyeY + 10;
  780.  
  781.   // Cheek blush intensity determines if we draw
  782.   if (physics.cheekBlush > 0.1f) {
  783.     // Left cheek using U8g2 drawDisc
  784.     u8g2.drawDisc(leftEyeX - 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
  785.    
  786.     // Right cheek using U8g2 drawDisc
  787.     u8g2.drawDisc(rightEyeX + 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
  788.   }
  789. }
  790.  
  791. /****** RENDERING: MAIN DRAW FUNCTION *****/
  792. void drawFace() {
  793.   // U8g2 picture loop for double buffering
  794.   u8g2.firstPage();
  795.   do {
  796.     // Calculate face position with micro-movements
  797.     int16_t faceCenterX = SCREEN_WIDTH / 2 + (int16_t)(animation.microMoveX * 2.0f);
  798.     int16_t faceCenterY = SCREEN_HEIGHT / 2 + (int16_t)(animation.microMoveY * 2.0f);
  799.    
  800.     // Increased eye radius for better visibility
  801.     int16_t eyeRadius = 12;
  802.     int16_t eyeSeparation = 30;
  803.     int16_t eyeY = faceCenterY - 10;
  804.    
  805.     // Draw eyes
  806.     drawLeftEye(faceCenterX - eyeSeparation / 2, eyeY, eyeRadius);
  807.     drawRightEye(faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  808.    
  809.     // Draw eyebrows
  810.     drawEyebrows(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
  811.    
  812.     // Draw nose
  813.     drawNose(faceCenterX, faceCenterY);
  814.    
  815.     // Draw mouth
  816.     drawMouth(faceCenterX, faceCenterY);
  817.    
  818.     // Draw cheeks
  819.     drawCheeks(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY);
  820.    
  821.     // Display frame count
  822.     u8g2.setFont(u8g2_font_5x7_tf);
  823.     u8g2.setCursor(0, 7);
  824.     u8g2.print(animation.frameCount);
  825.    
  826.   } while (u8g2.nextPage());
  827. }
  828.  
  829. /****** BUTTON EVENT HANDLER *****/
  830. void handleButtonPress() {
  831.   // Cycle to next emotion
  832.   animation.targetEmotion = (Emotion)((animation.currentEmotion + 1) % 8);
  833.   animation.lastInputTime = millis();
  834.   animation.autonomousMode = false;
  835.  
  836.   // Blink indicator LED
  837.   digitalWrite(emotionButton_LED_PIN_D2, HIGH);
  838.   delay(100);
  839.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  840. }
  841.  
  842. /****** SETUP FUNCTION *****/
  843. void setup(void) {
  844.   // Initialize serial for debugging
  845.   Serial.begin(115200);
  846.   delay(100);
  847.  
  848.   // Configure GPIO pins
  849.   pinMode(faceDisplay_PushButton_PIN_D1, INPUT_PULLUP);
  850.   pinMode(emotionButton_LED_PIN_D2, OUTPUT);
  851.   digitalWrite(emotionButton_LED_PIN_D2, LOW);
  852.  
  853.   // Initialize U8g2 OLED display
  854.   u8g2.begin();
  855.   u8g2.enableUTF8Print();
  856.  
  857.   // Clear and show startup message
  858.   u8g2.firstPage();
  859.   do {
  860.     u8g2.setFont(u8g2_font_5x7_tf);
  861.     u8g2.drawStr(32, 32, "Initializing...");
  862.   } while (u8g2.nextPage());
  863.  
  864.   delay(1000);
  865.  
  866.   // Initialize POTA for OTA updates
  867.   Serial.println(F("[SETUP] Initializing POTA..."));
  868.   pota.begin(WIFI_SSID, WIFI_PASSWORD);
  869.   Serial.println(F("[SETUP] POTA initialized with WiFi credentials from secrets.h"));
  870.  
  871.   // Register OTA available callback
  872.   pota.onOTAAvailable(onOTAAvailable);
  873.   Serial.println(F("[SETUP] OTA callback registered"));
  874.  
  875.   // Perform mandatory OTA check
  876.   Serial.println(F("[SETUP] Checking for available OTA updates..."));
  877.   pota.checkAndPerformOTA();
  878.   Serial.println(F("[SETUP] OTA check complete"));
  879.  
  880.   // Initialize emotion configurations
  881.   initializeEmotionConfigs();
  882.  
  883.   // Initialize animation state
  884.   animation.currentEmotion = NEUTRAL;
  885.   animation.targetEmotion = NEUTRAL;
  886.   animation.autonomousMode = false;
  887.   animation.lastInputTime = millis();
  888.   animation.idleThreshold = 5000;  // 5 seconds
  889.   animation.frameTime = 0;
  890.   animation.lastFrameTime = 0;
  891.   animation.frameCount = 0;
  892.   animation.animationPhase = 0.0f;
  893.   animation.phaseVelocity = 0.0f;
  894.   animation.microMoveX = 0.0f;
  895.   animation.microMoveY = 0.0f;
  896.   animation.gazeX = 0.0f;
  897.   animation.gazeY = 0.0f;
  898.  
  899.   // Initialize physics state
  900.   physics.pupilX = 0.0f;
  901.   physics.pupilY = 0.0f;
  902.   physics.pupilVelX = 0.0f;
  903.   physics.pupilVelY = 0.0f;
  904.   physics.targetPupilX = 0.0f;
  905.   physics.targetPupilY = 0.0f;
  906.   physics.blinkValue = 0.0f;
  907.   physics.blinkVel = 0.0f;
  908.   physics.isBlinking = false;
  909.   physics.blinkStartTime = millis();
  910.   physics.eyelidOffset = 0.0f;
  911.   physics.eyebrowAngle = 0.0f;
  912.   physics.eyebrowTargetAngle = 0.0f;
  913.   physics.irisGlow = 0.3f;
  914.   physics.mouthHeight = 0.3f;
  915.   physics.mouthAngle = 0.0f;
  916.   physics.cheekBlush = 0.0f;
  917.   physics.noseWiggle = 0.0f;
  918.  
  919.   // Setup EasyButton
  920.   button.begin();
  921.   button.onPressed(handleButtonPress);
  922.  
  923.   // Initialize frame rate limiter
  924.   lastFrameTimeMs = millis();
  925.  
  926.   Serial.println(F("========================================"));
  927.   Serial.println(F("Emotional Baby - POTA OTA Firmware v2.0"));
  928.   Serial.println(F("========================================"));
  929.   Serial.println(F("Face animation system initialized!"));
  930.   Serial.println(F("Press button to cycle emotions."));
  931.   Serial.println(F("System idle for 5 seconds to enter autonomous mode."));
  932.   Serial.println(F("Target: 45 FPS animation rendering"));
  933.   Serial.println(F("WiFi: Connected for OTA updates"));
  934.   Serial.println(F("========================================"));
  935. }
  936.  
  937. /****** MAIN LOOP *****/
  938. void loop(void) {
  939.   // Service POTA for wireless connectivity and updates
  940.   pota.loop();
  941.  
  942.   // Check if OTA update is available and perform restart if needed
  943.   if (otaUpdateAvailable) {
  944.     Serial.println(F("[LOOP] OTA update available, restarting device..."));
  945.     pota.restart();
  946.     // Note: pota.restart() will not return, device will reboot
  947.   }
  948.  
  949.   // Read button input
  950.   button.read();
  951.  
  952.   // Check for idle timeout (autonomous mode)
  953.   unsigned long timeSinceLastInput = millis() - animation.lastInputTime;
  954.   if (timeSinceLastInput > animation.idleThreshold && !animation.autonomousMode) {
  955.     animation.autonomousMode = true;
  956.     Serial.println(F("Entering autonomous mode..."));
  957.   }
  958.  
  959.   // Update animation physics and state
  960.   updateAnimation();
  961.  
  962.   // Render face to OLED
  963.   drawFace();
  964.  
  965.   // Frame rate limiting (22ms per frame for 45 FPS)
  966.   unsigned long currentTime = millis();
  967.   unsigned long elapsedTime = currentTime - lastFrameTimeMs;
  968.   if (elapsedTime < FRAME_TIME_MS) {
  969.     delay(FRAME_TIME_MS - elapsedTime);
  970.   }
  971.   lastFrameTimeMs = millis();
  972. }
  973.  
  974. /* END CODE */
  975.  
Advertisement
Add Comment
Please, Sign In to add comment