Front view of the pet alarm and RFID "carrot" in sleep mode.
This project was built to wake me up in the mornings. It consists of a plush bunny with the components built in. It includes a real-time clock that displays the current time, as well as rotary encoders on the back to set hours and seconds for your alarm. For demo purposes, I replaced the traditional alarm with a timer setup. Press down on any of the rotary encoders to set the alarm/start the timer. Once the alarm goes off, the device makes a loud beeping sound, and the pet wakes up. You must fulfill three of the pet's hearts for it to sleep again. In any order, you can "pet" the touch sensor at the top of its head, "feed" it the RFID-tagged carrot to its mouth, and rock it like a baby for a few cycles until it turns off and is ready to be set again. The eyes of the plush and the front LCD will display feedback based on task completion, such as animations of the eyes and hearts.
Image holding the device for overall proportion and scale.
Detail photo of the rotary encoders in the back to adjust hours and minutes.
Image of petting the pet and expression change in the eyes.
Image of feeding the pet with the RFID carrot and expression change in the eyes.
In this video, I showcase how the pet alarm works by first setting a seven-second timer. Once the timer counts down, the pet alarm starts to beep. First, I pet the top of its head with the touch sensor until it shows "1/3 hearts" on the LCD. Next, I feed the pet with the RFID carrot tag by touching it to the pet's mouth, until the LCD shows "2/3 hearts". Finally, I continuously rock the pet side to side until the LCD shows "3/3 hearts" and the pet goes back to sleep mode.
The initial wiring of the components for testing before soldering. I initially wired everything to a medium breadboard, except the accelerometer, which I attached to a separate mini breadboard to individually move around to detect the motion. This did not yet include some components such as the RFID tag and LCD screen.
The overall view of the soldered + other components wired up into the Arduino Uno. Staring at the jumbled mess before I have to fit it into the plushie, and regretting the choice of using solid wires instead of stranded ones.
The original design of the exterior of the device was modeled in Rhino, which was going to be 3D printed. Due to timing constraints with printing, it was not included in the final outcome.
An overall view of the soldered components on a protoboard before wiring them to the Arduino Uno. I soldered most of the components, such as the OLED screens, LCD screen, buzzer, RFID tag, and real-time clock. The rest of the components would be plugged into the mini breadboard for ease of access and fitting into the plushie. I definitely practiced my soldering skills during this part.
Detailed photo of one of the cutouts for wires. This one is for the USB wire that plugs from the Arduino Uno into the power supply or laptop. Feeling bad for sacrificing a plushie.
Overall, I was satisfied with the final outcome of the project. I remember thinking in the latter half of the project that I was being too ambitious, but it was really rewarding to see that my vision was able to come to life. There were many happy accidents as well as things I would do differently if I were to do it again. The first thing I would change would definitely be from the solid soldered wires to the stranded ones, as it would have made it a lot easier to fit the components and wires into the plushie neatly. The reason I chose the solid ones was that I was more confident with them when it came to soldering, and I thought it would be more flexible for wire management, but there were limitations on how flexible they really could be. Something else would have been using a different type of tilt sensor rather than the 3-axis accelerometer, because then I would be able to focus on that side-to-side motion. This was also a comment that I received during critique feedback, saying, "I think using a gyro instead of an accelerometer would make it easier to detect the actual rocking motion." I definitely agree with this comment, and it pointed me in the direction of using a gyro. I was having some trouble getting the rocking to activate, and I ended up having to shake the pet viciously sometimes to get it to work. I think using a gyro would allow for a more natural interaction. I would also have replaced the rotary encoders with simple potentiometers. My idea for the rotary encoders was that they could turn infinitely, and I liked that some of them had increments that you could feel as you turned them, which I thought would be a good interaction for setting the time; however, I faced struggles with getting them to increment smoothly and reflect on the LCD accurately. Another comment I received during critique feedback I agreed with was, "I wish it wasn’t a beeping sound but something more related to the bunny. Crying sound could be a good idea. Also after you complete each task the volume can go lower and lower because the screen was a bit hard to tell." Because I used a buzzer instead of a speaker, I couldn't customize the sounds that the pet made. This is something I would have loved to explore, and I can imagine using an MP3 player and speaker instead to customize different sounds for the pet waking up, getting pet, being fed, being rocked back and forth, etc., to make the pet a lot more expressive and the interactions a lot more immersive. The buzzer gradually getting quieter as you completed the tasks, was actually something I tried to prototype, but I couldn't seem to get it to work. I also tried to vary the frequency of the beeps based on task completion, but I struggled with that as well. In addition, my initial plan with the exterior was going to be a 3D printed shell that resembled a cat with cutouts for the components, which I would then wrap and fit in a pastel rainbow fuzzy cloth I got off Amazon. I modeled a shell exterior for the components in Rhino that I was going to 3D print and use instead of the plush, but it was way too large and wouldn't print in time. That was one lesson I learned throughout the process, which was to be more aware of the scale of 3D models when it comes to printing. To improvise, I picked up a bunny plush that I thought would be a good scale for the device and instead removed some filling and cut out holes for the components, gluing them in place. This ended up being a suitable alternative, and I think it actually turned out better than the initial plan could have been, which I was happy with. If I were to continue iterating, I would also want to solder the rest of the components that were plugged into the mini breadboard, so that those connections were secure inside the plushie and didn't take up so much space. Ideally, I would stitch up the gap in the back of the plushie's head that reveals all of the components so that everything would rest inside unseen. I would also want to clean up the areas around the cutouts so that they are neater, through methods like trimming the fur around the edges or cutting the holes more evenly. I would also take the extra stuffing and fill in some parts so that the device was more structurally stable and kept the plushie's form. This was my first time experimenting with Arduino components inside of an untraditional form, like a plushie, and I learned a lot about how important wiring and positioning of the components are, as well as how prototyping methods differ from hard vs. soft forms of fabrication. I had a lot of fun with this project!
/*
Pet Alarm
Silvia Shin
This project creates an interactive pet alarm clock using an Arduino Uno.
The user sets a countdown timer using two rotary encoders (minutes and seconds).
When the timer ends, the pet awakes and the alarm goes off,
where the user must complete three physical tasks to silence it:
1) Pet the capacitive touch sensor (sustained hold),
2) Rock the device enough times (3-axis accelerometer),
3) Present an RFID tag (“feeding” the pet).
Two OLED displays show animated eyes with expressions
upon completing each task, as well as sleep/awake.
A LCD display shows time, timer settings, and number of hearts earned.
An active buzzer produces alarm beeping sounds.
Pin mapping:
Arduino pin | Role | Description
2 input capacitive touch sensor
3 output buzzer click output
4 input minutes encoder CLK
5 input minutes encoder DT
A1 input minutes encoder SW
6 input seconds encoder CLK
7 input seconds encoder DT
A2 input seconds encoder SW
A4 (SDA) I2C RTC, LCD, accelerometer, left OLED
A5 (SCL) I2C RTC, LCD, accelerometer, left OLED
8 I/O SDA for right OLED
9 I/O SCL for right OLED
10 output RFID (SS/SDA)
A0 output RFID reset
11 (MOSI) output SPI to RFID reader
12 (MISO) input SPI from RFID reader
13 (SCK) output SPI clock to RFID reader
3.3V power accelerometer, RFID reader
5V power LCD, RTC breakout, touch sensor, encoders, OLEDs
GND power common ground for all components
Credits:
This project incorporates standard examples and libraries from Adafruit
(RTClib, ADXL345 Unified, Adafruit_Sensor), U8g2 (SSD1306 graphics),
and the MFRC522 SPI library. Full code was written with assistance from ChatGPT.
*/
#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
#include <SPI.h>
#include <MFRC522.h>
#include <U8g2lib.h>
#include <math.h>
// ---------------- Pins ----------------
#define PIN_TOUCH 2
#define PIN_BUZZ 3
#define M_CLK 4
#define M_DT 5
#define M_SW A1
#define S_CLK 6
#define S_DT 7
#define S_SW A2
#define RFID_SS 10
#define RFID_RST A0
#define SOFT_SDA 8
#define SOFT_SCL 9
// ---------------- Behavior / Timing ----------------
#define BUZZ_ENABLE 1
// ---- Tiered beep pacing: 0→superfast, 1→fast, 2→medium, 3→slow ----
#define BEEP_ON_MS 25 // single short click (keeps it quiet but clear)
#define BEEP_GAP_SUPERFAST 80 // hearts=0
#define BEEP_GAP_FAST 240 // hearts=1
#define BEEP_GAP_MED 480 // hearts=2
#define BEEP_GAP_SLOW 960 // hearts=3
const unsigned long PET_HOLD_MS = 3000; // 3 s touch
const int ROCK_TARGET = 1; // easier rocking
// Rocking sensitivity (hysteresis)
const float TILT_ENTER_DEG = 6.0; // angle to enter a side
const float TILT_EXIT_DEG = 2.5; // angle to return to center
// Encoder tuning
const unsigned long DETENT_TIMEOUT_MS = 60; // commit step if stalled mid-cycle
// ---------------- Devices ----------------
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2); // change to 0x3F if scan says so
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
// OLEDs (U8g2 page-mode => very low RAM)
U8G2_SSD1306_128X64_NONAME_1_HW_I2C eyeL(U8G2_R0, U8X8_PIN_NONE); // A4/A5
U8G2_SSD1306_128X64_NONAME_1_SW_I2C eyeR(U8G2_R0, SOFT_SCL, SOFT_SDA, U8X8_PIN_NONE); // D9/D8
// RFID
MFRC522 mfrc522(RFID_SS, RFID_RST);
// ---------------- State ----------------
enum Mode { SET_TIMER, RUN_TIMER, RINGING, TASKS, DONE };
Mode mode = SET_TIMER;
int setMin = 0, setSec = 7;
unsigned long timerStartMs = 0, timerDurationMs = 0;
// -------- Robust quadrature encoders (1 detent = 1 step + timeout) --------
const int8_t QDEC[16] = {
0,-1, 1, 0,
1, 0, 0,-1,
-1, 0, 0, 1,
0, 1,-1, 0
};
uint8_t m_prev = 0; int8_t m_acc = 0; unsigned long m_lastMove = 0;
uint8_t s_prev = 0; int8_t s_acc = 0; unsigned long s_lastMove = 0;
// Push debouncing
unsigned long lastPressMs = 0;
const unsigned long PRESS_DEBOUNCE_MS = 180;
// Touch (robust hold)
static unsigned long touchOnMs = 0; // start time of current touch window
bool petDone=false;
// Rocking
int rockCycles=0;
// Feeding
bool feedDone=false;
// Buzzer state
unsigned long buzzMarkMs=0;
bool buzzActive=false;
// Eye timing
unsigned long reactUntil=0;
unsigned long lastTouchActiveMs = 0;
unsigned long lastRockActiveMs = 0;
unsigned long lastFeedMs = 0;
// Eye geometry (CENTER each OLED)
const int Lcx=64, Ly=32; // center of 128x64
const int Rx=64, Ry=32;
const int rEye = 18;
const int rPup = 5;
// Rocking FSM
enum TiltState { TILT_CENTER = 0, TILT_POS = 1, TILT_NEG = -1 };
static TiltState tiltState = TILT_CENTER;
// Fwds
void showTimeOnLCD(); void showTimerOnLCD(unsigned long remMs);
void updateEncoders(); void commitEncoderTimeouts();
void resetTasks();
float readRollDeg(); bool checkRFID(); void quietBeepPattern(byte tasksDone);
// Eyes (single-eye styles)
void drawEyesSleep(); // idle sleeping
void drawEyesAwakeAlarm(); // awake/alarm (sparkly wandering pupil)
void drawEyesPettingBoth(); // big cute ^ eye (closed, happy)
void drawEyesFeedingBoth(); // big filled iris + heart highlight (both eyes)
void drawEyesRockingBoth(); // rocking side-to-side + blinks
void drawEyesHappy(); // after all tasks complete
inline unsigned long nowMs(){ return millis(); }
// -------------------------------------------------------
void setup(){
pinMode(PIN_TOUCH, INPUT); // HW-139 default active-HIGH
pinMode(PIN_BUZZ, OUTPUT);
pinMode(M_CLK, INPUT_PULLUP);
pinMode(M_DT, INPUT_PULLUP);
pinMode(M_SW, INPUT_PULLUP);
pinMode(S_CLK, INPUT_PULLUP);
pinMode(S_DT, INPUT_PULLUP);
pinMode(S_SW, INPUT_PULLUP);
Serial.begin(9600);
Wire.begin();
rtc.begin();
lcd.init(); lcd.backlight();
accel.begin();
accel.setRange(ADXL345_RANGE_2_G); // more sensitivity for small tilts
eyeL.begin();
eyeR.begin();
SPI.begin(); mfrc522.PCD_Init();
// seed encoder previous states so there’s no boot jump
m_prev = ((digitalRead(M_CLK)&1)<<1) | (digitalRead(M_DT)&1);
s_prev = ((digitalRead(S_CLK)&1)<<1) | (digitalRead(S_DT)&1);
m_lastMove = s_lastMove = nowMs();
drawEyesSleep();
showTimeOnLCD();
}
void loop(){
// Read encoders continuously
updateEncoders();
commitEncoderTimeouts();
switch(mode){
case SET_TIMER: {
// show time + set UI
showTimeOnLCD();
lcd.setCursor(0,1);
lcd.print(F("Set "));
if(setMin<10) lcd.print('0'); lcd.print(setMin);
lcd.print(':');
if(setSec<10) lcd.print('0'); lcd.print(setSec);
lcd.print(F(" Press M/S"));
drawEyesSleep();
// start timer on press (debounced)
if((digitalRead(M_SW)==LOW || digitalRead(S_SW)==LOW) &&
(nowMs() - lastPressMs) > PRESS_DEBOUNCE_MS){
timerDurationMs = (unsigned long)setMin*60000UL + (unsigned long)setSec*1000UL;
timerStartMs = nowMs();
mode = RUN_TIMER;
lastPressMs = nowMs();
}
} break;
case RUN_TIMER: {
unsigned long elapsed = nowMs() - timerStartMs;
if(elapsed >= timerDurationMs){ resetTasks(); mode = RINGING; break; }
unsigned long rem = timerDurationMs - elapsed;
showTimerOnLCD(rem);
drawEyesSleep();
} break;
case RINGING: {
mode = TASKS; drawEyesAwakeAlarm();
} break;
case TASKS: {
// --------- Inputs & task state updates ---------
// Touch (3s hold with dropout tolerance)
int raw = digitalRead(PIN_TOUCH); // 0 idle, 1 touched
bool touched = (raw == HIGH);
if (touched) {
lastTouchActiveMs = nowMs();
if (touchOnMs == 0) touchOnMs = millis();
unsigned long held = millis() - touchOnMs;
if (!petDone && held >= PET_HOLD_MS) {
petDone = true;
reactUntil = millis() + 250;
}
} else {
if (touchOnMs && (millis() - touchOnMs) > 150) touchOnMs = 0;
}
// Rock cycles via hysteresis state machine (more sensitive)
float roll = readRollDeg();
TiltState newState = tiltState;
if (roll > TILT_ENTER_DEG) newState = TILT_POS;
else if (roll < -TILT_ENTER_DEG) newState = TILT_NEG;
else if (fabsf(roll) < TILT_EXIT_DEG) newState = TILT_CENTER;
if (newState != tiltState) {
if ((tiltState == TILT_NEG && newState == TILT_POS) ||
(tiltState == TILT_POS && newState == TILT_NEG)) {
rockCycles++;
reactUntil = nowMs() + 150;
lastRockActiveMs = nowMs();
}
if (newState != TILT_CENTER) lastRockActiveMs = nowMs();
tiltState = newState;
}
// RFID feed
if(!feedDone && checkRFID()){
feedDone = true;
lastFeedMs = nowMs();
reactUntil = nowMs()+250;
}
// --------- Visuals ---------
unsigned long t = nowMs();
bool feedingNow = (t - lastFeedMs) <= 700;
bool pettingNow = (t - lastTouchActiveMs) <= 300;
bool rockingNow = (t - lastRockActiveMs) <= 600;
if (feedingNow) drawEyesFeedingBoth();
else if (pettingNow) drawEyesPettingBoth();
else if (rockingNow) drawEyesRockingBoth();
else if (petDone && rockCycles>=ROCK_TARGET && feedDone) drawEyesHappy();
else drawEyesAwakeAlarm();
// --------- NOW compute hearts & drive LCD + buzzer ---------
int hearts = (petDone?1:0) + (rockCycles>=ROCK_TARGET?1:0) + (feedDone?1:0);
lcd.setCursor(0,0); lcd.print(F(" ALARM!! "));
lcd.setCursor(0,1); lcd.print(F("Hearts: ")); lcd.print(hearts); lcd.print(F("/3 "));
if(BUZZ_ENABLE) quietBeepPattern(hearts);
// finish
if(petDone && rockCycles>=ROCK_TARGET && feedDone){
// keep slow beeping at 3 hearts until DONE transition
delay(500);
mode = DONE;
}
delay(20);
} break;
case DONE: {
showTimeOnLCD();
drawEyesSleep();
// stop buzzer when we exit task mode
#if BUZZ_ENABLE
digitalWrite(PIN_BUZZ, LOW);
#endif
buzzActive = false;
mode = SET_TIMER;
delay(200);
} break;
}
}
// ---------------- Encoders (quadrature; 1 detent = 1 step + timeout) ----------------
void updateEncoders(){
// Minutes
uint8_t m_cur = ((digitalRead(M_CLK)&1)<<1) | (digitalRead(M_DT)&1);
uint8_t m_idx = (m_prev<<2) | m_cur;
int8_t dM = QDEC[m_idx];
if (dM != 0) { m_acc += dM; m_prev = m_cur; m_lastMove = nowMs(); }
if (m_acc >= 4) { setMin++; if (setMin > 59) setMin = 0; m_acc = 0; }
else if (m_acc <= -4) { setMin--; if (setMin < 0) setMin = 59; m_acc = 0; }
// Seconds
uint8_t s_cur = ((digitalRead(S_CLK)&1)<<1) | (digitalRead(S_DT)&1);
uint8_t s_idx = (s_prev<<2) | s_cur;
int8_t dS = QDEC[s_idx];
if (dS != 0) { s_acc += dS; s_prev = s_cur; s_lastMove = nowMs(); }
if (s_acc >= 4) { setSec++; if (setSec > 59) setSec = 0; s_acc = 0; }
else if (s_acc <= -4) { setSec--; if (setSec < 0) setSec = 59; s_acc = 0; }
}
// If encoder stalls mid-cycle, commit one step after a short timeout
void commitEncoderTimeouts(){
unsigned long t = nowMs();
if (m_acc != 0 && (t - m_lastMove) > DETENT_TIMEOUT_MS) {
if (m_acc > 0) { setMin++; if (setMin > 59) setMin = 0; }
else { setMin--; if (setMin < 0) setMin = 59; }
m_acc = 0;
}
if (s_acc != 0 && (t - s_lastMove) > DETENT_TIMEOUT_MS) {
if (s_acc > 0) { setSec++; if (setSec > 59) setSec = 0; }
else { setSec--; if (setSec < 0) setSec = 59; }
s_acc = 0;
}
}
// ---------------- LCD helpers ----------------
void showTimeOnLCD(){
DateTime n = rtc.now();
lcd.setCursor(0,0);
if(n.hour()<10) lcd.print('0'); lcd.print(n.hour()); lcd.print(':');
if(n.minute()<10) lcd.print('0'); lcd.print(n.minute());
lcd.print(F(" "));
}
void showTimerOnLCD(unsigned long remMs){
unsigned long remS = remMs/1000UL; int mm = remS/60UL; int ss = remS%60UL;
lcd.setCursor(0,0); lcd.print(F("Timer "));
if(mm<10) lcd.print('0'); lcd.print(mm); lcd.print(':');
if(ss<10) lcd.print('0'); lcd.print(ss); lcd.print(F(" "));
lcd.setCursor(0,1); lcd.print(F("M:"));
if(setMin<10) lcd.print('0'); lcd.print(setMin); lcd.print(F(" S:"));
if(setSec<10) lcd.print('0'); lcd.print(setSec); lcd.print(F(" "));
}
// ---------------- Tasks helpers ----------------
void resetTasks(){
petDone=false; touchOnMs=0;
rockCycles=0;
feedDone=false;
buzzActive=false; buzzMarkMs=0;
reactUntil=0;
lastTouchActiveMs = lastRockActiveMs = lastFeedMs = 0;
tiltState = TILT_CENTER;
}
float readRollDeg(){
sensors_event_t e; accel.getEvent(&e);
float ax=e.acceleration.x, ay=e.acceleration.y, az=e.acceleration.z;
return atan2(ay, sqrt(ax*ax+az*az)) * 180.0/PI;
}
bool checkRFID(){
if(!mfrc522.PICC_IsNewCardPresent()) return false;
if(!mfrc522.PICC_ReadCardSerial()) return false;
mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1();
return true;
}
// ---------------- Quiet buzzer ----------------
// Non-blocking tempo by hearts: emits one short "click" every GAP ms
void quietBeepPattern(byte hearts){
// Select gap by hearts (0..3)
unsigned int gap =
(hearts >= 3) ? BEEP_GAP_SLOW :
(hearts == 2) ? BEEP_GAP_MED :
(hearts == 1) ? BEEP_GAP_FAST : BEEP_GAP_SUPERFAST;
unsigned long t = nowMs();
// Start a short click?
if(!buzzActive && (t - buzzMarkMs) >= gap){
buzzActive = true;
buzzMarkMs = t;
#if BUZZ_ENABLE
digitalWrite(PIN_BUZZ, HIGH);
#endif
}
// End the short click after BEEP_ON_MS
if(buzzActive && (t - buzzMarkMs) >= BEEP_ON_MS){
buzzActive = false;
buzzMarkMs = t; // mark end time; next gap measured from here
#if BUZZ_ENABLE
digitalWrite(PIN_BUZZ, LOW);
#endif
}
}
/*** ---------------- Eye animations (single-eye, centered) ---------------- ***/
static unsigned long eyeFrameT = 0;
inline void beginPages(){ eyeFrameT = nowMs(); }
// Sleeping: centered flat eyelid + occasional micro-blink line
void drawEyeSleeping(U8G2 &d, int cx, int cy){
d.drawHLine(cx-20, cy, 40);
if ((nowMs() % 1000) < 120) d.drawHLine(cx-20, cy-1, 40);
}
void drawEyeAlarm(U8G2 &d, int cx, int cy){
d.drawCircle(cx, cy, rEye);
float phase = (eyeFrameT % 1300) / 1300.0 * TWO_PI;
int px = cx + (int)(sin(phase)*5);
int py = cy + (int)(cos(phase*1.1)*3);
d.drawDisc(px, py, rPup);
// tiny sparkle at UR of iris
int sx = cx + 7, sy = cy - 7;
d.drawPixel(sx, sy); d.drawHLine(sx-2, sy, 5); d.drawVLine(sx, sy-2, 5);
}
// Petting: big caret eye (^)
void drawEyePetting(U8G2 &d, int cx, int cy){
d.drawLine(cx-16, cy+9, cx, cy-12);
d.drawLine(cx, cy-12, cx+16, cy+9);
d.drawLine(cx-16, cy+10, cx, cy-11);
d.drawLine(cx, cy-11, cx+16, cy+10);
}
// carve a pixel heart (for feeding)
void carveHeart(U8G2 &d, int cx, int cy){
d.drawDisc(cx-4, cy-3, 4);
d.drawDisc(cx+4, cy-3, 4);
for(int i=0;i<6;i++){
d.drawLine(cx-8+i, cy, cx, cy+10+i);
d.drawLine(cx+8-i, cy, cx, cy+10+i);
}
}
// Feeding: filled iris with carved heart highlight
void drawEyeEating(U8G2 &d, int cx, int cy){
d.drawCircle(cx, cy, rEye);
d.drawDisc(cx, cy, rEye-2);
d.setDrawColor(0);
carveHeart(d, cx, cy);
d.setDrawColor(1);
d.drawCircle(cx, cy, rEye);
}
// Rocking: pupil rocks with quick blink
void drawEyeRocking(U8G2 &d, int cx, int cy){
d.drawCircle(cx, cy, rEye);
if ((eyeFrameT % 1200) < 120) {
d.drawHLine(cx-20, cy, 40);
} else {
float phase = (eyeFrameT % 900) / 900.0 * TWO_PI;
int px = cx + (int)(sin(phase)*7);
if (px < (cx-rEye+2)) px = cx-rEye+2;
if (px > (cx+rEye-2)) px = cx+rEye-2;
d.drawDisc(px, cy, rPup);
}
}
// ---- Render ONE eye per OLED ----
void drawEyesSleep(){
eyeL.firstPage(); do { drawEyeSleeping(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawEyeSleeping(eyeR, Rx, Ry); } while (eyeR.nextPage());
}
void drawEyesAwakeAlarm(){
beginPages();
eyeL.firstPage(); do { drawEyeAlarm(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawEyeAlarm(eyeR, Rx, Ry); } while (eyeR.nextPage());
}
void drawEyesPettingBoth(){
beginPages();
eyeL.firstPage(); do { drawEyePetting(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawEyePetting(eyeR, Rx, Ry); } while (eyeR.nextPage());
}
void drawEyesFeedingBoth(){
beginPages();
eyeL.firstPage(); do { drawEyeEating(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawEyeEating(eyeR, Rx, Ry); } while (eyeR.nextPage());
}
void drawEyesRockingBoth(){
beginPages();
eyeL.firstPage(); do { drawEyeRocking(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawEyeRocking(eyeR, Rx, Ry); } while (eyeR.nextPage());
}
void drawEyesHappy(){
auto drawHappyArc = [&](U8G2 &d, int cx, int cy){
for(int i=0;i<8;i++) d.drawLine(cx-20+i*4, cy+4, cx-17+i*4, cy+1);
};
eyeL.firstPage(); do { drawHappyArc(eyeL, Lcx, Ly); } while (eyeL.nextPage());
eyeR.firstPage(); do { drawHappyArc(eyeR, Rx, Ry); } while (eyeR.nextPage());
}