Lighting can bring your tabletop terrain to life, especially when it comes to sci-fi vehicles. In this quick guide, I’ll walk you through how I added working red and blue emergency lights to a cyberpunk police cruiser model with just a few cheap components.
What You’ll Need:
- ESP32 development board
- Red and blue 5mm LEDs (optional extra warn lights for headlights)
- Some basic wiring (use Dupont wires for no soldering)
- Soldering gear (optional, but recommended)
- Power source of choice
Step 1: Wiring It Up
Start by connecting the long legs (positive) of the red and blue LEDs to pins 18 and 19 on the ESP32 board. The short legs (negative) go to ground. That’s your basic circuit done!
Next, plug the board into your computer and open the free Arduino IDE software. If coding isn’t your thing, just ask a language model (like Claude or ChatGPT) to generate a simple flashing sequence for emergency lights. Upload the code, and you’ll be good to go. See the code I used below:
/*
ESP32 Police Lights with Pulsing Headlights
This sketch simulates police car lights using two LEDs (red and blue)
connected to pins 18 and 19 of an ESP32, and adds pulsing headlights on pin 17.
The police LEDs will flash in alternating patterns while the headlights pulse
with a breathing effect.
*/
// Pin definitions
const int HEADLIGHT_LED = 17; // Headlight LED connected to pin 17
const int RED_LED = 18; // Red police light connected to pin 18
const int BLUE_LED = 19; // Blue police light connected to pin 19
// Timing variables (in milliseconds)
const int FLASH_DELAY = 150; // Delay between flashes
const int PATTERN_DELAY = 1000; // Delay between pattern changes
// Headlight pulsing parameters (adjust these to change the pulsing behavior)
const int PULSE_STEP = 10; // Step size for breathing effect (higher = faster pulse)
// Try values between 1-20
const int PULSE_MIN = 0; // Minimum brightness (0 = fully off)
const int PULSE_MAX = 255; // Maximum brightness (255 = fully on)
// Adjust these min/max values to change the range
// Global variable to track the pulse value
int pulseValue = 0;
int pulseDirection = 1; // 1 for increasing, -1 for decreasing
void setup() {
// Initialize the LED pins as outputs
pinMode(RED_LED, OUTPUT);
pinMode(BLUE_LED, OUTPUT);
pinMode(HEADLIGHT_LED, OUTPUT);
// Initially all LEDs are off
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
digitalWrite(HEADLIGHT_LED, LOW);
}
// Function to update the pulsing headlights
void updateHeadlights() {
// Update the pulse value based on the direction
pulseValue += PULSE_STEP * pulseDirection;
// Change direction if we hit the limits
if (pulseValue >= PULSE_MAX) {
pulseValue = PULSE_MAX;
pulseDirection = -1;
} else if (pulseValue <= PULSE_MIN) {
pulseValue = PULSE_MIN;
pulseDirection = 1;
}
// Set the analog output value (brightness)
analogWrite(HEADLIGHT_LED, pulseValue);
}
void loop() {
// Pattern 1: Alternating flashes
for (int i = 0; i < 5; i++) {
updateHeadlights();
// Red LED on, Blue LED off
digitalWrite(RED_LED, HIGH);
digitalWrite(BLUE_LED, LOW);
delay(FLASH_DELAY);
updateHeadlights();
// Red LED off, Blue LED on
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, HIGH);
delay(FLASH_DELAY);
}
// Pattern 2: Double flashes
for (int i = 0; i < 3; i++) {
updateHeadlights();
// Red double flash
digitalWrite(RED_LED, HIGH);
digitalWrite(BLUE_LED, LOW);
delay(FLASH_DELAY);
digitalWrite(RED_LED, LOW);
updateHeadlights();
delay(FLASH_DELAY / 2);
digitalWrite(RED_LED, HIGH);
delay(FLASH_DELAY);
digitalWrite(RED_LED, LOW);
updateHeadlights();
delay(FLASH_DELAY / 2);
// Blue double flash
digitalWrite(BLUE_LED, HIGH);
delay(FLASH_DELAY);
digitalWrite(BLUE_LED, LOW);
updateHeadlights();
delay(FLASH_DELAY / 2);
digitalWrite(BLUE_LED, HIGH);
delay(FLASH_DELAY);
digitalWrite(BLUE_LED, LOW);
delay(FLASH_DELAY / 2);
}
// Pattern 3: Both LEDs flashing together and then alternating
for (int i = 0; i < 2; i++) {
updateHeadlights();
// Both LEDs on
digitalWrite(RED_LED, HIGH);
digitalWrite(BLUE_LED, HIGH);
delay(FLASH_DELAY * 2);
updateHeadlights();
// Both LEDs off
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
delay(FLASH_DELAY);
// Alternating quick flashes
for (int j = 0; j < 6; j++) {
updateHeadlights();
digitalWrite(RED_LED, j % 2 == 0 ? HIGH : LOW);
digitalWrite(BLUE_LED, j % 2 == 0 ? LOW : HIGH);
delay(FLASH_DELAY / 2);
}
updateHeadlights();
// Both LEDs off
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
delay(FLASH_DELAY);
}
}
Step 2: Modifying the STL File
With the circuit working, I turned to the 3D model itself. I edited the STL file to include mounting holes for 5mm LEDs in the light bar. I also carved out space for smaller headlight LEDs while I was at it.
Note – You can get the modified version of this file here
To avoid wasting filament, I printed a quick cross-section of the vehicle to test the LED fit. Once I was happy with it, I went ahead and printed the full model using my Bambu Labs P1S.
Step 3: Assembly
With the parts printed, I installed the LEDs and connected everything to the ESP32 board. A quick test confirmed everything was working perfectly. The flashing red and blue lights added just the right touch of realism to the vehicle.
Adding lighting to your prints doesn’t have to be expensive or complicated, and it adds so much character to your builds. Hopefully this inspires you to try it for yourself!
Want to grab the police cruiser STL – Grab the STL file here


