// ============================================================
// PERSONALITY BOT v2 — Raspberry Pi Pico / Wokwi
// Sensors : MPU6050 (I2C) + DHT22 (digital)
// Display : SSD1306 128x64 OLED (I2C)
// Extras : Passive buzzer, momentary button
// ============================================================
//
// STATE MACHINE:
// NEUTRAL → shows temperature & humidity (default idle)
// SLEEP → display off, wakes only on button press → HAPPY
// HAPPY → button pressed → happy face → back to NEUTRAL on shake or button press
// ANGRY → too much shake → angry face + buzzer 2s once → back to NEUTRAL when calm
//
// ============================================================
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
// ===== PIN DEFINITIONS =====
#define DHTPIN 10
#define DHTTYPE DHT22
#define BUTTON_PIN 11
#define BUZZER_PIN 6
// ===== OLED CONFIG =====
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
// ===== THRESHOLDS =====
#define GRAVITY 9.8f
#define ANGRY_THRESH 5.0f // raise this if triggers too easily, lower if too hard
// ===== TIMING =====
#define LOOP_DELAY 100 // ms between sensor polls
#define DEBOUNCE_MS 300 // button debounce window
#define SLEEP_TIMEOUT 30000 // 30s no button press → sleep
#define BUZZER_FREQ 250 // Hz — angry buzzer tone
#define BUZZER_DUR 2000 // ms — buzzer plays once for 2s
#define DHT_INTERVAL 2000 // ms — DHT22 sample rate
// ===== OBJECTS =====
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_MPU6050 mpu;
DHT dht(DHTPIN, DHTTYPE);
// ===== STATE MACHINE =====
enum State { NEUTRAL, SLEEP, HAPPY, ANGRY };
State currentState = NEUTRAL;
// ===== TIMERS =====
unsigned long lastInteraction = 0; // last button press (for sleep timer)
unsigned long lastDHTRead = 0;
unsigned long angryStartTime = 0; // when angry started (for buzzer once)
bool buzzerDone = false; // so buzzer only plays once per angry
// ===== CACHED SENSOR VALUES =====
float cachedTemp = 0.0f;
float cachedHum = 0.0f;
// ===== BUTTON STATE =====
bool lastButtonState = HIGH;
unsigned long lastPressTime = 0;
// ============================================================
// FACE DRAWING
// ============================================================
void drawFaceOutline() {
display.drawCircle(64, 30, 28, WHITE);
}
void drawNeutralInfo() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(28, 4);
display.print(F("[ SYSTEM INFO ]"));
display.drawLine(0, 14, 128, 14, WHITE);
display.setCursor(10, 22);
display.print(F("Temp : "));
display.print(cachedTemp, 1);
display.print((char)247);
display.print(F("C"));
display.setCursor(10, 36);
display.print(F("Humid: "));
display.print(cachedHum, 1);
display.print(F("%"));
display.drawLine(0, 50, 128, 50, WHITE);
display.setCursor(14, 55);
display.print(F("Press btn = happy :)"));
display.display();
}
void drawHappyFace() {
display.clearDisplay();
drawFaceOutline();
display.fillCircle(50, 22, 5, WHITE);
display.fillCircle(78, 22, 5, WHITE);
display.drawLine(50, 42, 64, 50, WHITE);
display.drawLine(64, 50, 78, 42, WHITE);
display.fillCircle(38, 34, 2, WHITE);
display.fillCircle(90, 34, 2, WHITE);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(34, 57);
display.print(F(":D"));
display.display();
}
void drawAngryFace() {
display.clearDisplay();
drawFaceOutline();
display.fillCircle(50, 24, 3, WHITE);
display.fillCircle(78, 24, 3, WHITE);
display.drawLine(40, 14, 54, 19, WHITE);
display.drawLine(74, 19, 88, 14, WHITE);
display.drawLine(52, 48, 64, 41, WHITE);
display.drawLine(64, 41, 76, 48, WHITE);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(6, 57);
display.print(F("STOP SHAKING ME!"));
display.display();
}
// ============================================================
// SETUP
// ============================================================
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
noTone(BUZZER_PIN);
Wire.begin();
// --- OLED ---
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("OLED init failed"));
while (true);
}
display.setTextColor(WHITE);
// Splash screen
display.clearDisplay();
display.setTextSize(1);
display.setCursor(18, 20);
display.println(F("Personality Bot"));
display.setCursor(30, 36);
display.println(F("Starting..."));
display.display();
delay(1500);
// --- MPU6050 ---
if (!mpu.begin(0x68)) {
if (!mpu.begin(0x69)) {
Serial.println(F("MPU6050 init failed"));
while (true);
}
}
mpu.setAccelerometerRange(MPU6050_RANGE_2_G);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
// --- DHT22 ---
dht.begin();
delay(2000);
cachedTemp = dht.readTemperature();
cachedHum = dht.readHumidity();
if (isnan(cachedTemp)) cachedTemp = 0.0f;
if (isnan(cachedHum)) cachedHum = 0.0f;
lastDHTRead = millis();
lastInteraction = millis();
Serial.println(F("Boot complete."));
}
// ============================================================
// LOOP
// ============================================================
void loop() {
unsigned long now = millis();
// ===== 1. READ MPU6050 =====
sensors_event_t a, g, temp_event;
mpu.getEvent(&a, &g, &temp_event);
float rawMag = sqrt(
a.acceleration.x * a.acceleration.x +
a.acceleration.y * a.acceleration.y +
a.acceleration.z * a.acceleration.z
);
float moveMag = rawMag - GRAVITY;
// ===== 2. READ DHT22 =====
if (now - lastDHTRead >= DHT_INTERVAL) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h)) cachedHum = h;
if (!isnan(t)) cachedTemp = t;
lastDHTRead = now;
}
// ===== 3. BUTTON DETECTION =====
bool buttonState = digitalRead(BUTTON_PIN);
bool buttonPressed = false;
// Detect the exact moment button goes from not pressed (HIGH) to pressed (LOW)
if (buttonState == LOW && lastButtonState == HIGH) {
if (now - lastPressTime > DEBOUNCE_MS) {
lastPressTime = now;
lastInteraction = now;
buttonPressed = true;
Serial.println(F("Button pressed!"));
}
}
lastButtonState = buttonState;
// ===== 4. STATE MACHINE =====
switch (currentState) {
// ---- NEUTRAL ----
case NEUTRAL:
if (buttonPressed) {
// Button → happy
currentState = HAPPY;
Serial.println(F("→ HAPPY"));
break;
}
if (moveMag > ANGRY_THRESH) {
// Too much shake → angry
currentState = ANGRY;
angryStartTime = now;
buzzerDone = false;
Serial.println(F("→ ANGRY"));
break;
}
if (now - lastInteraction >= SLEEP_TIMEOUT) {
// No button for 30s → sleep
currentState = SLEEP;
display.ssd1306_command(SSD1306_DISPLAYOFF);
Serial.println(F("→ SLEEP"));
break;
}
drawNeutralInfo();
break;
// ---- SLEEP ----
case SLEEP:
// Only button wakes it up
if (buttonPressed) {
display.ssd1306_command(SSD1306_DISPLAYON);
currentState = HAPPY;
Serial.println(F("→ HAPPY (wake)"));
}
break;
// ---- HAPPY ----
case HAPPY:
drawHappyFace();
noTone(BUZZER_PIN);
// Shake while happy → go angry
if (moveMag > ANGRY_THRESH) {
currentState = ANGRY;
angryStartTime = now;
buzzerDone = false;
Serial.println(F("→ ANGRY"));
break;
}
// Button press again → back to neutral
if (buttonPressed) {
currentState = NEUTRAL;
lastInteraction = now;
Serial.println(F("→ NEUTRAL"));
}
break;
// ---- ANGRY ----
case ANGRY:
drawAngryFace();
// Play buzzer once for 2 seconds
if (!buzzerDone) {
tone(BUZZER_PIN, BUZZER_FREQ, BUZZER_DUR);
buzzerDone = true;
}
// Go back to neutral only when shaking stops
if (moveMag <= ANGRY_THRESH) {
noTone(BUZZER_PIN);
currentState = NEUTRAL;
lastInteraction = now;
Serial.println(F("→ NEUTRAL (calm)"));
}
break;
}
// ===== 5. DEBUG =====
Serial.print(F("State: "));
Serial.print(currentState);
Serial.print(F(" moveMag: "));
Serial.print(moveMag, 2);
Serial.print(F(" T: "));
Serial.print(cachedTemp, 1);
Serial.print(F("C H: "));
Serial.print(cachedHum, 1);
Serial.println(F("%"));
delay(LOOP_DELAY);
}