// ============================================================
// PERSONALITY BOT — Optimised & Bug-Fixed
// Sensors : MPU6050 (I2C) + DHT22 (digital)
// Display : SSD1306 128x64 OLED (I2C)
// Extras : Passive buzzer, momentary button
// ============================================================
#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 (gravity-compensated) =====
// At rest, raw magnitude ≈ 9.8 m/s² → we subtract baseline
// so 0 = still, positive = actual movement.
#define GRAVITY_BASELINE 9.8f
#define HAPPY_THRESH 3.0f // gentle shake (raw ~12.8)
#define ANGRY_THRESH 8.0f // hard shake (raw ~17.8)
// ===== TIMING =====
#define LOOP_DELAY 100 // ms between sensor polls
#define BUTTON_DEBOUNCE 300 // ms button lockout
#define BUZZER_HAPPY_FREQ 1000
#define BUZZER_HAPPY_DUR 200
#define BUZZER_ANGRY_FREQ 200
#define BUZZER_ANGRY_DUR 500
// ===== OBJECTS =====
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_MPU6050 mpu;
DHT dht(DHTPIN, DHTTYPE);
// ===== STATE MACHINE =====
enum Mood { CALM, HAPPY, ANGRY };
Mood currentMood = CALM;
// ===== CACHED SENSOR VALUES =====
float cachedTemp = 0.0f;
float cachedHum = 0.0f;
unsigned long lastDHTRead = 0;
#define DHT_INTERVAL 2000 // DHT22 max sample rate ~0.5 Hz
// ============================================================
// FACE DRAWING HELPERS
// ============================================================
// Shared face outline so all moods look consistent
void drawFaceOutline() {
display.drawCircle(64, 30, 28, WHITE); // head circle
}
// Eyes: normal round eyes
void drawNormalEyes() {
display.fillCircle(50, 22, 4, WHITE); // left eye
display.fillCircle(78, 22, 4, WHITE); // right eye
}
// Eyes: angry — small and narrowed (filled triangles as brows above dots)
void drawAngryEyes() {
// Pupils (smaller to look tense)
display.fillCircle(50, 24, 3, WHITE);
display.fillCircle(78, 24, 3, WHITE);
// Eyebrows angled inward toward nose bridge (V-shape)
// Left brow: high outside, low inside → right-leaning
display.drawLine(40, 14, 54, 19, WHITE);
// Right brow: low inside, high outside → left-leaning
display.drawLine(74, 19, 88, 14, WHITE);
}
// Mouth: flat / neutral line
void drawNeutralMouth() {
display.drawLine(52, 44, 76, 44, WHITE);
}
// Mouth: smile — two angled lines forming a V-arc
void drawSmileMouth() {
display.drawLine(50, 42, 64, 50, WHITE);
display.drawLine(64, 50, 78, 42, WHITE);
}
// Mouth: frown — inverted smile
void drawFrownMouth() {
display.drawLine(52, 48, 64, 41, WHITE);
display.drawLine(64, 41, 76, 48, WHITE);
}
// ============================================================
// FULL FACE RENDERERS
// ============================================================
void drawCalmFace(float temp, float hum) {
display.clearDisplay();
drawFaceOutline();
drawNormalEyes();
drawNeutralMouth();
// Temp + humidity bar along the bottom
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(2, 56);
display.print(temp, 1);
display.print((char)247); // degree symbol
display.print("C ");
display.print(hum, 1);
display.print("%");
display.display();
}
void drawHappyFace() {
display.clearDisplay();
drawFaceOutline();
// Bigger, excited eyes
display.fillCircle(50, 22, 5, WHITE);
display.fillCircle(78, 22, 5, WHITE);
drawSmileMouth();
// Little cheek blush dots
display.fillCircle(38, 34, 2, WHITE);
display.fillCircle(90, 34, 2, WHITE);
display.display();
}
void drawAngryFace() {
display.clearDisplay();
drawFaceOutline();
drawAngryEyes();
drawFrownMouth();
// Warning text below face
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(6, 57);
display.print("STOP SHAKING ME!");
display.display();
}
// ============================================================
// SETUP
// ============================================================
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
Wire.begin();
// --- OLED ---
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("OLED init failed — check wiring/address"));
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 (try 0x68 then 0x69) ---
if (!mpu.begin(0x68)) {
Serial.println(F("MPU6050 not on 0x68, trying 0x69..."));
if (!mpu.begin(0x69)) {
Serial.println(F("MPU6050 init failed — check wiring"));
while (true);
}
}
// Reduce accel range for better shake sensitivity
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
// --- DHT22 ---
dht.begin();
// Prime DHT cache
delay(2000);
cachedTemp = dht.readTemperature();
cachedHum = dht.readHumidity();
if (isnan(cachedTemp)) cachedTemp = 0.0f;
if (isnan(cachedHum)) cachedHum = 0.0f;
lastDHTRead = millis();
Serial.println(F("Boot complete."));
}
// ============================================================
// LOOP
// ============================================================
void loop() {
unsigned long now = millis();
// ===== 1. READ MPU6050 =====
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
float rawMag = sqrt(
a.acceleration.x * a.acceleration.x +
a.acceleration.y * a.acceleration.y +
a.acceleration.z * a.acceleration.z
);
// Subtract gravity so 0 = truly at rest
float moveMag = rawMag - GRAVITY_BASELINE;
// ===== 2. READ DHT22 (non-blocking, rate-limited) =====
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 — triggers HAPPY (with debounce) =====
static unsigned long lastButtonTime = 0;
if (digitalRead(BUTTON_PIN) == LOW && (now - lastButtonTime > BUTTON_DEBOUNCE)) {
lastButtonTime = now;
currentMood = HAPPY;
}
// ===== 4. SHAKE DETECTION (only update if not already reacting) =====
// Button press takes priority over motion detection
if (currentMood == CALM) {
if (moveMag > ANGRY_THRESH) {
currentMood = ANGRY;
} else if (moveMag > HAPPY_THRESH) {
currentMood = HAPPY;
}
}
// ===== 5. REACT TO MOOD =====
switch (currentMood) {
case CALM:
drawCalmFace(cachedTemp, cachedHum);
noTone(BUZZER_PIN);
break;
case HAPPY:
drawHappyFace();
tone(BUZZER_PIN, BUZZER_HAPPY_FREQ, BUZZER_HAPPY_DUR);
delay(BUZZER_HAPPY_DUR);
currentMood = CALM;
break;
case ANGRY:
drawAngryFace();
tone(BUZZER_PIN, BUZZER_ANGRY_FREQ, BUZZER_ANGRY_DUR);
delay(BUZZER_ANGRY_DUR);
currentMood = CALM;
break;
}
// ===== 6. DEBUG OUTPUT =====
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);
}