Community project

ClawdBot Virtual Pet Companion

ESP32
Photo of ClawdBot Virtual Pet Companion
Generated with AI

Idea Labs

Published September 9, 2026

ClawdBot is a virtual pet companion that lives on an ESP32-powered OLED display, responding to care actions through three buttons. Feed, play with, or put to sleep the digital creature, which changes mood based on neglect and care—complete with sound effects and a visual status indicator.

This guide provides a complete wiring diagram, parts list, and Arduino firmware to bring ClawdBot to life. Builders will learn how to interface buttons, drive an SSD1306 OLED display, generate tones with a piezo buzzer, and implement state-based pet behavior logic. Assembly takes under an hour and requires only basic soldering skills.

Wiring diagram

Wiring diagram for ClawdBot Virtual Pet Companion

Gather all the parts

QtyComponent
1

SSD1306 OLED Face

Displays moods, care score, and idle timer.

1

Feed Button

Feeds the ClawdBot and resets hunger.

1

Play Button

Triggers play interaction and happy state.

1

Sleep Button

Puts the bot into sleepy calm mode.

1

Piezo Buzzer

Chirps and emotional feedback tones.

1

Status LED

Blink pattern reflects mood intensity.

1

220 Ω resistor

Current limiting or transistor-base resistor required by the wiring.

Assemble it in 4 steps

1. Wire the OLED face display

Connect OLED VCC to 3.3V, GND to ground, SDA to GPIO21, and SCL to GPIO22.

  • Confirm the I2C address — most modules use 0x3C.

2. Add the three care buttons

Wire Feed button between GPIO32 and GND, Play button between GPIO33 and GND, and Sleep button between GPIO25 and GND. Internal pull-ups are enabled in code.

  • Color-code the buttons (green=feed, blue=play, purple=sleep) for easier identification.

3. Connect buzzer and status LED

Wire piezo buzzer signal to GPIO26 (other lead to GND). Connect status LED anode to GPIO27 through a 220 Ω resistor, cathode to GND.

  • The LED blinks fast when cranky, slow when sleepy, and stays solid when happy.

4. Upload and interact

Flash the sketch. ClawdBot starts happy — if you ignore it for 30 seconds it gets hungry, then bored, then cranky. Press the buttons to keep it happy and maintain the care score.

  • Adjust the idle thresholds in code to make ClawdBot needier or more independent.

Review all connections

1. Connections between "clawdbot-oled-1" and "ESP32"

Functionclawdbot-oled-1ESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

2. Connections between "feed-button-1" and "ESP32"

Functionfeed-button-1ESP32
groundGNDGND
digitalSIGGPIO 32

3. Connections between "play-button-1" and "ESP32"

Functionplay-button-1ESP32
groundGNDGND
digitalSIGGPIO 33

4. Connections between "sleep-button-1" and "ESP32"

Functionsleep-button-1ESP32
groundGNDGND
digitalSIGGPIO 25

5. Connections between "clawdbot-buzzer-1" and "ESP32"

Functionclawdbot-buzzer-1ESP32
groundGNDGND
pwmSIGGPIO 26

6. Connections between "clawdbot-led-1" and "ESP32"

Functionclawdbot-led-1ESP32
groundGNDGND
digitalSIG220-ohm-resistor:BGPIO 27

7. Connections between "clawdbot-tamagotchi-220-ohm-resistor-1" and "ESP32"

Functionclawdbot-tamagotchi-220-ohm-resistor-1ESP32
current-limitGPIO 27
current-limitstatus-led:SIGEXT

Deploy the firmware

schematik_esp32.inoOpen in Schematik
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SDA_PIN 21
#define SCL_PIN 22
#define BTN_FEED 32
#define BTN_PLAY 33
#define BTN_SLEEP 25
#define BUZZER_PIN 26
#define STATUS_LED 27

Adafruit_SSD1306 display(128, 64, &Wire, -1);

enum Mood { HAPPY, HUNGRY, SLEEPY, BORED, CRANKY };
Mood mood = HAPPY;
unsigned long lastCareMs = 0;
unsigned long lastBtnMs = 0;
unsigned long lastDecayMs = 0;
int careScore = 100;

const char* moodName(Mood m) {
  switch (m) {
    case HAPPY:  return "Happy  ^_^";
    case HUNGRY: return "Hungry >_<";
    case SLEEPY: return "Sleepy -_-";
    case BORED:  return "Bored  ._.";
    case CRANKY: return "Cranky !!!";
    default:     return "Unknown";
  }
}

void playTone(int freq, int dur) {
  tone(BUZZER_PIN, freq, dur);
  delay(dur + 10);
  noTone(BUZZER_PIN);
}

void feedAction() {
  playTone(1760, 60);
  playTone(2100, 60);
  mood = HAPPY;
  careScore = min(100, careScore + 30);
  lastCareMs = millis();
}

void playAction() {
  playTone(1480, 50);
  playTone(1760, 50);
  playTone(2200, 60);
  mood = HAPPY;
  careScore = min(100, careScore + 20);
  lastCareMs = millis();
}

void sleepAction() {
  playTone(980, 90);
  playTone(780, 120);
  mood = SLEEPY;
  careScore = min(100, careScore + 15);
  lastCareMs = millis();
}

void setup() {
  Serial.begin(115200);
  delay(100);

  Wire.begin(SDA_PIN, SCL_PIN);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  pinMode(BTN_FEED, INPUT_PULLUP);
  pinMode(BTN_PLAY, INPUT_PULLUP);
  pinMode(BTN_SLEEP, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, HIGH);

  lastCareMs = millis();
  Serial.println("ClawdBot ready");
}

void loop() {
  unsigned long idleMs = millis() - lastCareMs;
  unsigned long now = millis();

  if (idleMs > 90000) {
    mood = CRANKY;
    if (now - lastDecayMs >= 1000) {
      careScore = max(0, careScore - 1);
      lastDecayMs = now;
    }
  }
  else if (idleMs > 60000) mood = BORED;
  else if (idleMs > 30000) mood = HUNGRY;

  if (now - lastBtnMs > 200) {
    if (!digitalRead(BTN_FEED))  { feedAction();  lastBtnMs = now; }
    if (!digitalRead(BTN_PLAY))  { playAction();  lastBtnMs = now; }
    if (!digitalRead(BTN_SLEEP)) { sleepAction(); lastBtnMs = now; }
  }

  display.clearDisplay();
  display.setCursor(0, 0);
  display.println("  ClawdBot Tamagochi");
  display.println("--------------------");
  display.print("Mood:  ");
  display.println(moodName(mood));
  display.printf("Care:  %d%%\n", careScore);
  display.printf("Idle:  %lus\n", idleMs / 1000);
  display.println();
  display.println("Feed / Play / Sleep");
  display.display();

  if (mood == CRANKY) {
    digitalWrite(STATUS_LED, (millis() / 180) % 2);
  } else if (mood == SLEEPY) {
    digitalWrite(STATUS_LED, (millis() / 800) % 2);
  } else {
    digitalWrite(STATUS_LED, HIGH);
  }

  delay(30);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik