StairSense

StairSense is an experimental project of mine that’s meant you lift up your boring staircase!

More seriously, it provides functional and adaptive lighting which is cool during the day and enhances safety at night while still being cool. B-) It can also detect if, God forbid, someone falls on the stairs.

It combines distributed computing, wireless microcontroller coordination via MQTT and ultrasonic sensing!

What does StairSense consist of?

It consists of a couple of microcontrollers (ESP32s) that are each responsible for a section of the staircase. For example, MCU0 is responsible for steps 0 to 4, MCU1 for steps 5 to 9 and so on…

StairSense_Overview

Each microcontroller is connected to a couple of HC-SR04 ultrasonic sensors and WS2812B addressable LED strips. A pair of ultrasonic sensor and LED strip for each step.

HC-SR04

The ultrasonic sensor is used to detect if someone is goes there or if something is on the step. It’s a cheap, low-maintenance, easy-to-set-up and non-invasive. It even works in the dark!
The only limitation is that you need an atmosphere, otherwise the sound wave wouldn’t have a medium to travel!

WS2812B

And of course, the pièce de resistance is the addressable LED strip for each step. It’s really the showpiece, as this what people will see when they climb the stairs. It’s also the most “expensive” component in the system.

The microcontrollers cost about $8 and the ultrasonic sensors go for about $2 each (and could even be cheaper, if bought in bulk). A 1-metre LED strip cost about $12.

It’s addressable which means any individual pixel could be controlled! This gives rise to so many possibilties and lighting patterns.

All the prices listed are in Canadian Dollars (CAD).

Server

StairSense_Sys_Architecture

There is also a server in the equation. It helps with coordination and edge computing tasks like fall detection.
I am still experimenting with the communication protocol.

I was initially using ESP-NOW for communication among the microcontrollers, as a true peer-to-peer system. However, I later decided to include a server to offload a great deal of the work off of the MCUs. In my opinion, this decision also improved maintainability. I am using Go for the server.

How does it sense someone climbing the stairs?

It uses the ultrasonic sensor, on each step. When the system boots up, it automatically calibrates itself and establishes a threshhold.

StairSense_Sys_Architecture

And if someone goes by, their foot being in front of the sensor, decreases that distance. The system knows that there is something on that step.

StairSense_Sys_Architecture

What is the current state of the project?

I am still working on it. What you are seeing on this page is just prototyping. I will update the page as I make more progress. The software is closed source for now.

Some features worth showing so far

Until next time, here a some of “cool” features in this prototype.

Auto calibration / animation

Every time the system boots up, it automatically calibrates itself.

After the system is calibrated, it tries to connect to the Wi-Fi network and the server. (You can see that by then end, with the blue flashing LED on the microcontroller.)

Night mode

When luminosity drops below a threshold or after a configured time interval, Night Mode is enabled. In this mode, StairSense maintains a low idle illumination (only a few LEDs light up) and brightens as someone approaches. The LEDs ramp up smoothly to avoid sudden glare.

Static and dynamic pattern support

Both static patterns (like the blue color pattern) and dynamic ones (like the moving rainbox pattern) are both supported!

Using a single TRIG line with more than one HC-SR04

The HC-SR04 sensors were not designed to be driven by a singular shared TRIG line. During my testing, it worked fine. A distinct ECHO line is still needed for each. Below is the simplified, but comprehensive logic being used.

void getDistances()
{
  // 1. Trigger all sensors at once
  // TRIGGER_PIN is shared among the sensor

  digitalWrite(TRIGGER_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);

  // 2. Wait for the echoes in parallel (non blocking)
  for (int i = 0; i < 3; i++)
  {
    echoStart[i] = 0;
    echoEnd[i] = 0;
    started[i] = false;
    ended[i] = false;
  }

  startTime = micros();

  while (true)
  {
    unsigned long now = micros();
    if (now - startTime > TIMEOUT)
      break;

    // Check each echo pin
    if (!started[0] && digitalRead(ECHO_PIN_1) == HIGH)
    {
      echoStart[0] = now;
      started[0] = true;
    }
    if (started[0] && !ended[0] && digitalRead(ECHO_PIN_1) == LOW)
    {
      echoEnd[0] = now;
      ended[0] = true;
    }

    if (!started[1] && digitalRead(ECHO_PIN_2) == HIGH)
    {
      echoStart[1] = now;
      started[1] = true;
    }
    if (started[1] && !ended[1] && digitalRead(ECHO_PIN_2) == LOW)
    {
      echoEnd[1] = now;
      ended[1] = true;
    }

    if (!started[2] && digitalRead(ECHO_PIN_3) == HIGH)
    {
      echoStart[2] = now;
      started[2] = true;
    }
    if (started[2] && !ended[2] && digitalRead(ECHO_PIN_3) == LOW)
    {
      echoEnd[2] = now;
      ended[2] = true;
    }

    // Break if all echoes received
    if (ended[0] && ended[1] && ended[2])
      break;
  }

  // 3. Calculate distances
  for (int i = 0; i < 3; i++)
  {
    if (started[i] && ended[i] && echoEnd[i] > echoStart[i])
    {
      unsigned long duration = echoEnd[i] - echoStart[i];
      distances[i] = duration / 58.0; // in cm 
    }
    else
    {
      distances[i] = -1; // timeout or invalid
    }
  }
}

This is not officially supported in the sensor’s datasheet. In my testing, it worked pretty well. Considering the setup (each step is staggered and above each other), cross-talk is unlikely to occur.


v1shan.com · 2025