Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /********* Pleasedontcode.com **********
- Pleasedontcode thanks you for automatic code generation! Enjoy your code!
- - Terms and Conditions:
- You have a non-exclusive, revocable, worldwide, royalty-free license
- for personal and commercial use. Attribution is optional; modifications
- are allowed, but you're responsible for code maintenance. We're not
- liable for any loss or damage. For full terms,
- please visit pleasedontcode.com/termsandconditions.
- - Project: **Digital Emotions**
- - Version: 006
- - Source Code NOT compiled for: ESP32S3 Dev Module
- - Source Code created on: 2026-03-09 21:35:24
- ********* Pleasedontcode.com **********/
- /****** SYSTEM REQUIREMENTS *****/
- /****** SYSTEM REQUIREMENT 1 *****/
- /* SH1106 1.3" OLED display (128x64) with U8g2 */
- /* library, I2C address 0x3C. Procedural animated */
- /* face: eyes with physics-based pupils, blinking, */
- /* iris glow, eyelids, eyebrows, mouth, cheeks, nose. */
- /* 45 FPS rendering. */
- /****** SYSTEM REQUIREMENT 2 *****/
- /* 8 emotion states (neutral, happy, sad, excited, */
- /* sleepy, curious, shy, playful) with unique */
- /* animations. Touch button (D1) cycles emotions. LED */
- /* (D2) indicates state. Idle timeout triggers */
- /* autonomous baby behaviors with random emotion */
- /* changes. */
- /****** SYSTEM REQUIREMENT 3 *****/
- /* Modular architecture: physics engine, animation */
- /* state machine, emotion config presets. Fully */
- /* commented code, optimized for ESP32-S3, minimal */
- /* memory footprint, frame rate limited to 45 FPS. */
- /****** SYSTEM REQUIREMENT 4 *****/
- /* [POTA] Include POTA.h and secrets.h. Init POTA in */
- /* setup() with WiFi creds from secrets.h. Call */
- /* pota.loop() in loop(). */
- /****** SYSTEM REQUIREMENT 5 *****/
- /* [POTA] MANDATORY OTA: call */
- /* pota.checkAndPerformOTA() in setup() after */
- /* begin(). Register pota.onOTAAvailable(cb) where cb */
- /* sets a bool flag. In loop() when flag is true call */
- /* pota.restart(). Without OTA the device cannot */
- /* update remotely. */
- /****** END SYSTEM REQUIREMENTS *****/
- /* START CODE */
- /****** DEFINITION OF LIBRARIES *****/
- #include <U8g2lib.h> // https://github.com/olikraus/u8g2
- #include <EasyButton.h> // https://github.com/evert-arias/EasyButton
- #include "secrets.h" // WiFi and authentication credentials
- #include "POTA.h" // POTA OTA update management
- /****** SYSTEM REQUIREMENTS *****/
- // SR1: SH1106 1.3" OLED display (128x64) with U8g2 library, I2C address 0x3C.
- // Procedural animated face: eyes with physics-based pupils, blinking, iris glow,
- // eyelids, eyebrows, mouth, cheeks, nose. 45 FPS rendering.
- // SR2: 8 emotion states (neutral, happy, sad, excited, sleepy, curious, shy, playful)
- // with unique animations. Touch button (D1) cycles emotions. LED (D2) indicates
- // state. Idle timeout triggers autonomous baby behaviors with random emotion
- // changes.
- // SR3: Modular architecture: physics engine, animation state machine, emotion config
- // presets. Fully commented code, optimized for ESP32-S3, minimal memory footprint,
- // frame rate limited to 45 FPS.
- // SR4: [POTA] Include POTA.h and secrets.h. Init POTA in setup() with WiFi creds from
- // secrets.h. Call pota.loop() in loop(). Dashboard widgets configured.
- // SR5: [POTA] MANDATORY OTA: call pota.checkAndPerformOTA() in setup() after begin().
- // Register pota.onOTAAvailable(cb) where cb sets a bool flag. In loop() when
- // flag is true call pota.restart(). Without OTA the device cannot update remotely.
- /****** FUNCTION PROTOTYPES *****/\nvoid setup(void);
- void loop(void);
- void onOTAAvailable(); // OTA availability callback
- /***** DEFINITION OF DIGITAL INPUT PINS *****/
- const uint8_t faceDisplay_PushButton_PIN_D1 = 1;
- /***** DEFINITION OF DIGITAL OUTPUT PINS *****/
- const uint8_t emotionButton_LED_PIN_D2 = 2;
- /****** OLED DISPLAY CONFIGURATION *****/
- // U8g2 display: SH1106 1.3" OLED 128x64 with I2C at address 0x3C
- U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 21, 22);
- // Display dimensions
- #define SCREEN_WIDTH 128
- #define SCREEN_HEIGHT 64
- /****** EASY BUTTON CONFIGURATION *****/
- EasyButton button(faceDisplay_PushButton_PIN_D1);
- /****** POTA OTA MANAGEMENT *****/
- // Global POTA instance for OTA update management
- POTA pota;
- // OTA update flag - set by callback, triggers restart in loop()
- volatile bool otaUpdateAvailable = false;
- /****** EMOTION ENUMERATION *****/
- enum Emotion {
- NEUTRAL = 0,
- HAPPY = 1,
- SAD = 2,
- EXCITED = 3,
- SLEEPY = 4,
- CURIOUS = 5,
- SHY = 6,
- PLAYFUL = 7
- };
- /****** PHYSICS ENGINE STRUCTURE *****/
- struct PhysicsState {
- // Eye pupil tracking physics
- float pupilX; // Current pupil X position (-1.0 to 1.0)
- float pupilY; // Current pupil Y position (-1.0 to 1.0)
- float pupilVelX; // Pupil velocity X (inertia)
- float pupilVelY; // Pupil velocity Y (inertia)
- float targetPupilX; // Target pupil X position
- float targetPupilY; // Target pupil Y position
- // Blinking physics
- float blinkValue; // 0.0 (open) to 1.0 (closed)
- float blinkVel; // Blinking velocity for smooth animation
- bool isBlinking; // Currently blinking
- unsigned long blinkStartTime;
- // Eyelid animation
- float eyelidOffset; // Additional eyelid movement beyond blink
- // Eyebrow animation
- float eyebrowAngle; // Eyebrow rotation in degrees
- float eyebrowTargetAngle;
- // Iris glow
- float irisGlow; // 0.0 to 1.0 glow intensity
- // Mouth expression
- float mouthHeight; // Mouth curvature
- float mouthAngle; // Mouth angle/tilt
- // Cheek blush
- float cheekBlush; // Cheek color intensity
- // Nose animation
- float noseWiggle; // Nose movement micro-animation
- };
- /****** ANIMATION STATE STRUCTURE *****/
- struct AnimationState {
- Emotion currentEmotion;
- Emotion targetEmotion;
- // Autonomous behavior
- bool autonomousMode;
- unsigned long lastInputTime;
- unsigned long idleThreshold; // 5000ms = 5 seconds
- // Animation timing
- unsigned long frameTime; // Current frame timestamp
- unsigned long lastFrameTime;
- float deltaTime; // Time since last frame in seconds
- uint32_t frameCount; // Total frames rendered
- // Animation phase for cyclic behaviors
- float animationPhase; // 0.0 to 1.0, wraps around
- float phaseVelocity; // Controls animation speed based on emotion
- // Micro-movement
- float microMoveX; // Subtle position jitter
- float microMoveY; // Subtle position jitter
- // Gaze direction
- float gazeX; // Where the eyes are looking
- float gazeY;
- };
- /****** EMOTION CONFIGURATION STRUCTURE *****/
- struct EmotionConfig {
- float animationSpeed; // How fast animations play
- float blinkFrequency; // How often to blink (blinks per second)
- float pupilTrackingIntensity; // How responsive pupil is to gaze
- float microMoveAmount; // Amount of micro-movement jitter
- int8_t eyebrowAngle; // Base eyebrow angle in degrees
- float mouthHeight; // Base mouth curve height
- float cheekColor; // Cheek blush intensity (0.0-1.0)
- float irisGlowIntensity; // Iris glow brightness
- };
- /****** GLOBAL STATE VARIABLES *****/
- PhysicsState physics;
- AnimationState animation;
- EmotionConfig emotionConfigs[8];
- /****** FRAME RATE LIMITING VARIABLES *****/
- unsigned long lastFrameTimeMs = 0;
- const unsigned long FRAME_TIME_MS = 22; // 22ms per frame for 45 FPS
- /****** INITIALIZE EMOTION CONFIGURATIONS *****/
- void initializeEmotionConfigs() {
- // NEUTRAL: calm, baseline state
- emotionConfigs[NEUTRAL] = {
- 1.0f, // animationSpeed
- 1.5f, // blinkFrequency (blinks per second)
- 0.7f, // pupilTrackingIntensity
- 0.5f, // microMoveAmount
- 0, // eyebrowAngle
- 0.3f, // mouthHeight
- 0.1f, // cheekColor
- 0.3f // irisGlowIntensity
- };
- // HAPPY: cheerful, animated
- emotionConfigs[HAPPY] = {
- 1.5f, // animationSpeed (faster)
- 2.0f, // blinkFrequency
- 0.8f, // pupilTrackingIntensity
- 1.0f, // microMoveAmount (more movement)
- 15, // eyebrowAngle (raised)
- 0.7f, // mouthHeight (big smile)
- 0.6f, // cheekColor (strong blush)
- 0.5f // irisGlowIntensity
- };
- // SAD: depressed, slower movements
- emotionConfigs[SAD] = {
- 0.5f, // animationSpeed (slower)
- 0.8f, // blinkFrequency
- 0.5f, // pupilTrackingIntensity
- 0.2f, // microMoveAmount
- -15, // eyebrowAngle (lowered)
- -0.5f, // mouthHeight (frown)
- 0.2f, // cheekColor
- 0.1f // irisGlowIntensity
- };
- // EXCITED: very animated, rapid blinks
- emotionConfigs[EXCITED] = {
- 2.0f, // animationSpeed (very fast)
- 3.0f, // blinkFrequency
- 0.9f, // pupilTrackingIntensity
- 1.5f, // microMoveAmount
- 20, // eyebrowAngle
- 0.8f, // mouthHeight
- 0.7f, // cheekColor
- 0.7f // irisGlowIntensity
- };
- // SLEEPY: slow, droopy eyes
- emotionConfigs[SLEEPY] = {
- 0.3f, // animationSpeed
- 0.3f, // blinkFrequency (frequent half-blinks)
- 0.3f, // pupilTrackingIntensity
- 0.1f, // microMoveAmount
- -20, // eyebrowAngle (relaxed)
- 0.2f, // mouthHeight
- 0.05f, // cheekColor
- 0.05f // irisGlowIntensity
- };
- // CURIOUS: searching movements, alert
- emotionConfigs[CURIOUS] = {
- 1.3f, // animationSpeed
- 1.2f, // blinkFrequency
- 1.0f, // pupilTrackingIntensity (maximum)
- 0.8f, // microMoveAmount
- 10, // eyebrowAngle
- 0.4f, // mouthHeight
- 0.2f, // cheekColor
- 0.6f // irisGlowIntensity
- };
- // SHY: minimal movements, down-looking
- emotionConfigs[SHY] = {
- 0.6f, // animationSpeed
- 1.0f, // blinkFrequency
- 0.4f, // pupilTrackingIntensity
- 0.3f, // microMoveAmount
- -10, // eyebrowAngle
- 0.1f, // mouthHeight
- 0.4f, // cheekColor (blushing)
- 0.2f // irisGlowIntensity
- };
- // PLAYFUL: bouncy, dynamic movements
- emotionConfigs[PLAYFUL] = {
- 1.8f, // animationSpeed
- 2.5f, // blinkFrequency
- 0.85f, // pupilTrackingIntensity
- 1.3f, // microMoveAmount
- 12, // eyebrowAngle
- 0.6f, // mouthHeight
- 0.5f, // cheekColor
- 0.6f // irisGlowIntensity
- };
- }
- /****** PHYSICS ENGINE: UPDATE PUPIL TRACKING *****/
- void updatePupilTracking(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Calculate target pupil position based on gaze
- physics.targetPupilX = animation.gazeX * config.pupilTrackingIntensity;
- physics.targetPupilY = animation.gazeY * config.pupilTrackingIntensity;
- // Inertia damping: smooth acceleration towards target
- float inertiaFactor = 5.0f * config.pupilTrackingIntensity;
- float maxVelocity = 2.0f;
- // Update velocity with acceleration towards target
- physics.pupilVelX += (physics.targetPupilX - physics.pupilX) * inertiaFactor * deltaTime;
- physics.pupilVelY += (physics.targetPupilY - physics.pupilY) * inertiaFactor * deltaTime;
- // Clamp velocity
- physics.pupilVelX = constrain(physics.pupilVelX, -maxVelocity, maxVelocity);
- physics.pupilVelY = constrain(physics.pupilVelY, -maxVelocity, maxVelocity);
- // Apply damping (friction)
- float damping = 0.92f;
- physics.pupilVelX *= damping;
- physics.pupilVelY *= damping;
- // Update position
- physics.pupilX += physics.pupilVelX * deltaTime;
- physics.pupilY += physics.pupilVelY * deltaTime;
- // Clamp pupil position within iris circle, not extending beyond
- float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
- if (pupilDist > 0.65f) {
- float scale = 0.65f / pupilDist;
- physics.pupilX *= scale;
- physics.pupilY *= scale;
- }
- }
- /****** PHYSICS ENGINE: UPDATE BLINKING *****/
- void updateBlinking(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Calculate blink duration based on emotion
- float blinkDuration = 0.15f; // 150ms standard blink
- float blinkInterval = 1.0f / config.blinkFrequency;
- if (!physics.isBlinking) {
- // Check if it's time to blink
- float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
- // Add some randomness to blink timing
- float randomBlink = 0.8f + (random(40) / 100.0f); // 0.8 to 1.2 variation
- if (timeSinceBlink > blinkInterval * randomBlink) {
- physics.isBlinking = true;
- physics.blinkStartTime = millis();
- physics.blinkVel = 4.0f / blinkDuration; // Velocity to close eyes
- }
- } else {
- // Animate blinking
- float timeSinceBlink = (millis() - physics.blinkStartTime) / 1000.0f;
- if (timeSinceBlink < 0.075f) {
- // Closing phase (first 75ms)
- physics.blinkValue += physics.blinkVel * deltaTime;
- physics.blinkValue = min(physics.blinkValue, 1.0f);
- } else if (timeSinceBlink < 0.15f) {
- // Opening phase (next 75ms)
- physics.blinkValue -= physics.blinkVel * deltaTime;
- physics.blinkValue = max(physics.blinkValue, 0.0f);
- } else {
- // Blink complete
- physics.isBlinking = false;
- physics.blinkValue = 0.0f;
- physics.blinkStartTime = millis();
- }
- }
- }
- /****** PHYSICS ENGINE: UPDATE EYEBROW ANIMATION *****/
- void updateEyebrows(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Base eyebrow angle from emotion config
- physics.eyebrowTargetAngle = config.eyebrowAngle;
- // Add micro-animations based on emotion phase
- float emotionInfluence = sin(animation.animationPhase * M_PI * 2.0f);
- switch(animation.currentEmotion) {
- case HAPPY:
- case EXCITED:
- physics.eyebrowTargetAngle += emotionInfluence * 5.0f;
- break;
- case CURIOUS:
- physics.eyebrowTargetAngle += abs(emotionInfluence) * 3.0f;
- break;
- case SHY:
- physics.eyebrowTargetAngle -= abs(emotionInfluence) * 2.0f;
- break;
- default:
- break;
- }
- // Smoothly interpolate to target angle
- float lerpFactor = 8.0f * deltaTime;
- physics.eyebrowAngle += (physics.eyebrowTargetAngle - physics.eyebrowAngle) * lerpFactor;
- }
- /****** PHYSICS ENGINE: UPDATE MOUTH EXPRESSION *****/
- void updateMouthExpression(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Base mouth height from emotion
- float targetMouthHeight = config.mouthHeight;
- // Add animation variation
- float animVariation = sin(animation.animationPhase * M_PI * 2.0f);
- targetMouthHeight += animVariation * 0.1f * config.animationSpeed;
- // Add mouth angle (smile tilt)
- physics.mouthAngle = config.eyebrowAngle * 0.3f;
- // Smooth interpolation
- float lerpFactor = 6.0f * deltaTime;
- physics.mouthHeight += (targetMouthHeight - physics.mouthHeight) * lerpFactor;
- }
- /****** PHYSICS ENGINE: UPDATE CHEEK BLUSH *****/
- void updateCheeks(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Base cheek blush from emotion
- float targetCheekBlush = config.cheekColor;
- // Pulse the blush slightly based on animation phase
- float pulseInfluence = (sin(animation.animationPhase * M_PI * 2.0f) + 1.0f) * 0.5f;
- targetCheekBlush += pulseInfluence * 0.15f * config.animationSpeed;
- // Smooth interpolation
- float lerpFactor = 3.0f * deltaTime;
- physics.cheekBlush += (targetCheekBlush - physics.cheekBlush) * lerpFactor;
- physics.cheekBlush = constrain(physics.cheekBlush, 0.0f, 1.0f);
- }
- /****** PHYSICS ENGINE: UPDATE IRIS GLOW *****/
- void updateIrisGlow(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Base glow intensity from emotion
- float targetGlow = config.irisGlowIntensity;
- // Pulse glow subtly
- float glowPulse = (sin(animation.animationPhase * M_PI * 4.0f) + 1.0f) * 0.5f;
- targetGlow += glowPulse * 0.1f;
- // Smooth interpolation
- float lerpFactor = 4.0f * deltaTime;
- physics.irisGlow += (targetGlow - physics.irisGlow) * lerpFactor;
- physics.irisGlow = constrain(physics.irisGlow, 0.0f, 1.0f);
- }
- /****** PHYSICS ENGINE: UPDATE NOSE WIGGLE *****/
- void updateNose(float deltaTime) {
- // Nose wiggles subtly with high-frequency animation
- physics.noseWiggle = sin(animation.animationPhase * M_PI * 8.0f) * 0.5f;
- }
- /****** PHYSICS ENGINE: UPDATE MICRO-MOVEMENTS *****/
- void updateMicroMovements(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Generate micro-movement using noise-like pattern
- float time = millis() / 1000.0f;
- // Use sine waves at different frequencies to create natural micro-jitter
- animation.microMoveX = sin(time * 2.3f) * 0.5f + sin(time * 3.7f) * 0.3f;
- animation.microMoveY = cos(time * 1.9f) * 0.5f + cos(time * 4.1f) * 0.3f;
- // Scale by emotion's micro-move amount
- animation.microMoveX *= config.microMoveAmount;
- animation.microMoveY *= config.microMoveAmount;
- }
- /****** ANIMATION ENGINE: UPDATE GAZE *****/
- void updateGaze(float deltaTime) {
- // Generate gaze pattern based on emotion
- float time = millis() / 1000.0f;
- switch(animation.currentEmotion) {
- case NEUTRAL:
- // Gentle scanning
- animation.gazeX = sin(time * 0.5f) * 0.7f;
- animation.gazeY = cos(time * 0.7f) * 0.5f;
- break;
- case HAPPY:
- // Looking upward and around
- animation.gazeX = sin(time * 0.8f) * 0.8f;
- animation.gazeY = 0.5f + cos(time * 0.6f) * 0.3f;
- break;
- case SAD:
- // Looking downward
- animation.gazeX = sin(time * 0.3f) * 0.4f;
- animation.gazeY = -0.6f + sin(time * 0.5f) * 0.2f;
- break;
- case EXCITED:
- // Rapid, wide scanning
- animation.gazeX = sin(time * 1.5f);
- animation.gazeY = cos(time * 1.8f);
- break;
- case SLEEPY:
- // Slowly drooping gaze
- animation.gazeX = sin(time * 0.2f) * 0.3f;
- animation.gazeY = -0.5f - abs(sin(time * 0.3f)) * 0.3f;
- break;
- case CURIOUS:
- // Focused searching
- animation.gazeX = sin(time * 1.2f) * 0.9f;
- animation.gazeY = cos(time * 1.0f) * 0.8f;
- break;
- case SHY:
- // Shy glancing down
- animation.gazeX = sin(time * 0.4f) * 0.3f;
- animation.gazeY = -0.7f + sin(time * 0.6f) * 0.2f;
- break;
- case PLAYFUL:
- // Bouncy, playful eye movement
- animation.gazeX = sin(time * 1.3f) * 0.85f;
- animation.gazeY = abs(sin(time * 1.6f)) * 0.7f;
- break;
- }
- // Clamp gaze to valid range
- animation.gazeX = constrain(animation.gazeX, -1.0f, 1.0f);
- animation.gazeY = constrain(animation.gazeY, -1.0f, 1.0f);
- }
- /****** ANIMATION ENGINE: UPDATE AUTONOMOUS BEHAVIOR *****/
- void updateAutonomousBehavior(float deltaTime) {
- // Generate random gestures and state changes in autonomous mode
- static float autonomousPhase = 0.0f;
- // Slowly accumulate phase
- autonomousPhase += deltaTime * 0.2f;
- if (autonomousPhase > 1.0f) {
- autonomousPhase -= 1.0f;
- // Randomly change emotion during autonomous mode
- if (random(100) < 30) { // 30% chance each cycle
- animation.targetEmotion = (Emotion)(random(8));
- }
- }
- // Add more pronounced micro-movements in autonomous mode
- float autonomousJitter = sin(millis() / 500.0f) * 0.3f;
- animation.microMoveX += autonomousJitter;
- animation.microMoveY += cos(millis() / 700.0f) * 0.3f;
- }
- /****** ANIMATION ENGINE: UPDATE ANIMATION PHASE *****/
- void updateAnimationPhase(float deltaTime) {
- EmotionConfig& config = emotionConfigs[animation.currentEmotion];
- // Update animation phase (cycles 0.0 to 1.0)
- animation.animationPhase += deltaTime * config.animationSpeed;
- if (animation.animationPhase > 1.0f) {
- animation.animationPhase -= 1.0f;
- }
- }
- /****** ANIMATION ENGINE: UPDATE EMOTION TRANSITION *****/
- void updateEmotionTransition(float deltaTime) {
- // Smooth transition between emotions
- if (animation.currentEmotion != animation.targetEmotion) {
- // Immediate emotion change
- animation.currentEmotion = animation.targetEmotion;
- }
- }
- /****** ANIMATION ENGINE: MAIN UPDATE LOOP *****/
- void updateAnimation() {
- // Calculate delta time
- unsigned long currentTime = millis();
- animation.frameTime = currentTime;
- if (animation.lastFrameTime == 0) {
- animation.lastFrameTime = currentTime;
- }
- animation.deltaTime = (currentTime - animation.lastFrameTime) / 1000.0f;
- animation.lastFrameTime = currentTime;
- // Cap delta time to prevent physics instability
- if (animation.deltaTime > 0.033f) {
- animation.deltaTime = 0.033f;
- }
- // Update physics systems
- updatePupilTracking(animation.deltaTime);
- updateBlinking(animation.deltaTime);
- updateEyebrows(animation.deltaTime);
- updateMouthExpression(animation.deltaTime);
- updateCheeks(animation.deltaTime);
- updateIrisGlow(animation.deltaTime);
- updateNose(animation.deltaTime);
- updateMicroMovements(animation.deltaTime);
- updateGaze(animation.deltaTime);
- // Update animation control
- updateAnimationPhase(animation.deltaTime);
- updateEmotionTransition(animation.deltaTime);
- // Update autonomous behavior if active
- if (animation.autonomousMode) {
- updateAutonomousBehavior(animation.deltaTime);
- }
- // Increment frame count
- animation.frameCount++;
- }
- /****** RENDERING: DRAW LEFT EYE *****/
- void drawLeftEye(int16_t centerX, int16_t centerY, int16_t radius) {
- // Eye white (sclera) using U8g2
- u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
- // Apply eyelid closure (blink effect)
- if (physics.blinkValue > 0.1f) {
- int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
- eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
- // Top eyelid with safe bounds
- int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
- int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
- int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
- int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
- if (safeWidth > 0 && safeHeight > 0) {
- u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
- }
- }
- // Calculate pupil position with physics
- float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
- float pupilScale = 1.0f;
- if (pupilDist > 0.65f) {
- pupilScale = 0.65f / pupilDist;
- }
- int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
- int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
- // Iris (darker circle)
- u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
- // Iris glow effect (small highlights)
- if (physics.irisGlow > 0.0f) {
- int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
- if (glowIntensity > 50) {
- u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
- }
- }
- }
- /****** RENDERING: DRAW RIGHT EYE *****/
- void drawRightEye(int16_t centerX, int16_t centerY, int16_t radius) {
- // Eye white (sclera) using U8g2
- u8g2.drawCircle(centerX, centerY, radius, U8G2_DRAW_ALL);
- // Apply eyelid closure (blink effect)
- if (physics.blinkValue > 0.1f) {
- int16_t eyelidHeight = (int16_t)(radius * 2.0f * physics.blinkValue);
- eyelidHeight = min(eyelidHeight, (int16_t)(radius * 2));
- // Top eyelid with safe bounds
- int16_t safeX = max((int16_t)(centerX - radius), (int16_t)0);
- int16_t safeY = max((int16_t)(centerY - radius), (int16_t)0);
- int16_t safeWidth = min((int16_t)(radius * 2), (int16_t)(SCREEN_WIDTH - safeX));
- int16_t safeHeight = min(eyelidHeight, (int16_t)(SCREEN_HEIGHT - safeY));
- if (safeWidth > 0 && safeHeight > 0) {
- u8g2.drawBox(safeX, safeY, safeWidth, safeHeight);
- }
- }
- // Calculate pupil position with physics
- float pupilDist = sqrt(physics.pupilX * physics.pupilX + physics.pupilY * physics.pupilY);
- float pupilScale = 1.0f;
- if (pupilDist > 0.65f) {
- pupilScale = 0.65f / pupilDist;
- }
- int16_t pupilX = centerX + (int16_t)(physics.pupilX * pupilScale * (radius - 5));
- int16_t pupilY = centerY + (int16_t)(physics.pupilY * pupilScale * (radius - 5));
- // Iris (darker circle)
- u8g2.drawDisc(pupilX, pupilY, radius / 2, U8G2_DRAW_ALL);
- // Iris glow effect (small highlights)
- if (physics.irisGlow > 0.0f) {
- int8_t glowIntensity = (int8_t)(physics.irisGlow * 255);
- if (glowIntensity > 50) {
- u8g2.drawCircle(pupilX - 2, pupilY - 2, 2, U8G2_DRAW_ALL);
- }
- }
- }
- /****** RENDERING: DRAW EYEBROWS *****/
- void drawEyebrows(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY, int16_t eyeRadius) {
- int16_t eyebrowY = eyeY - eyeRadius - 8;
- int16_t eyebrowWidth = eyeRadius + 5;
- // Convert angle to slope for drawing
- int8_t angleOffset = (int8_t)(physics.eyebrowAngle * 0.3f);
- // Left eyebrow using U8g2 drawLine with step=1
- u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset,
- leftEyeX + eyebrowWidth, eyebrowY - angleOffset);
- u8g2.drawLine(leftEyeX - eyebrowWidth, eyebrowY + angleOffset + 1,
- leftEyeX + eyebrowWidth, eyebrowY - angleOffset + 1);
- // Right eyebrow using U8g2 drawLine with step=1
- u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset,
- rightEyeX + eyebrowWidth, eyebrowY + angleOffset);
- u8g2.drawLine(rightEyeX - eyebrowWidth, eyebrowY - angleOffset + 1,
- rightEyeX + eyebrowWidth, eyebrowY + angleOffset + 1);
- }
- /****** RENDERING: DRAW MOUTH *****/
- void drawMouth(int16_t centerX, int16_t centerY) {
- int16_t mouthWidth = 20;
- int16_t mouthY = centerY + 10;
- // Mouth curve based on expression
- int16_t curveHeight = (int16_t)(physics.mouthHeight * 8.0f);
- if (curveHeight > 0) {
- // Happy/smiling mouth (arc) using U8g2 drawLine with step=1
- for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
- int16_t y = (int16_t)(curveHeight - (x * x) / (float)(mouthWidth * mouthWidth) * curveHeight);
- u8g2.drawPixel(centerX + x, mouthY + y);
- }
- } else if (curveHeight < 0) {
- // Sad mouth (inverted arc) using U8g2 drawLine with step=1
- for (int16_t x = -mouthWidth; x <= mouthWidth; x += 1) {
- int16_t y = (int16_t)(curveHeight + (x * x) / (float)(mouthWidth * mouthWidth) * abs(curveHeight));
- u8g2.drawPixel(centerX + x, mouthY + y);
- }
- } else {
- // Neutral mouth (straight line)
- u8g2.drawLine(centerX - mouthWidth, mouthY, centerX + mouthWidth, mouthY);
- }
- }
- /****** RENDERING: DRAW NOSE *****/
- void drawNose(int16_t centerX, int16_t centerY) {
- int16_t noseX = centerX + (int16_t)(physics.noseWiggle * 2.0f);
- int16_t noseY = centerY - 2;
- // Simple nose (small triangle)
- u8g2.drawLine(noseX, noseY, noseX - 2, noseY + 3);
- u8g2.drawLine(noseX, noseY, noseX + 2, noseY + 3);
- u8g2.drawLine(noseX - 2, noseY + 3, noseX + 2, noseY + 3);
- }
- /****** RENDERING: DRAW CHEEKS *****/
- void drawCheeks(int16_t leftEyeX, int16_t rightEyeX, int16_t eyeY) {
- int16_t cheekRadius = 5;
- int16_t cheekY = eyeY + 10;
- // Cheek blush intensity determines if we draw
- if (physics.cheekBlush > 0.1f) {
- // Left cheek using U8g2 drawDisc
- u8g2.drawDisc(leftEyeX - 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
- // Right cheek using U8g2 drawDisc
- u8g2.drawDisc(rightEyeX + 20, cheekY, cheekRadius, U8G2_DRAW_ALL);
- }
- }
- /****** RENDERING: MAIN DRAW FUNCTION *****/
- void drawFace() {
- // U8g2 picture loop for double buffering
- u8g2.firstPage();
- do {
- // Calculate face position with micro-movements
- int16_t faceCenterX = SCREEN_WIDTH / 2 + (int16_t)(animation.microMoveX * 2.0f);
- int16_t faceCenterY = SCREEN_HEIGHT / 2 + (int16_t)(animation.microMoveY * 2.0f);
- // Increased eye radius for better visibility
- int16_t eyeRadius = 12;
- int16_t eyeSeparation = 30;
- int16_t eyeY = faceCenterY - 10;
- // Draw eyes
- drawLeftEye(faceCenterX - eyeSeparation / 2, eyeY, eyeRadius);
- drawRightEye(faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
- // Draw eyebrows
- drawEyebrows(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY, eyeRadius);
- // Draw nose
- drawNose(faceCenterX, faceCenterY);
- // Draw mouth
- drawMouth(faceCenterX, faceCenterY);
- // Draw cheeks
- drawCheeks(faceCenterX - eyeSeparation / 2, faceCenterX + eyeSeparation / 2, eyeY);
- // Display frame count
- u8g2.setFont(u8g2_font_5x7_tf);
- u8g2.setCursor(0, 7);
- u8g2.print(animation.frameCount);
- } while (u8g2.nextPage());
- }
- /****** BUTTON EVENT HANDLER *****/
- void handleButtonPress() {
- // Cycle to next emotion
- animation.targetEmotion = (Emotion)((animation.currentEmotion + 1) % 8);
- animation.lastInputTime = millis();
- animation.autonomousMode = false;
- // Blink indicator LED
- digitalWrite(emotionButton_LED_PIN_D2, HIGH);
- delay(100);
- digitalWrite(emotionButton_LED_PIN_D2, LOW);
- }
- /****** OTA CALLBACK FUNCTION *****/
- // Called by POTA when an OTA update is available on the server
- void onOTAAvailable() {
- // Set flag to trigger restart in main loop
- otaUpdateAvailable = true;
- Serial.println(F("[OTA] Update available! Will restart after next frame."));
- }
- /****** POTA DASHBOARD WIDGET SETUP *****/
- // Configure POTA dashboard widgets for visualization and control
- void setupPOTADashboard() {
- // Add dashboard widget for current emotion state
- pota.addWidget("emotion_state", "Emotion State", "text");
- // Add dashboard widget for frame count monitoring
- pota.addWidget("frame_count", "Frame Count", "number");
- // Add dashboard widget for autonomous mode status
- pota.addWidget("autonomous_mode", "Autonomous Mode", "toggle");
- // Add dashboard widget for FPS display
- pota.addWidget("fps_display", "FPS", "number");
- // Add dashboard widget for WiFi signal strength
- pota.addWidget("wifi_signal", "WiFi Signal", "number");
- // Add dashboard widget for device uptime
- pota.addWidget("device_uptime", "Device Uptime (sec)", "number");
- // Add dashboard widget for firmware version
- pota.addWidget("firmware_version", "Firmware Version", "text");
- }
- /****** UPDATE POTA DASHBOARD VALUES *****/
- // Periodically update dashboard widget values
- void updatePOTADashboard() {
- // Array of emotion names for display
- const char* emotionNames[] = {
- "NEUTRAL", "HAPPY", "SAD", "EXCITED",
- "SLEEPY", "CURIOUS", "SHY", "PLAYFUL"
- };
- // Update emotion state widget
- pota.updateWidget("emotion_state", emotionNames[animation.currentEmotion]);
- // Update frame count widget
- pota.updateWidget("frame_count", (int)animation.frameCount);
- // Update autonomous mode widget
- pota.updateWidget("autonomous_mode", animation.autonomousMode ? 1 : 0);
- // Calculate and update FPS
- static unsigned long lastFPSTime = 0;
- static uint32_t framesSinceLastFPS = 0;
- unsigned long currentTime = millis();
- framesSinceLastFPS++;
- if (currentTime - lastFPSTime >= 1000) {
- float fps = (framesSinceLastFPS * 1000.0f) / (currentTime - lastFPSTime);
- pota.updateWidget("fps_display", (int)fps);
- lastFPSTime = currentTime;
- framesSinceLastFPS = 0;
- }
- // Update WiFi signal strength (RSSI in dBm)
- int32_t rssi = WiFi.RSSI();
- pota.updateWidget("wifi_signal", (int)rssi);
- // Update device uptime in seconds
- unsigned long uptime = millis() / 1000;
- pota.updateWidget("device_uptime", (int)uptime);
- // Update firmware version
- pota.updateWidget("firmware_version", FIRMWARE_VERSION);
- }
- /****** SETUP FUNCTION *****/
- void setup(void) {
- // Initialize serial for debugging
- Serial.begin(115200);
- delay(100);
- // Configure GPIO pins
- pinMode(faceDisplay_PushButton_PIN_D1, INPUT_PULLUP);
- pinMode(emotionButton_LED_PIN_D2, OUTPUT);
- digitalWrite(emotionButton_LED_PIN_D2, LOW);
- // Initialize U8g2 OLED display
- u8g2.begin();
- u8g2.enableUTF8Print();
- // Clear and show startup message
- u8g2.firstPage();
- do {
- u8g2.setFont(u8g2_font_5x7_tf);
- u8g2.drawStr(32, 32, "Initializing...");
- } while (u8g2.nextPage());
- delay(1000);
- // Initialize emotion configurations
- initializeEmotionConfigs();
- // Initialize animation state
- animation.currentEmotion = NEUTRAL;
- animation.targetEmotion = NEUTRAL;
- animation.autonomousMode = false;
- animation.lastInputTime = millis();
- animation.idleThreshold = 5000; // 5 seconds
- animation.frameTime = 0;
- animation.lastFrameTime = 0;
- animation.frameCount = 0;
- animation.animationPhase = 0.0f;
- animation.phaseVelocity = 0.0f;
- animation.microMoveX = 0.0f;
- animation.microMoveY = 0.0f;
- animation.gazeX = 0.0f;
- animation.gazeY = 0.0f;
- // Initialize physics state
- physics.pupilX = 0.0f;
- physics.pupilY = 0.0f;
- physics.pupilVelX = 0.0f;
- physics.pupilVelY = 0.0f;
- physics.targetPupilX = 0.0f;
- physics.targetPupilY = 0.0f;
- physics.blinkValue = 0.0f;
- physics.blinkVel = 0.0f;
- physics.isBlinking = false;
- physics.blinkStartTime = millis();
- physics.eyelidOffset = 0.0f;
- physics.eyebrowAngle = 0.0f;
- physics.eyebrowTargetAngle = 0.0f;
- physics.irisGlow = 0.3f;
- physics.mouthHeight = 0.3f;
- physics.mouthAngle = 0.0f;
- physics.cheekBlush = 0.0f;
- physics.noseWiggle = 0.0f;
- // Setup EasyButton
- button.begin();
- button.onPressed(handleButtonPress);
- // Initialize frame rate limiter
- lastFrameTimeMs = millis();
- // Initialize POTA OTA management
- Serial.println(F("[POTA] Initializing POTA OTA system..."));
- pota.begin(WIFI_SSID, WIFI_PASSWORD, AUTH_TOKEN, SERVER_SECRET);
- // Register OTA availability callback
- pota.onOTAAvailable(onOTAAvailable);
- // Check for available OTA updates and perform if found
- Serial.println(F("[POTA] Checking for OTA updates..."));
- pota.checkAndPerformOTA();
- // Setup POTA dashboard widgets
- setupPOTADashboard();
- Serial.println(F("Face animation system initialized!"));
- Serial.println(F("Press button to cycle emotions."));
- Serial.println(F("System idle for 5 seconds to enter autonomous mode."));
- Serial.println(F("[POTA] OTA system ready and monitoring for updates."));
- }
- /****** MAIN LOOP *****/
- void loop(void) {
- // Handle POTA OTA updates and WiFi connectivity
- pota.loop();
- // Check if OTA update is available and trigger restart
- if (otaUpdateAvailable) {
- Serial.println(F("[OTA] Restarting device for OTA update..."));
- u8g2.firstPage();
- do {
- u8g2.setFont(u8g2_font_5x7_tf);
- u8g2.drawStr(20, 32, "OTA Update...");
- } while (u8g2.nextPage());
- delay(500);
- pota.restart(); // Restart device to apply OTA update
- }
- // Read button input
- button.read();
- // Check for idle timeout (autonomous mode)
- unsigned long timeSinceLastInput = millis() - animation.lastInputTime;
- if (timeSinceLastInput > animation.idleThreshold && !animation.autonomousMode) {
- animation.autonomousMode = true;
- Serial.println(F("Entering autonomous mode..."));
- }
- // Update animation physics and state
- updateAnimation();
- // Render face to OLED
- drawFace();
- // Update POTA dashboard with current system status
- updatePOTADashboard();
- // Frame rate limiting (22ms per frame for 45 FPS)
- unsigned long currentTime = millis();
- unsigned long elapsedTime = currentTime - lastFrameTimeMs;
- if (elapsedTime < FRAME_TIME_MS) {
- delay(FRAME_TIME_MS - elapsedTime);
- }
- lastFrameTimeMs = millis();
- }
- /* END CODE */
Advertisement
Add Comment
Please, Sign In to add comment