#include <EEPROM.h>
// inputs
#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
// EEPROM
#define EEPROM_ADDR_PATTERN 0
// variables
byte pattern = 0;
bool hazardState = false;
unsigned long lastHazardChange = 0;
bool lastMoodState = true; // Switch uses INPUT_PULLUP, default HIGH
// Gesture
bool gestureInProgress = false;
unsigned long gestureStartTime = 0;
const unsigned long gestureMaxDuration = 1000;
byte gestureState = 0;
// === Blink Patterns ===
// 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;
}
void runHazardPattern(byte p, bool forBack = false) {
bool leftVal = LOW;
bool rightVal = LOW;
switch (p) {
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 = (millis() / 100) % 2;
rightVal = !leftVal;
// backVal = leftVal;
// numVal = rightVal;
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;
}
if (forBack) {
// Only drive BACK_OUT for certain patterns
if (forBack && (p == 1 || p == 2 || p == 3)) {
digitalWrite(BACK_OUT, leftVal);
digitalWrite(LEFT_OUT, leftVal);
digitalWrite(RIGHT_OUT, rightVal);
} else {
unsigned long t = millis() % 1000;
bool on = (t < 100) || (t >= 200 && t < 300) || (t >= 400 && t < 500);
leftVal = rightVal = on;
digitalWrite(BACK_OUT, leftVal);
bool left = !digitalRead(LEFT_IN);
bool right = !digitalRead(RIGHT_IN);
if (!hazardState && !left && !right) {
digitalWrite(LEFT_OUT, leftVal);
digitalWrite(RIGHT_OUT, leftVal);
}
}
} else {
digitalWrite(LEFT_OUT, leftVal);
digitalWrite(RIGHT_OUT, rightVal);
}
}
void savePattern(byte p) {
if (EEPROM.read(EEPROM_ADDR_PATTERN) != p) {
EEPROM.update(EEPROM_ADDR_PATTERN, p);
delay(10);
}
}
void setup() {
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);
Serial.begin(9600);
byte savedPattern = EEPROM.read(EEPROM_ADDR_PATTERN);
pattern = (savedPattern <= 9) ? savedPattern : 0;
// Show saved pattern at startup for 2 seconds
unsigned long showStart = millis();
while (millis() - showStart < 5000) {
runHazardPattern(pattern);
if (digitalRead(BACK_MOOD) == LOW) {
runHazardPattern(pattern, true);
}
}
digitalWrite(LEFT_OUT, LOW);
digitalWrite(RIGHT_OUT, LOW);
digitalWrite(BACK_OUT, LOW);
digitalWrite(NUM_OUT, HIGH);
}
void loop() {
bool left = !digitalRead(LEFT_IN);
bool right = !digitalRead(RIGHT_IN);
bool back = !digitalRead(BACK_IN);
bool backMood = !digitalRead(BACK_MOOD);
bool hazardBtn = !digitalRead(HAZARD_BTN); // Not used in this logic
// Back Light Logic
if (back) {
if (backMood) {
// Pattern mode ON --> e.g., blinking
runHazardPattern(pattern, true);
} else {
// Normal mode --> solid ON
digitalWrite(BACK_OUT, HIGH);
// Break with hazer
bool leftVal = LOW;
unsigned long t = millis() % 1000;
bool on = (t < 100) || (t >= 200 && t < 300) || (t >= 400 && t < 500);
leftVal = on;
if (!hazardState && !left && !right) {
digitalWrite(LEFT_OUT, leftVal);
digitalWrite(RIGHT_OUT, leftVal);
}
}
} else {
// Back NOT pressed --> ensure it's OFF
digitalWrite(BACK_OUT, LOW);
}
if (digitalRead(HAZARD_MOOD_SW) == LOW) {
detectPatternSwitch(hazardBtn);
} else {
handleGesture(left, right);
}
if (hazardState && !left && !right) {
runHazardPattern(pattern);
} else if (left) {
digitalWrite(LEFT_OUT, blink500());
digitalWrite(RIGHT_OUT, LOW);
} else if (right) {
digitalWrite(RIGHT_OUT, blink500());
digitalWrite(LEFT_OUT, LOW);
} else {
digitalWrite(LEFT_OUT, LOW);
digitalWrite(RIGHT_OUT, LOW);
}
}
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;
if (currentState != lastReadState) {
lastDebounceTime = millis(); // reset debounce timer
lastReadState = currentState;
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (currentState != lastStableState) {
lastStableState = currentState;
if (currentState == LOW) {
// OFF: Save the time to check if next ON is fast
lastHazardOffTime = millis();
hazardState = false;
Serial.println("Hazard OFF");
} else {
// ON: Check if it was a quick OFF-ON
if (millis() - lastHazardOffTime < 800) {
pattern = (pattern + 1) % 70;
savePattern(pattern);
Serial.print("Pattern changed to: ");
Serial.println(pattern);
}
hazardState = true;
Serial.println("Hazard ON");
}
}
}
}
void handleGesture(bool left, bool right) {
static byte gestureStep = 0;
static unsigned long gestureStartTime = 0;
const unsigned long gestureTimeout = 1000; // 1 second
unsigned long now = millis();
switch (gestureStep) {
// Start with LEFT ON
case 0:
if (left && !right) {
gestureStep = 1;
gestureStartTime = now;
} else if (right && !left) {
gestureStep = 4;
gestureStartTime = now;
}
break;
// LEFT OFF
case 1:
if (!left && !right) {
gestureStep = 2;
}
break;
// RIGHT ON
case 2:
if (right && !left) {
gestureStep = 3;
}
break;
// RIGHT OFF → Toggle Hazard
case 3:
if (!left && !right) {
hazardState = !hazardState;
Serial.println("Hazard toggled via gesture");
gestureStep = 0;
}
break;
// Start with RIGHT ON
case 4:
if (!right && !left) {
gestureStep = 5;
}
break;
// LEFT ON
case 5:
if (left && !right) {
gestureStep = 6;
}
break;
// LEFT OFF → Change Pattern
case 6:
if (!left && !right) {
pattern = (pattern + 1) % 70;
savePattern(pattern);
Serial.print("Pattern changed via gesture to: ");
Serial.println(pattern);
gestureStep = 0;
}
break;
}
// Timeout reset
if (gestureStep != 0 && (now - gestureStartTime > gestureTimeout)) {
gestureStep = 0;
}
}