// ============================================================
// Anti-Sleep Driver Alert System — Wokwi Edition v4.0
// Features:
// - MPU6050 head tilt/nod detection (real I2C)
// - PIR motion fallback detection
// - Fatigue score engine (0-100)
// - Escalating alarm (3 levels: warn, alert, critical)
// - Flashing LED (not just solid ON)
// - Vibration motor on pin 6
// - Eye blink input via button on pin 5 (simulates IR blink sensor)
// - Speed simulation via potentiometer on A0
// - Alarm count tracking
// - Non-blocking throughout — zero delay() calls
// ============================================================
#include <Wire.h>
// ── Pin Definitions ─────────────────────────────────────────
#define PIR_PIN 2 // PIR motion sensor
#define BUZZER_PIN 8 // Passive buzzer
#define LED_PIN 3 // Alert LED (PWM)
#define RESET_BTN 4 // Manual alarm reset
#define BLINK_BTN 5 // Simulates eye blink sensor (press = blink detected)
#define VIBRO_PIN 6 // Vibration motor
#define SPEED_PIN A0 // Potentiometer = simulated vehicle speed
// ── MPU6050 I2C ─────────────────────────────────────────────
#define MPU_ADDR 0x68
#define MPU_PWR 0x6B
#define MPU_ACCEL 0x3B
// ── Timing Constants ────────────────────────────────────────
const unsigned long BOOT_MS = 3000UL;
const unsigned long INACTIVITY_MS = 5000UL; // Alarm if no motion 5s
const unsigned long ALARM_AUTO_OFF_MS = 12000UL; // Auto-silence after 12s
const unsigned long PIR_DEBOUNCE_MS = 100UL;
const unsigned long BLINK_DEBOUNCE_MS = 300UL;
const unsigned long FATIGUE_DECAY_MS = 3000UL; // Fatigue drops after 3s of calm
const unsigned long LED_FLASH_FAST_MS = 150UL; // Critical alarm flash speed
const unsigned long LED_FLASH_SLOW_MS = 500UL; // Warning flash speed
const unsigned long LOG_MS = 1000UL;
const unsigned long MPU_SAMPLE_MS = 200UL; // Read MPU every 200ms
const unsigned long VIBRO_PULSE_MS = 400UL; // Vibration pulse length
// ── Fatigue Score Weights ────────────────────────────────────
// Score 0-100. Higher = more fatigued. Alarm fires at threshold.
const int SCORE_ALARM_WARN = 30; // Level 1: slow beep
const int SCORE_ALARM_ALERT = 60; // Level 2: fast beep + LED flash
const int SCORE_ALARM_CRITICAL = 85; // Level 3: max buzz + vibration
// ── Tilt Thresholds (MPU6050 raw accel units) ────────────────
// At rest flat: AX~0, AY~0, AZ~16384 (1g)
// Head nod forward: AX goes significantly positive
// Head tilt sideways: AY changes
const int TILT_THRESHOLD = 5000; // Raw units, tune in Wokwi
const int NOD_THRESHOLD = 6000; // Forward nod (stronger signal)
// ── State Variables ─────────────────────────────────────────
unsigned long lastActivityTime = 0;
unsigned long alarmStartTime = 0;
unsigned long lastPirHighTime = 0;
unsigned long lastBlinkTime = 0;
unsigned long lastResetBtnTime = 0;
unsigned long lastBlinkTime2 = 0; // For LED flash timing
unsigned long lastLogTime = 0;
unsigned long lastMpuSample = 0;
unsigned long lastFatigueDecay = 0;
unsigned long vibroStartTime = 0;
bool systemReady = false;
bool alarmActive = false;
bool pirLastState = false;
bool ledFlashState = false;
bool vibroActive = false;
int fatigueScore = 0; // 0-100
int alarmLevel = 0; // 0=none, 1=warn, 2=alert, 3=critical
int alarmCount = 0; // Total alarms fired this session
int blinkCount = 0; // Blinks detected this session
int nodCount = 0; // Head nods detected this session
// MPU6050 last readings
int16_t ax = 0, ay = 0, az = 0;
// ── Forward Declarations ────────────────────────────────────
void initMPU();
void readMPU(unsigned long now);
void handleBoot(unsigned long now);
void handleMonitoring(unsigned long now);
void handleFatigueDecay(unsigned long now);
void handleLedFlash(unsigned long now);
void handleVibro(unsigned long now);
void triggerAlarm(unsigned long now, int level);
void silenceAlarm(unsigned long now, const char* reason);
void printStatus(unsigned long now);
int readSimulatedSpeed();
// ============================================================
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(RESET_BTN, INPUT_PULLUP);
pinMode(BLINK_BTN, INPUT_PULLUP);
pinMode(VIBRO_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(VIBRO_PIN, LOW);
noTone(BUZZER_PIN);
Wire.begin();
initMPU();
Serial.begin(9600);
Serial.println(F("============================================"));
Serial.println(F(" Driver Drowsiness Alert System v4.0 "));
Serial.println(F("============================================"));
Serial.println(F("[BOOT] MPU6050 initialized."));
Serial.println(F("[BOOT] Sensors warming up..."));
Serial.println(F("[INFO] BLINK_BTN (pin5) = simulate eye blink"));
Serial.println(F("[INFO] SPEED_POT (A0) = simulate vehicle speed"));
Serial.println(F("[INFO] RESET_BTN (pin4) = silence alarm"));
Serial.println(F("--------------------------------------------"));
}
// ============================================================
void loop() {
unsigned long now = millis();
if (!systemReady) {
handleBoot(now);
return;
}
handleMonitoring(now);
handleFatigueDecay(now);
handleLedFlash(now);
handleVibro(now);
printStatus(now);
}
// ============================================================
void initMPU() {
Wire.beginTransmission(MPU_ADDR);
Wire.write(MPU_PWR);
Wire.write(0x00); // Wake up MPU6050
Wire.endTransmission(true);
}
// ============================================================
void readMPU(unsigned long now) {
if (now - lastMpuSample < MPU_SAMPLE_MS) return;
lastMpuSample = now;
Wire.beginTransmission(MPU_ADDR);
Wire.write(MPU_ACCEL);
Wire.endTransmission(false);
Wire.requestFrom(MPU_ADDR, 6, true);
ax = (Wire.read() << 8) | Wire.read();
ay = (Wire.read() << 8) | Wire.read();
az = (Wire.read() << 8) | Wire.read();
// Detect forward head nod (AX spike)
if (abs(ax) > NOD_THRESHOLD) {
nodCount++;
fatigueScore = min(100, fatigueScore + 15);
lastActivityTime = now; // nod counts as "activity" but adds fatigue
Serial.print(F("[NOD] Head nod detected! Fatigue+15 Score:"));
Serial.println(fatigueScore);
}
// Detect dangerous sideways tilt (AY spike — microsleep head drop)
if (abs(ay) > TILT_THRESHOLD) {
fatigueScore = min(100, fatigueScore + 20);
Serial.print(F("[TILT] Head tilt detected! Fatigue+20 Score:"));
Serial.println(fatigueScore);
}
}
// ============================================================
void handleBoot(unsigned long now) {
static unsigned long lastBlink = 0;
static bool blinkState = false;
static unsigned long lastPrint = 0;
if (now - lastBlink >= 400UL) {
lastBlink = now;
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState ? HIGH : LOW);
}
if (now - lastPrint >= 1000UL) {
lastPrint = now;
if (now < BOOT_MS) {
Serial.print(F("[BOOT] Starting in "));
Serial.print(((long)BOOT_MS - (long)now) / 1000L);
Serial.println(F("s..."));
}
}
if (now >= BOOT_MS) {
digitalWrite(LED_PIN, LOW);
systemReady = true;
lastActivityTime = now;
Serial.println(F("============================================"));
Serial.println(F("[ACTIVE] System armed. Monitoring driver."));
Serial.println(F("[ACTIVE] Fatigue score: 0/100"));
Serial.println(F("============================================"));
}
}
// ============================================================
void handleMonitoring(unsigned long now) {
// ── Read MPU6050 ─────────────────────────────────────────
readMPU(now);
// ── Reset Button (highest priority) ──────────────────────
if (digitalRead(RESET_BTN) == LOW) {
if (now - lastResetBtnTime >= 250UL) {
lastResetBtnTime = now;
lastActivityTime = now;
fatigueScore = max(0, fatigueScore - 20);
if (alarmActive) {
silenceAlarm(now, "manual reset");
} else {
Serial.print(F("[BTN] Reset pressed. Fatigue-20 Score:"));
Serial.println(fatigueScore);
}
}
return;
}
// ── Eye Blink Button (pin 5) ──────────────────────────────
// In real hardware: replace with IR reflectance sensor on eyelid
// In Wokwi: press button to simulate a blink event
if (digitalRead(BLINK_BTN) == LOW) {
if (now - lastBlinkTime >= BLINK_DEBOUNCE_MS) {
lastBlinkTime = now;
lastActivityTime = now;
blinkCount++;
// Frequent blinks = fatigue signal
fatigueScore = min(100, fatigueScore + 5);
Serial.print(F("[BLINK] Eye blink #"));
Serial.print(blinkCount);
Serial.print(F(" Fatigue+5 Score:"));
Serial.println(fatigueScore);
}
return;
}
// ── PIR Motion (body movement = driver is awake) ──────────
bool pirRaw = digitalRead(PIR_PIN);
bool motionConfirmed = false;
if (pirRaw == HIGH) {
if (!pirLastState) {
lastPirHighTime = now;
pirLastState = true;
} else if ((now - lastPirHighTime) >= PIR_DEBOUNCE_MS) {
motionConfirmed = true;
}
} else {
pirLastState = false;
lastPirHighTime = 0;
}
if (motionConfirmed) {
lastActivityTime = now;
fatigueScore = max(0, fatigueScore - 5); // motion reduces fatigue
if (alarmActive) {
silenceAlarm(now, "motion detected");
}
return;
}
// ── Speed-based inactivity scaling ───────────────────────
// Higher speed = shorter tolerance for inactivity
// Potentiometer 0-1023 maps to 0-120 km/h
int speed = readSimulatedSpeed();
unsigned long dynamicInactivityMs = INACTIVITY_MS;
if (speed > 80) {
dynamicInactivityMs = 3000UL; // 3s at highway speed
} else if (speed > 40) {
dynamicInactivityMs = 4000UL; // 4s at moderate speed
} else if (speed < 10) {
dynamicInactivityMs = 8000UL; // 8s at near-stop (traffic)
}
// ── Inactivity + Fatigue Score combined ──────────────────
unsigned long idleMs = now - lastActivityTime;
// Inactivity adds to fatigue score progressively
if (idleMs > 2000UL && !alarmActive) {
static unsigned long lastIdleScore = 0;
if (now - lastIdleScore >= 1000UL) {
lastIdleScore = now;
fatigueScore = min(100, fatigueScore + 3);
}
}
// ── Determine alarm level from fatigue score ──────────────
if (!alarmActive) {
if (fatigueScore >= SCORE_ALARM_CRITICAL || idleMs >= dynamicInactivityMs * 2) {
triggerAlarm(now, 3);
} else if (fatigueScore >= SCORE_ALARM_ALERT || idleMs >= dynamicInactivityMs) {
triggerAlarm(now, 2);
} else if (fatigueScore >= SCORE_ALARM_WARN) {
triggerAlarm(now, 1);
}
}
// ── Auto-silence ─────────────────────────────────────────
if (alarmActive && (now - alarmStartTime) >= ALARM_AUTO_OFF_MS) {
silenceAlarm(now, "auto timeout");
lastActivityTime = now;
Serial.println(F("[WARN] Auto-silenced. Monitoring continues."));
}
}
// ============================================================
// Fatigue score gradually decays when driver is calm
void handleFatigueDecay(unsigned long now) {
if (alarmActive) return;
if (now - lastFatigueDecay >= FATIGUE_DECAY_MS) {
lastFatigueDecay = now;
if (fatigueScore > 0) {
fatigueScore = max(0, fatigueScore - 1);
}
}
}
// ============================================================
// Non-blocking LED flash — speed depends on alarm level
void handleLedFlash(unsigned long now) {
if (!alarmActive) {
digitalWrite(LED_PIN, LOW);
return;
}
unsigned long flashSpeed = (alarmLevel == 3) ? LED_FLASH_FAST_MS : LED_FLASH_SLOW_MS;
if (now - lastBlinkTime2 >= flashSpeed) {
lastBlinkTime2 = now;
ledFlashState = !ledFlashState;
digitalWrite(LED_PIN, ledFlashState ? HIGH : LOW);
}
}
// ============================================================
// Vibration motor pulses only on critical alarm level
void handleVibro(unsigned long now) {
if (alarmLevel < 3 || !alarmActive) {
if (vibroActive) {
digitalWrite(VIBRO_PIN, LOW);
vibroActive = false;
}
return;
}
// Pulse vibration motor every 800ms
static unsigned long lastVibroPulse = 0;
if (now - lastVibroPulse >= 800UL) {
lastVibroPulse = now;
vibroActive = true;
vibroStartTime = now;
digitalWrite(VIBRO_PIN, HIGH);
}
// Turn off after pulse duration
if (vibroActive && (now - vibroStartTime) >= VIBRO_PULSE_MS) {
vibroActive = false;
digitalWrite(VIBRO_PIN, LOW);
}
}
// ============================================================
void triggerAlarm(unsigned long now, int level) {
if (alarmActive && level <= alarmLevel) return; // don't downgrade
alarmActive = true;
alarmLevel = level;
alarmStartTime = now;
alarmCount++;
// Escalating buzzer frequency and pattern
if (level == 1) {
tone(BUZZER_PIN, 800); // Low warning tone
} else if (level == 2) {
tone(BUZZER_PIN, 1200); // Alert tone
} else {
tone(BUZZER_PIN, 1800); // Critical — highest frequency
}
Serial.print(F("[ALARM-L"));
Serial.print(level);
Serial.print(F("] Fatigue:"));
Serial.print(fatigueScore);
Serial.print(F("/100 TotalAlarms:"));
Serial.print(alarmCount);
Serial.print(F(" Speed:"));
Serial.print(readSimulatedSpeed());
Serial.println(F("km/h"));
}
// ============================================================
void silenceAlarm(unsigned long now, const char* reason) {
alarmActive = false;
alarmLevel = 0;
ledFlashState = false;
digitalWrite(LED_PIN, LOW);
digitalWrite(VIBRO_PIN, LOW);
noTone(BUZZER_PIN);
digitalWrite(BUZZER_PIN, LOW);
lastActivityTime = now;
Serial.print(F("[RESET] Cleared — "));
Serial.print(reason);
Serial.print(F(" FatigueScore:"));
Serial.println(fatigueScore);
}
// ============================================================
// Heartbeat log every second
void printStatus(unsigned long now) {
if (now - lastLogTime < LOG_MS) return;
lastLogTime = now;
if (alarmActive) return; // alarm messages already printed
int speed = readSimulatedSpeed();
long idleSec = (now - lastActivityTime) / 1000L;
Serial.print(F("[OK] Score:"));
Serial.print(fatigueScore);
Serial.print(F("/100 Idle:"));
Serial.print(idleSec);
Serial.print(F("s Speed:"));
Serial.print(speed);
Serial.print(F("km/h Blinks:"));
Serial.print(blinkCount);
Serial.print(F(" Nods:"));
Serial.println(nodCount);
}
// ============================================================
// Map potentiometer (0-1023) to speed (0-120 km/h)
int readSimulatedSpeed() {
int raw = analogRead(SPEED_PIN);
return map(raw, 0, 1023, 0, 120);
}