#include <EEPROM.h>
// --- Pin setup ---
#define LEFT_IN 2
#define RIGHT_IN 3
#define BACK_IN 4
#define HAZARD_BTN 5
#define HAZARD_MOOD_SW 6
#define BACK_MOOD 7
// outputs
#define LEFT_OUT 8
#define RIGHT_OUT 9
#define BACK_OUT 10
#define NUM_OUT 11
// --- Variables ---
unsigned long lastLeftTime = 0;
unsigned long lastRightTime = 0;
unsigned long leftInterval = 0;
unsigned long rightInterval = 0;
unsigned long lastSignalTime = 0;
unsigned long lastHazardPress = 0;
unsigned long comboPressStart = 0;
bool hazardState = false;
bool hazardBlinkState = false;
bool leftActive = false;
bool rightActive = false;
bool hazardPaused = false;
bool lastHazardSw = HIGH;
bool comboPressed = false;
// --- Logs Visual ---
bool logs = true;
const unsigned long signalTimeout = 1000; // 1 sec timeout
const unsigned long doubleClickGap = 400; // double press within 400 ms
const unsigned long longPressTime = 1200; // hold time for prev pattern
const unsigned long gestureWindow = 500; // time gap allowed between left+right press
const unsigned long gestureHoldMin = 50; // both must stay LOW at least this long
// --- Pattern management ---
byte pattern = 0; // Current pattern index
const int totalPatterns = 10;
// EEPROM address
#define EEPROM_ADDR_PATTERN 0
// --- Function prototypes ---
void runHazardPattern(byte pat, bool forBack = false);
void changePattern(int dir);
bool blinkOutput();
// --- Main Setup ---
void setup() {
Serial.begin(9600);
pinMode(LEFT_IN, INPUT_PULLUP);
pinMode(RIGHT_IN, INPUT_PULLUP);
pinMode(BACK_IN, INPUT_PULLUP);
pinMode(BACK_MOOD, INPUT_PULLUP);
pinMode(HAZARD_BTN, INPUT_PULLUP);
pinMode(HAZARD_MOOD_SW, INPUT_PULLUP);
pinMode(LEFT_OUT, OUTPUT);
pinMode(RIGHT_OUT, OUTPUT);
pinMode(BACK_OUT, OUTPUT);
pinMode(NUM_OUT, OUTPUT);
// Load saved pattern
pattern = EEPROM.read(EEPROM_ADDR_PATTERN);
if (pattern >= totalPatterns) pattern = 0;
unsigned long showStart = millis();
while (millis() - showStart < 3000) {
runHazardPattern(pattern);
if (digitalRead(BACK_MOOD) == LOW) {
runHazardPattern(pattern, true);
}
}
digitalWrite(NUM_OUT, HIGH);
Serial.println("Hazard + Pulse detection started...");
Serial.print("Loaded pattern: ");
Serial.println(pattern);
}
// --- Main Loop ---
void loop() {
static bool lastLeftState = HIGH;
static bool lastRightState = HIGH;
bool leftState = digitalRead(LEFT_IN);
bool rightState = digitalRead(RIGHT_IN);
bool hazardSw = !digitalRead(HAZARD_BTN);
bool moodMode = digitalRead(HAZARD_MOOD_SW); // HIGH=Normal, LOW=Gesture
bool back = !digitalRead(BACK_IN);
bool backMood = !digitalRead(BACK_MOOD);
unsigned long now = millis();
// =========================
// NORMAL MODE (Switch Based)
// =========================
if (moodMode) {
detectPatternSwitch(hazardSw);
}
// =========================
// GESTURE MODE (Left+Right Combo) - Robust for real switches
// =========================
else {
handleGesture(!leftState, !rightState);
}
// --- Detect LEFT pulse ---
if (lastLeftState == HIGH && leftState == LOW) {
unsigned long diff = now - lastLeftTime;
if (diff > 100) {
leftInterval = diff;
leftActive = true;
rightActive = false;
lastSignalTime = now;
if (logs) {
Serial.print("LEFT pulse detected! Interval: ");
Serial.println(leftInterval);
}
if (hazardState) {
hazardPaused = true;
hazardState = false;
if (logs)
Serial.println("Turn signal detected → Hazard paused");
}
}
lastLeftTime = now;
}
// --- Detect RIGHT pulse ---
if (lastRightState == HIGH && rightState == LOW) {
unsigned long diff = now - lastRightTime;
if (diff > 100) {
rightInterval = diff;
rightActive = true;
leftActive = false;
lastSignalTime = now;
if (logs) {
Serial.print("RIGHT pulse detected! Interval: ");
Serial.println(rightInterval);
}
if (hazardState) {
hazardPaused = true;
hazardState = false;
if (logs)
Serial.println("Turn signal detected → Hazard paused");
}
}
lastRightTime = now;
}
lastLeftState = leftState;
lastRightState = rightState;
// --- Timeout ---
if ((leftActive || rightActive) && (now - lastSignalTime > signalTimeout)) {
leftActive = false;
rightActive = false;
digitalWrite(LEFT_OUT, LOW);
digitalWrite(RIGHT_OUT, LOW);
if (logs)
Serial.println("No pulses detected - stopping turn blink");
if (hazardPaused) {
hazardPaused = true;
hazardState = true;
hazardBlinkState = false; // Reset blink phase
if (logs)
Serial.println("Resuming hazard blinking...");
}
}
// --- Hazard patterns ---
if (hazardState && !leftActive && !rightActive) {
runHazardPattern(pattern);
}
// --- Turn signal blinking ---
if (!hazardState) {
if (leftActive) {
digitalWrite(LEFT_OUT, blinkOutput());
digitalWrite(RIGHT_OUT, LOW);
} else if (rightActive) {
digitalWrite(RIGHT_OUT, blinkOutput());
digitalWrite(LEFT_OUT, LOW);
} else if (!hazardPaused) {
digitalWrite(LEFT_OUT, LOW);
digitalWrite(RIGHT_OUT, LOW);
}
}
// --- Back Light Logic ---
if (back) {
if (backMood) {
runHazardPattern(pattern, true);
} else {
digitalWrite(BACK_OUT, HIGH);
bool leftVal = LOW;
unsigned long t = millis() % 1000;
bool on = (t < 100) || (t >= 200 && t < 300) || (t >= 400 && t < 500);
leftVal = on;
if (!hazardState && !leftActive && !rightActive) {
digitalWrite(LEFT_OUT, leftVal);
digitalWrite(RIGHT_OUT, leftVal);
}
}
} else {
digitalWrite(BACK_OUT, LOW);
}
}
// --- Handel Switch Gesture Logic ---
void handleGesture(bool left, bool right) {
static byte gestureStep = 0;
static unsigned long gestureStartTime = 0;
const unsigned long gestureTimeout = 1500; // 1.5 seconds window
unsigned long now = millis();
switch (gestureStep) {
// --- WAITING FOR FIRST INPUT ---
case 0:
if (left && !right) { // Start LEFT→RIGHT gesture (toggle or next)
gestureStep = 1;
gestureStartTime = now;
} else if (right && !left) { // Start RIGHT→LEFT gesture (previous)
gestureStep = 4;
gestureStartTime = now;
}
break;
// --- LEFT→RIGHT PATH (Toggle / Next) ---
case 1:
if (!left && !right) { // LEFT released
gestureStep = 2;
}
break;
case 2:
if (right && !left) { // RIGHT pressed after LEFT
gestureStep = 3;
}
break;
case 3:
if (!left && !right) { // RIGHT released → Toggle Hazard
if (logs)
Serial.println("Hazard toggled via gesture");
if (!hazardPaused) {
hazardState = !hazardState;
if (logs)
{ Serial.print("Hazard State: ");
Serial.println(hazardState ? "ON" : "OFF");
}
} else {
hazardPaused = false;
hazardState = false;
if (logs)
Serial.println("Hazard resumed OFF");
}
gestureStep = 0;
} else if (right && !left && (now - gestureStartTime > 800)) {
// Hold RIGHT after LEFT = Next Pattern
if (hazardPaused) {
pattern = (pattern + 1) % totalPatterns;
savePattern(pattern);
hazardBlinkState = false;
if (logs) {
Serial.print("⏩ Next Pattern → ");
Serial.println(pattern);
}
}
gestureStep = 0;
}
break;
// --- RIGHT→LEFT PATH (Previous Pattern) ---
case 4:
if (!right && !left) { // RIGHT released
gestureStep = 5;
}
break;
case 5:
if (left && !right) { // LEFT pressed after RIGHT
gestureStep = 6;
}
break;
case 6:
if (!left && !right) { // LEFT released → Previous Pattern
if (hazardPaused) {
pattern = (pattern == 0) ? totalPatterns - 1 : pattern - 1;
savePattern(pattern);
hazardBlinkState = false;
if (logs) {
Serial.print("⏪ Previous Pattern → ");
Serial.println(pattern);
}
}
gestureStep = 0;
}
break;
}
// Timeout → reset if gesture takes too long
if (gestureStep != 0 && (now - gestureStartTime > gestureTimeout)) {
gestureStep = 0;
}
}
// --- Hazerd Switch Control Logic ---
void detectPatternSwitch(bool currentState) {
static bool lastStableState = false;
static bool lastReadState = false;
static unsigned long lastDebounceTime = 0;
static const unsigned long debounceDelay = 50;
static unsigned long lastHazardOffTime = 0;
static unsigned long lastToggleTime = 0;
static byte quickToggleCount = 0; // counts fast ON/OFF toggles
unsigned long now = millis();
// --- Debounce handling ---
if (currentState != lastReadState) {
lastDebounceTime = now;
lastReadState = currentState;
}
if ((now - lastDebounceTime) > debounceDelay) {
if (currentState != lastStableState) {
lastStableState = currentState;
// --- Switch Turned OFF ---
if (currentState == LOW) {
lastHazardOffTime = now;
hazardState = false;
if (logs) Serial.println("Hazard OFF");
}
// --- Switch Turned ON ---
else {
// Check for quick ON/OFF toggles
if (now - lastToggleTime < 600) {
quickToggleCount++;
} else {
quickToggleCount = 1; // reset if too slow
}
lastToggleTime = now;
// --- Triple quick ON/OFF → PREVIOUS pattern ---
if (quickToggleCount >= 3) {
quickToggleCount = 0; // reset counter
pattern = (pattern == 0) ? totalPatterns - 1 : pattern - 1;
savePattern(pattern);
if (logs) {
Serial.print("⏪ Previous pattern (3x toggle) → ");
Serial.println(pattern);
}
}
// --- Single quick OFF-ON → NEXT pattern ---
else if (now - lastHazardOffTime < 800) {
pattern = (pattern + 1) % totalPatterns;
savePattern(pattern);
if (logs) {
Serial.print("⏩ Next pattern (quick toggle) → ");
Serial.println(pattern);
}
}
hazardState = true;
if (logs) Serial.println("Hazard ON");
}
}
}
}
// --- Blink helper ---
bool blinkOutput() {
static unsigned long lastBlink = 0;
static bool state = false;
unsigned long now = millis();
if (now - lastBlink > 250) {
lastBlink = now;
state = !state;
}
return state;
}
// Blink ON for 500ms, OFF for 500ms
bool blink500() {
return (millis() / 500) % 2;
}
// Very fast strobe-like blink: ON for 50ms every 200ms
bool blinkStrobe() {
return (millis() % 200 < 50);
}
// Double flash: ON-100ms, OFF-100ms, ON-100ms, OFF-1200ms
bool blinkDouble() {
unsigned long t = millis() % 1500;
return (t < 100 || (t > 200 && t < 300));
}
// Alternate blink based on interval (e.g., 300ms)
bool blinkAlternate(unsigned long interval) {
return (millis() / interval) % 2 == 0;
}
// Triple blink: ON-OFF-ON-OFF-ON-OFF, rest
bool blinkTriple() {
unsigned long t = millis() % 1200;
return (t < 100 || (t >= 200 && t < 300) || (t >= 400 && t < 500));
}
// General blink pattern: 2 ON/OFF phases
bool blinkPattern(unsigned long on1, unsigned long off1, unsigned long on2, unsigned long off2) {
unsigned long total = on1 + off1 + on2 + off2;
unsigned long t = millis() % total;
return (t < on1 || (t >= on1 + off1 && t < on1 + off1 + on2));
}
// Ramp up/down brightness logic approximated to ON/OFF
bool blinkRamp(bool invert = false) {
unsigned long t = millis() % 1024;
byte level = t < 512 ? t / 8 : (1023 - t) / 8;
return invert ? level < 32 : level > 32;
}
// Bounce style blink: ON at start and end of 10-step cycle
bool blinkBounce(bool invert = false) {
unsigned long t = (millis() / 50) % 10;
bool val = (t == 0 || t == 9);
return invert ? !val : val;
}
// Wave pattern: ON for first half of duration
bool blinkWave(unsigned long duration) {
unsigned long t = millis() % duration;
return t < (duration / 2);
}
// Quick pulse blink: ON for 100ms in 1 second
bool blinkPulse(bool invert = false) {
unsigned long t = millis() % 1000;
bool val = t < 100;
return invert ? !val : val;
}
// Alternate ON/OFF based on odd/even groups
bool blinkOddEven(bool isOdd) {
unsigned long t = (millis() / 200) % 4;
return isOdd ? (t == 1 || t == 3) : (t == 0 || t == 2);
}
// ON for 1s, then blink for 100ms after 0.5s gap
bool blinkSteadyThenBlink() {
unsigned long t = millis() % 2000;
return t < 1000 || (t >= 1500 && t < 1600);
}
// Short ON, long OFF, then long ON, short OFF
bool blinkShortLong() {
unsigned long t = millis() % 800;
return (t < 100 || (t >= 400 && t < 800));
}
// Randomized blink: changes state every 100–600ms
bool blinkRandom() {
static unsigned long lastChange = 0;
static bool state = false;
if (millis() - lastChange > random(100, 600)) {
state = !state;
lastChange = millis();
}
return state;
}
// Flash pattern: 3 short flashes within 500ms
bool blinkFlasher() {
unsigned long t = millis() % 500;
return (t < 50 || (t >= 150 && t < 200) || (t >= 300 && t < 350));
}
// Heartbeat-style double blink
bool blinkHeartbeat() {
unsigned long t = millis() % 1500;
return (t < 100 || (t >= 300 && t < 400));
}
// Pure chaos: random ON/OFF based on random() threshold
bool blinkChaos() {
return random(0, 10) > 7;
}
// Inverted version of pulse
bool blinkInvertedPulse(bool invert = false) {
unsigned long t = millis() % 600;
bool val = (t >= 300);
return invert ? !val : val;
}
// --- Hazard pattern handler ---
void runHazardPattern(byte pat, bool forBack) {
static unsigned long lastTime = 0;
static bool state = false;
unsigned long now = millis();
bool leftVal = LOW;
bool rightVal = LOW;
switch (pat) {
case 0: leftVal = rightVal = blink500(); break;
case 1:
leftVal = rightVal = blinkStrobe();
// backVal = numVal = leftVal;
break;
case 2:
leftVal = rightVal = blinkDouble();
// backVal = numVal = leftVal;
break;
case 3:
leftVal = (millis() / 300) % 2;
rightVal = !leftVal;
break;
case 4: leftVal = rightVal = (millis() / 1000) % 2; break;
case 5:
leftVal = rightVal = blinkOutput();
break;
case 6:
if ((millis() / 400) % 2 == 0) {
leftVal = blinkStrobe();
rightVal = LOW;
} else {
rightVal = blinkStrobe();
leftVal = LOW;
}
// backVal = leftVal;
// numVal = rightVal;
break;
case 7:
{
unsigned long t = millis() % 600;
leftVal = (t < 300);
rightVal = (t >= 300);
// backVal = leftVal;
// numVal = rightVal;
}
break;
case 8:
{
unsigned long t = millis() % 1024;
byte level = t < 512 ? t / 8 : (1023 - t) / 8;
leftVal = rightVal = (level > 32);
}
break;
case 9:
{
unsigned long t = millis() % 1000;
bool on = (t < 100) || (t >= 200 && t < 300) || (t >= 400 && t < 500);
leftVal = rightVal = on;
}
break;
case 10:
leftVal = blinkAlternate(200);
rightVal = !leftVal;
break;
case 11:
leftVal = blinkTriple();
rightVal = leftVal;
// backVal = LOW;
// numVal = LOW;
break;
case 12:
leftVal = blinkPattern(100, 100, 300, 100);
rightVal = leftVal;
break;
case 13:
leftVal = blinkPattern(200, 200, 200, 200);
rightVal = !leftVal;
break;
case 14:
leftVal = (millis() % 700) < 350;
rightVal = (millis() % 1400) < 700;
break;
case 15:
leftVal = blinkRamp();
rightVal = blinkRamp(true);
break;
case 16:
leftVal = blinkBounce();
rightVal = blinkBounce(true);
break;
case 17:
leftVal = blinkWave(300);
rightVal = blinkWave(600);
break;
case 18:
leftVal = blinkPulse();
rightVal = blinkPulse(true);
break;
case 19:
leftVal = rightVal = blinkPattern(100, 100, 100, 400);
break;
case 20:
leftVal = blinkOddEven(true);
rightVal = blinkOddEven(false);
break;
case 21:
leftVal = (millis() % 900) < 100;
rightVal = (millis() % 900) > 800;
break;
case 22:
leftVal = (millis() / 150) % 5 == 0;
rightVal = (millis() / 150) % 5 == 2;
break;
case 23:
leftVal = blinkSteadyThenBlink();
rightVal = leftVal;
break;
case 24:
leftVal = blinkShortLong();
rightVal = !leftVal;
break;
case 25:
leftVal = blinkPattern(50, 50, 50, 150);
rightVal = leftVal;
break;
case 26:
leftVal = rightVal = (millis() % 600) < 50 || (millis() % 600) > 550;
break;
case 27:
leftVal = (millis() / 80) % 2;
rightVal = (millis() / 160) % 2;
break;
case 28:
leftVal = rightVal = blinkRandom();
break;
case 29:
leftVal = blinkPattern(100, 400, 100, 400);
rightVal = blinkPattern(400, 100, 400, 100);
break;
case 30:
leftVal = rightVal = (millis() / 250) % 4 == 0;
break;
case 31:
leftVal = rightVal = blinkFlasher();
break;
case 32:
leftVal = !((millis() / 100) % 2);
rightVal = (millis() / 300) % 2;
break;
case 33:
leftVal = rightVal = (millis() % 700 < 100);
break;
case 34:
leftVal = (millis() / 100) % 5 == 0;
rightVal = (millis() / 100) % 5 == 2;
break;
case 35:
leftVal = blinkHeartbeat();
rightVal = leftVal;
break;
case 36:
leftVal = blinkChaos();
rightVal = !leftVal;
break;
case 37:
leftVal = rightVal = blinkPattern(50, 50, 200, 200);
break;
case 38:
leftVal = blinkInvertedPulse();
rightVal = blinkInvertedPulse(true);
break;
case 39:
leftVal = (millis() / 123) % 2;
rightVal = (millis() / 321) % 2;
break;
case 40:
leftVal = (millis() % 400) < 200;
rightVal = (millis() % 400) > 100;
break;
case 41:
leftVal = (millis() / 250) % 3 == 0;
rightVal = (millis() / 250) % 3 == 1;
break;
case 42:
leftVal = rightVal = blinkPattern(50, 50, 50, 50);
break;
case 43:
leftVal = blinkPulse();
rightVal = blinkBounce();
break;
case 44:
leftVal = blinkRamp();
rightVal = blinkPulse(true);
break;
case 45:
leftVal = blinkPattern(70, 70, 70, 70);
rightVal = blinkPattern(140, 140, 140, 140);
break;
case 46:
leftVal = blinkSteadyThenBlink();
rightVal = blinkRamp(true);
break;
case 47:
leftVal = rightVal = (millis() / 75) % 2;
break;
case 48:
leftVal = (millis() / 100) % 2;
rightVal = (millis() / 200) % 2;
break;
case 49:
leftVal = rightVal = (millis() % 1000 < 600);
break;
case 50:
leftVal = rightVal = blinkPattern(20, 20, 20, 300);
break;
case 51:
leftVal = blinkWave(100);
rightVal = blinkWave(300);
break;
case 52:
leftVal = blinkHeartbeat();
rightVal = blinkHeartbeat();
break;
case 53:
leftVal = blinkPattern(250, 500, 250, 500);
rightVal = blinkPattern(500, 250, 500, 250);
break;
case 54:
leftVal = blinkChaos();
rightVal = blinkChaos();
break;
case 55:
leftVal = (millis() % 600 < 100);
rightVal = (millis() % 600 >= 300 && millis() % 600 < 400);
break;
case 56:
leftVal = blinkPattern(100, 100, 100, 100);
rightVal = blinkPattern(300, 100, 300, 100);
break;
case 57:
leftVal = (millis() / 60) % 2;
rightVal = (millis() / 90) % 2;
break;
case 58:
leftVal = rightVal = blinkFlasher();
break;
case 59:
leftVal = (millis() / 120) % 2;
rightVal = (millis() / 240) % 2;
break;
case 60:
leftVal = (millis() % 1000 < 100);
rightVal = (millis() % 1000 >= 500 && millis() % 1000 < 600);
break;
case 61:
leftVal = blinkPattern(50, 50, 150, 50);
rightVal = blinkPattern(150, 50, 50, 50);
break;
case 62:
leftVal = (millis() / 80) % 5 == 0;
rightVal = (millis() / 80) % 5 == 4;
break;
case 63:
leftVal = blinkPattern(100, 100, 50, 50);
rightVal = blinkPattern(50, 50, 100, 100);
break;
case 64:
leftVal = (millis() % 700) < 350;
rightVal = (millis() % 700) > 175;
break;
case 65:
leftVal = blinkPulse();
rightVal = blinkPulse(true);
break;
case 66:
leftVal = blinkPattern(60, 60, 60, 300);
rightVal = blinkPattern(300, 60, 60, 60);
break;
case 67:
leftVal = blinkBounce();
rightVal = blinkRamp();
break;
case 68:
leftVal = (millis() / 100) % 2;
rightVal = (millis() / 100 + 1) % 2;
break;
case 69:
leftVal = rightVal = blinkPattern(30, 30, 30, 30);
break;
}
// Write outputs to LEDs
digitalWrite(LEFT_OUT, leftVal ? HIGH : LOW);
digitalWrite(RIGHT_OUT, rightVal ? HIGH : LOW);
if (forBack) {
bool on = (millis() % 1000 < 200);
digitalWrite(BACK_OUT, on);
}
}
// --- Pattern save helper ---
void savePattern(byte p) {
static unsigned long lastSave = 0;
if (millis() - lastSave < 1000) return; // 1s gap between saves
lastSave = millis();
if (EEPROM.read(EEPROM_ADDR_PATTERN) != p) {
EEPROM.update(EEPROM_ADDR_PATTERN, p);
delay(5);
if (logs) {
Serial.print("Pattern saved to EEPROM: ");
Serial.println(p);
}
}
}
// --- Pattern change helper ---
void changePattern(int dir) {
static unsigned long lastPatternChange = 0;
if (millis() - lastPatternChange < 400) return; // debounce fast changes
lastPatternChange = millis();
if (dir > 0)
pattern = (pattern + 1) % totalPatterns;
else
pattern = (pattern == 0) ? (totalPatterns - 1) : (pattern - 1);
savePattern(pattern);
// Reset blink pattern timing
hazardBlinkState = false;
if (logs) {
Serial.print("Pattern changed to: ");
Serial.println(pattern);
}
}