Community project

Indoor Elderly Assistance Robot

ESP32
Photo of Indoor Elderly Assistance Robot
Generated with AI

Neralay Nabi

Published September 21, 2026

This indoor assistance robot delivers medicine and supplies to elderly residents in care facilities. Built around an ESP32 safety controller paired with a Raspberry Pi 4 navigation computer, the robot combines real-time motor control, battery management, and obstacle detection with SLAM-based autonomous routing. The guide provides a complete wiring diagram, parts list, firmware for the ESP32 safety layer, and step-by-step assembly instructions for the rolling base, power distribution, motor drive section, and sensor integration.

The robot uses dual 6 V encoder gearmotors driven by a TB6612FNG H-bridge, an MPU-6050 IMU for stability monitoring, an INA219 current sensor for battery health tracking, and a RPLIDAR A1 for 2D mapping and collision avoidance. Power comes from a protected 18650 Li-ion battery pack with regulated rails for the motors, logic, and compute boards. Safety features include emergency stop, bumper switches, and watchdog timeouts to ensure reliable operation in shared indoor spaces.

Wiring diagram

Wiring diagram for Indoor Elderly Assistance Robot

Gather all the parts

QtyComponent
1

TB6612FNG dual H-bridge motor-driver module

TB6612FNG, 6 V motor rail

A small dual motor driver board that lets the ESP32 safely control the two wheel motors.

1

6 V TT encoder gearmotor, left wheel

6 V 160 RPM encoder gearmotor

A low-speed geared wheel motor with two encoder pulse outputs for measuring the left wheel's movement.

1

6 V TT encoder gearmotor, right wheel

6 V 160 RPM encoder gearmotor

A low-speed geared wheel motor with two encoder pulse outputs for measuring the right wheel's movement.

1

DFRobot SEN0142 Fermion MPU-6050 6 DOF Sensor Breakout

MPU-6050

DFRobot SEN0142 MPU-6050 breakout with 3-5 V board input, I2C interface, onboard I2C pull-ups, and i2cdevlib Arduino example coverage.

1

Adafruit INA219 High-Side DC Current Sensor

INA219

INA219 high-side current and bus-voltage monitor breakout. It is powered from 3.3V or 5V and communicates over I2C. Route the measured load current through VIN+ and VIN-; those shunt terminals are part of the power path, not MCU GPIO.

1

Raspberry Pi 4 Model B (4 GB), navigation computer

4 GB RAM

The main navigation computer that runs mapping, route planning, the Wi-Fi dashboard, and wheel-encoder odometry.

1

SLAMTEC RPLIDAR A1 USB 2D laser scanner

RPLIDAR A1 USB kit

A spinning laser distance scanner that gives the navigation computer a 360-degree indoor view for mapping and obstacle avoidance.

1

2S2P protected 18650 Li-ion battery pack

7.4 V nominal, 5200 mAh

A rechargeable 7.4 volt nominal battery pack that gives the robot usable indoor run time.

1

2S 10 A Li-ion BMS protection board

2S, 10 A minimum

A battery protection board that prevents unsafe over-charge, over-discharge, short-circuit, and excessive-current conditions.

1

8.4 V 2 A CC/CV Li-ion charger

8.4 V, 2 A

A wall-powered charger that safely recharges the two-cell battery pack through the BMS charging connector.

1

24v Buck Converter

Set to 5.1 V, 5 A

LM2596-based adjustable step-down buck converter module. Commonly used to regulate a higher battery rail, such as a 2S 18650 pack, down to 5V for Arduino logic. It is a regulator, not a charger or battery protection board.

1

6 V 3 A buck regulator

Set to 6.0 V

A step-down power module adjusted to 6 volts for the motors and their encoder electronics.

1

Normally-closed latching emergency-stop mushroom switch

NC power contact + NC auxiliary contact + 10 kΩ pull-up

A large push-to-lock safety switch that physically removes motor power and reports the stop state to the ESP32.

1

Dual normally-closed front bumper switches with resistor sense board

2 × NC switches + 2 × 10 kΩ pull-up resistors

Two spring bumper switches that create a safe analog stop signal when the robot touches an object.

1

3.3 V active piezo buzzer module

Active buzzer

A small sounder that makes clear medicine-reminder, low-battery, and safety-alert tones.

1

RGB status LED module with built-in resistors

3.3 V module

A three-color indicator that shows ready, navigating, charging, fault, and emergency-stop states.

Assemble it in 8 steps

1. Build the rolling base and medicine tray

Fit the left_motor and right_motor on opposite sides of a low, rigid chassis, then attach the wheels and a rear caster. Bolt a shallow tray with raised sides above the chassis; add a lid, elastic strap, or latch so a medicine box cannot slide out while the robot turns.

  • Keep the battery low and centered between the wheels so the robot is less likely to tip.
  • Leave a clear, high mounting position at the front for the LiDAR so the tray does not block its view.
  • Do not carry loose tablets or an open drink on the tray; a sudden stop can throw them out.
  • Do not treat this robot as a mobility aid or let a person lean on it.

2. Install the battery, protection board, and charger

Secure battery_pack in a covered holder. Connect PACK+ to bms B+ (battery positive), MID to bms BM (the join between the two cell groups), and PACK- to bms B- (battery negative). Connect charger DC+ to bms P+ and charger DC- to bms P-; mount the charger connector where it is easy to reach or align with a dock.

  • Use a prebuilt matched 2S2P pack with a proper fuse and insulated leads.
  • Label the charger socket clearly as 8.4 V Li-ion charging only.
  • Never connect the 8.4 V charger directly to individual cells or reverse the battery leads — that can cause overheating or fire.
  • Do not charge the robot unattended or inside a sealed enclosure.

3. Make the regulated power rails

Connect bms P+ to battery_monitor VIN+ (battery current measurement), then connect battery_monitor VIN- to both buck_5v VIN+ and buck_6v VIN+. Join every regulator negative pin, bms P-, and module ground to the same GND return. Set buck_5v to 5.1 V before attaching the navigation computer, and set buck_6v to 6.0 V before attaching the motors.

  • Measure each regulator output with a multimeter first: 5.1 V for logic/USB equipment and 6.0 V for the motor rail.
  • Use thicker wire for the battery, BMS, buck converters, emergency stop, driver VM, and motors.
  • Do not connect a regulator output until its voltage has been measured; an incorrectly adjusted buck converter can destroy the Raspberry Pi or motors.
  • Keep battery wiring away from sharp chassis edges so vibration cannot wear through insulation.

4. Wire the motor cutoff and drive section

Connect buck_6v VOUT+ to estop NC IN, then estop NC OUT to motor_driver VM; this makes pressing the mushroom button physically remove motor power. Connect motor_driver A01 and A02 to left_motor M+ and M-, and B01 and B02 to right_motor M+ and M-. Connect motor_driver VCC to 3V3 (logic power), GND to GND (ground), AIN1 to GPIO25 (left direction), AIN2 to GPIO26 (left direction), PWMA to GPIO27 (left speed), BIN1 to GPIO13 (right direction), BIN2 to GPIO14 (right direction), PWMB to GPIO32 (right speed), and STBY to GPIO33 (driver enable).

  • With the wheels raised off the table for the first test, command a slow forward motion; swap the two motor wires on one motor if it turns the wrong direction.
  • Place the emergency-stop mushroom button where the elderly person or helper can reach it quickly.
  • Pressing the emergency-stop must stop both wheels immediately; do not operate the robot until this is true.
  • Keep fingers, clothing, and wires clear of the wheels during tests.

5. Connect the ESP32 safety controller and notification parts

Connect the ESP32 3V3 and GND rails to the shared logic rails. Connect imu VIN to 3V3 (power), GND to GND (ground), SDA to GPIO21 (data), and SCL to GPIO22 (clock). Connect battery_monitor VCC to 3V3 (power), GND to GND (ground), SDA to GPIO21 (data), and SCL to GPIO22 (clock). Connect buzzer VCC to 3V3 (power), GND to GND (ground), and SIG to GPIO4 (alert). Connect status_led VCC to 3V3 (power), GND to GND (ground), RED to GPIO12 (fault light), and GREEN to GPIO15 (moving light). Connect the emergency-stop auxiliary contact to GPIO23 (stop-state signal) using its 10 kΩ pull-up resistor to 3V3.

  • Mount the IMU flat near the center of the chassis, with its arrow pointing toward the robot front.
  • The LED module must include its own resistors; bare LEDs require one resistor on each color lead.
  • Do not use a bare LED without a series resistor — it can burn out the LED or damage the controller pin.
  • The 3.3 V ESP32 pins must never be connected directly to a 5 V signal.

6. Install the bumpers and encoder wiring

Mount front_bumper so either front edge presses a switch before the chassis reaches an obstacle. Wire its GND to GND (ground), LEFT SENSE to GPIO34 (left collision signal), and RIGHT SENSE to GPIO35 (right collision signal); its resistor board supplies the needed 3.3 V-safe pull-ups. Connect each motor encoder VCC to 5V (power) and ENC GND to GND (ground). Connect left_motor ENC A and ENC B to navigation_pi LEFT ENC A and LEFT ENC B (wheel distance signals), and connect right_motor ENC A and ENC B to navigation_pi RIGHT ENC A and RIGHT ENC B (wheel distance signals).

  • Use the intended 3.3 V-safe encoder outputs or add proper level shifters if your encoder board produces 5 V pulses.
  • Route encoder wires away from motor wires to reduce false counts.
  • A pressed bumper or broken bumper wire must be treated as a stop condition; do not bypass these switches.
  • Never feed a 5 V encoder pulse directly into a Raspberry Pi GPIO pin — it can permanently damage the Pi.

7. Fit the navigation computer and LiDAR

Connect buck_5v VOUT+ to navigation_pi 5V IN (power) and VOUT- to navigation_pi GND (ground). Connect navigation_pi UART TX to ESP32 GPIO16 (motion commands) and navigation_pi UART RX to ESP32 GPIO17 (safety status). Connect lidar USB DATA to navigation_pi USB LIDAR (map data), lidar 5V to 5V (power), and lidar GND to GND (ground). Mount the LiDAR level and above the tray rim, facing into open space.

  • Use a short USB lead with strain relief so vibration does not loosen it.
  • Keep the LiDAR's rotating window clean and uncovered; it is the robot's main view of walls and furniture.
  • Do not power the Raspberry Pi from an unmeasured battery rail; it needs the regulated 5.1 V supply.
  • Do not look closely into the LiDAR window or cover it while the scanner is running.

8. Perform a safe bench test before floor driving

Raise the chassis so the wheels cannot touch the floor. Power on, verify the red light is on until a valid drive command arrives, press the emergency-stop, and confirm the motor driver loses its 6 V supply. Then test the bumper switches, buzzer, battery reading, LiDAR scan, and low-speed wheel direction. Only after these tests should the robot map one clear room at walking pace.

  • Set the navigation software speed limit to a slow walking pace or less, about 0.2 m/s, and keep a helper beside it for the first mapped route.
  • Create the bed, bedroom, kitchen, medicine-table, living-room, and charger locations in the dashboard only after the map is stable.
  • If localization is lost, battery is low, a bumper is hit, or sensor data stops, the robot must stop rather than guessing where to drive.
  • The charger is not an autonomous docking system in this prototype; guide it to the connector/dock until a separately tested docking alignment system is added.

Review all connections

1. Connections between "battery_pack" and "ESP32"

Functionbattery_packESP32
powerPACK+2S 10 A Li-ion BMS protection board B+EXT
powerMID2S 10 A Li-ion BMS protection board BMEXT
groundPACK-2S 10 A Li-ion BMS protection board B-EXT

2. Connections between "charger" and "ESP32"

FunctionchargerESP32
powerDC+2S 10 A Li-ion BMS protection board P+EXT
groundDC-2S 10 A Li-ion BMS protection board P-EXT

3. Connections between "bms" and "ESP32"

FunctionbmsESP32
powerP+Adafruit INA219 High-Side DC Current Sensor VIN+EXT
groundP-GND

4. Connections between "battery_monitor" and "ESP32"

Functionbattery_monitorESP32
dataVIN-24v Buck Converter VIN+EXT
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

5. Connections between "buck_5v" and "ESP32"

Functionbuck_5vESP32
groundVIN-GND
powerVOUT+Raspberry Pi 4 Model B (4 GB), navigation computer 5V INEXT
groundVOUT-GND

6. Connections between "buck_6v" and "ESP32"

Functionbuck_6vESP32
powerVIN+Adafruit INA219 High-Side DC Current Sensor VIN-EXT
groundVIN-GND
powerVOUT+Normally-closed latching emergency-stop mushroom switch NC INEXT
groundVOUT-GND

7. Connections between "estop" and "ESP32"

FunctionestopESP32
powerNC OUTTB6612FNG dual H-bridge motor-driver module VMEXT
digitalAUX SENSEGPIO 23

8. Connections between "motor_driver" and "ESP32"

Functionmotor_driverESP32
powerVCC3V3
groundGNDGND
digitalAIN1GPIO 25
digitalAIN2GPIO 26
pwmPWMAGPIO 27
digitalBIN1GPIO 13
digitalBIN2GPIO 14
pwmPWMBGPIO 32
digitalSTBYGPIO 33

9. Connections between "left_motor" and "ESP32"

Functionleft_motorESP32
powerM+TB6612FNG dual H-bridge motor-driver module A01EXT
powerM-TB6612FNG dual H-bridge motor-driver module A02EXT
powerENC VCC5V
groundENC GNDGND
analogENC ARaspberry Pi 4 Model B (4 GB), navigation computer LEFT ENC AEXT
analogENC BRaspberry Pi 4 Model B (4 GB), navigation computer LEFT ENC BEXT

10. Connections between "right_motor" and "ESP32"

Functionright_motorESP32
powerM+TB6612FNG dual H-bridge motor-driver module B01EXT
powerM-TB6612FNG dual H-bridge motor-driver module B02EXT
powerENC VCC5V
groundENC GNDGND
analogENC ARaspberry Pi 4 Model B (4 GB), navigation computer RIGHT ENC AEXT
analogENC BRaspberry Pi 4 Model B (4 GB), navigation computer RIGHT ENC BEXT

11. Connections between "imu" and "ESP32"

FunctionimuESP32
powerVIN3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

12. Connections between "navigation_pi" and "ESP32"

Functionnavigation_piESP32
groundGNDGND
uartUART TXGPIO 16
uartUART RXGPIO 17
dataUSB LIDARSLAMTEC RPLIDAR A1 USB 2D laser scanner USB DATAEXT

13. Connections between "lidar" and "ESP32"

FunctionlidarESP32
power5V5V
groundGNDGND

14. Connections between "front_bumper" and "ESP32"

Functionfront_bumperESP32
groundGNDGND
analogLEFT SENSEGPIO 34
analogRIGHT SENSEGPIO 35

15. Connections between "buzzer" and "ESP32"

FunctionbuzzerESP32
powerVCC3V3
groundGNDGND
digitalSIGGPIO 4

16. Connections between "status_led" and "ESP32"

Functionstatus_ledESP32
powerVCC3V3
groundGNDGND
digitalREDGPIO 12
digitalGREENGPIO 15

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>

// ESP32 real-time safety and motor controller for the indoor assistance robot.
// The Raspberry Pi performs SLAM, map storage, route planning, and dashboard UI.


// Forward declarations
bool readIna219Register(uint8_t reg, int16_t &value);
float readBusVoltage();
int batteryPercent(float volts);
bool estopPressed();
bool leftBumperPressed();
bool rightBumperPressed();
bool hazardPresent();
void setMotorChannel(int in1, int in2, int pwmPin, int speedValue);
void stopMotors(const char *reason);
void driveMotors(int leftSpeed, int rightSpeed);
void beep(unsigned int durationMs);
void sendStatus();
void handleCommand(String command);

constexpr int LEFT_IN1 = 25;
constexpr int LEFT_IN2 = 26;
constexpr int LEFT_PWM = 27;
constexpr int RIGHT_IN1 = 13;
constexpr int RIGHT_IN2 = 14;
constexpr int RIGHT_PWM = 32;
constexpr int MOTOR_STBY = 33;
constexpr int ESTOP_PIN = 23;
constexpr int BUMPER_LEFT_PIN = 34;
constexpr int BUMPER_RIGHT_PIN = 35;
constexpr int BUZZER_PIN = 4;
constexpr int LED_RED_PIN = 12;
constexpr int LED_GREEN_PIN = 15;
constexpr int I2C_SDA_PIN = 21;
constexpr int I2C_SCL_PIN = 22;
constexpr int PI_UART_RX = 16;
constexpr int PI_UART_TX = 17;

constexpr uint8_t INA219_ADDRESS = 0x40;
constexpr uint32_t COMMAND_TIMEOUT_MS = 500;
constexpr uint8_t MAX_PWM = 115;  // limits the robot to a slow indoor speed

String commandBuffer;
unsigned long lastMotionCommandMs = 0;
unsigned long lastStatusMs = 0;
bool moving = false;
bool safetyStopped = true;

bool readIna219Register(uint8_t reg, int16_t &value) {
  Wire.beginTransmission(INA219_ADDRESS);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return false;
  if (Wire.requestFrom((int)INA219_ADDRESS, 2) != 2) return false;
  value = (int16_t)((Wire.read() << 8) | Wire.read());
  return true;
}

float readBusVoltage() {
  int16_t raw = 0;
  if (!readIna219Register(0x02, raw)) return -1.0f;
  return ((raw >> 3) * 4.0f) / 1000.0f;
}

int batteryPercent(float volts) {
  if (volts < 0.0f) return -1;
  // Conservative 2S Li-ion estimate under a light robot load.
  float percent = (volts - 6.4f) * 100.0f / (8.35f - 6.4f);
  if (percent < 0.0f) percent = 0.0f;
  if (percent > 100.0f) percent = 100.0f;
  return (int)(percent + 0.5f);
}

bool estopPressed() {
  // NC auxiliary contact holds this pin low when healthy; a press or broken wire reads high.
  return digitalRead(ESTOP_PIN) == HIGH;
}

bool leftBumperPressed() {
  // NC contact holds this input low when healthy; a press or broken wire reads high.
  return digitalRead(BUMPER_LEFT_PIN) == HIGH;
}

bool rightBumperPressed() {
  return digitalRead(BUMPER_RIGHT_PIN) == HIGH;
}

bool hazardPresent() {
  return estopPressed() || leftBumperPressed() || rightBumperPressed();
}

void setMotorChannel(int in1, int in2, int pwmPin, int speedValue) {
  int magnitude = constrain(abs(speedValue), 0, MAX_PWM);
  if (speedValue > 0) {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  } else if (speedValue < 0) {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
  } else {
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
  }
  ledcWrite(pwmPin, magnitude);
}

void stopMotors(const char *reason) {
  setMotorChannel(LEFT_IN1, LEFT_IN2, LEFT_PWM, 0);
  setMotorChannel(RIGHT_IN1, RIGHT_IN2, RIGHT_PWM, 0);
  digitalWrite(MOTOR_STBY, LOW);
  moving = false;
  safetyStopped = true;
  digitalWrite(LED_RED_PIN, HIGH);
  digitalWrite(LED_GREEN_PIN, LOW);
  Serial2.print("EVENT,STOP,");
  Serial2.println(reason);
}

void driveMotors(int leftSpeed, int rightSpeed) {
  if (hazardPresent()) {
    stopMotors("SAFETY_INPUT");
    return;
  }
  digitalWrite(MOTOR_STBY, HIGH);
  setMotorChannel(LEFT_IN1, LEFT_IN2, LEFT_PWM, leftSpeed);
  setMotorChannel(RIGHT_IN1, RIGHT_IN2, RIGHT_PWM, rightSpeed);
  moving = (leftSpeed != 0 || rightSpeed != 0);
  safetyStopped = !moving;
  digitalWrite(LED_RED_PIN, LOW);
  digitalWrite(LED_GREEN_PIN, moving ? HIGH : LOW);
}

void beep(unsigned int durationMs) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(durationMs);
  digitalWrite(BUZZER_PIN, LOW);
}

void sendStatus() {
  float volts = readBusVoltage();
  int percent = batteryPercent(volts);
  Serial2.print("STATUS,battery_v=");
  Serial2.print(volts, 2);
  Serial2.print(",battery_pct=");
  Serial2.print(percent);
  Serial2.print(",moving=");
  Serial2.print(moving ? "1" : "0");
  Serial2.print(",estop=");
  Serial2.print(estopPressed() ? "1" : "0");
  Serial2.print(",bumper_left=");
  Serial2.print(leftBumperPressed() ? "1" : "0");
  Serial2.print(",bumper_right=");
  Serial2.println(rightBumperPressed() ? "1" : "0");
}

void handleCommand(String command) {
  command.trim();
  command.toUpperCase();

  if (command == "STOP" || command == "ESTOP") {
    stopMotors("REMOTE_STOP");
    beep(120);
    return;
  }
  if (command == "STATUS") {
    sendStatus();
    return;
  }
  if (command == "REMINDER") {
    beep(350);
    return;
  }
  if (command.startsWith("DRIVE,")) {
    int comma = command.indexOf(',', 6);
    if (comma < 0) {
      Serial2.println("ERROR,BAD_DRIVE_COMMAND");
      return;
    }
    int leftSpeed = command.substring(6, comma).toInt();
    int rightSpeed = command.substring(comma + 1).toInt();
    lastMotionCommandMs = millis();
    driveMotors(leftSpeed, rightSpeed);
    return;
  }
  Serial2.println("ERROR,UNKNOWN_COMMAND");
}

void setup() {
  pinMode(LEFT_IN1, OUTPUT);
  pinMode(LEFT_IN2, OUTPUT);
  pinMode(RIGHT_IN1, OUTPUT);
  pinMode(RIGHT_IN2, OUTPUT);
  pinMode(MOTOR_STBY, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(ESTOP_PIN, INPUT_PULLUP);
  pinMode(BUMPER_LEFT_PIN, INPUT);
  pinMode(BUMPER_RIGHT_PIN, INPUT);

  ledcAttach(LEFT_PWM, 20000, 8);
  ledcAttach(RIGHT_PWM, 20000, 8);
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, PI_UART_RX, PI_UART_TX);

  stopMotors("BOOT");
  Serial2.println("READY,ESP32_SAFETY_CONTROLLER");
}

void loop() {
  if (hazardPresent() && moving) stopMotors("SAFETY_INPUT");
  if (moving && millis() - lastMotionCommandMs > COMMAND_TIMEOUT_MS) stopMotors("COMMAND_TIMEOUT");

  while (Serial2.available()) {
    char c = (char)Serial2.read();
    if (c == '\n' || c == '\r') {
      if (commandBuffer.length() > 0) {
        handleCommand(commandBuffer);
        commandBuffer = "";
      }
    } else if (commandBuffer.length() < 80) {
      commandBuffer += c;
    } else {
      commandBuffer = "";
      Serial2.println("ERROR,COMMAND_TOO_LONG");
    }
  }

  if (millis() - lastStatusMs >= 1000) {
    lastStatusMs = millis();
    sendStatus();
    float volts = readBusVoltage();
    if (volts > 0.0f && volts < 6.8f) {
      Serial2.println("EVENT,LOW_BATTERY");
      if (moving) stopMotors("LOW_BATTERY");
    }
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik