Introduction: ChronoFlux — a Retro-Futuristic Edge-Lit Nixie Revival

About: Maker | IoT Enthusiast | Electronics hobbyist

ChronoFlux — A Retro-Futuristic Edge-Lit Nixie Revival is my modern re-imagination of the iconic Nixie tube clock. Instead of delicate gas-filled tubes, I designed a fully custom display made from stacked laser-cut acrylic digits, each illuminated using individually addressable LEDs to create a clean, glowing, retro-futuristic aesthetic.

The entire clock is powered by a XIAO ESP32S3, paired with a DS3231 RTC for stable timekeeping and a NEO-6M GPS module that automatically calibrates the time and adjusts the timezone. It’s housed inside a 3D-printed wood-textured enclosure, designed in Fusion 360 and completely fabricated in our college Fab Lab.

My goal with ChronoFlux was to blend old-school charm with modern electronics, creating a unique timepiece that feels both nostalgic and futuristic at the same time.

Supplies

Supplies

Electronics

  1. Seeed Studio XIAO ESP32S3 – Main controller for LEDs, GPS, and RTC
  2. DS3231 RTC Module– Accurate real-time clock
  3. NEO-6M GPS Module – Automatic time + timezone synchronization
  4. WS2812B Individually Addressable LEDs – Edge lighting for acrylic digits
  5. 5V Buck Converter Module – Stable power regulation
  6. USB-C Power Port – Input from any phone charger
  7. Jumper wires & connectors
  8. Small perfboard or PCB (optional) for neat wiring

Display Materials

  1. Laser-cut Acrylic Sheets (Transparent)(2 mm thickness)– For stacked digit panels

3D Printing & Mechanical

  1. Wood-Texture PLA Filament – For the retro-futuristic enclosure
  2. Black PLA Filament - For acrylic spacing and housing
  3. 3D Printer – Any FDM printer
  4. Fusion 360 Design Files (your custom model)
  5. Screws(M2) & Adhesive (as needed for assembly)

Tools

  1. Laser Cutter – For cutting and engraving acrylic
  2. 3D Printer – For enclosure parts
  3. Soldering Iron + Solder
  4. Cutters, pliers, screwdriver set
  5. Sandpaper (optional) – For finishing edges of acrylic/3D prints

Step 1:

Step 1: Research, Planning & Designing the Clock

Every great project starts with a good plan. Before I built ChronoFlux, I spent some time understanding how Nixie clocks work, how acrylic edge-lighting behaves, and what design choices would make the final clock look clean, bright, and reliable.

This step is all about gathering information, building ideas, and turning them into a clear design.

🔍 1. Getting Information & Understanding the Concept

I wanted to capture the beautiful warm glow of old Nixie tubes, but using materials and tools that are easy for anyone to find in a modern makerspace.

So I started with three questions:

  1. How do Nixie tubes show numbers?
  2. → They use stacked metal number shapes inside a glass tube.
  3. How can we recreate that effect today?
  4. → Using transparent acrylic sheets, each engraved with a number.
  5. How do we light each layer independently?
  6. → By placing individually addressable LEDs (WS2812B/SK6812) at the bottom.

This “stacked acrylic + edge lighting” method gives a retro glow, but with a modern aesthetic — exactly what I wanted.

🧠 2. Design Thinking: Choosing the Style & Experience

Before opening any CAD software, I thought about how I want the clock to feel, not just how it should look.

I wanted the clock to be:

  1. Retro-futuristic – A mix between vintage Nixie tubes and modern minimalism.
  2. Warm and aesthetic – Something that looks handmade and crafted with care.
  3. Beginner-friendly – Simple enough that anyone can recreate or customize it.
  4. Modular – Every block (digit) can be serviced or replaced if needed.

I also wanted the user experience to be smooth:

  1. Plug in USB-C → Clock lights up instantly
  2. GPS automatically sets the correct time
  3. Brightness is comfortable and even
  4. No complicated buttons or menus

Thinking through user experience early makes the final project feel more polished and professional.

📝 3. Planning the Build

With the concept clear, I started breaking the project into manageable parts:

Acrylic Display Section

  1. Each digit has 10 acrylic sheets (0–9).
  2. Only one sheet lights at a time using LEDs.
  3. Black separators prevent light bleeding from one layer to the next.

Electronics Section

  1. XIAO ESP32S3 → Brain of the clock
  2. DS3231 RTC → Keeps time accurately
  3. NEO-6M GPS → Auto-calibrates the time and timezone
  4. USB-C port + buck converter → Simple and safe powering method

Enclosure Section

  1. Fully 3D printed using wood-texture PLA
  2. Designed to look clean and handcrafted
  3. Must have:
  4. space for wires
  5. vents for heat
  6. removable covers
  7. easy access for assembly

Manufacturing

  1. Acrylic → Laser cut
  2. Wood-texture body → 3D printed
  3. Electronics → Soldered on small perfboard
  4. Everything assembled in our college Fab Lab

Planning everything early saved me a lot of time later — especially when aligning LEDs with acrylic.

💻 4. Designing in AutoCAD & Fusion 360

Once I had the full idea in my head, I started bringing it to life digitally.

AutoCAD (for Acrylic Digits)

AutoCAD is perfect for 2D work, so I used it to:

  1. Draw digit outlines (0–9)
  2. Add engraving lines
  3. Set the exact dimensions for laser cutting
  4. Make sure all digits stack perfectly without any offset
  5. Export files for the laser cutter

Fusion 360 (for the Clock Body)

This is where the fun really started.

In Fusion 360, I:

  1. Modeled the entire enclosure
  2. Applied wood-grain texture to visualize the final look
  3. Checked clearances so everything fits smoothly
  4. Designed a removable back cover for easy repair

Fusion 360 helped me catch mistakes before printing anything — saving filament, time, and frustration.

Step 2:

Step 2: Manufacturing the Parts

With the design complete, it’s time to bring ChronoFlux to life!

In this step, we’ll manufacture all the physical parts — the 3D-printed wooden enclosure, the black inner frame, and the laser-cut acrylic digit plates. Even if you’re a beginner, don’t worry — we’ll go slowly and clearly through each part of the process.

🌲 1. 3D Printing the Wooden Case (Wood-Effect PLA)

The enclosure of ChronoFlux is designed to look retro, warm, and handcrafted. To achieve this, we use Wood-Texture PLA and a special wood grain technique during 3D printing.

We printed everything using a Bambu Lab A1 Combo in our college Fab Lab.

🔧 1.1 Preparing the Wood Texture in Bambu Studio

To get the natural wooden look, we follow the method demonstrated in these two excellent videos:

  1. https://youtu.be/dPqu9Sk01jc?si=Lq2xsLUWskayY7i7
  2. https://makerworld.com/en/models/868884-add-wood-grain-effects-to-your-models-using-a-smal

These tutorials explain how to create beautiful wood grain patterns on your 3D prints using:

✔ Variable layer heights

✔ Color/texture tricks

✔ Surface pattern variations

✔ Simple slicer adjustments

Beginner summary of the technique:

  1. Import your Fusion 360 model into Bambu Studio.
  2. Select your wood-texture PLA filament preset.
  3. Enable features like:
  4. Variable Layer Height
  5. Surface Texture (light or medium)
  6. Random Z-seam
  7. Apply a slight modifier mesh (as shown in the video) to create grain lines.
  8. Preview the texture before slicing.
  9. Slice the model and get your G-code ready for printing.

Even if you’ve never added wood grain to a print before, these videos + Bambu Studio make it very beginner friendly.

🖨️ 1.2 Printing the Wooden Parts on the Bambu Lab A1 Combo

Once sliced, send the print to the Bambu A1 Combo.

Tips for a successful print:

  1. Use 0.2 mm or 0.24 mm layer height for good details.
  2. Slow down the outer wall speed slightly for smooth finish.
  3. Keep the bed temperature around 50–60°C for PLA.
  4. Make sure your filament is dry for best texture quality.
  5. If possible, print the top cover facing upward for better grain effect.

You will see the wood grain come alive as the printer builds the layers — this is one of the most satisfying parts of the project!

🖤 2. Printing the Black Internal Frame

Apart from the wooden outer shell, ChronoFlux also uses black 3D-printed parts for:

  1. LED support channels
  2. Acrylic digit separators
  3. Mounting brackets
  4. Internal electronics housing

These are printed using any standard black PLA or black PETG.

Print settings:

  1. 0.2 mm layer height
  2. 20–30% infill
  3. No supports for most parts
  4. 210°C nozzle / 60°C bed (PLA)

The black interior helps block light leakage, ensuring the acrylic digits glow cleanly and uniformly.

3. Cutting & Engraving the Acrylic Digit Panels

The acrylic digits are the heart of the display. These must be cut accurately so that stacking them creates the “Nixie tube” illusion.

We used transparent acrylic sheets (2 mm or 3 mm) and cut them on our laser cutter in the Fab Lab.

🔥 Steps to manufacture the acrylic plates:

  1. Open your AutoCAD digit design file (0–9).
  2. Export or save it as DXF for the laser cutter.
  3. Place your acrylic sheet on the cutting bed.
  4. For each digit:
  5. Cut the outer shape
  6. Engrave the number in the center

Engraving Notes for Beginners

  1. Use light engraving for a soft glow
  2. Use deep engraving if you want stronger light diffusion
  3. Keep protective film on until engraving is done to avoid scratches

🧱 3.1 Stacking the Layers

Each digit block will have:

  1. 10 engraved acrylic plates
  2. Black separators in between
  3. A bottom LED slot for illumination

Make sure all pieces are perfectly aligned — this affects the final clarity of each glowing number.

Step 3:

Step 3: Electronics & Wiring

Now that all the parts are manufactured, it’s time to bring ChronoFlux to life electronically.

This section might look complex at first, but don’t worry — we’ll go step-by-step, slowly and clearly. Even if this is your first electronics project, you will be able to complete it confidently.

By the end of this step, you will have:

✔ The ESP32S3 wired

✔ LEDs connected

✔ RTC installed

✔ GPS module connected

✔ Power system completed

✔ Clean, organized wiring inside the enclosure

Let’s begin!

1. Understanding the Electronics Block

ChronoFlux has four main electronic components:

1. XIAO ESP32S3

The “brain” of the clock.

It controls LEDs, fetches GPS data, reads RTC time, and updates the display.

2. WS2812B / SK6812 LEDs

These light up the acrylic digit layers.

Each LED is individually addressable → perfect for selecting digits.

3. DS3231 RTC Module

A highly accurate real-time clock.

Keeps time even if power is removed.

4. NEO-6M GPS Module

Used for:

  1. Auto time calibration
  2. Auto timezone detection
  3. Correcting drift

5. Power Section (USB-C + Buck Converter)

Takes 5V from a phone charger and delivers stable power to the clock.

🔌 2. Wiring Diagram (Beginner-Friendly Explanation)

Here’s how all components connect.

We won’t use complex circuit diagrams — instead, let’s use simple, plain-English wiring.

🔹 ESP32S3 → LEDs

The LEDs have 3 wires: 5V, GND, DIN

  1. LED 5V → Buck Converter 5V Output
  2. LED GND → ESP32S3 GND
  3. LED DIN → ESP32S3 Pin D10 (recommended)

Why D6?

It's a stable GPIO and works well for long LED chains.

Important:

Connect all grounds together.

This includes:

  1. ESP32S3 GND
  2. Buck converter GND
  3. LED GND
  4. RTC GND
  5. GPS GND

A shared ground ensures stable LEDs and zero flicker.

🔹 ESP32S3 → RTC (DS3231)

The DS3231 uses I²C, so only 4 wires needed:

  1. RTC VCC → 3.3V on ESP32S3
  2. RTC GND → ESP32S3 GND
  3. RTC SCL → ESP32S3 Pin D6 (SCL)
  4. RTC SDA → ESP32S3 Pin D5 (SDA)

This gives accurate local time at all times.

🔹 ESP32S3 → GPS (NEO-6M)

The GPS uses UART (TX/RX):

  1. GPS VCC → 5V
  2. GPS GND → GND
  3. GPS TX → ESP32S3 RX
  4. GPS RX → ESP32S3 TX

This lets the ESP32S3 receive satellite time data.

🔹 USB-C → Buck Converter → Circuit

The system is powered by any phone charger using a USB-C port.

  1. USB-C 5V → Buck Converter IN+
  2. USB-C GND → Buck Converter IN-
  3. Buck Converter OUT 5V → LED 5V + GPS VCC
  4. Buck Converter OUT GND → shared ground

The ESP32S3 already has a built-in regulator, so it gets power through its USB-C port or through 5V pin.

🛠 3. Assembly Tips for Beginners

Here are tips that make wiring clean, safe, and reliable.

Tip 1: Use Color-Coded Wires

  1. Red → Power
  2. Black → Ground
  3. Yellow/Green → Signal

Makes debugging easy.

Tip 2: Short, Organized Wires

Don’t leave long loose wires inside the case.

Short, neat connections:

  1. reduce noise
  2. prevent accidental bending
  3. look much more professional

Tip 3: LED Direction Matters

LED strips have arrows showing the data direction.

Make sure DIN connects to the first LED in the chain.

Tip 4: Add a Capacitor & Resistor (Optional but recommended)

For best LED stability:

  1. 1000 µF electrolytic capacitor between LED 5V and GND
  2. 330–470 Ω resistor between ESP32S3 data pin and LED DIN

This protects LEDs from voltage spikes.

Tip 5: Test Before Installing

Always test the electronics outside the case first.

Upload a basic LED test code:

  1. Check LEDs light up
  2. Check GPS locks
  3. Check RTC time reads correctly

Once confirmed, install them inside the case.

Step 4:

⭐ Step 4: Programming ChronoFlux — Detailed Guide & Code Explanation

Goal of this step:

Make ChronoFlux read GPS coordinates, compute a timezone offset from longitude, set the DS3231 RTC to local time (UTC + timezone), and then use the RTC as the authoritative source to update the LEDs that light each acrylic plate.

🔁 Overall flow (plain language)

  1. The ESP32 reads NMEA data from the GPS over Serial1.
  2. When GPS has a valid UTC time and a valid location, the code computes a timezone offset (approx: round(longitude / 15.0)) or uses a manual override.
  3. The code converts GPS UTC → local time using the timezone and stores that local time in the DS3231 RTC.
  4. After the first successful sync, the RTC becomes the clock source for display.
  5. The GPS is used occasionally (hourly by default) to re-sync the RTC and update the stored timezone if the device moves.

✅ Libraries required

Install the following via Arduino Library Manager (Sketch → Include Library → Manage Libraries):

  1. FastLED — controls WS2812/SK6812 LEDs
  2. TinyGPSPlus — parses GPS NMEA sentences
  3. RTClib (Adafruit) — DS3231 support
  4. Preferences (bundled with ESP32 core) — simple non-volatile key/value storage

🔌 Pin mapping (as used in code)


LED data -> D10
GPS TX -> ESP RX -> D7 (Serial1 RX pin)
GPS RX <- ESP TX -> D6 (Serial1 TX pin)
RTC -> I2C -> A4 (SDA), A5 (SCL)
Power -> USB-C (or 5V pin)

✂ Full code (compact version for reference)

Paste this into Arduino IDE (select the XIAO ESP32S3 board). The detailed explanation below references the functions and snippets from this code.

#include <Wire.h>
#include <RTClib.h>
#include <TinyGPSPlus.h>
#include <FastLED.h>
#include <Preferences.h>

#define LED_PIN 10
#define GPS_RX_PIN 7
#define GPS_TX_PIN 6

const uint8_t LEDS_PER_DIGIT = 10;
const uint8_t NUM_DIGITS = 6;
const uint16_t NUM_LEDS = LEDS_PER_DIGIT * NUM_DIGITS;

#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 80

#define GPS_BAUD 9600
TinyGPSPlus gps;
RTC_DS3231 rtc;
Preferences prefs;

const char *PREF_NAMESPACE = "chrono";
const char *KEY_AUTO = "auto_tz";
const char *KEY_TZH = "tz_hours";
const char *KEY_TZM = "tz_mins";

bool tz_auto = true;
int tz_hours = 0;
int tz_extra_mins = 0;

unsigned long lastGpsSync = 0;
const unsigned long GPS_SYNC_INTERVAL = 60UL * 60UL * 1000UL; // 1 hour

inline uint16_t ledIndex(uint8_t block, uint8_t number) {
return (uint16_t)block * LEDS_PER_DIGIT + number;
}

void showDigit(uint8_t block, uint8_t number, CRGB color = CRGB::White) {
uint16_t base = block * LEDS_PER_DIGIT;
for (uint8_t i = 0; i < LEDS_PER_DIGIT; ++i) leds[base + i] = CRGB::Black;
leds[base + number] = color;
}

void displayTime(int hour, int minute, int second) {
uint8_t Ht = hour / 10, Ho = hour % 10;
uint8_t Mt = minute / 10, Mo = minute % 10;
uint8_t St = second / 10, So = second % 10;

if (NUM_DIGITS == 4) {
showDigit(0, Ht); showDigit(1, Ho);
showDigit(2, Mt); showDigit(3, Mo);
} else {
showDigit(0, Ht); showDigit(1, Ho);
showDigit(2, Mt); showDigit(3, Mo);
showDigit(4, St); showDigit(5, So);
}
FastLED.show();
}

void loadPrefs();
void savePrefs();
void setRtcFromGps();
void applyTimezoneToRTC(DateTime utcDT, int tz_h, int tz_m);

void setup() {
Serial.begin(115200);
delay(100);
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear(); FastLED.show();

Wire.begin();
if (!rtc.begin()) Serial.println("RTC not found!");
Serial1.begin(GPS_BAUD, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);

prefs.begin(PREF_NAMESPACE, false);
loadPrefs();

// quick LED scan
for (int b = 0; b < NUM_DIGITS; ++b) {
for (int n = 0; n < LEDS_PER_DIGIT; ++n) {
FastLED.clear();
leds[ledIndex(b, n)] = CRGB::Blue;
FastLED.show();
delay(20);
}
}
FastLED.clear(); FastLED.show();

Serial.println("Setup complete");
}

void loop() {
while (Serial1.available()) gps.encode(Serial1.read());

if (gps.time.isValid() && gps.date.isValid() && gps.location.isValid()) {
if (tz_auto) {
float lon = gps.location.lng();
int computed_tz = (int)round(lon / 15.0);
if (computed_tz != tz_hours) {
tz_hours = computed_tz;
tz_extra_mins = 0;
savePrefs();
Serial.print("Auto timezone computed: "); Serial.println(tz_hours);
}
}
if (millis() - lastGpsSync > GPS_SYNC_INTERVAL) {
setRtcFromGps();
lastGpsSync = millis();
}
}

DateTime now = rtc.now();
displayTime(now.hour(), now.minute(), now.second());

if (Serial.available()) {
String line = Serial.readStringUntil('\n');
line.trim();
line.toUpperCase();
if (line.startsWith("TZ ")) {
if (line.indexOf("AUTO") != -1) {
tz_auto = true; savePrefs();
Serial.println("Timezone set to AUTO");
} else {
char buf[64]; line.toCharArray(buf, sizeof(buf));
int h=0,m=0; char *tok = strtok(buf," ");
tok = strtok(NULL," "); if (tok) h=atoi(tok);
tok = strtok(NULL," "); if (tok) m=atoi(tok);
tz_auto=false; tz_hours=h; tz_extra_mins=m; savePrefs();
Serial.print("Manual timezone stored: "); Serial.print(tz_hours); Serial.print("h "); Serial.print(tz_extra_mins); Serial.println("m");
}
} else {
Serial.println("Use: TZ AUTO or TZ <hours> <minutes>");
}
}

delay(200);
}

void setRtcFromGps() {
if (!gps.time.isValid() || !gps.date.isValid()) return;
DateTime utcDT(gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second());
int tz_h = tz_hours; int tz_m = tz_extra_mins;
applyTimezoneToRTC(utcDT, tz_h, tz_m);
Serial.print("RTC set to local time with tz "); Serial.print(tz_h); Serial.print("h "); Serial.print(tz_m); Serial.println("m");
}

void applyTimezoneToRTC(DateTime utcDT, int tz_h, int tz_m) {
uint32_t epoch = utcDT.unixtime();
long offset = (long)tz_h * 3600L + (long)tz_m * 60L;
uint32_t localEpoch = epoch + offset;
DateTime localDT(localEpoch);
rtc.adjust(localDT);
}

void loadPrefs() {
int auto_i = prefs.getInt(KEY_AUTO, -1);
if (auto_i == -1) { tz_auto = true; tz_hours = 0; tz_extra_mins = 0; savePrefs(); return; }
tz_auto = (auto_i == 1);
tz_hours = prefs.getInt(KEY_TZH, 0);
tz_extra_mins = prefs.getInt(KEY_TZM, 0);
Serial.print("Loaded prefs: auto="); Serial.print(tz_auto); Serial.print(" tz="); Serial.print(tz_hours); Serial.print(":"); Serial.println(tz_extra_mins);
}

void savePrefs() {
prefs.putInt(KEY_AUTO, tz_auto ? 1 : 0);
prefs.putInt(KEY_TZH, tz_hours);
prefs.putInt(KEY_TZM, tz_extra_mins);
}

📚 Detailed explanation (by sections)

1) Top: includes and constants


#include <Wire.h>
#include <RTClib.h>
#include <TinyGPSPlus.h>
#include <FastLED.h>
#include <Preferences.h>
  1. Wire = I²C master for DS3231.
  2. RTClib = simple DS3231 interface.
  3. TinyGPSPlus = parse GPS NMEA sentences easily.
  4. FastLED = control LED strip.
  5. Preferences = store timezone settings permanently (in flash).

Pin and LED layout constants follow. LEDS_PER_DIGIT = 10 means each digit block has 10 acrylic plates (0–9), and NUM_DIGITS = 6 means we show HH:MM:SS (adjust to 4 if you want HH:MM only).

2) LED helper functions


inline uint16_t ledIndex(uint8_t block, uint8_t number) {
return (uint16_t)block * LEDS_PER_DIGIT + number;
}
  1. Maps logical (block, number) to a linear LED index in the strip.
  2. Example: block 2 (third digit), number 7 → 2*10 + 7 = 27.

void showDigit(uint8_t block, uint8_t number, CRGB color = CRGB::White) {
uint16_t base = block * LEDS_PER_DIGIT;
for (uint8_t i = 0; i < LEDS_PER_DIGIT; ++i) leds[base + i] = CRGB::Black;
leds[base + number] = color;
}
  1. Clears the whole block (turn off all 10 plates) then lights only the target plate.
  2. This models the Nixie behavior: only one numeral visible per digit.

void displayTime(int hour, int minute, int second) { ... }
  1. Takes the hour/minute/second, splits into digits, and calls showDigit() for each block in the correct order.

3) Persistent timezone storage

Functions loadPrefs() and savePrefs() use Preferences to remember:

  1. whether timezone is auto (tz_auto),
  2. tz_hours (e.g., +5), and
  3. tz_extra_mins (e.g., 30 for IST +5:30).

This prevents recalculating or reconfiguring every boot. Preferences stores values in flash in a safe manner for microcontrollers.

4) setup()

Key steps:

  1. Initialize Serial for debug.
  2. Initialize FastLED and clear the LEDs.
  3. Wire.begin() and rtc.begin() to initialize I²C/RTC.
  4. Serial1.begin(GPS_BAUD, ...) to start the hardware serial used by the GPS (on D7/D6).
  5. prefs.begin(...) and loadPrefs() to restore timezone settings.
  6. A small LED scan visually confirms wiring and LED functionality.

Tip: If rtc.begin() prints error, check I²C wiring (SDA → A4, SCL → A5, 3.3V → VCC, GND → GND).

5) loop()

This is the real-time control:


while (Serial1.available()) gps.encode(Serial1.read());
  1. Continuously feed GPS bytes to TinyGPSPlus for parsing.

If GPS has valid time, date, and location:

  1. If tz_auto is true, compute timezone hours: round(longitude / 15.0) (15° of longitude per time zone hour). Update prefs if changed.
  2. If it's time to sync (once per hour, by default), call setRtcFromGps().

Then:

  1. Get current time from rtc.now() and call displayTime(...) to update LEDs.

Serial commands:

  1. TZ AUTO → enable auto timezone.
  2. TZ <hours> <minutes> → set manual timezone (e.g., TZ 5 30).

6) setRtcFromGps() and applyTimezoneToRTC()


void setRtcFromGps() {
if (!gps.time.isValid() || !gps.date.isValid()) return;
DateTime utcDT(gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second());
int tz_h = tz_hours; int tz_m = tz_extra_mins;
applyTimezoneToRTC(utcDT, tz_h, tz_m);
}
  1. Build a DateTime object that represents UTC (from GPS).
  2. Then call applyTimezoneToRTC() with the chosen timezone.

void applyTimezoneToRTC(DateTime utcDT, int tz_h, int tz_m) {
uint32_t epoch = utcDT.unixtime();
long offset = (long)tz_h * 3600L + (long)tz_m * 60L;
uint32_t localEpoch = epoch + offset;
DateTime localDT(localEpoch);
rtc.adjust(localDT);
}
  1. Convert UTC DateTime to Unix epoch (seconds since 1970), add timezone offset (hours and extra minutes), convert back to DateTime, then write it to the RTC.
  2. After this, rtc.now() will return local time directly.

Why use epoch conversion?

It handles date rollovers and DST-independent math reliably (e.g., adding +5 hours to 23:30 on the previous day properly results in next-day time).

🔍 Example: How timezone is computed from GPS

  1. Suppose GPS returns longitude = 77.5946 (Bengaluru).
  2. tz = round(77.5946 / 15.0) = round(5.173) = 5 → +5 hours.
  3. You can then set tz_extra_mins = 30 manually if you want +5:30 (India).

Important: This auto approach works well for whole-hour zones. For half-hour/45-minute zones, use the Serial override.

🧪 Testing & Debugging checklist (practical)

  1. Serial Monitor: Open at 115200 baud. You’ll see logs: prefs loaded, RTC found, auto timezone computed, sync messages.
  2. GPS Fix: For first-time config, place the GPS outside or by a window. Wait until gps.location.isValid() becomes true.
  3. RTC Check: Before GPS write, try rtc.now() reading in a small test sketch to ensure RTC responds.
  4. LED test: The LED scan at startup should light each plate briefly. If not:
  5. Check data pin (D10) wiring and resistor on data line.
  6. Check 5V power to LEDs and common ground.
  7. Timezone override: In Serial Monitor type TZ 5 30 then press Enter — this sets +5:30 persistently. Type TZ AUTO to go back.
  8. Force Sync (quick test): temporarily set GPS_SYNC_INTERVAL to 10,000 (10 s) for development; remember to restore later.

⚠️ Safety & reliability tips

  1. Put a 1000 µF electrolytic capacitor between 5V and GND right at the LED input.
  2. Add a 330–470 Ω resistor in series with LED data pin (between D10 and first LED DIN).
  3. Keep brightness moderate (BRIGHTNESS = 80) to avoid power draw issues. If you set full white at max brightness, each LED can draw ~60 mA. Multiply by LED count. Ensure the buck can supply this current.
  4. If your GPS or LEDs cause resets, check for voltage sag; adding bulk capacitance or a beefier supply fixes that.

✨ Enhancements (next steps)

  1. Add a small push-button to toggle 12/24-hour mode (or to manually force sync).
  2. Implement DST logic for specific countries (requires a DST table or internet lookup).
  3. Add an OTA update handler so you can flash new firmware wirelessly (ESP32 supports OTA).
  4. Add battery backup for RTC and LEDs for graceful shutdown.

✅ Final notes for beginners

  1. Take the project slow — get the RTC working first, then test GPS parsing, then LED control.
  2. Use small test sketches for each subsystem if you get stuck (e.g., a sketch that only reads GPS and prints lat/lon; another that only runs a LED chase).
  3. Persisting timezone in Preferences means you usually only need to set timezone once.

Step 5:

Step 5: Final Assembly & Testing

Now that all the electronics are working and the firmware is ready, it’s time to assemble ChronoFlux into its final form.

This is the step where all your hard work — the acrylic plates, the 3D-printed wood-textured case, the LEDs, and the electronics — come together to create the finished clock.

Even if you're a beginner, follow these steps slowly and carefully, and you'll get a perfect result.

🧱 1. Preparing the Acrylic Digit Blocks

Each digit block consists of:

  1. 10 engraved acrylic plates (0–9)
  2. 10 black separators (light isolation)
  3. 1 bottom LED slot
  4. 1 top cover
  5. 1 rear support panel (optional depending on your design)

✔ 1.1 Clean the plates

Remove the protective film from the acrylic only when you're ready to assemble.

Wipe each plate gently with a microfiber cloth so the engraving lights up cleanly.

✔ 1.2 Arrange them in order

Lay the plates in this order:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Keeping them organized prevents mistakes later.

✔ 1.3 Insert black separators

Place one thin black separator between each number plate.

This improves:

  1. contrast
  2. brightness
  3. reduces light bleed between numbers

✔ 1.4 Align the stack

Make sure all plates are perfectly aligned on all four edges.

A simple trick: press the stack against a flat table edge before clamping or gluing.

🌈 2. Installing the LED Strip Under Each Block

✔ 2.1 Cut the LED strip

For one digit block, you only need 10 LEDs.

Cut the strip carefully on the copper pads.

✔ 2.2 Verify data direction

LED strips show arrows → direction of data flow.

Make sure the arrow points towards the stack of digit plates.

✔ 2.3 Insert the LED strip into the LED channel

Ensure LEDs sit exactly under the acrylic plates.

✔ 2.4 Secure the strip

Hot glue or double-sided tape works well, but avoid covering the LED lens.

🔌 3. Mounting the Electronics Inside the Enclosure

✔ 3.1 Install the XIAO ESP32S3

Place it inside and secure it with tiny screws or adhesive pads.

Ensure the USB-C port lines up with the enclosure.

✔ 3.2 Mount the RTC

Attach the DS3231 so the battery faces upward or sideways.

Don’t trap the CR2032 battery — it must be replaceable.

✔ 3.3 Install the GPS module

Place the antenna facing upwards for better satellite reception.

✔ 3.4 Buck converter placement

Secure the buck converter away from the LEDs (heat separation).

Adjust the voltage before connecting it:

  1. Output should be 5.0 V exactly.
  2. Double-check with a multimeter.

✔ 3.5 Route the wires cleanly

Keep wires short and flat so the case closes smoothly and safely.

✔ 3.6 Connect all modules

Follow the wiring you completed in Step 3:

  1. XIAO → LEDs
  2. XIAO → RTC
  3. XIAO → GPS
  4. USB-C → Buck → LEDs/GPS
  5. All grounds together

🧪 4. First Power-On Test

Plug the clock into a standard USB-C phone charger.

✔ 4.1 ESP32S3 lights up

The on-board indicator should turn on.

✔ 4.2 LED block test

You should see the startup LED scan (from the code in Step 4).

If not:

  1. check LED data pin
  2. check 5V supply
  3. check LED direction arrows

✔ 4.3 GPS sync

Move the device near a window.

The GPS may take 30–120 seconds to obtain the first fix.

Once GPS locks:

  1. The time is automatically adjusted
  2. The timezone is computed
  3. The RTC is updated

✔ 4.4 RTC test

Unplug the clock → plug it back in

The time continues correctly → RTC is working.

🎯 5. Aligning and Installing the Digit Blocks

Now that the electronics work:

✔ 5.1 Place the digit blocks directly above the LEDs

Light output should be centered under each engraved digit.

✔ 5.2 Secure the stack

Use screws or small dabs of adhesive on the corners (never glue the LED section).

✔ 5.3 Install the top covers

This finishes the retro-futuristic look.

🟢 6. Functional Testing Checklist

Time:

  1. Does the time display correctly?
  2. Do the digits change at the correct second/minute/hour?

Brightness:

  1. Are all plates evenly bright?
  2. Any light bleeding? (Add more separators if needed)

GPS:

  1. Does the clock set the correct local time?
  2. Does timezone auto-detection work?
  3. If incorrect, set manual timezone via Serial: TZ 5 30

RTC:

  1. Unplug the clock for 30 seconds → plug back in
  2. Time should continue accurately

Power:

  1. No overheating?
  2. No flickering?
  3. Buck converter stable at 5V?

🔧 7. Optional Finishing Touches

You can add:

  1. Rubber feet under the enclosure
  2. A matte clear coat on wooden surfaces
  3. Cable management clips
  4. A small “settings” button (for 12/24-hour toggle)

📝 A Small Note From My Build Journey

While building ChronoFlux with full excitement and passion, something unexpected happened — my laptop display got damaged right in the middle of the build.

Because of that, I couldn’t complete the full physical build exactly the way I envisioned it.

But instead of stopping, I still wanted to show how ChronoFlux would have looked and behaved once fully assembled.

So I generated a small demo video using AI — just to help you visualize the final aesthetic, the glow of the digits, and the animations this clock was designed to produce.

Even though I couldn’t finish the physical prototype this time, I made sure the entire tutorial is complete, detailed, and fully replicable for anyone who wants to build their own ChronoFlux.

And I hope someone out there completes it fully — maybe even better than I dreamed.

Step 6:

Step 6: Conclusion — Bringing ChronoFlux to Life

Building ChronoFlux — A Retro-Futuristic Edge-Lit Nixie Revival has been an incredibly rewarding journey.

From early design sketches to the final glowing digits, this project combines electronics, design thinking, CNC fabrication, 3D printing, coding, and creative engineering into one unified build that any beginner can follow and enjoy.

Whether you’re a hobbyist, a student, or someone exploring digital fabrication for the first time, this project shows how accessible and fun modern making can be.

🌟 What You’ve Learned

By following this tutorial, you now understand:

✔ Acrylic Edge-Lit Digit Fabrication

How stacked engraved plates can replace traditional Nixie tubes.

✔ 3D Printing With Wood Texture

Using Bambu Studio’s special slicer techniques to create realistic wood grain finishes.

✔ Electronics Integration

Wiring the ESP32S3, GPS, RTC, LEDs, and power system safely and cleanly.

✔ GPS Time Synchronization

Using a NEO-6M GPS module to:

  1. read UTC time
  2. get GPS coordinates
  3. compute timezone from longitude
  4. set the RTC automatically

✔ Real-Time Clock Operation

Using the DS3231 for rock-solid timekeeping even when GPS isn’t available.

✔ ESP32 Programming

Driving LED animations, mapping digits, saving settings in flash, and parsing GPS data.

🚀 Why This Project Matters

This project reimagines the beauty of vintage Nixie tubes using modern, accessible materials:

  1. No high voltage
  2. No fragile tubes
  3. No rare components
  4. Fully customizable
  5. Automated time calibration
  6. 3D-printable enclosure
  7. Beginner-friendly electronics

Most importantly, it shows how creativity + technology can reinvent something old and turn it into something uniquely futuristic.

🛠️ Ideas for Future Improvements

Here are some awesome features you can add next:

1. WiFi Time Sync (NTP)

Use WiFi as an optional backup for GPS indoors.

2. Custom Animations

Fade-in effects, Nixie-style digit shuffle, breathing glows, or color themes.

3. Touch or Button Interface

To change:

  1. brightness
  2. timezone
  3. color
  4. animation mode
  5. 12/24-hour mode

4. Ambient Sensors

Add a light sensor to auto-adjust brightness based on room lighting.

5. Battery Backup

Power the LEDs for a few seconds and keep the clock running during power cuts.

6. Desktop App / Bluetooth Settings

Configure the clock wirelessly from your phone or laptop.

❤️ Final Thoughts

ChronoFlux is more than a clock — it’s a statement piece that blends retro charm with modern engineering.

It stands as proof that with the right tools, creativity, and a willingness to learn, anyone can build something truly unique.

If you recreate this project, add your own twist!

Customize the enclosure, rework the digits, change the animations, or expand the electronics — that’s the beauty of open-source making.