//***********************************************************************************************
// Robo Eyes Simulation for Wokwi
// Complete version with embedded library - SOLID EYES VERSION
//***********************************************************************************************
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <Wire.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ===== RoboEyes Library Implementation =====
// Mood definitions
#define DEFAULT 0
#define TIRED 1
#define ANGRY 2
#define HAPPY 3
#define SLEEPY 4
#define SURPRISED 5
#define ANNOYED 6
#define SAD 7
#define WINK 8
class RoboEyes {
private:
Adafruit_SSD1306* disp;
// Eye properties
int eyeWidth = 36;
int eyeHeight = 36;
int eyeRadius = 8;
int leftEyeX = 30;
int rightEyeX = 74;
int eyeY = 14;
bool eyesOpen = true;
int currentMood = DEFAULT;
bool specialAnim = false;
unsigned long animStart = 0;
int animOffset = 0;
int animPhase = 0;
bool winkRight = true;
bool blinking = false;
unsigned long blinkTimer = 0;
public:
RoboEyes(Adafruit_SSD1306& display) {
disp = &display;
}
void begin(int width, int height, int fps) {
// Initialize display settings
randomSeed(analogRead(0)); // For random blinking
}
void setPosition(int pos) {
// Set eye position (simplified)
if (pos == DEFAULT) {
leftEyeX = 30;
rightEyeX = 74;
eyeY = 14;
}
}
void close() {
eyesOpen = false;
}
void open() {
eyesOpen = true;
}
void setMood(int mood) {
currentMood = mood;
specialAnim = false;
}
void anim_laugh() {
specialAnim = true;
animStart = millis();
animPhase = 0;
}
void anim_blink() {
blinking = true;
blinkTimer = millis();
}
void anim_wink(bool rightEye = true) {
winkRight = rightEye;
specialAnim = true;
animStart = millis();
currentMood = WINK;
}
void update() {
// Handle blinking
if (!specialAnim && random(1000) < 5 && millis() - blinkTimer > 3000) {
anim_blink();
}
if (blinking) {
unsigned long elapsed = millis() - blinkTimer;
if (elapsed < 100) {
eyesOpen = false;
} else if (elapsed < 200) {
eyesOpen = true;
blinking = false;
}
}
// Handle special animations
if (specialAnim) {
unsigned long elapsed = millis() - animStart;
switch(currentMood) {
case HAPPY:
// Happy bouncy animation
animOffset = (elapsed % 200 < 100) ? 2 : -2;
if (elapsed > 1000) {
specialAnim = false;
animOffset = 0;
}
break;
case WINK:
if (elapsed < 300) {
eyesOpen = !winkRight; // Close one eye
} else if (elapsed < 600) {
eyesOpen = true; // Open both
} else {
specialAnim = false;
currentMood = DEFAULT;
}
break;
}
}
// Draw eyes
drawEyes();
}
private:
void drawEyes() {
disp->clearDisplay();
// Draw status text
disp->setCursor(0, 0);
disp->setTextSize(1);
disp->setTextColor(SSD1306_WHITE);
// Display mood
const char* moodStr = "DEFAULT";
switch(currentMood) {
case TIRED: moodStr = "TIRED"; break;
case ANGRY: moodStr = "ANGRY"; break;
case HAPPY: moodStr = "HAPPY"; break;
case SLEEPY: moodStr = "SLEEPY"; break;
case SURPRISED: moodStr = "SURPRISED"; break;
case ANNOYED: moodStr = "ANNOYED"; break;
case SAD: moodStr = "SAD"; break;
case WINK: moodStr = "WINK"; break;
}
disp->print("Mood: ");
disp->print(moodStr);
if (specialAnim) disp->print(" *");
if (blinking) disp->print(" BLINK");
// Calculate eye position and size based on mood
int drawY = eyeY + animOffset;
int currentEyeHeight = eyesOpen ? eyeHeight : 2;
int currentEyeWidth = eyeWidth;
int currentLeftEyeX = leftEyeX;
int currentRightEyeX = rightEyeX;
// Adjust eyes based on mood
switch(currentMood) {
case TIRED:
currentEyeHeight = eyesOpen ? eyeHeight - 10 : 2;
drawY += 5;
break;
case ANGRY:
drawY -= 3;
break;
case HAPPY:
currentEyeHeight = eyesOpen ? eyeHeight - 8 : 2;
drawY += 4;
break;
case SLEEPY:
currentEyeHeight = eyesOpen ? eyeHeight - 15 : 2;
drawY += 8;
break;
case SURPRISED:
currentEyeWidth = eyeWidth - 5;
currentEyeHeight = eyeHeight + 5;
drawY -= 3;
currentLeftEyeX += 2;
currentRightEyeX += 2;
break;
case ANNOYED:
currentEyeHeight = eyesOpen ? eyeHeight - 5 : 2;
drawY += 3;
currentEyeWidth = eyeWidth - 3;
break;
case SAD:
currentEyeHeight = eyesOpen ? eyeHeight - 12 : 2;
drawY += 6;
break;
}
// Draw left eye (if not winking)
if (eyesOpen || (currentMood == WINK && winkRight)) {
disp->fillRoundRect(currentLeftEyeX, drawY, currentEyeWidth, currentEyeHeight, eyeRadius, SSD1306_WHITE);
} else {
// Draw closed eye as a line
disp->fillRoundRect(currentLeftEyeX, drawY + currentEyeHeight/2 - 1, currentEyeWidth, 2, 1, SSD1306_WHITE);
}
// Draw right eye (if not winking)
if (eyesOpen || (currentMood == WINK && !winkRight)) {
disp->fillRoundRect(currentRightEyeX, drawY, currentEyeWidth, currentEyeHeight, eyeRadius, SSD1306_WHITE);
} else {
// Draw closed eye as a line
disp->fillRoundRect(currentRightEyeX, drawY + currentEyeHeight/2 - 1, currentEyeWidth, 2, 1, SSD1306_WHITE);
}
// Mood-specific overlays (no pupils, just eye shapes)
if (eyesOpen) {
switch(currentMood) {
case TIRED:
// Tired eyelids
disp->fillTriangle(currentLeftEyeX, drawY,
currentLeftEyeX + currentEyeWidth, drawY,
currentLeftEyeX, drawY + currentEyeHeight/2,
SSD1306_BLACK);
disp->fillTriangle(currentRightEyeX, drawY,
currentRightEyeX + currentEyeWidth, drawY,
currentRightEyeX + currentEyeWidth, drawY + currentEyeHeight/2,
SSD1306_BLACK);
break;
case ANGRY:
// Angry eyebrows
disp->fillTriangle(currentLeftEyeX - 5, drawY - 5,
currentLeftEyeX + currentEyeWidth/2, drawY - 8,
currentLeftEyeX + currentEyeWidth + 5, drawY - 3,
SSD1306_WHITE);
disp->fillTriangle(currentRightEyeX - 5, drawY - 3,
currentRightEyeX + currentEyeWidth/2, drawY - 8,
currentRightEyeX + currentEyeWidth + 5, drawY - 5,
SSD1306_WHITE);
break;
case HAPPY:
// Happy smile lines under eyes
for(int i = 0; i < 3; i++) {
disp->drawFastHLine(currentLeftEyeX + 5 + i*8, drawY + currentEyeHeight + 2, 6, SSD1306_WHITE);
disp->drawFastHLine(currentRightEyeX + 5 + i*8, drawY + currentEyeHeight + 2, 6, SSD1306_WHITE);
}
break;
case SLEEPY:
// Sleepy Zzz
disp->setCursor(currentLeftEyeX - 10, drawY + currentEyeHeight + 10);
disp->print("Z");
disp->setCursor(currentRightEyeX + currentEyeWidth + 2, drawY + currentEyeHeight + 10);
disp->print("Z");
break;
case SAD:
// Sad tears
disp->fillCircle(currentLeftEyeX + currentEyeWidth/2, drawY + currentEyeHeight + 3, 2, SSD1306_WHITE);
disp->fillCircle(currentRightEyeX + currentEyeWidth/2, drawY + currentEyeHeight + 3, 2, SSD1306_WHITE);
break;
}
}
// Display footer
disp->setCursor(0, 54);
disp->print("Solid Eyes v1.0");
disp->display();
}
};
// ===== End of RoboEyes Library =====
// Create RoboEyes instance
RoboEyes roboEyes(display);
// EVENT TIMER
unsigned long eventTimer;
int currentEvent = 0;
const int totalEvents = 9;
void setup() {
Serial.begin(115200);
Serial.println("Robo Eyes Simulator Starting...");
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
// Startup robo eyes
roboEyes.begin(SCREEN_WIDTH, SCREEN_HEIGHT, 100);
roboEyes.setPosition(DEFAULT);
roboEyes.open();
eventTimer = millis();
currentEvent = 0;
}
void loop() {
roboEyes.update();
// ANIMATION SEQUENCE - Cycles through all moods
unsigned long elapsed = millis() - eventTimer;
if (elapsed > 2000) {
currentEvent = (currentEvent + 1) % totalEvents;
eventTimer = millis();
switch(currentEvent) {
case 0:
roboEyes.setMood(DEFAULT);
break;
case 1:
roboEyes.setMood(HAPPY);
roboEyes.anim_laugh();
break;
case 2:
roboEyes.setMood(TIRED);
break;
case 3:
roboEyes.setMood(ANGRY);
break;
case 4:
roboEyes.setMood(SLEEPY);
break;
case 5:
roboEyes.setMood(SURPRISED);
break;
case 6:
roboEyes.setMood(ANNOYED);
break;
case 7:
roboEyes.setMood(SAD);
break;
case 8:
roboEyes.anim_wink(random(2)); // Random wink direction
break;
}
Serial.print("Event: ");
Serial.println(currentEvent);
}
}