Crisna Maria SobyAFSIDHA M JAiswarya Achu
Published

Smart Restroom Occupancy Monitor

A smart, privacy-friendly system that shows real time rest room availability using a Time-of-Flight sensor

BeginnerFull instructions provided10 hours55
Smart Restroom Occupancy Monitor

Things used in this project

Story

Read more

Schematics

Smart Restroom Occupancy Monitor - Circuit Connection Diagram

This schematic illustrates the hardware connections of the Smart Restroom Occupancy & Hygiene Monitoring System. It shows the wiring between the Seeed Studio XIAO ESP32S3, VL53L0X Time-of-Flight sensor, SSD1306 OLED display, status LEDs, and the required power and I²C connections used in the prototype.Connect the components according to the wiring diagram. Upload the provided Arduino sketch to the Seeed Studio XIAO ESP32S3 using the Arduino IDE. After powering the system, the VL53L0X sensor detects restroom occupancy, the OLED displays the current status, and the LEDs indicate availability in real time.

Code

Smart Restroom Occupancy & Hygiene Monitoring System

C/C++
Arduino firmware for the Smart Restroom Occupancy & Hygiene Monitoring System. The program reads data from the VL53L0X Time-of-Flight sensor to detect restroom occupancy, updates the OLED display and status LEDs, counts restroom usage, monitors soap dispenser usage through an FSR sensor, and generates maintenance alerts when the predefined threshold is reached.1. Install the ESP32 board package in the Arduino IDE.
2. Install the required libraries: Adafruit GFX, Adafruit SSD1306, Adafruit VL53L0X, and Wire.
3. Connect the hardware according to the circuit diagram.
4. Select the Seeed Studio XIAO ESP32S3 board and the correct COM port.
5. Upload the sketch to the board.
6. Power the system and monitor the OLED display and Serial Monitor for real-time occupancy and soap usage information.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_VL53L0X.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_VL53L0X lox;

// LED Pins
const int RED_LED = D2;
const int GREEN_LED = D3;

// Distance threshold (mm)
const int threshold = 600;

// Cleaning alert after these many uses
const int CLEAN_LIMIT = 20;

int usageCount = 0;
bool occupied = false;

void setup() {

  Serial.begin(115200);

  Wire.begin();

  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);

  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (1);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0,0);
  display.println("Starting...");
  display.display();

  if (!lox.begin()) {

    display.clearDisplay();
    display.setCursor(0,0);
    display.println("Sensor Error");
    display.display();

    while (1);
  }

  delay(1000);
}

void loop() {

  VL53L0X_RangingMeasurementData_t measure;

  lox.rangingTest(&measure, false);

  if (measure.RangeStatus != 4) {

    int distance = measure.RangeMilliMeter;

    display.clearDisplay();

    display.setTextSize(2);
    display.setCursor(0,0);

    if (distance < threshold) {

      display.println("OCCUPIED");

      digitalWrite(RED_LED, HIGH);
      digitalWrite(GREEN_LED, LOW);

      if (!occupied) {
        occupied = true;
        usageCount++;
      }

    } else {

      display.println("VACANT");

      digitalWrite(RED_LED, LOW);
      digitalWrite(GREEN_LED, HIGH);

      occupied = false;
    }

    display.setTextSize(1);

    display.setCursor(0,30);
    display.print("Distance:");
    display.print(distance);
    display.println(" mm");

    display.print("Uses: ");
    display.println(usageCount);

    if (usageCount >= CLEAN_LIMIT) {

      display.println("Cleaning Needed!");

      Serial.println("***** ALERT *****");
      Serial.println("Cleaning Required");
      Serial.println("*****************");

    }

    display.display();

    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.print(" mm   Uses: ");
    Serial.println(usageCount);

  }

  delay(200);
}

Smart Restroom Occupancy & Hygiene Monitoring System XIAO ESP32S3 + VL53L0X (ToF) + OLED (SSD1306)

C/C++
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_VL53L0X.h>

// ---------------- OLED CONFIG ----------------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR    0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// ---------------- ToF SENSOR ----------------
Adafruit_VL53L0X lox;
const int OCCUPANCY_THRESHOLD_MM = 600;   // distance below this = occupied

// ---------------- STATUS LEDs ----------------
const int RED_LED   = D2;   // Occupied indicator
const int GREEN_LED = D3;   // Vacant indicator

// ---------------- RESTROOM USAGE COUNTER ----------------
// Counts each new VACANT -> OCCUPIED transition (one "use")
unsigned long usageCount = 0;
bool wasOccupied = false;   // tracks previous occupancy state for edge detection

// ---------------- FSR SOAP LEVEL ----------------
const int FSR_PIN = A0;
// Adjust according to your sensor readings
const int FSR_MIN = 100;     // No pressure value
const int FSR_MAX = 3000;    // Maximum pressure value
const int SOAP_MAX = 20;
int soapCount = 0;

// ---------------- TIMING ----------------
const unsigned long LOOP_DELAY_MS = 200;

void setup() {
  Serial.begin(115200);
  delay(500);

  Wire.begin();

  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  pinMode(FSR_PIN, INPUT);

  Serial.println("========================================");
  Serial.println(" SMART RESTROOM MONITORING SYSTEM");
  Serial.println("========================================");

  // ---- OLED INIT ----
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("OLED init FAILED");
    while (1) { delay(10); }
  }
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 20);
  display.println("Starting...");
  display.display();

  // ---- ToF SENSOR INIT ----
  if (!lox.begin()) {
    Serial.println("VL53L0X NOT FOUND!");
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0, 5);
    display.println("TOF ERROR");
    display.display();
    while (1) { delay(10); }
  }
  Serial.println("VL53L0X Ready");
  Serial.println("Soap Level Sensor Ready");
  Serial.println("----------------------------------------");

  delay(800);
}

void loop() {
  handleOccupancy();
  handleSoapDispenser();
  delay(LOOP_DELAY_MS);
}

// ============================================================
// Reads the ToF sensor, updates OLED + LEDs with occupancy status,
// and counts a restroom "use" on every VACANT -> OCCUPIED edge
// ============================================================
void handleOccupancy() {
  VL53L0X_RangingMeasurementData_t measure;
  lox.rangingTest(&measure, false);

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);

  if (measure.RangeStatus != 4) {  // valid reading
    int distance = measure.RangeMilliMeter;
    bool isOccupied = (distance < OCCUPANCY_THRESHOLD_MM);

    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" mm");

    if (isOccupied) {
      // ---- OCCUPIED ----
      digitalWrite(RED_LED, HIGH);
      digitalWrite(GREEN_LED, LOW);

      display.setCursor(0, 10);
      display.println("OCCUPIED");

      Serial.println("Status: OCCUPIED");

      // Detect a fresh entry (edge: was vacant, now occupied)
      if (!wasOccupied) {
        usageCount++;
        Serial.print("New Use Detected! Total Uses: ");
        Serial.println(usageCount);
      }
    } else {
      // ---- VACANT ----
      digitalWrite(RED_LED, LOW);
      digitalWrite(GREEN_LED, HIGH);

      display.setCursor(0, 10);
      display.println("VACANT");

      Serial.println("Status: VACANT");
    }

    wasOccupied = isOccupied;

    // Print running usage count every cycle
    Serial.print("Restroom Uses: ");
    Serial.println(usageCount);

  } else {
    // ---- SENSOR OUT OF RANGE / INVALID ----
    digitalWrite(RED_LED, LOW);
    digitalWrite(GREEN_LED, LOW);

    display.setCursor(0, 20);
    display.println("NO DATA");

    Serial.println("Status: Out of Range");
  }

  // Show usage count on OLED (bottom-left, small text)
  display.setTextSize(1);
  display.setCursor(0, 45);
  display.print("Uses: ");
  display.println(usageCount);

  // Show soap restock warning on the same screen if triggered
  if (soapCount >= SOAP_MAX) {
    display.setCursor(0, 55);
    display.println("RESTOCK SOAP!");
  }

  display.display();
}

// ============================================================
// Reads FSR and converts force into soap count (0-20)
// ============================================================
void handleSoapDispenser() {
  int fsrValue = analogRead(FSR_PIN);

  // Convert analog value to soap count
  soapCount = map(fsrValue, FSR_MIN, FSR_MAX, 0, SOAP_MAX);

  // Limit range
  if (soapCount < 0) {
    soapCount = 0;
  }
  if (soapCount > SOAP_MAX) {
    soapCount = SOAP_MAX;
  }

  Serial.print("FSR Force: ");
  Serial.print(fsrValue);
  Serial.print("   Soap Count: ");
  Serial.print(soapCount);
  Serial.println("/20");

  if (soapCount >= SOAP_MAX) {
    Serial.println("** RESTOCK SOAP **");
  }
}> OCCUPIED transition (one "use")
unsigned long usageCount = 0;
bool wasOccupied = false;   // tracks previous occupancy state for edge detection

// ---------------- FSR SOAP LEVEL ----------------
const int FSR_PIN = A0;
// Adjust according to your sensor readings
const int FSR_MIN = 100;     // No pressure value
const int FSR_MAX = 3000;    // Maximum pressure value
const int SOAP_MAX = 20;
int soapCount = 0;

// ---------------- TIMING ----------------
const unsigned long LOOP_DELAY_MS = 200;

void setup() {
  Serial.begin(115200);
  delay(500);

  Wire.begin();

  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  pinMode(FSR_PIN, INPUT);

  Serial.println("========================================");
  Serial.println(" SMART RESTROOM MONITORING SYSTEM");
  Serial.println("========================================");

  // ---- OLED INIT ----
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("OLED init FAILED");
    while (1) { delay(10); }
  }
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 20);
  display.println("Starting...");
  display.display();

  // ---- ToF SENSOR INIT ----
  if (!lox.begin()) {
    Serial.println("VL53L0X NOT FOUND!");
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0, 5);
    display.println("TOF ERROR");
    display.display();
    while (1) { delay(10); }
  }
  Serial.println("VL53L0X Ready");
  Serial.println("Soap Level Sensor Ready");
  Serial.println("----------------------------------------");

  delay(800);
}

void loop() {
  handleOccupancy();
  handleSoapDispenser();
  delay(LOOP_DELAY_MS);
}

// ============================================================
// Reads the ToF sensor, updates OLED + LEDs with occupancy status,
// and counts a restroom "use" on every VACANT -> OCCUPIED edge
// ============================================================
void handleOccupancy() {
  VL53L0X_RangingMeasurementData_t measure;
  lox.rangingTest(&measure, false);

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);

  if (measure.RangeStatus != 4) {  // valid reading
    int distance = measure.RangeMilliMeter;
    bool isOccupied = (distance < OCCUPANCY_THRESHOLD_MM);

    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" mm");

    if (isOccupied) {
      // ---- OCCUPIED ----
      digitalWrite(RED_LED, HIGH);
      digitalWrite(GREEN_LED, LOW);

      display.setCursor(0, 10);
      display.println("OCCUPIED");

      Serial.println("Status: OCCUPIED");

      // Detect a fresh entry (edge: was vacant, now occupied)
      if (!wasOccupied) {
        usageCount++;
        Serial.print("New Use Detected! Total Uses: ");
        Serial.println(usageCount);
      }
    } else {
      // ---- VACANT ----
      digitalWrite(RED_LED, LOW);
      digitalWrite(GREEN_LED, HIGH);

      display.setCursor(0, 10);
      display.println("VACANT");

      Serial.println("Status: VACANT");
    }

    wasOccupied = isOccupied;

    // Print running usage count every cycle
    Serial.print("Restroom Uses: ");
    Serial.println(usageCount);

  } else {
    // ---- SENSOR OUT OF RANGE / INVALID ----
    digitalWrite(RED_LED, LOW);
    digitalWrite(GREEN_LED, LOW);

    display.setCursor(0, 20);
    display.println("NO DATA");

    Serial.println("Status: Out of Range");
  }

  // Show usage count on OLED (bottom-left, small text)
  display.setTextSize(1);
  display.setCursor(0, 45);
  display.print("Uses: ");
  display.println(usageCount);

  // Show soap restock warning on the same screen if triggered
  if (soapCount >= SOAP_MAX) {
    display.setCursor(0, 55);
    display.println("RESTOCK SOAP!");
  }

  display.display();
}

// ============================================================
// Reads FSR and converts force into soap count (0-20)
// ============================================================
void handleSoapDispenser() {
  int fsrValue = analogRead(FSR_PIN);

  // Convert analog value to soap count
  soapCount = map(fsrValue, FSR_MIN, FSR_MAX, 0, SOAP_MAX);

  // Limit range
  if (soapCount < 0) {
    soapCount = 0;
  }
  if (soapCount > SOAP_MAX) {
    soapCount = SOAP_MAX;
  }

  Serial.print("FSR Force: ");
  Serial.print(fsrValue);
  Serial.print("   Soap Count: ");
  Serial.print(soapCount);
  Serial.println("/20");

  if (soapCount >= SOAP_MAX) {
    Serial.println("** RESTOCK SOAP **");
  }
}

Credits

Crisna Maria Soby
1 project • 2 followers
AFSIDHA M J
1 project • 2 followers
Aiswarya Achu
1 project • 2 followers

Comments