A reactive autonomous desktop companion running dynamic state machines and presence tracking feedback loops.
Initial blueprints intended for a 30-pin ESP32 processing architecture to utilize its built-in touch controllers and wireless expansion lines. However, to eliminate hardware debugging variables during initial development, operations were moved to an Arduino Uno.
This strategy allows the core behavioral loops, timing controls, and display frame buffering structures to be locked down completely before shifting to the high-performance dual-core ecosystem.
Manages the main application timeline, triggers ultrasonic pulses, calculates echo windows, and schedules display updates via the I2C pipeline.
A low-power monochrome screen driven via the 2-wire I2C communication protocol to print changing graphic arrays instantly.
Measures exact physical distance by evaluating supersonic echo travel intervals to track user presence.
Generates distinct variable-frequency square waves to create audible emotional chirps during state changes.
Arranged as a structural inline voltage divider configuration. It scales down the 5V Echo output of the sensor down to a safe 3.3V logic line level, preparing the hardware mapping for the future ESP32 controller migration.
| Module Component | Microcontroller Pin | Voltage Bus |
|---|---|---|
| OLED SDA Pin | Hardware SDA (A4) | 3.3V Power Rail |
| OLED SCL Pin | Hardware SCL (A5) | 3.3V Power Rail |
| HC-SR04 TRIG Pin | Digital Pin D9 | 5.0V Power Rail |
| HC-SR04 ECHO Pin | Digital Pin D10 (via Divider) | 5.0V Power Rail |
| Passive Buzzer Line | Digital Pin D8 | 5.0V Power Rail |
Normal State: Runs clean round eyes with nested white specular spots, triggering automatic blinks natively inside the CSS timeline loop.
Excited State: Occurs when the HC-SR04 sensor registers an object under 20cm. Displays maximized eye scales with a vibrating jitter animation, simulating rapid hardware recognition events.
Happy & Sleepy States: Progressive idle states calculated after 7 seconds and 10 seconds of sensor inactivity. Screen shifts to curved eyes or small sleepy frames while emitting calm multi-tone frequency feedback.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define TRIG_PIN 9
#define ECHO_PIN 10
#define BUZZER_PIN 8
unsigned long lastBlink = 0;
unsigned long lastIdle = 0;
unsigned long blinkInterval = 3000;
bool isExcited = false;
bool isBlinked = false;
// ─────────────────────────────────────────
// DISTANCE
// ─────────────────────────────────────────
long getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 20000);
return duration * 0.034 / 2;
}
// ─────────────────────────────────────────
// BLINK HANDLER
// runs independently, call after every face draw
// ─────────────────────────────────────────
void handleBlink(void (*faceFn)()) {
unsigned long now = millis();
if (!isBlinked && now - lastBlink > blinkInterval) {
// draw closed eyes with same mouth style
display.clearDisplay();
// cute closed eyes — curved lines
display.drawLine(18, 28, 28, 24, SSD1306_WHITE);
display.drawLine(28, 24, 38, 28, SSD1306_WHITE);
display.drawLine(82, 28, 92, 24, SSD1306_WHITE);
display.drawLine(92, 24, 102, 28, SSD1306_WHITE);
// keep mouth
display.drawFastHLine(44, 54, 40, SSD1306_WHITE);
display.display();
delay(120);
isBlinked = true;
lastBlink = now;
blinkInterval = random(2000, 5000);
faceFn(); // redraw face after blink
} else {
isBlinked = false;
}
}
// ─────────────────────────────────────────
// MOUTH HELPERS
// ─────────────────────────────────────────
void drawNeutralMouth() {
// cute small curved mouth
display.drawPixel(54, 55, SSD1306_WHITE);
display.drawPixel(55, 56, SSD1306_WHITE);
display.drawFastHLine(56, 57, 16, SSD1306_WHITE);
display.drawPixel(72, 56, SSD1306_WHITE);
display.drawPixel(73, 55, SSD1306_WHITE);
}
void drawSmile() {
// big cute smile
display.drawPixel(44, 52, SSD1306_WHITE);
display.drawPixel(45, 54, SSD1306_WHITE);
display.drawPixel(46, 55, SSD1306_WHITE);
display.drawFastHLine(47, 56, 34, SSD1306_WHITE);
display.drawPixel(81, 55, SSD1306_WHITE);
display.drawPixel(82, 54, SSD1306_WHITE);
display.drawPixel(83, 52, SSD1306_WHITE);
}
void drawOpenMouth() {
// cute open O mouth
display.drawRoundRect(50, 50, 28, 13, 6, SSD1306_WHITE);
display.fillRoundRect(51, 51, 26, 11, 5, SSD1306_BLACK);
// cute tongue
display.fillRoundRect(57, 57, 14, 5, 3, SSD1306_WHITE);
}
void drawFrown() {
display.drawPixel(44, 57, SSD1306_WHITE);
display.drawPixel(45, 56, SSD1306_WHITE);
display.drawPixel(46, 55, SSD1306_WHITE);
display.drawFastHLine(47, 54, 34, SSD1306_WHITE);
display.drawPixel(81, 55, SSD1306_WHITE);
display.drawPixel(82, 56, SSD1306_WHITE);
display.drawPixel(83, 57, SSD1306_WHITE);
}
void drawSleepyMouth() {
// tiny mouth slightly open
display.drawFastHLine(52, 55, 24, SSD1306_WHITE);
display.drawFastHLine(52, 57, 24, SSD1306_WHITE);
display.drawFastVLine(52, 55, 2, SSD1306_WHITE);
display.drawFastVLine(75, 55, 2, SSD1306_WHITE);
// ZZZ bubbles
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(100, 18);
display.print("z");
display.setCursor(107, 11);
display.print("z");
display.setCursor(113, 5);
display.print("z");
}
// ─────────────────────────────────────────
// FACES
// ─────────────────────────────────────────
void drawNormalFace() {
display.clearDisplay();
// cute round eyes with shine
display.fillCircle(32, 28, 14, SSD1306_WHITE);
display.fillCircle(35, 26, 6, SSD1306_BLACK);
display.fillCircle(30, 22, 3, SSD1306_WHITE); // shine
display.fillCircle(96, 28, 14, SSD1306_WHITE);
display.fillCircle(99, 26, 6, SSD1306_BLACK);
display.fillCircle(94, 22, 3, SSD1306_WHITE); // shine
drawNeutralMouth();
display.display();
}
void drawExcitedFace() {
display.clearDisplay();
// big sparkly eyes
display.fillCircle(32, 26, 18, SSD1306_WHITE);
display.fillCircle(36, 23, 7, SSD1306_BLACK);
display.fillCircle(28, 19, 4, SSD1306_WHITE); // big shine
display.fillCircle(38, 30, 2, SSD1306_WHITE); // small shine
display.fillCircle(96, 26, 18, SSD1306_WHITE);
display.fillCircle(100, 23, 7, SSD1306_BLACK);
display.fillCircle(92, 19, 4, SSD1306_WHITE);
display.fillCircle(102, 30, 2, SSD1306_WHITE);
// rosy cheeks
display.drawCircle(14, 38, 5, SSD1306_WHITE);
display.drawCircle(15, 38, 5, SSD1306_WHITE);
display.drawCircle(113, 38, 5, SSD1306_WHITE);
display.drawCircle(114, 38, 5, SSD1306_WHITE);
drawOpenMouth();
display.display();
}
void drawHappyFace() {
display.clearDisplay();
// happy squint eyes
display.fillRoundRect(16, 22, 32, 14, 7, SSD1306_WHITE);
display.fillRect(16, 22, 32, 7, SSD1306_BLACK);
display.fillRoundRect(80, 22, 32, 14, 7, SSD1306_WHITE);
display.fillRect(80, 22, 32, 7, SSD1306_BLACK);
// rosy cheeks
display.drawCircle(14, 40, 5, SSD1306_WHITE);
display.drawCircle(15, 40, 5, SSD1306_WHITE);
display.drawCircle(113, 40, 5, SSD1306_WHITE);
display.drawCircle(114, 40, 5, SSD1306_WHITE);
// sparkles
display.drawPixel(6, 20, SSD1306_WHITE);
Transition tracking operations to a high-speed dual-core controller to implement integrated multi-threaded scheduling loops and hardware processing capabilities.
Incorporate small PWM micro servo brackets to map physical face tracking movements matching external distance data coordinates.
Upgrade from 1-bit monochrome constraints to a vibrant modern display window, opening up complex alpha channels and rich digital animations.
Initialize localized async network servers on the chip to construct immediate wireless control dashboard interfaces accessible via desktop IP paths.