#include <LedControl.h>
#include <Arduino.h>
#include <avr/pgmspace.h>
// ------------------------ TUNABLE AUTO INDICATOR SETTINGS --------------------
unsigned long autoRunMs = 2500; // total ms for one running "AUTO" sequence
unsigned long autoIntervalMs = 8000; // ms between running "AUTO" re-flashes
// ------------------------ NEW TUNABLES: MARVIN / QUIZZAGAN -------------------
unsigned long marvinStepMs = 400; // per-step delay inside MARVIN sequence
unsigned long marvinPauseMs = 1200; // pause after MARVIN completes
unsigned long quizzaganStepMs = 400; // per-step delay inside QUIZZAGAN
unsigned long quizzaganPauseMs = 1200; // pause after QUIZZAGAN completes
// ----------------------------- MAX7219 PINS --------------------------------
const int DIN1 = 7;
const int CS1 = 6;
const int CLK1 = 5;
const int DIN2 = 12;
const int CS2 = 11;
const int CLK2 = 10;
LedControl lc1 = LedControl(DIN1, CLK1, CS1, 1); // LEFT eye
LedControl lc2 = LedControl(DIN2, CLK2, CS2, 1); // RIGHT eye
// ------------------------------ INPUT / SIGNAL PINS -------------------------
// D1 will be used as the indicator signal:
// LOW -> SCROLL WORDS mode active
// HIGH -> Normal eye animations / not-scrolling
// NOTE: D1 is Serial TX on Arduino Nano. Using it will affect Serial TX.
const int PIN_D1_SIGNAL = 1;
const int PIN_D2_LEFT = 2; // physical D2 (triggers RIGHT animation)
const int PIN_D3_RIGHT = 3; // physical D3 (triggers LEFT animation)
const int PIN_A0_MODE = A0; // behavior mode button (NO to GND, INPUT_PULLUP)
const int PIN_D4_AUTO = 4; // auto mode enable (LOW = auto)
const int PIN_D13_RESET = 13; // external pause (LOW = pause). Treated as priority pause.
// --------------------------- INTERRUPT FLAGS --------------------------------
volatile bool leftTriggered = false;
volatile bool rightTriggered = false;
// ----------------------------- MODES & STATES ------------------------------
enum BehaviorMode : uint8_t {
BEHAV_DEFAULT = 0,
BEHAV_HEART = 1,
BEHAV_SMALL = 2,
BEHAV_SLEEPY = 3,
BEHAV_ANGRY = 4,
BEHAV_MARVIN = 5,
BEHAV_QUIZZAGAN = 6,
BEHAV_OFF = 7, // fully disabled displays
BEHAV_BLOCK = 8, // steady 4x4 block
BEHAV_SCROLL = 9 // INTERNAL: Scroll Words mode (entered/exited by long-press A0)
};
BehaviorMode currentBehaviorMode = BEHAV_DEFAULT;
BehaviorMode previousBehaviorMode = BEHAV_DEFAULT; // store when entering scroll mode
// -------------------- Scroll Words state --------------------
const int NUM_SCROLLS = 9;
int scrollIndex = 0; // 0..NUM_SCROLLS-1 current selected scroll animation
bool exitScrollMode = false; // set when long-press exit requested
// --------------------------- BUTTON / DEBOUNCE ------------------------------
bool lastA0State = HIGH;
bool a0PressLatched = false; // short-press latch
bool a0LongLatched = false; // long-press latch
unsigned long a0LowStartTime = 0;
const unsigned long A0_DEBOUNCE_MS = 60;
const unsigned long A0_LONG_MS = 3000; // 3 seconds long press
unsigned long lastActivityMs = 0;
const unsigned long BORED_MS = 15000; // 15s idle -> bored animation
bool modeChanged = false;
// -------------------------- D13 / PAUSE STATE -------------------------------
bool pausedByD13 = false; // true when D13 forced a pause (LOW)
bool interruptedByAutoDuringScroll = false;
// ============================================================================
// EYE PATTERNS (8x8 bitmaps) - moved to PROGMEM
// ============================================================================
// (all your PROGMEM patterns remain unchanged... copy/paste from your original code)
const uint8_t PROGMEM eyeOpen[8] = {
B00111100, B01111110, B11111111, B11100111,
B11100111, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeWink[8] = {
B00000000, B01111110, B11111111, B11100111,
B11100111, B11111111, B01111110, B00000000
};
const uint8_t PROGMEM eyeClosed[8] = {
B00000000, B00000000, B00000000, B11111111,
B11111111, B00000000, B00000000, B00000000
};
const uint8_t PROGMEM eyeClosedTotal[8] = {
B00000000, B00000000, B00000000, B00000000,
B11111111, B00000000, B00000000, B00000000
};
const uint8_t PROGMEM eyeRight1[8] = {
B00111100, B01111110, B11111111, B11001111,
B11001111, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeRight2[8] = {
B00111100, B01111110, B11111111, B10011111,
B10011111, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeRight3[8] = {
B00111100, B01111110, B11111111, B00111111,
B00111111, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeLeft1[8] = {
B00111100, B01111110, B11111111, B11110011,
B11110011, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeLeft2[8] = {
B00111100, B01111110, B11111111, B11111001,
B11111001, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeLeft3[8] = {
B00111100, B01111110, B11111111, B11111100,
B11111100, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeHeart1[8] = {
B01100110, B11111111, B11111111, B11111111,
B11111111, B01111110, B00111100, B00011000
};
const uint8_t PROGMEM eyeHeart2[8] = {
B00000000, B00100100, B01111110, B01111110,
B01111110, B00111100, B00011000, B00000000
};
const uint8_t PROGMEM eyeHeart3[8] = {
B00000000, B00000000, B00100100, B00111100,
B00111100, B00011000, B00000000, B00000000
};
const uint8_t PROGMEM eyeSmall1[8] = {
B01111110, B00000000, B00111100, B01111110,
B01100110, B01100110, B01111110, B00111100
};
const uint8_t PROGMEM eyeSmall2[8] = {
B01111110, B00000000, B00000000, B01111110,
B01100110, B01100110, B01111110, B00111100
};
const uint8_t PROGMEM eyeSmall3[8] = {
B01111110, B00000000, B00000000, B00000000,
B01100110, B01100110, B01111110, B00111100
};
const uint8_t PROGMEM eyeSmall4[8] = {
B01111110, B00000000, B00000000, B00000000,
B00000000, B01100110, B01111110, B00111100
};
const uint8_t PROGMEM eyeSmall5[8] = {
B01111110, B00000000, B00000000, B00000000,
B00000000, B00000000, B01111110, B00111100
};
const uint8_t PROGMEM eyeSmall6[8] = {
B01111110, B00000000, B00000000, B00000000,
B00000000, B00000000, B00000000, B00111100
};
const uint8_t PROGMEM eyeM[8] = {
B10000001, B11000011, B11100111, B11111111,
B11111111, B11011011, B11000011, B11000011
};
const uint8_t PROGMEM eyeA[8] = {
B00111100, B01111110, B11000011, B11000011,
B11111111, B11111111, B11000011, B11000011
};
const uint8_t PROGMEM eyeR[8] = {
B11111100, B11111110, B11000011, B11000011,
B11111110, B11111100, B11001110, B11000111
};
const uint8_t PROGMEM eyeV[8] = {
B11000011, B11000011, B11000011, B11000011,
B11100111, B01111110, B00111100, B00011000
};
const uint8_t PROGMEM eyeI[8] = {
B01111110, B01111110, B00011000, B00011000,
B00011000, B00011000, B01111110, B01111110
};
const uint8_t PROGMEM eyeN[8] = {
B11000011, B11000011, B11100011, B11110011,
B11011011, B11001111, B11000111, B11000011
};
const uint8_t PROGMEM eyeQ[8] = {
B00111100,
B01111110,
B11000011,
B11000011,
B11011011,
B11001111,
B01111110,
B00111101
};
const uint8_t PROGMEM eyeU[8] = {
B11000011,
B11000011,
B11000011,
B11000011,
B11000011,
B11000011,
B01111110,
B00111100
};
const uint8_t PROGMEM eyeZ[8] = {
B11111111,
B11111111,
B00000111,
B00001110,
B00011100,
B00111000,
B11111111,
B11111111
};
const uint8_t PROGMEM eyeG2[8] = {
B00111110,
B01111111,
B11000011,
B11000000,
B11001111,
B11000011,
B01111110,
B00111100
};
const uint8_t PROGMEM eyeT[8] = {
B11111111,
B00011000,
B00011000,
B00011000,
B00011000,
B00011000,
B00011000,
B00011000
};
const uint8_t PROGMEM eyeO[8] = {
B00111100, B01111110, B11111111, B11100111,
B11100111, B11111111, B01111110, B00111100
};
const uint8_t PROGMEM eyeBlank[8] = {
B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000
};
const uint8_t PROGMEM eyeSleep[8] = {
B00000000,
B00111100,
B01111110,
B11111111,
B11111111,
B01111110,
B00111100,
B00000000
};
const uint8_t PROGMEM eyeAngry[8] = {
B11111111,
B11111111,
B11100111,
B11000011,
B11000011,
B11111111,
B01111110,
B00111100
};
const uint8_t PROGMEM eyeBlock[8] = {
B01111110,
B00000000,
B00111100,
B00111100,
B00111100,
B00111100,
B00000000,
B00000000,
};
const uint8_t PROGMEM eye2[8] = {
B00111100,
B01111110,
B11100111,
B00011110,
B00011100,
B00111000,
B11111111,
B11111111
};
// ============================================================================
// 5x7 FONT (columns) for A-Z, space and comma - in PROGMEM
// ============================================================================
const uint8_t PROGMEM font5x7_A_to_Z[33][5] = {
{0x7E,0x11,0x11,0x11,0x7E}, // A
{0x7F,0x49,0x49,0x49,0x36}, // B
{0x3E,0x41,0x41,0x41,0x22}, // C
{0x7F,0x41,0x41,0x22,0x1C}, // D
{0x7F,0x49,0x49,0x49,0x41}, // E
{0x7F,0x09,0x09,0x09,0x01}, // F
{0x3E,0x41,0x49,0x49,0x7A}, // G
{0x7F,0x08,0x08,0x08,0x7F}, // H
{0x00,0x41,0x7F,0x41,0x00}, // I
{0x20,0x40,0x41,0x3F,0x01}, // J
{0x7F,0x08,0x14,0x22,0x41}, // K
{0x7F,0x40,0x40,0x40,0x40}, // L
{0x7F,0x02,0x04,0x02,0x7F}, // M
{0x7F,0x04,0x08,0x10,0x7F}, // N
{0x3E,0x41,0x41,0x41,0x3E}, // O
{0x7F,0x09,0x09,0x09,0x06}, // P
{0x3E,0x41,0x51,0x21,0x5E}, // Q
{0x7F,0x09,0x19,0x29,0x46}, // R
{0x46,0x49,0x49,0x49,0x31}, // S
{0x01,0x01,0x7F,0x01,0x01}, // T
{0x3F,0x40,0x40,0x40,0x3F}, // U
{0x1F,0x20,0x40,0x20,0x1F}, // V
{0x3F,0x40,0x38,0x40,0x3F}, // W
{0x63,0x14,0x08,0x14,0x63}, // X
{0x07,0x08,0x70,0x08,0x07}, // Y
{0x61,0x51,0x49,0x45,0x43}, // Z
// Special characters
{0x02,0x01,0x59,0x09,0x06}, // ? -> 26
{0x00,0x00,0x5F,0x00,0x00}, // ! -> 27
{0x14,0x7F,0x14,0x7F,0x14}, // # -> 28
{0x00,0x08,0x08,0x08,0x00}, // - -> 29
{0x36,0x49,0x55,0x22,0x50}, // & -> 30
{0x00,0x60,0x60,0x00,0x00}, // . -> 31
{0x00,0x40,0x60,0x00,0x00} // , -> 32
};
const uint8_t PROGMEM FONT_COMMA[1] = { 0x80 }; // small comma/dot (optional)
// ============================================================================
// FORWARD DECLARATIONS
// ============================================================================
void systemResetAndDisable(); // kept for full reset on demand (not called automatically now)
void checkD13();
void handleExternalPause(); // new
void checkD2D3();
void updateModeButton();
void smartDelay(unsigned long ms, bool allowD2D3, bool breakOnModeChange);
void showBoth(const uint8_t pattern[]);
void showPair(const uint8_t leftPattern[], const uint8_t rightPattern[]);
void autoMode();
void displayIdleAnimation();
void displayBehaviorDefault();
void displayBehaviorHeart();
void displayBehaviorSmall();
void displayBehaviorMarvin();
void displayBehaviorSleepy();
void displayBehaviorAngry();
void displayBehaviorQuizzagan();
void displayBehaviorOff();
void displayBehaviorBlock(); // NEW safe version (outside AUTO)
void displayBehaviorScrollWords(); // NEW
void displaySequenceLeft();
void displaySequenceRight();
void showReactionFace();
void showBoredAnimation();
void showRunningAutoOnce(unsigned long totalMs);
void scrollCueBlink(); // subtle cue before a scroll animation
// Scroll functions
void displayScrollAnim0();
void displayScrollAnim1();
void displayScrollAnim2();
void displayScrollAnim3();
void displayScrollAnim4();
void displayScrollAnim5();
void displayScrollAnim6();
void displayScrollAnim7();
void displayScrollAnim8();
void drawPupilFrame(int xL, int yL, int xR, int yR);
// -----------------------------------------------------------------------------
// INTERRUPT SERVICE ROUTINES
// -----------------------------------------------------------------------------
void handleLeftInterrupt() { leftTriggered = true; }
void handleRightInterrupt() { rightTriggered = true; }
// -----------------------------------------------------------------------------
// BASIC DISPLAY HELPERS (read patterns from PROGMEM)
// -----------------------------------------------------------------------------
void showBoth(const uint8_t pattern[]) {
for (int row = 0; row < 8; row++) {
uint8_t v = pgm_read_byte(&pattern[row]);
lc1.setRow(0, row, v);
lc2.setRow(0, row, v);
}
}
void showPair(const uint8_t leftPattern[], const uint8_t rightPattern[]) {
for (int row = 0; row < 8; row++) {
uint8_t vl = pgm_read_byte(&leftPattern[row]);
uint8_t vr = pgm_read_byte(&rightPattern[row]);
lc1.setRow(0, row, vl);
lc2.setRow(0, row, vr);
}
}
// -----------------------------------------------------------------------------
// Scroll monitoring pins mapping & helpers
// -----------------------------------------------------------------------------
/*
Mapping: scroll 0 -> A1, 1 -> A2, 2 -> A3, 3 -> A4, 4 -> A5, 5 -> A6,
6 -> D0, 7 -> D8, 8 -> D9
WARNING:
- D0 = Serial RX, D1 = Serial TX (already used as PIN_D1_SIGNAL).
Driving these as outputs will conflict with Serial usage.
- A1 is used to seed the RNG (we seed before reconfiguring A1 to OUTPUT).
*/
const int scrollMonitorPins[9] = { A1, A2, A3, A4, A5, A6, 0, 8, 9 };
const int NUM_SCROLL_MONITORS = 9;
void restoreAllScrollMonitorPins() {
for (int i = 0; i < NUM_SCROLL_MONITORS; i++) {
digitalWrite(scrollMonitorPins[i], HIGH);
}
}
void setScrollMonitorForIndex(int index, bool active) {
if (index < 0 || index >= NUM_SCROLL_MONITORS) {
restoreAllScrollMonitorPins();
return;
}
for (int i = 0; i < NUM_SCROLL_MONITORS; i++) {
if (i == index && active) {
digitalWrite(scrollMonitorPins[i], LOW);
} else {
digitalWrite(scrollMonitorPins[i], HIGH);
}
}
}
// -----------------------------------------------------------------------------
// SAFETY & SMART DELAY
// -----------------------------------------------------------------------------
void checkD13() {
if (digitalRead(PIN_D13_RESET) == LOW) {
handleExternalPause();
}
}
void handleExternalPause() {
pausedByD13 = true;
bool wasScrolling = (currentBehaviorMode == BEHAV_SCROLL);
detachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT));
detachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT));
digitalWrite(PIN_D1_SIGNAL, HIGH);
// restore monitor pins to safe state while paused
restoreAllScrollMonitorPins();
while (digitalRead(PIN_D13_RESET) == LOW) {
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(80);
}
pausedByD13 = false;
if (wasScrolling) {
digitalWrite(PIN_D1_SIGNAL, LOW);
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
} else {
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
lastActivityMs = millis();
}
}
void smartDelay(unsigned long ms, bool allowD2D3, bool breakOnModeChange) {
unsigned long start = millis();
while (millis() - start < ms) {
delay(1);
checkD13();
updateModeButton();
if (allowD2D3) {
checkD2D3();
}
if (breakOnModeChange && modeChanged) return;
if (pausedByD13) return;
}
}
// -----------------------------------------------------------------------------
// Helper: isAuto()
// -----------------------------------------------------------------------------
bool isAuto() {
return digitalRead(PIN_D4_AUTO) == LOW;
}
// -----------------------------------------------------------------------------
// BUTTON HANDLERS (A0 disabled while AUTO active)
// -----------------------------------------------------------------------------
void updateModeButton() {
if (isAuto()) return;
if (pausedByD13) return;
bool reading = digitalRead(PIN_A0_MODE);
unsigned long now = millis();
if (lastA0State == HIGH && reading == LOW) {
a0LowStartTime = now;
}
if (reading == LOW) {
if (!a0LongLatched && (now - a0LowStartTime) >= A0_LONG_MS) {
a0LongLatched = true;
a0PressLatched = true;
modeChanged = true;
if (currentBehaviorMode != BEHAV_SCROLL) {
previousBehaviorMode = currentBehaviorMode;
scrollIndex = 0;
exitScrollMode = false;
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(40);
for (int i = 0; i < 1; i++) {
showBoth(eyeM);
smartDelay(350, false, false);
showBoth(eyeQ);
smartDelay(350, false, false);
showBoth(eyeBlank);
smartDelay(80, false, false);
}
currentBehaviorMode = BEHAV_SCROLL;
digitalWrite(PIN_D1_SIGNAL, LOW);
displayBehaviorScrollWords();
modeChanged = false;
} else {
exitScrollMode = true;
currentBehaviorMode = previousBehaviorMode;
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(40);
for (int i = 0; i < 1; i++) {
showBoth(eyeM);
smartDelay(350, false, false);
showBoth(eyeQ);
smartDelay(350, false, false);
showBoth(eyeBlank);
smartDelay(80, false, false);
}
digitalWrite(PIN_D1_SIGNAL, HIGH);
}
}
else if (!a0PressLatched && (now - a0LowStartTime) >= A0_DEBOUNCE_MS && !a0LongLatched) {
a0PressLatched = true;
if (currentBehaviorMode == BEHAV_SCROLL) {
scrollIndex = (scrollIndex + 1) % NUM_SCROLLS;
modeChanged = true;
lastActivityMs = now;
} else {
const int NUM_USER_BEHAVIORS = BEHAV_SCROLL; // BEHAV_SCROLL == 9, so user-cycle is 0..8
currentBehaviorMode = (BehaviorMode)((currentBehaviorMode + 1) % NUM_USER_BEHAVIORS);
modeChanged = true;
lastActivityMs = now;
digitalWrite(PIN_D1_SIGNAL, HIGH);
}
}
} else {
a0PressLatched = false;
a0LongLatched = false;
}
lastA0State = reading;
}
// -----------------------------------------------------------------------------
// RESET HANDLING (kept but not auto-called by D13 anymore)
// -----------------------------------------------------------------------------
void systemResetAndDisable() {
lc1.clearDisplay(0);
lc2.clearDisplay(0);
detachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT));
detachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT));
setup();
while (digitalRead(PIN_D13_RESET) == LOW) { }
}
// -----------------------------------------------------------------------------
// D2 / D3 POLLING CHECK
// -----------------------------------------------------------------------------
void checkD2D3() {
if (isAuto()) return;
if (pausedByD13) return;
if (digitalRead(PIN_D2_LEFT) == LOW) displaySequenceRight();
if (digitalRead(PIN_D3_RIGHT) == LOW) displaySequenceLeft();
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
void setup() {
pinMode(PIN_D4_AUTO, INPUT_PULLUP);
pinMode(PIN_D2_LEFT, INPUT_PULLUP);
pinMode(PIN_D3_RIGHT, INPUT_PULLUP);
pinMode(PIN_A0_MODE, INPUT_PULLUP);
pinMode(PIN_D13_RESET, INPUT);
pinMode(PIN_D1_SIGNAL, OUTPUT);
lc1.shutdown(0, false);
lc1.setIntensity(0, 8);
lc1.clearDisplay(0);
lc2.shutdown(0, false);
lc2.setIntensity(0, 8);
lc2.clearDisplay(0);
digitalWrite(PIN_D1_SIGNAL, HIGH);
// Seed RNG from A1 while it's still safe to read as analog input
randomSeed(analogRead(A1));
// initialize scroll monitor pins as OUTPUT and idle HIGH
for (int i = 0; i < NUM_SCROLL_MONITORS; i++) {
pinMode(scrollMonitorPins[i], OUTPUT);
digitalWrite(scrollMonitorPins[i], HIGH); // default idle = HIGH
}
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
lastActivityMs = millis();
}
// -----------------------------------------------------------------------------
// SCROLL WORDS MODE (master loop) - interruptible and priority-aware
// -----------------------------------------------------------------------------
void displayBehaviorScrollWords() {
modeChanged = false;
exitScrollMode = false;
detachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT));
detachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT));
digitalWrite(PIN_D1_SIGNAL, LOW);
bool interruptedByAuto = false;
bool interruptedByD13 = false;
while (!exitScrollMode) {
if (digitalRead(PIN_D13_RESET) == LOW) {
interruptedByD13 = true;
pausedByD13 = true;
handleExternalPause();
if (isAuto()) {
interruptedByAuto = true;
break;
} else {
pausedByD13 = false;
digitalWrite(PIN_D1_SIGNAL, LOW);
}
}
if (isAuto()) {
interruptedByAuto = true;
break;
}
modeChanged = false;
// Activate the monitor pin for this scroll index while the scroll runs.
setScrollMonitorForIndex(scrollIndex, true);
// Call the selected scroll animation. These functions (scrollText) may
// return early if interrupted; our monitor pin will be restored below.
switch (scrollIndex) {
case 0: displayScrollAnim0(); break;
case 1: displayScrollAnim1(); break;
case 2: displayScrollAnim2(); break;
case 3: displayScrollAnim3(); break;
case 4: displayScrollAnim4(); break;
case 5: displayScrollAnim5(); break;
case 6: displayScrollAnim6(); break;
case 7: displayScrollAnim7(); break;
case 8: displayScrollAnim8(); break;
default: displayScrollAnim0(); break;
}
// Ensure the monitor pins are restored after the scroll finishes/returns.
setScrollMonitorForIndex(scrollIndex, false);
if (modeChanged) {
if (exitScrollMode) break;
modeChanged = false;
continue;
}
smartDelay(80, false, true);
if (exitScrollMode) break;
}
// ensure pins are restored on any exit path
restoreAllScrollMonitorPins();
if (!interruptedByAuto && !interruptedByD13) {
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
digitalWrite(PIN_D1_SIGNAL, HIGH);
currentBehaviorMode = previousBehaviorMode;
modeChanged = false;
exitScrollMode = false;
lastActivityMs = millis();
} else {
// preserve scroll state for re-entry
}
}
// -----------------------------------------------------------------------------
// SCROLLER HELPERS (reads font from PROGMEM) & column buffer builder
// -----------------------------------------------------------------------------
void buildColumnBufferForText(const char* txt, uint8_t *cols, int &colCount) {
int idx = 0;
for (int b = 0; b < 16; b++) {
cols[idx++] = 0x00;
}
for (const char* p = txt; *p != '\0'; ++p) {
char c = *p;
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
if (c >= 'A' && c <= 'Z') {
int fi = c - 'A';
for (int k = 0; k < 5; k++) cols[idx++] = pgm_read_byte(&font5x7_A_to_Z[fi][k]);
cols[idx++] = 0x00;
} else if (c == ' ') {
for (int s = 0; s < 6; s++) cols[idx++] = 0x00;
} else {
int fi = -1;
switch(c) {
case '.': fi = 31; break;
case ',': fi = 32; break;
case '?': fi = 26; break;
case '!': fi = 27; break;
case '#': fi = 28; break;
case '&': fi = 30; break;
case '-': fi = 29; break;
default: break;
}
if (fi >= 0) {
for (int k = 0; k < 5; k++) cols[idx++] = pgm_read_byte(&font5x7_A_to_Z[fi][k]);
cols[idx++] = 0x00;
} else {
for (int s = 0; s < 6; s++) cols[idx++] = 0x00;
}
}
if (idx > 1024 - 10) break;
}
colCount = idx;
}
void renderWindowFromColumns(const uint8_t *cols, int colCount, int offset) {
byte leftBmp[8];
byte rightBmp[8];
for (int r = 0; r < 8; r++) {
leftBmp[r] = 0x00;
rightBmp[r] = 0x00;
}
for (int c = 0; c < 16; c++) {
int colIdx = offset + c;
uint8_t colData = 0x00;
if (colIdx >= 0 && colIdx < colCount) colData = cols[colIdx];
for (int row = 0; row < 7; row++) {
bool pixelOn = (colData & (1 << row)) != 0;
if (pixelOn) {
int targetRow = row + 1;
if (targetRow >= 8) continue;
if (c < 8) leftBmp[targetRow] |= (1 << (7 - c));
else rightBmp[targetRow] |= (1 << (7 - (c - 8)));
}
}
}
for (int r = 0; r < 8; r++) {
lc1.setRow(0, r, leftBmp[r]);
lc2.setRow(0, r, rightBmp[r]);
}
}
// Smoothly scroll the given text across both matrices.
void scrollText(const char* txt, unsigned long speedMs) {
static uint8_t cols[1024];
int colCount = 0;
buildColumnBufferForText(txt, cols, colCount);
int maxOffset = colCount - 16;
if (maxOffset < 0) maxOffset = 0;
for (int offset = 0; offset <= maxOffset; offset++) {
if (digitalRead(PIN_D13_RESET) == LOW) {
pausedByD13 = true;
handleExternalPause();
if (isAuto()) {
return;
}
}
if (isAuto()) {
return;
}
if (modeChanged || exitScrollMode) return;
renderWindowFromColumns(cols, colCount, offset);
smartDelay(speedMs, false, true);
if (pausedByD13) return;
}
if (!modeChanged && !exitScrollMode) {
smartDelay(200, false, true);
lc1.clearDisplay(0);
lc2.clearDisplay(0);
smartDelay(80, false, true);
}
}
// -----------------------------------------------------------------------------
// Scroll animation implementations (phrases you provided)
// -----------------------------------------------------------------------------
void displayScrollAnim0() { scrollText("HI THERE! MY NAME IS E-MAR.", 110); }
void displayScrollAnim1() { scrollText("GOOD MORNING. I HOPE YOU ARE DOING WELL TODAY", 110); }
void displayScrollAnim2() { scrollText("GOOD AFTERNOON! WISHING YOU A PLEASANT REST OF THE DAY", 110); }
void displayScrollAnim3() { scrollText("YOUR PERSONALITY IS AMAZING, KEEP SHINING", 110); }
void displayScrollAnim4() { scrollText("THANK YOU! HAVE A WONDERFUL DAY", 110); }
void displayScrollAnim5() { scrollText("HOW MAY I ASSIST YOU?", 110); }
void displayScrollAnim6() { scrollText("WOULD YOU MIND TO TALK WITH MY CREATOR", 110); }
void displayScrollAnim7() { scrollText("GREAT CHAT! I HAVE TO HEAD OFF", 110); }
void displayScrollAnim8() { scrollText("GOD BLESS US ALL.", 110); }
// -----------------------------------------------------------------------------
// RUNNING "AUTO" MARQUEE – SMOOTH VERSION
// -----------------------------------------------------------------------------
void showRunningAutoOnce(unsigned long totalMs) {
const uint8_t* letters[] = { eyeA, eyeU, eyeT, eyeO };
const int numLetters = 4;
const int stepsPerLetter = 2;
unsigned long frameMs = totalMs / (numLetters * stepsPerLetter);
if (frameMs < 50) frameMs = 50;
for (int i = 0; i < numLetters; i++) {
const uint8_t* leftLetter = letters[i];
const uint8_t* rightLetter = (i < numLetters-1) ? letters[i+1] : eyeBlank;
showPair(leftLetter, rightLetter);
smartDelay(frameMs, false, true);
showPair(eyeBlank, rightLetter);
smartDelay(frameMs, false, true);
}
showPair(eyeBlank, eyeBlank);
smartDelay(50, false, true);
}
// ------------------------- REPLACEMENT autoMode() ---------------------------
void autoMode() {
leftTriggered = false;
rightTriggered = false;
detachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT));
detachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT));
digitalWrite(PIN_D1_SIGNAL, HIGH);
showRunningAutoOnce(autoRunMs);
unsigned long lastAutoIndicatorMs = millis();
while (isAuto()) {
checkD13();
if (millis() - lastAutoIndicatorMs >= autoIntervalMs) {
showRunningAutoOnce(autoRunMs);
lastAutoIndicatorMs = millis();
}
switch (currentBehaviorMode) {
case BEHAV_SLEEPY:
displayBehaviorSleepy();
break;
case BEHAV_ANGRY:
displayBehaviorAngry();
break;
case BEHAV_MARVIN:
displayBehaviorMarvin();
break;
case BEHAV_QUIZZAGAN:
displayBehaviorQuizzagan();
break;
case BEHAV_HEART:
displayBehaviorHeart();
break;
case BEHAV_SMALL:
displayBehaviorSmall();
break;
case BEHAV_BLOCK:
if (random(0,10) < 8) {
showBoth(eyeBlock);
smartDelay(150, false, true);
} else {
showBoth(eyeBlank);
smartDelay(150, false, true);
}
break;
case BEHAV_DEFAULT:
default:
showBoth(eyeOpen); smartDelay(200, false, true);
if (random(0, 6) == 0) {
showBoth(eyeWink); smartDelay(80, false, true);
showBoth(eyeClosedTotal); smartDelay(80, false, true);
}
break;
}
if (modeChanged) modeChanged = false;
checkD13();
}
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
lastActivityMs = millis();
}
// LEFT / RIGHT MANUAL SEQUENCES
void displaySequenceLeft() {
lastActivityMs = millis();
while (digitalRead(PIN_D3_RIGHT) == LOW) {
if (isAuto()) {
autoMode();
return;
}
if (pausedByD13) return;
showBoth(eyeOpen); smartDelay(80, false, false);
showBoth(eyeLeft1); smartDelay(80, false, false);
showBoth(eyeLeft2); smartDelay(80, false, false);
showBoth(eyeLeft3); smartDelay(120, false, false);
showBoth(eyeLeft2); smartDelay(80, false, false);
showBoth(eyeLeft1); smartDelay(80, false, false);
showBoth(eyeOpen); smartDelay(80, false, false);
showBoth(eyeWink); smartDelay(80, false, false);
showBoth(eyeClosed); smartDelay(80, false, false);
showBoth(eyeClosedTotal); smartDelay(80, false, false);
showBoth(eyeClosed); smartDelay(80, false, false);
showBoth(eyeWink); smartDelay(80, false, false);
showReactionFace();
if (digitalRead(PIN_D3_RIGHT) == HIGH) {
displayIdleAnimation();
return;
}
}
}
void displaySequenceRight() {
lastActivityMs = millis();
while (digitalRead(PIN_D2_LEFT) == LOW) {
if (isAuto()) {
autoMode();
return;
}
if (pausedByD13) return;
showBoth(eyeOpen); smartDelay(80, false, false);
showBoth(eyeRight1); smartDelay(80, false, false);
showBoth(eyeRight2); smartDelay(80, false, false);
showBoth(eyeRight3); smartDelay(120, false, false);
showBoth(eyeRight2); smartDelay(80, false, false);
showBoth(eyeRight1); smartDelay(80, false, false);
showBoth(eyeOpen); smartDelay(80, false, false);
showBoth(eyeWink); smartDelay(80, false, false);
showBoth(eyeClosed); smartDelay(80, false, false);
showBoth(eyeClosedTotal); smartDelay(80, false, false);
showBoth(eyeClosed); smartDelay(80, false, false);
showBoth(eyeWink); smartDelay(80, false, false);
showReactionFace();
if (digitalRead(PIN_D2_LEFT) == HIGH) {
displayIdleAnimation();
return;
}
}
}
// BEHAVIOR MODES
void displayBehaviorDefault() {
modeChanged = false;
for (int i = 0; i < 30; i++) {
showBoth(eyeOpen);
smartDelay(60, true, true);
if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
}
showBoth(eyeWink); smartDelay(40, true, true); if (modeChanged) return;
showBoth(eyeClosed); smartDelay(40, true, true); if (modeChanged) return;
showBoth(eyeClosedTotal); smartDelay(40, true, true); if (modeChanged) return;
showBoth(eyeOpen); smartDelay(80, true, true); if (modeChanged) return;
}
void displayBehaviorHeart() {
modeChanged = false;
showPair(eyeHeart1, eyeHeart3); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeHeart2, eyeHeart2); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeHeart3, eyeHeart1); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeBlank, eyeBlank); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeHeart3, eyeHeart1); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeHeart2, eyeHeart2); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeHeart1, eyeHeart3); smartDelay(120, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeBlank, eyeBlank); smartDelay(120, true, true); if (modeChanged) return;
}
void displayBehaviorSmall() {
modeChanged = false;
showBoth(eyeSmall1); smartDelay(600, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSmall2); smartDelay(60, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSmall3); smartDelay(60, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSmall4); smartDelay(60, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSmall5); smartDelay(60, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSmall6); smartDelay(120, true, true); if (modeChanged) return;
}
void displayBehaviorMarvin() {
modeChanged = false;
showPair(eyeBlank, eyeM); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeM, eyeA); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeA, eyeR); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeR, eyeV); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeV, eyeI); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeI, eyeN); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeN, eyeBlank); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showPair(eyeBlank, eyeBlank); smartDelay(marvinStepMs, true, true); if (modeChanged) return;
smartDelay(marvinPauseMs, true, true);
}
void displayBehaviorSleepy() {
modeChanged = false;
showBoth(eyeOpen); smartDelay(300, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSleep); smartDelay(400, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeClosed); smartDelay(400, true, true); if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
showBoth(eyeSleep); smartDelay(500, true, true); if (modeChanged) return;
}
void displayBehaviorAngry() {
modeChanged = false;
const float CXF = 4.0f;
const float CYF = 3.0f;
const float MIN_RADIUS = 0.0f;
const float MAX_RADIUS = 3.0f;
const int STEPS_PER_REV = 12;
const int REVOLUTIONS = 3;
const int CYCLES = 3;
const unsigned long FRAME_MS = 70;
const int FRAMES_PER_CYCLE = STEPS_PER_REV * REVOLUTIONS;
for (int c = 0; c < CYCLES; c++) {
for (int f = 0; f < FRAMES_PER_CYCLE; f++) {
checkD13();
if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
float t = (float)f / (float)FRAMES_PER_CYCLE;
float rFactor = 0.5f * (1.0f - cos(2.0f * PI * t));
float radius = MIN_RADIUS + (MAX_RADIUS - MIN_RADIUS) * rFactor;
float angle = 2.0f * PI * (REVOLUTIONS * t);
float xL_f = CXF + radius * cos(angle);
float yL_f = CYF + radius * sin(angle);
float xR_f = CXF + radius * cos(-angle);
float yR_f = CYF + radius * sin(-angle);
int xL = (int)round(xL_f);
int yL = (int)round(yL_f);
int xR = (int)round(xR_f);
int yR = (int)round(yR_f);
xL = constrain(xL, 0, 7);
yL = constrain(yL, 0, 7);
xR = constrain(xR, 0, 7);
yR = constrain(yR, 0, 7);
drawPupilFrame(xL, yL, xR, yR);
smartDelay(FRAME_MS, true, true);
checkD13();
if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
}
}
showBoth(eyeSmall1);
smartDelay(220, true, true);
if (modeChanged) return;
}
void displayBehaviorQuizzagan() {
modeChanged = false;
const uint8_t* wordLeft[11] = { eyeBlank, eyeQ, eyeU, eyeI, eyeZ, eyeZ, eyeA, eyeG2, eyeA, eyeN, eyeBlank };
const uint8_t* wordRight[11] = { eyeQ, eyeU, eyeI, eyeZ, eyeZ, eyeA, eyeG2, eyeA, eyeN, eyeBlank, eyeBlank };
for (int i = 0; i < 10; i++) {
showPair(wordLeft[i], wordRight[i]);
smartDelay(quizzaganStepMs, true, true);
if (modeChanged) return;
if (isAuto()) return;
if (pausedByD13) return;
}
showPair(eyeBlank,eyeBlank);
smartDelay(quizzaganPauseMs, true, true);
}
void displayBehaviorOff() {
modeChanged = false;
showBoth(eyeBlank);
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(20);
lc1.setIntensity(0, 0);
lc2.setIntensity(0, 0);
delay(5);
lc1.shutdown(0, true);
lc2.shutdown(0, true);
detachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT));
detachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT));
while (!modeChanged) {
checkD13();
updateModeButton();
smartDelay(100, false, true);
}
lc1.shutdown(0, false);
lc2.shutdown(0, false);
lc1.setIntensity(0, 8);
lc2.setIntensity(0, 8);
lc1.clearDisplay(0);
lc2.clearDisplay(0);
attachInterrupt(digitalPinToInterrupt(PIN_D2_LEFT), handleLeftInterrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_D3_RIGHT), handleRightInterrupt, FALLING);
modeChanged = false;
}
// -----------------------------------------------------------------------------
// SAFE displayBehaviorBlock() used outside AUTO (will return immediately on AUTO or D13 pause)
// -----------------------------------------------------------------------------
void displayBehaviorBlock() {
modeChanged = false;
while (!modeChanged) {
if (isAuto()) return;
if (random(0, 10) < 8) {
showBoth(eyeBlock);
smartDelay(400 + random(0, 200), false, true);
} else {
showBoth(eyeBlank);
smartDelay(100 + random(0, 150), false, true);
}
checkD2D3();
if (leftTriggered) {
leftTriggered = false;
displaySequenceLeft();
}
if (rightTriggered) {
rightTriggered = false;
displaySequenceRight();
}
smartDelay(300, true, true);
if (isAuto()) return;
}
}
// -----------------------------------------------------------------------------
// REACTIONS / BORED / IDLE
// -----------------------------------------------------------------------------
void showReactionFace() {
switch (currentBehaviorMode) {
case BEHAV_SLEEPY:
showBoth(eyeSleep);
smartDelay(180, false, false);
break;
case BEHAV_ANGRY:
showBoth(eyeAngry);
smartDelay(180, false, false);
break;
case BEHAV_QUIZZAGAN:
showPair(eyeQ, eyeU);
smartDelay(220, false, false);
break;
case BEHAV_HEART:
showPair(eyeHeart1, eyeHeart1);
smartDelay(200, false, false);
break;
case BEHAV_SMALL:
showBoth(eyeSmall3);
smartDelay(200, false, false);
break;
case BEHAV_MARVIN:
showPair(eyeM, eyeN);
smartDelay(220, false, false);
break;
case BEHAV_OFF:
showBoth(eyeBlank);
smartDelay(180, false, false);
break;
case BEHAV_BLOCK:
showBoth(eyeBlock);
smartDelay(180, false, false);
break;
case BEHAV_DEFAULT:
default:
showBoth(eyeOpen);
smartDelay(180, false, false);
break;
}
}
void showBoredAnimation() {
showBoth(eyeLeft2); smartDelay(250, true, true);
showBoth(eyeRight2); smartDelay(250, true, true);
showBoth(eyeOpen); smartDelay(400, true, true);
showBoth(eyeClosed); smartDelay(300, true, true);
showBoth(eyeSleep); smartDelay(400, true, true);
lastActivityMs = millis();
}
void displayIdleAnimation() {
modeChanged = false;
if (millis() - lastActivityMs > BORED_MS) {
showBoredAnimation();
return;
}
switch (currentBehaviorMode) {
case BEHAV_HEART:
displayBehaviorHeart();
break;
case BEHAV_SMALL:
displayBehaviorSmall();
break;
case BEHAV_MARVIN:
displayBehaviorMarvin();
break;
case BEHAV_SLEEPY:
displayBehaviorSleepy();
break;
case BEHAV_ANGRY:
displayBehaviorAngry();
break;
case BEHAV_QUIZZAGAN:
displayBehaviorQuizzagan();
break;
case BEHAV_OFF:
displayBehaviorOff();
break;
case BEHAV_BLOCK:
displayBehaviorBlock();
break;
case BEHAV_DEFAULT:
default:
displayBehaviorDefault();
break;
}
}
// -----------------------------------------------------------------------------
// MAIN LOOP - AUTO and D13 priority (check AUTO and D13 first).
// -----------------------------------------------------------------------------
void loop() {
checkD13();
if (isAuto()) {
BehaviorMode savedMode = currentBehaviorMode;
bool resumeScrollAfterAuto = (savedMode == BEHAV_SCROLL);
autoMode();
if (resumeScrollAfterAuto && !pausedByD13) {
currentBehaviorMode = BEHAV_SCROLL;
digitalWrite(PIN_D1_SIGNAL, LOW);
displayBehaviorScrollWords();
} else {
currentBehaviorMode = savedMode;
modeChanged = false;
lastActivityMs = millis();
}
return;
}
if (digitalRead(PIN_D13_RESET) == LOW) {
handleExternalPause();
if (isAuto()) return;
if (currentBehaviorMode == BEHAV_SCROLL && !pausedByD13) {
digitalWrite(PIN_D1_SIGNAL, LOW);
displayBehaviorScrollWords();
return;
}
}
updateModeButton();
if (leftTriggered) {
leftTriggered = false;
displaySequenceRight();
} else if (rightTriggered) {
rightTriggered = false;
displaySequenceLeft();
} else if (digitalRead(PIN_D2_LEFT) == LOW) {
displaySequenceRight();
} else if (digitalRead(PIN_D3_RIGHT) == LOW) {
displaySequenceLeft();
} else {
displayIdleAnimation();
}
}
// Draw 3x3 pupils helper (fixed to write RAM buffers directly)
void drawPupilFrame(int xL, int yL, int xR, int yR) {
uint8_t leftBmp[8];
uint8_t rightBmp[8];
for (int i = 0; i < 8; i++) { leftBmp[i] = 0x00; rightBmp[i] = 0x00; }
xL = constrain(xL, 0, 7); yL = constrain(yL, 0, 7);
xR = constrain(xR, 0, 7); yR = constrain(yR, 0, 7);
for (int dy = -1; dy <= 1; dy++) {
int yy = yL + dy;
if (yy < 0 || yy > 7) continue;
for (int dx = -1; dx <= 1; dx++) {
int xx = xL + dx;
if (xx < 0 || xx > 7) continue;
leftBmp[yy] |= (1 << (7 - xx));
}
}
for (int dy = -1; dy <= 1; dy++) {
int yy = yR + dy;
if (yy < 0 || yy > 7) continue;
for (int dx = -1; dx <= 1; dx++) {
int xx = xR + dx;
if (xx < 0 || xx > 7) continue;
rightBmp[yy] |= (1 << (7 - xx));
}
}
// Write the RAM-created bitmaps directly to the MAX7219s (do NOT use showPair which expects PROGMEM)
for (int row = 0; row < 8; row++) {
lc1.setRow(0, row, leftBmp[row]);
lc2.setRow(0, row, rightBmp[row]);
}
}
// -----------------------------------------------------------------------------
// SCROLL CUE BLINK - subtle one-time cue before a new scroll animation starts
// -----------------------------------------------------------------------------
void scrollCueBlink() {
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(80);
showBoth(eyeBlock);
delay(120);
lc1.clearDisplay(0);
lc2.clearDisplay(0);
delay(60);
lastActivityMs = millis();
}