This project began after I came across a stair lighting video on YouTube.
The concept was simple but fascinating: two mmWave radars detect a person's movement direction, and the stair lights illuminate sequentially like flowing water, either from bottom to top or from top to bottom.
The original project was built around Home Assistant and included a complete automation setup and tutorial.
My first reaction was:
"I want to build one too."
However, it didn't take long before I realized I didn't want to replicate the original design exactly.
Why I Didn't Choose Home AssistantThe reason was fairly simple.
Although Home Assistant is an incredibly powerful platform, it doesn't really fit my environment.
I live in a relatively small apartment where devices are located close together, and I generally prefer lightweight standalone systems rather than maintaining a large smart-home ecosystem.
There was also a practical networking consideration.
I primarily rely on mobile internet and didn't want to maintain additional network infrastructure just for a stair lighting system.
At the same time, the two radar nodes still needed a way to communicate wirelessly.
That ultimately led me to ESP-NOW.
As it turns out, ESP-NOW is almost ideal for this kind of project:
- No router required
- No cloud dependency
- Low latency
- Direct peer-to-peer communication
- Lightweight enough for embedded systems
The system consists of:
- A radar detects movement or human presence
- Presence information is transmitted via ESP-NOW
- The system determines walking direction
- The LED state machine executes the animation
- The lights remain on briefly before gradually turning off
Unlike cloud-based smart-home solutions, everything runs locally.
This results in extremely low response latency while eliminating the need for Wi-Fi infrastructure.
Hardware SelectionPower SystemPower delivery turned out to be one of the most important parts of the entire project.
Rather than powering the LED strip directly from the ESP32, I used an LED driver board I used the LED driver board I had helped design earlier as the power foundation.
The power system was designed around the following requirements:
- 12V input
- Stable 12V output for LED strips
- Stable 5V output for control electronics
- Independent LED power delivery
This allows the ESP32-C6 to focus entirely on logic processing and communication without handling LED current loads.
I’ve used this driver board to create some pretty nice-looking demos in before, and it’s both user-friendly and quite stable. So I plan to use it again for my LED this time.
Radar SensorLike the original author’s stairs lighting project, I chose an mmWave radar instead of a simpler PIR sensor. However, I opted for the LD2410C radar for the following reasons:
- Static human detection
- Better sensitivity adjustment
- Bluetooth support
- OTA firmware updates
- Official configuration application
- Greater flexibility for future experiments
Compared with PIR sensors, the LD2410C is clearly better suited for responsive indoor interaction systems.
The First PrototypeThe prototype was intentionally simple.
At this stage, the only goal was:
"Can a radar directly control an LED strip?"
The software architecture was minimal:
- Read radar status
- Determine whether someone is present
- Turn LEDs on or off
Everything was still written in a straightforward procedural style.
Despite its simplicity, this stage was extremely important because it validated:
- Radar communication
- LED control
- Human presence detection
- Static target detection
For the first time, the project felt real.
Adding ESP-NOW CommunicationThe next step was establishing ESP-NOW communication between two ESP32-C6 nodes.
This was the point where the project evolved from a single-device prototype into a distributed system.
The lower radar node transmitted:
- Target status
- Distance
- Radar mode
- Motion-related data
The upper node then combined local and remote radar information to determine walking direction.
Initially, most of the challenges seemed manageable:
- Enum type conversions
- ESP32 Arduino Core API changes
- ESP-NOW callback updates
- Structure synchronization
Annoying, but solvable.
The real challenge came later.
When the System Started Becoming UnstableAs more features were added, the software gradually became harder to manage.
The main loop slowly turned into a combination of:
- Radar polling
- Communication handling
- Animation logic
- Direction detection
- Timeout management
- Serial debugging
- State tracking
And, inevitably:
delay()- Nested
ifstatements - Repeated logic
- Blocking animations
started appearing everywhere.
The frustrating part was that every individual module worked perfectly on its own. But once everything was running together, strange behavior appeared:
- Incorrect direction detection
- Random triggers
- Stuttering animations
- Lights refusing to turn off
- Timing conflicts
At first I blamed unstable hardware or ESP32 libraries.
Eventually I realized the real problem was something else entirely.
It was the architecture.
Understanding the Real ProblemAt some point, I noticed a pattern that existed in many of my previous projects. As soon as multiple modules started interacting, the entire system became fragile. The problem wasn't any individual feature. The problem was that everything lived inside loop().
Everything depended on everything else. Long delays blocked communication. Animation timing affected sensor polling. State logic became tightly coupled.
The project had reached a point where adding new features was harder than implementing the features themselves. That was when I decided to rethink the software architecture completely.
Moving to an FSM ArchitectureInstead of treating the project as one large program, I split it into several independent finite state machines. The final architecture evolved into three major subsystems.
Radar FSMResponsible only for:
- Reading radar sensors
- Debouncing sensor data
- Producing stable presence snapshots
Responsible only for:
- Determining walking direction
- Tracking passage states
- Generating lighting events
Responsible only for:
- Running animations
- Rendering LED effects
- Managing animation timing
Meanwhile, the loop() function itself became nothing more than a scheduler:
void loop() {
unsigned long now = millis();
if (taskDue(now, lastRadarTaskAt, RADAR_POLL_MS)) {
radarRunFsm(now);
}
if (taskDue(now, lastPassageTaskAt, PASSAGE_POLL_MS)) {
passageRunFsm(now);
}
if (taskDue(now, lastLedTaskAt, LED_POLL_MS)) {
ledRunFsm(now);
}
}This became the single biggest turning point of the project.
Suddenly the system was:
- Predictable
- Stable
- Easier to debug
- Easier to expand
Most importantly:
The modules finally stopped interfering with each other.New Features That Appeared Naturally
Once the architecture became stable, many features that previously felt difficult suddenly became straightforward to implement.
Entrance Preview LightingWhen someone approaches the stairs, the system no longer lights the entire staircase immediately.
Instead, the first 10 LEDs near the detected entrance illuminate first.
It creates a much more natural experience.
The staircase feels aware of your presence.
Directional Flow AnimationOnce both radar nodes confirm the walking direction, the LEDs flow accordingly:
- Bottom → Top when walking upstairs
- Top → Bottom when walking downstairs
This was the original visual effect that inspired the project.
Stair Occupancy HoldIf someone stops on the staircase, the lights remain on.
The system does not force an immediate timeout simply because the animation has finished.
Delayed Turn-OffWhen the staircase becomes empty, the lights remain illuminated for a configurable period before turning off.
This creates a much smoother user experience.
Radar DebouncingEven mmWave radar sensors exhibit occasional fluctuations.
To improve reliability, I introduced a debouncing mechanism:
updateDebouncer(...)A presence state is only considered valid after remaining stable for a predefined period.
This eventually became one of the biggest contributors to overall system stability.
Eventually, I Started Redesigning the HardwareOnce the software architecture stabilized, another issue became increasingly obvious:
At present, the system looks more like a makeshift lab setup than a finished project, and it looks a bit unsightly sitting on the stairs. Furthermore, anyone who has used XIAO knows that their buttons are simply too small. Every debugging session, firmware update, and hardware modification has become increasingly cumbersome.
So eventually I redesigned the entire system into a dedicated PCB. Lucky enough, Seeed offers an mmWave radar module compatible with the XIAO. After testing it, I found that its functionality is very similar to that of the LD2410C. Seeed’s open-source schematics were a huge help.
But at the beginning, I wasn't trying to create a completely new board. Instead, I started with an existing LED Driver Board design. My goal was simple:
Reduce wiring and integrate everything into a single board.
This was actually my first custom PCB design. I made some practical improvements to the LED driver board:
- XIAO ESP32-C6 integration
- Radar interfaces
- Light sensor integration
- Expansion GPIO headers
- The Boot button is exposed externally instead of requiring access to the tiny button on the XIAO module
- Unused GPIOs are routed to edge headers for future expansion
At first glance, these changes seemed straightforward.
They weren't.
Learning What Every Component Actually DoesOne challenge I quickly discovered was that I didn't fully understand many parts of the original LED driver circuit.
Before modifying anything, I had to work backwards and learn why every component existed.
For each unfamiliar component, I found myself repeatedly asking:
- What does this part do?
- Why is it here?
- What would happen if I removed it?
- Does its position matter?
This process eventually grew into a personal hardware knowledge base where I documented PCB design concepts, layout practices, and common mistakes.
Instead of blindly copying circuits, I wanted to understand them.
Only after understanding the original design could I confidently begin modifying it.
Like many beginners, I initially believed that finishing the schematic meant the hard work was over. Then I opened the PCB layout editor, and immediately realized I was wrong.
The schematic looked neat and organized, and the PCB looked like chaos: connections crossed everywhere, components seemed impossible to place, nothing fit the way I imagined.
My first attempt was to use the automatic router, the software technically connected everything, but the result looked terrible.
Some edge connectors ended up near the center of the board, power traces wandered across the PCB, signal paths took bizarre routes, the board was electrically connected, but it wasn't a design I would actually want to manufacture.
Eventually I deleted most of the routing and started over.
One trace at a time.
Every Component Has Its Own PersonalityOne lesson that surprised me was that PCB design isn't simply about connecting pins together. Different components have very different layout requirements.
A schematic tells you what should connect. A PCB layout determines whether those connections actually work well.
For example:
Input Protection ComponentsThe fuse, reverse-polarity protection diode, and TVS diode need to be placed as close as possible to the 12V power input.
Their job is to stop electrical problems before those problems enter the rest of the board.
If they are placed too far away, they become significantly less effective.
Switching Power ComponentsThe inductor used by the 5V switching regulator behaves very differently.
It carries relatively large currents and generates electromagnetic noise.
Because of that:
- Traces should be short and wide
- Nearby sensitive circuitry should be avoided
- The surrounding area should remain relatively clear
Ignoring these recommendations can introduce instability and noise throughout the board.
Sensitive Control CircuitsMeanwhile, small capacitors and feedback components used for voltage regulation have their own requirements.
These parts should remain close to the regulator IC.
Long traces can introduce noise and reduce regulation performance.
The more I learned, the more I realized that PCB layout is almost a form of physical engineering logic.
The schematic defines relationships.
The layout defines behavior.
Another challenge was thermal management: LED systems can draw significant current. Even when everything works electrically, excessive heat can become a long-term reliability problem.
For this part of the design, I borrowed several techniques from the original LED Driver Board.
Components carrying large currents were grouped together. Power nets were merged into large copper pours instead of relying entirely on thin traces. Large copper areas were used to distribute current more effectively and spread heat across the PCB.
I also added numerous thermal vias connecting copper regions between layers. These vias help transfer heat into larger copper areas and improve overall cooling performance.
It's not the kind of thing that immediately stands out when looking at the finished board. But it has a major impact on reliability.
From Modules to a PlatformBy the time the PCB was complete, the project had changed significantly. Ironically, the goal at the beginning was simply to make a staircase light. But the hardware journey ended up teaching me almost as much as the software side of the project.
Due to a recent shortage, my new mmWave radar hasn't arrived yet. Once they're integrated with the control board, I'll give them a proper test run on my staircase instead of just placing the light strips on a long table.
Future Development and Community FeedbackOne interesting aspect of this project is that the controller board itself is already built upon a previous design iteration.
Now that both the software architecture and PCB prototype are functioning reliably, I'd like to continue exploring how far this idea can evolve as a compact standalone sensing and lighting platform.
Several future directions are currently being considered.
Ambient-Light Adaptive BrightnessThe Light Sensor provides light-sensing information that could be used to automatically adjust LED brightness based on environmental conditions.
For example:
- Brighter during daytime
- Dimmer at night
- Reduced power consumption in dark environments
The two radar nodes can do more than simply detect occupancy.
By leveraging distance gate information from both ends of the staircase, it may be possible to estimate a person's position on the stairs.
Instead of illuminating the entire staircase, the lighting could dynamically follow the user as they move.
Web-Based ConfigurationFuture firmware versions may include a built-in web interface that allows users to configure the system without modifying code.
Potential settings include:
- Stair length (LED count)
- Animation speed
- Radar distance gate sensitivity
- Hold duration
- Brightness limits
- OTA firmware updates
This would make adapting the system to different stair installations significantly easier.
What Would You Add?At this point, I'm curious to see where the community would take this project next.
If you were building a radar-based stair lighting system, what features would you add?
Would you focus on:
- Smarter lighting effects?
- More accurate position tracking?
- Energy efficiency?
- Multi-floor installations?
- Something completely different?
I'd love to hear new ideas and see where this project could go next.








Comments