Traffic Lights, Chaos, and Cybersecurity — Yeti’s IoT Simulation
Hey everyone — Yeti here, back with another bright (and blinking) adventure from the snowy streets of IoT security. ❄️
Today, we’re building and hacking a traffic light simulation using an Arduino board.
Why? Because traffic control systems — both virtual and physical — are a critical part of our smart city infrastructure, and understanding how they work helps us understand how they can be attacked, manipulated, or protected.
Tools Used
- Arduino Uno
- Jumper cables
- LED lights (Red, Yellow, Green)
- Resistors (220Ω or 330Ω)
- Breadboard
The Concept
Traffic lights might seem simple — just LEDs turning on and off — but in the real world, these are networked systems communicating between intersections, sensors, and control centers.
When these systems are connected to insecure networks, or when firmware is outdated, they can become entry points for attackers.
Our simulation demonstrates the difference between a normal, functioning light cycle and a hacked, chaotic version that mimics potential interference or malicious firmware injection.
Normal Working Traffic Light
Video:
Code:
int REDPIN = 13;
int YELLOWPIN = 12;
int GREENPIN = 11;
void delaySeconds(int seconds) {
delay(seconds * 1000);
}
void setup() {
// Configure the pins as output
pinMode(REDPIN, OUTPUT);
pinMode(YELLOWPIN, OUTPUT);
pinMode(GREENPIN, OUTPUT);
}
void loop() {
// Red light ON
digitalWrite(REDPIN, HIGH);
digitalWrite(YELLOWPIN, LOW);
digitalWrite(GREENPIN, LOW);
delaySeconds(10);
// Yellow light ON
digitalWrite(REDPIN, LOW);
digitalWrite(YELLOWPIN, HIGH);
digitalWrite(GREENPIN, LOW);
delaySeconds(10);
// Green light ON
digitalWrite(REDPIN, LOW);
digitalWrite(YELLOWPIN, LOW);
digitalWrite(GREENPIN, HIGH);
delaySeconds(10);
}
This is a normal traffic light cycle — predictable, timed, and safe. Everything runs like clockwork.
Hacked:
In the hacked version, we inject malicious firmware that introduces randomness, flickering, and erratic signal changes — simulating what might happen if an attacker gained access to the controller board.
The malicious code allows:
- Serial commands to toggle modes remotely
- Randomized timing and light patterns (creating unsafe intersections)
- Turbo mode for visual chaos
Video:
Code:
// Ultra-Fast Flicker & Movement — TURBO-capable
// LEDs only. For demo/blog visuals. No networks.
const int REDPIN = 13;
const int YELLOWPIN = 12;
const int GREENPIN = 11;
const int BUTTONPIN = 2; // toggle hacked mode (INPUT_PULLUP)
bool hackedMode = false;
bool turbo = false;
unsigned long lastButtonChange = 0;
const unsigned long debounceMs = 40;
// Controls (defaults tuned for fast visuals)
int chaosLevel = 4; // 1..5
int flickerIntensity = 8; // 1..12 (higher = more aggressive flicker)
int moveSpeedMs = 30; // base speed (ms) — lower = faster
void delaySeconds(float seconds) {
delay((unsigned long)(seconds * 1000.0));
}
void setup() {
pinMode(REDPIN, OUTPUT);
pinMode(YELLOWPIN, OUTPUT);
pinMode(GREENPIN, OUTPUT);
pinMode(BUTTONPIN, INPUT_PULLUP);
Serial.begin(115200);
randomSeed(analogRead(A0));
showAllOff();
Serial.println("Ready. Commands: HACK, LEVEL <1-5>, FLICKER <1-12>, SPEED <20-500>, TURBO, STATUS, PATTERN <n>");
Serial.println("Press button to toggle hacked mode.");
}
void loop() {
handleSerial();
handleButton();
if (!hackedMode) {
normalTrafficCycleFast();
} else {
hackedWithMotionFast();
}
}
void handleSerial() {
if (!Serial.available()) return;
String line = Serial.readStringUntil('\n');
line.trim();
line.toUpperCase();
if (line == "") return;
if (line == "HACK") {
hackedMode = !hackedMode;
Serial.print("HACKED MODE = "); Serial.println(hackedMode ? "ON" : "OFF");
showAllOff(); delay(120);
} else if (line.startsWith("LEVEL")) {
int sp = line.indexOf(' ');
if (sp > 0) {
int lvl = line.substring(sp + 1).toInt();
if (lvl >= 1 && lvl <= 5) { chaosLevel = lvl; Serial.print("Chaos level = "); Serial.println(chaosLevel); }
else Serial.println("Use LEVEL 1-5");
}
} else if (line.startsWith("FLICKER")) {
int sp = line.indexOf(' ');
if (sp > 0) {
int v = line.substring(sp + 1).toInt();
if (v >= 1 && v <= 12) { flickerIntensity = v; Serial.print("Flicker = "); Serial.println(flickerIntensity); }
else Serial.println("Use FLICKER 1-12");
}
} else if (line.startsWith("SPEED")) {
int sp = line.indexOf(' ');
if (sp > 0) {
int v = line.substring(sp + 1).toInt();
if (v >= 20 && v <= 500) { moveSpeedMs = v; Serial.print("Move speed ms = "); Serial.println(moveSpeedMs); }
else Serial.println("Use SPEED 20..500");
}
} else if (line == "TURBO") {
turbo = !turbo;
Serial.print("TURBO = "); Serial.println(turbo ? "ON" : "OFF");
} else if (line == "STATUS") {
Serial.print("HACKED MODE = "); Serial.println(hackedMode ? "ON" : "OFF");
Serial.print("Chaos Level = "); Serial.println(chaosLevel);
Serial.print("Flicker = "); Serial.println(flickerIntensity);
Serial.print("MoveSpeed ms = "); Serial.println(moveSpeedMs);
Serial.print("TURBO = "); Serial.println(turbo ? "ON" : "OFF");
} else if (line.startsWith("PATTERN")) {
int sp = line.indexOf(' ');
int p = (sp>0) ? line.substring(sp+1).toInt() : -1;
runSinglePatternFast(p);
} else {
Serial.println("Unknown command.");
}
}
void handleButton() {
static int lastBtn = HIGH;
int btn = digitalRead(BUTTONPIN);
if (btn != lastBtn) {
lastButtonChange = millis();
lastBtn = btn;
}
if ((millis() - lastButtonChange) > debounceMs && btn == LOW) {
hackedMode = !hackedMode;
Serial.print("Button toggled. HACKED MODE = "); Serial.println(hackedMode ? "ON" : "OFF");
showAllOff();
delay(160);
}
}
// Normal cycle shortened for fast comparison
void normalTrafficCycleFast() {
digitalWrite(REDPIN, HIGH); digitalWrite(YELLOWPIN, LOW); digitalWrite(GREENPIN, LOW);
delay(max(120, 800 - chaosLevel*100)); // ~120..700ms (very short)
digitalWrite(REDPIN, LOW); digitalWrite(GREENPIN, HIGH);
delay(max(140, 900 - chaosLevel*120));
digitalWrite(GREENPIN, LOW); digitalWrite(YELLOWPIN, HIGH);
delay(max(90, 400 - chaosLevel*50));
showAllOff(); delay(80);
}
// Hacked behavior tuned for speed; turbo halves many delays
void hackedWithMotionFast() {
int choice = random(0, 6 + chaosLevel);
if (random(0, 10) < chaosLevel + 3) choice = random(2, 6);
switch (choice) {
case 0: focusedFlickerFast(YELLOWPIN, flickerIntensity + chaosLevel); break;
case 1: cascadeWaveFast(4 + chaosLevel*2); break;
case 2: randomWalkMotionFast(8 + chaosLevel*6); break;
case 3: chaseLoopFast(10 + chaosLevel*10, max(10, moveSpeedMs - chaosLevel*6)); break;
case 4: microBlipsWithFlickerFast(8 + chaosLevel*6); break;
case 5: chaoticBurstFast(); break;
default: globalFlickerFast(6 + flickerIntensity + chaosLevel); break;
}
}
// Turbo-aware helpers: internalDelay returns scaled ms (halved when turbo)
int internalDelay(int ms) {
if (turbo) return max(8, ms / 2);
return max(6, ms);
}
// focused fast flicker
void focusedFlickerFast(int pin, int repeats) {
for (int i = 0; i < repeats; i++) {
digitalWrite(pin, HIGH);
delay(internalDelay(random(18, 60)));
digitalWrite(pin, LOW);
delay(internalDelay(random(10, 40)));
if (random(0,10) < 4) {
int adj = (pin==REDPIN)?YELLOWPIN:(pin==YELLOWPIN?GREENPIN:YELLOWPIN);
digitalWrite(adj, HIGH);
delay(internalDelay(random(8, 36)));
digitalWrite(adj, LOW);
}
}
showAllOff();
delay(internalDelay(random(30, 120)));
}
// fast cascade wave
void cascadeWaveFast(int passes) {
for (int p = 0; p < passes; p++) {
digitalWrite(REDPIN, HIGH); delay(internalDelay(moveSpeedMs));
digitalWrite(REDPIN, LOW);
digitalWrite(YELLOWPIN, HIGH); delay(internalDelay(moveSpeedMs));
digitalWrite(YELLOWPIN, LOW);
digitalWrite(GREENPIN, HIGH); delay(internalDelay(moveSpeedMs));
digitalWrite(GREENPIN, LOW);
// quick reverse
digitalWrite(GREENPIN, HIGH); delay(internalDelay(moveSpeedMs/2));
digitalWrite(GREENPIN, LOW);
digitalWrite(YELLOWPIN, HIGH); delay(internalDelay(moveSpeedMs/2));
digitalWrite(YELLOWPIN, LOW);
digitalWrite(REDPIN, HIGH); delay(internalDelay(moveSpeedMs/2));
digitalWrite(REDPIN, LOW);
}
showAllOff();
}
// fast random-walk motion
void randomWalkMotionFast(int steps) {
int pos = random(0,3);
for (int i=0;i<steps;i++) {
int pin = (pos==0?REDPIN:(pos==1?YELLOWPIN:GREENPIN));
digitalWrite(pin, HIGH);
delay(internalDelay(random(max(12, moveSpeedMs-30), moveSpeedMs+30)));
digitalWrite(pin, LOW);
int step = random(-1,2);
pos += step; if (pos<0) pos=0; if (pos>2) pos=2;
if (random(0,10) < chaosLevel+1) pos = (pos==0)?2:0;
}
showAllOff();
}
// fast chase loop (marquee)
void chaseLoopFast(int repeats, int speedMs) {
for (int i=0;i<repeats;i++) {
digitalWrite(REDPIN, HIGH); delay(internalDelay(speedMs));
digitalWrite(REDPIN, LOW);
digitalWrite(YELLOWPIN, HIGH); delay(internalDelay(speedMs));
digitalWrite(YELLOWPIN, LOW);
digitalWrite(GREENPIN, HIGH); delay(internalDelay(speedMs));
digitalWrite(GREENPIN, LOW);
}
showAllOff();
}
// micro blips + flicker overlay (fast)
void microBlipsWithFlickerFast(int cycles) {
for (int i=0;i<cycles;i++) {
int which = random(0,3);
int pin = (which==0?REDPIN:(which==1?YELLOWPIN:GREENPIN));
digitalWrite(pin, HIGH);
delay(internalDelay(random(8, 46)));
digitalWrite(pin, LOW);
if (random(0,10) < 4 + chaosLevel) globalFlickerFast(1 + random(0, flickerIntensity/3));
}
showAllOff();
}
void chaoticBurstFast() {
chaseLoopFast(4 + chaosLevel*2, max(8, moveSpeedMs/3));
digitalWrite(REDPIN, HIGH); digitalWrite(YELLOWPIN, HIGH); digitalWrite(GREENPIN, HIGH);
delay(internalDelay(random(40, 160)));
showAllOff();
focusedFlickerFast(random(0,3)==0?REDPIN:(random(0,2)==0?YELLOWPIN:GREENPIN), 3 + chaosLevel);
}
void globalFlickerFast(int times) {
for (int i=0;i<times;i++) {
digitalWrite(REDPIN, random(0,2));
digitalWrite(YELLOWPIN, random(0,2));
digitalWrite(GREENPIN, random(0,2));
delay(internalDelay(random(8, 60)));
}
showAllOff();
}
void runSinglePatternFast(int p) {
switch (p) {
case 0: focusedFlickerFast(YELLOWPIN, 10); break;
case 1: cascadeWaveFast(8); break;
case 2: randomWalkMotionFast(28); break;
case 3: chaseLoopFast(40, max(8, moveSpeedMs/2)); break;
case 4: microBlipsWithFlickerFast(28); break;
case 5: chaoticBurstFast(); break;
case 6: globalFlickerFast(18); break;
default: Serial.println("Pattern unknown. Use PATTERN 0-6"); break;
}
}
void showAllOff() {
digitalWrite(REDPIN, LOW);
digitalWrite(YELLOWPIN, LOW);
digitalWrite(GREENPIN, LOW);
}
Real world example
Researchers from the University of Michigan demonstrated vulnerabilities in traffic control systems that were connected to the public internet with minimal security.
They discovered:
- Weak or default credentials
- No encryption between devices
- Outdated legacy software
- Open network ports on industrial controllers
If you want to read more check it out here
Why This Is Important
Traffic control is more than lights — it’s life and safety infrastructure.
A malicious actor could:
- Cause traffic jams or collisions by manipulating signals
- Interrupt emergency vehicle routes
- Create denial of service in smart city systems
- Use compromised IoT devices as pivot points for larger network attacks
The danger isn’t theoretical — it’s very real. Even something as simple as an insecure microcontroller could be the weakest link in a municipal system.
Final Thoughts
Even something as ordinary as a traffic light can become an attack vector.
When we learn to simulate and understand these systems safely, we’re better equipped to secure the ones that keep our world running.
“In the wrong hands, even a blinking light can cause a blackout.” — 404 Yeti
Stay safe, stay frosty, and don’t let your lights get hacked. 🚦🐾