ZSZaid Saifan

Projects / Hospital Delivery Robot

Western University · Team design project

Hospital Autonomous Secure Delivery Robot

A delivery robot for moving supplies between hospital departments. It drives itself down a corridor, stops before it hits anything, and only opens its cargo box for an authorized RFID card.

My roleElectrical architecture, PCB shield, control integration
  • Altium Designer
  • Arduino C++
  • PCB assembly
  • RFID
  • Power rails
Fig. 03D render of the custom shield in Altium. Every header is labelled on the silkscreen.

BriefThe problem

Hospitals move medication, samples and supplies between departments all day. The work is repetitive and time-sensitive, and it carries a chain-of-custody requirement: the cargo can only go to the right person.

Our brief was a vehicle that drives a corridor on its own, refuses to run into anything, and will not release its cargo without authorization.

PCB designOff the breadboard, onto a custom shield

I designed the electrical architecture as a single-layer Altium PCB shield that stacks directly onto an Arduino Uno. Every subsystem gets its own labelled header: 5 V power, RFID, ultrasonic, motor driver, Bluetooth, temperature sensor, servo and LCD, plus an onboard push button with a 10 kΩ pull-down.

Labelling every connector on the silkscreen meant the harness could only go together one way, which removed a whole class of wiring mistakes during integration.

PowerTwo rails from one battery

  • One 11.1 V LiPo feeds two buck converters: a 5.1 V logic rail and a 7.9 V motor rail.
  • Keeping the rails separate holds motor switching noise off the sensors and the I²C bus.
  • Stable ultrasonic readings are what made the obstacle stop safe to rely on.

Control loopSensing and actuation

Five sensors and four actuator systems run in one microcontroller control loop:

  • HC-SR04 ultrasonic measures range ahead and stops the robot inside 20 cm.
  • RC522 RFID reader unlocks the cargo compartment only for an authorized tag.
  • HC-05 Bluetooth carries commands and telemetry.
  • DHT11 logs temperature and humidity inside the cargo box in transit.
  • A 16×2 LCD shows status, two DC motors run through an L298N, and a servo drives the latch.

Remote controlDriving it from a PS5 controller

A Python script on a laptop reads a PS5 controller with pygame and streams drive commands to the robot over the HC-05 Bluetooth link using pyserial at 9600 baud.

  • R2 is the throttle, L2 brakes and reverses, and the left stick steers, with a 10% deadzone so the robot holds a straight line.
  • Throttle and steering are mixed into separate left and right motor commands from −255 to 255, sent as a left,right line. The controller is polled every 20 ms, and a command is only sent when it changes, which keeps the Bluetooth link quiet.
  • Pressing ✕ toggles autopilot. The script sends AUTO or MAN to the robot and stops sending stick input while autopilot is on.
  • Quitting with Ctrl+C sends 0,0 first, so the motors stop instead of holding their last command.
ps5_bluetooth_controller.pyDownload .py
import pygame
import serial
import time

# ── Configuration ──────────────────────────────────────────────
BLUETOOTH_PORT = 'COM5'  # <--- CHANGE TO YOUR PORT
BAUD_RATE = 9600
UPDATE_RATE = 0.02
DEADZONE = 0.1

try:
    bt = serial.Serial(BLUETOOTH_PORT, BAUD_RATE, timeout=1)
    print("Bluetooth connected successfully!")
except Exception as e:
    print(f"Error connecting to Bluetooth: {e}")
    exit()

pygame.init()
pygame.joystick.init()

if pygame.joystick.get_count() == 0:
    print("No controller detected.")
    exit()

controller = pygame.joystick.Joystick(0)
controller.init()
print(f"Controller Connected: {controller.get_name()}")

# PS5 Controller Mappings
AXIS_STEER = 0
AXIS_L2 = 4
AXIS_R2 = 5
BTN_CROSS = 0  # The 'X' Button on PS5

last_command = ""
auto_mode = False
last_btn_state = False

print("\n--- PS5 Controls Active ---")
print("R2: Gas | L2: Brake/Reverse | L3: Steer")
print("Press 'X' to toggle Autopilot!")

try:
    while True:
        pygame.event.pump()

        # --- AUTOPILOT TOGGLE LOGIC ---
        btn_state = controller.get_button(BTN_CROSS)
        if btn_state and not last_btn_state:  # Button was just pushed down
            auto_mode = not auto_mode  # Toggle the state

            if auto_mode:
                bt.write(b"AUTO\n")
                print("\n[!] AUTOPILOT ENGAGED [!]\n")
            else:
                bt.write(b"MAN\n")
                print("\n[-] MANUAL CONTROL RESTORED [-]\n")

        last_btn_state = btn_state

        # --- MANUAL DRIVING LOGIC ---
        # We only calculate and send joystick data if Autopilot is OFF
        if not auto_mode:
            steer_axis = controller.get_axis(AXIS_STEER)
            l2_axis = controller.get_axis(AXIS_L2)
            r2_axis = controller.get_axis(AXIS_R2)

            if abs(steer_axis) < DEADZONE:
                steer_axis = 0.0

            brake = max(0.0, (l2_axis + 1.0) / 2.0)
            gas = max(0.0, (r2_axis + 1.0) / 2.0)

            throttle = gas - brake

            left_motor = throttle + steer_axis
            right_motor = throttle - steer_axis

            left_motor = max(-1.0, min(1.0, left_motor))
            right_motor = max(-1.0, min(1.0, right_motor))

            left_pwm = int(left_motor * 255)
            right_pwm = int(right_motor * 255)

            command = f"{left_pwm},{right_pwm}\n"

            if command != last_command:
                bt.write(command.encode('utf-8'))
                last_command = command
                # Only print the dashboard occasionally so it doesn't flood the terminal
                print(f"\rMANUAL >> L2: {brake:.2f} | R2: {gas:.2f} | Motor L: {left_pwm:4d} | Motor R: {right_pwm:4d}",
                      end="")

        time.sleep(UPDATE_RATE)

except KeyboardInterrupt:
    print("\nExiting. Stopping car...")
    bt.write(b"0,0\n")
    bt.close()
    pygame.quit()
CodeLaptop-side controller script: PS5 input → left/right motor commands over Bluetooth serial, with an autopilot toggle on ✕.

IntegrationBuild and debug

I specified the components, drew the schematics and kept the full bill of materials. I then hand-soldered and assembled the shield, and used a multimeter to trace each wiring and integration fault back to its root cause until every subsystem test passed.

ValidationResults

45 mincontinuous runtime on one 11.1 V LiPo
100%integration tests passed
< 20 cmverified obstacle-stop distance

FiguresBoard, wiring and build

Select any figure to open it full size.

05Contact

Let's build something.

I'm looking for 12–16 month co-op and internship terms starting May 2027 in power and electrical design, controls, robotics, embedded systems or hardware test.