#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int demo_mode = 1;
static const int max_animation_index = 17; // updated
int current_animation_index = 0;
int ref_eye_height = 40;
int ref_eye_width = 40;
int ref_space_between_eye = 10;
int ref_corner_radius = 10;
int left_eye_height = ref_eye_height;
int left_eye_width = ref_eye_width;
int left_eye_x = 32;
int left_eye_y = 32;
int right_eye_x = 32 + ref_eye_width + ref_space_between_eye;
int right_eye_y = 32;
int right_eye_height = ref_eye_height;
int right_eye_width = ref_eye_width;
void draw_eyes(bool update = true) {
display.clearDisplay();
int x = int(left_eye_x - left_eye_width / 2);
int y = int(left_eye_y - left_eye_height / 2);
display.fillRoundRect(x, y, left_eye_width, left_eye_height, ref_corner_radius, SSD1306_WHITE);
x = int(right_eye_x - right_eye_width / 2);
y = int(right_eye_y - right_eye_height / 2);
display.fillRoundRect(x, y, right_eye_width, right_eye_height, ref_corner_radius, SSD1306_WHITE);
if (update) display.display();
}
void center_eyes(bool update = true) {
left_eye_height = ref_eye_height;
left_eye_width = ref_eye_width;
right_eye_height = ref_eye_height;
right_eye_width = ref_eye_width;
left_eye_x = SCREEN_WIDTH / 2 - ref_eye_width / 2 - ref_space_between_eye / 2;
left_eye_y = SCREEN_HEIGHT / 2;
right_eye_x = SCREEN_WIDTH / 2 + ref_eye_width / 2 + ref_space_between_eye / 2;
right_eye_y = SCREEN_HEIGHT / 2;
draw_eyes(update);
}
void blink(int speed = 12) {
draw_eyes();
for (int i = 0; i < 3; i++) { left_eye_height -= speed; right_eye_height -= speed; draw_eyes(); delay(1); }
for (int i = 0; i < 3; i++) { left_eye_height += speed; right_eye_height += speed; draw_eyes(); delay(1); }
}
void sleep() {
left_eye_height = 2; right_eye_height = 2;
draw_eyes(true);
}
void wakeup() {
sleep();
for (int h = 0; h <= ref_eye_height; h += 2) {
left_eye_height = h; right_eye_height = h;
draw_eyes(true);
}
}
void happy_eye() {
center_eyes(false);
int offset = ref_eye_height / 2;
for (int i = 0; i < 10; i++) {
display.fillTriangle(
left_eye_x - left_eye_width / 2 - 1, left_eye_y + offset,
left_eye_x + left_eye_width / 2 + 1, left_eye_y + 5 + offset,
left_eye_x - left_eye_width / 2 - 1, left_eye_y + left_eye_height + offset, SSD1306_BLACK);
display.fillTriangle(
right_eye_x + right_eye_width / 2 + 1, right_eye_y + offset,
right_eye_x - left_eye_width / 2 - 1, right_eye_y + 5 + offset,
right_eye_x + right_eye_width / 2 + 1, right_eye_y + right_eye_height + offset, SSD1306_BLACK);
offset -= 2;
display.display(); delay(1);
}
display.display(); delay(1000);
}
void saccade(int direction_x, int direction_y) {
int xa = 8, ya = 6, ba = 8;
for (int i = 0; i < 1; i++) {
left_eye_x += xa * direction_x; right_eye_x += xa * direction_x;
left_eye_y += ya * direction_y; right_eye_y += ya * direction_y;
right_eye_height -= ba; left_eye_height -= ba;
draw_eyes(); delay(1);
}
for (int i = 0; i < 1; i++) {
left_eye_x += xa * direction_x; right_eye_x += xa * direction_x;
left_eye_y += ya * direction_y; right_eye_y += ya * direction_y;
right_eye_height += ba; left_eye_height += ba;
draw_eyes(); delay(1);
}
}
void move_big_eye(int direction) {
int os = 1, ma = 2, ba = 5;
for (int i = 0; i < 3; i++) {
left_eye_x += ma * direction; right_eye_x += ma * direction;
right_eye_height -= ba; left_eye_height -= ba;
if (direction > 0) { right_eye_height += os; right_eye_width += os; }
else { left_eye_height += os; left_eye_width += os; }
draw_eyes(); delay(1);
}
for (int i = 0; i < 3; i++) {
left_eye_x += ma * direction; right_eye_x += ma * direction;
right_eye_height += ba; left_eye_height += ba;
if (direction > 0) { right_eye_height += os; right_eye_width += os; }
else { left_eye_height += os; left_eye_width += os; }
draw_eyes(); delay(1);
}
delay(1000);
for (int i = 0; i < 3; i++) {
left_eye_x -= ma * direction; right_eye_x -= ma * direction;
right_eye_height -= ba; left_eye_height -= ba;
if (direction > 0) { right_eye_height -= os; right_eye_width -= os; }
else { left_eye_height -= os; left_eye_width -= os; }
draw_eyes(); delay(1);
}
for (int i = 0; i < 3; i++) {
left_eye_x -= ma * direction; right_eye_x -= ma * direction;
right_eye_height += ba; left_eye_height += ba;
if (direction > 0) { right_eye_height -= os; right_eye_width -= os; }
else { left_eye_height -= os; left_eye_width -= os; }
draw_eyes(); delay(1);
}
center_eyes();
}
void move_right_big_eye() { move_big_eye(1); }
void move_left_big_eye() { move_big_eye(-1); }
void angry_eye() {
center_eyes(false); draw_eyes(false);
display.fillTriangle(
left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2,
left_eye_x + left_eye_width / 2, left_eye_y - left_eye_height / 2,
left_eye_x - left_eye_width / 2, left_eye_y, SSD1306_BLACK);
display.fillTriangle(
right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2,
right_eye_x + right_eye_width / 2, right_eye_y - right_eye_height / 2,
right_eye_x + right_eye_width / 2, right_eye_y, SSD1306_BLACK);
display.display(); delay(2000);
blink(15); blink(15);
}
void annoyed_eye() {
center_eyes(false); draw_eyes(false);
display.fillRect(
right_eye_x - right_eye_width / 2,
right_eye_y - right_eye_height / 2,
right_eye_width, right_eye_height / 2, SSD1306_BLACK);
display.display(); delay(2000);
}
void sleepy_eye() {
center_eyes(false);
for (int lid = 0; lid <= ref_eye_height * 2 / 3; lid += 3) {
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, lid, SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, lid, SSD1306_BLACK);
display.display(); delay(30);
}
delay(1500);
for (int lid = ref_eye_height * 2 / 3; lid >= 0; lid -= 3) {
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, lid, SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, lid, SSD1306_BLACK);
display.display(); delay(30);
}
}
void hungry_eye() {
center_eyes(false); draw_eyes(false);
int pw = 12, ph = 14;
display.fillRect(left_eye_x - pw / 2, left_eye_y + left_eye_height / 2 - ph - 2, pw, ph, SSD1306_BLACK);
display.fillRect(right_eye_x - pw / 2, right_eye_y + right_eye_height / 2 - ph - 2, pw, ph, SSD1306_BLACK);
int drool_x = SCREEN_WIDTH / 2;
int drool_y = SCREEN_HEIGHT / 2 + ref_eye_height / 2 + 6;
for (int pulse = 0; pulse < 6; pulse++) {
display.fillCircle(drool_x, drool_y + pulse, 2, SSD1306_WHITE);
display.display(); delay(120);
display.fillCircle(drool_x, drool_y + pulse, 2, SSD1306_BLACK);
}
display.display(); delay(500);
}
void excited_eye() {
center_eyes(true);
for (int rep = 0; rep < 3; rep++) {
for (int s = 0; s <= 10; s += 2) {
left_eye_height = right_eye_height = ref_eye_height + s;
left_eye_width = right_eye_width = ref_eye_width + s;
draw_eyes(); delay(20);
}
for (int s = 10; s >= 0; s -= 2) {
left_eye_height = right_eye_height = ref_eye_height + s;
left_eye_width = right_eye_width = ref_eye_width + s;
draw_eyes(); delay(20);
}
blink(20); delay(80);
}
center_eyes();
}
// ── NEW ANIMATIONS ────────────────────────────────────────────────
// EATING: eyes scrunch shut rhythmically, like chewing, with small squish pulses
void eating_eye() {
center_eyes(true);
delay(300);
for (int chew = 0; chew < 5; chew++) {
// Scrunch down on the chew
for (int h = ref_eye_height; h >= 8; h -= 6) {
left_eye_height = h; right_eye_height = h;
// Eyes also squish slightly wider when scrunched
left_eye_width = ref_eye_width + (ref_eye_height - h) / 4;
right_eye_width = ref_eye_width + (ref_eye_height - h) / 4;
draw_eyes(); delay(15);
}
// Spring back open
for (int h = 8; h <= ref_eye_height; h += 6) {
left_eye_height = h; right_eye_height = h;
left_eye_width = ref_eye_width + (ref_eye_height - h) / 4;
right_eye_width = ref_eye_width + (ref_eye_height - h) / 4;
draw_eyes(); delay(15);
}
delay(120); // brief pause between chews
}
center_eyes();
}
// BORED: eyes half-lidded, slow side-to-side saccade, occasional slow blink
void bored_eye() {
center_eyes(false);
// Drop lids to half
int lid = ref_eye_height / 2;
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, lid, SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, lid, SSD1306_BLACK);
display.display();
delay(800);
// Slow drift left, pause, drift right, pause — all while keeping lids half-down
for (int d = 0; d < 2; d++) {
int dir = (d == 0) ? -1 : 1;
for (int step = 0; step < 8; step++) {
left_eye_x += dir;
right_eye_x += dir;
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, lid, SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, lid, SSD1306_BLACK);
display.display();
delay(60); // slow, sluggish movement
}
delay(700);
}
// Slow blink with lids still half-down
for (int h = ref_eye_height; h >= 2; h -= 4) {
left_eye_height = h; right_eye_height = h;
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, min(lid, h), SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, min(lid, h), SSD1306_BLACK);
display.display(); delay(25);
}
for (int h = 2; h <= ref_eye_height; h += 4) {
left_eye_height = h; right_eye_height = h;
draw_eyes(false);
display.fillRect(left_eye_x - left_eye_width / 2, left_eye_y - left_eye_height / 2, left_eye_width, min(lid, h), SSD1306_BLACK);
display.fillRect(right_eye_x - right_eye_width / 2, right_eye_y - right_eye_height / 2, right_eye_width, min(lid, h), SSD1306_BLACK);
display.display(); delay(25);
}
delay(500);
center_eyes();
}
// WAKING UP: starts fully closed, lids fight to open — stutter up, drop back, try again, finally open
void waking_up_eye() {
// Phase 1: fully asleep
left_eye_height = 2; right_eye_height = 2;
draw_eyes(true);
delay(800);
// Phase 2: first attempt — struggles to crack open, droops back
for (int h = 2; h <= 18; h += 3) {
left_eye_height = h; right_eye_height = h; draw_eyes(); delay(40);
}
delay(300);
for (int h = 18; h >= 2; h -= 4) { // droops back down
left_eye_height = h; right_eye_height = h; draw_eyes(); delay(30);
}
delay(500);
// Phase 3: second attempt — gets a bit further, droops again
for (int h = 2; h <= 28; h += 3) {
left_eye_height = h; right_eye_height = h; draw_eyes(); delay(35);
}
delay(400);
for (int h = 28; h >= 4; h -= 5) {
left_eye_height = h; right_eye_height = h; draw_eyes(); delay(25);
}
delay(600);
// Phase 4: finally forces eyes open all the way, slow and heavy
for (int h = 4; h <= ref_eye_height; h += 2) {
left_eye_height = h; right_eye_height = h; draw_eyes(); delay(40);
}
delay(200);
// Phase 5: overshoot blink (eyes pop wide, snap shut once, reopen) — feeling of "I'm up"
left_eye_height = ref_eye_height + 8;
right_eye_height = ref_eye_height + 8;
draw_eyes(); delay(150);
blink(18);
center_eyes();
}
// YAWNING: eyes slowly squeeze shut mid-yawn, hold, then reopen wide; head-tilt simulated
// by shifting both eyes upward slightly while mouth-open phase is shown via
// a wide black arc drawn below the eyes
void yawning_eye() {
center_eyes(false);
draw_eyes(true);
delay(400);
// Eyes drift upward slightly (head tilting back)
for (int shift = 0; shift < 5; shift++) {
left_eye_y--; right_eye_y--;
draw_eyes(true); delay(40);
}
// Eyes slowly close as mouth opens
for (int h = ref_eye_height; h >= 4; h -= 3) {
left_eye_height = h; right_eye_height = h;
draw_eyes(false);
// Draw "mouth" — a wide arc below eyes, grows as eyes close
int mouth_open = map(h, ref_eye_height, 4, 0, 14);
int mx = SCREEN_WIDTH / 2;
int my = SCREEN_HEIGHT / 2 + ref_eye_height / 2 + 6;
if (mouth_open > 2) {
display.fillRoundRect(mx - 16, my, 32, mouth_open, 4, SSD1306_WHITE);
}
display.display(); delay(35);
}
// Hold the yawn open
delay(700);
// Close mouth and reopen eyes
for (int h = 4; h <= ref_eye_height + 6; h += 3) {
left_eye_height = h; right_eye_height = h;
draw_eyes(false);
int mouth_open = map(h, 4, ref_eye_height, 14, 0);
int mx = SCREEN_WIDTH / 2;
int my = SCREEN_HEIGHT / 2 + ref_eye_height / 2 + 6;
if (mouth_open > 2) {
display.fillRoundRect(mx - 16, my, 32, mouth_open, 4, SSD1306_WHITE);
}
display.display(); delay(30);
}
// Eyes drift back to center and do a slow post-yawn blink
for (int shift = 0; shift < 5; shift++) {
left_eye_y++; right_eye_y++;
draw_eyes(true); delay(40);
}
delay(200);
blink(8);
center_eyes();
}
// ─────────────────────────────────────────────────────────────────
void launch_animation_with_index(int animation_index) {
if (animation_index > max_animation_index) animation_index = 0;
switch (animation_index) {
case 0: wakeup(); break;
case 1: center_eyes(true); break;
case 2: move_right_big_eye(); break;
case 3: move_left_big_eye(); break;
case 4: blink(10); break;
case 5: blink(20); break;
case 6: happy_eye(); break;
case 7: sleep(); break;
case 8:
center_eyes(true);
for (int i = 0; i < 20; i++) {
int dir_x = random(-1, 2);
int dir_y = random(-1, 2);
saccade(dir_x, dir_y); delay(1);
saccade(-dir_x, -dir_y); delay(1);
}
break;
case 9: angry_eye(); break;
case 10: annoyed_eye(); break;
case 11: sleepy_eye(); break;
case 12: hungry_eye(); break;
case 13: excited_eye(); break;
// ── new ──
case 14: eating_eye(); break;
case 15: bored_eye(); break;
case 16: waking_up_eye(); break;
case 17: yawning_eye(); break;
}
}
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
Serial.begin(115200);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Intellar.ca"));
display.display();
delay(2000);
sleep();
delay(2000);
}
void loop() {
if (demo_mode == 1) {
launch_animation_with_index(current_animation_index++);
if (current_animation_index > max_animation_index) current_animation_index = 0;
}
if (Serial.available()) {
String data = Serial.readString();
data.trim();
char cmd = data[0];
if (cmd == 'A') {
demo_mode = 0;
String arg = data.substring(1, data.length());
int anim = arg.toInt();
launch_animation_with_index(anim);
Serial.print(cmd);
Serial.print(arg);
}
}
}