edge lab LIVE! starts Tuesday in Munich. Grab a workshop seat and take home the board you build on. Code FRIEND-HACKSTER saves 20%. () Learn more!
amrin karodanSana FathimaSwetha S Pai
Published © GPL3+

Smart Drive-THRU

AI powered drive through system that recognises vehicle number plates to instantly retrieve order, reducing wait time and queues.

BeginnerFull instructions provided10 hours58
Smart Drive-THRU

Things used in this project

Hardware components

Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1
Jumper wires (generic)
Jumper wires (generic)
×3
Grove - OLED Display 0.96"
Seeed Studio Grove - OLED Display 0.96"
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1

Software apps and online services

VS Code
Microsoft VS Code
PlatformIO IDE
PlatformIO IDE

Hand tools and fabrication machines

foam board
Hot glue gun (generic)
Hot glue gun (generic)
car

Story

Read more

Schematics

Drive-TRU

Code

RESTLOGIN

JavaScript
import { app } from "./firebaseconfig.js";

import {
    getAuth,
    signInWithEmailAndPassword
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-auth.js";

const auth = getAuth(app);

document.getElementById("loginBtn").addEventListener("click", async () => {

    const email = document.getElementById("email").value.trim();
    const password = document.getElementById("password").value;

    try {

        await signInWithEmailAndPassword(auth, email, password);

        alert("Restaurant Login Successful");

        window.location.href = "dashboard.html";

    } catch (error) {

        alert(error.message);

    }

});

TRACKER

JavaScript
import { db } from "./firebaseconfig.js";

import {
    collection,
    query,
    where,
    onSnapshot
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

// Get vehicle number stored during checkout
const plate = localStorage.getItem("userLicensePlate");

console.log("Customer Plate:", plate);

if (!plate) {
    alert("Vehicle number not found.");
    throw new Error("No vehicle number found.");
}

// Listen for this customer's latest order
const q = query(
    collection(db, "orders"),
    where("vehicleNumber", "==", plate)
);

onSnapshot(q, (snapshot) => {

    console.log("Snapshot Size:", snapshot.size);

    if (snapshot.empty) {
        console.log("No order found.");
        return;
    }

    snapshot.forEach((document) => {

        const order = document.data();

        console.log("Realtime Order:", order);

        updateTrackingUI(order);

    });

}, (error) => {

    console.error("Firestore Error:", error);

});

function updateTrackingUI(order) {

    document.getElementById("display-plate").innerText = order.vehicleNumber;
    document.getElementById("display-id").innerText = "#" + order.vehicleNumber;

    const progressBar = document.getElementById("progressBar");

    const step2 = document.getElementById("step2");
    const step3 = document.getElementById("step3");
    const step4 = document.getElementById("step4");
    const step5 = document.getElementById("step5");

    const statusTitle = document.getElementById("statusTitle");
    const statusSubtitle = document.getElementById("statusSubtitle");

    const lockerBox = document.getElementById("lockerBox");
    const lockerText = document.getElementById("lockerText");

    // Reset
    progressBar.style.width = "0%";

    step2.className = "timeline-step";
    step3.className = "timeline-step";
    step4.className = "timeline-step";
    step5.className = "timeline-step";

    document.getElementById("step2Icon").innerText = "2";
    document.getElementById("step3Icon").innerText = "3";
    document.getElementById("step4Icon").innerText = "4";
    document.getElementById("step5Icon").innerText = "5";

    lockerBox.style.display = "none";

    switch (order.status) {

        case "Pending":

            progressBar.style.width = "0%";

            statusTitle.innerText = "📝 Order Received";
            statusSubtitle.innerText =
                "Your order has been placed successfully and is waiting for the kitchen.";

            break;

        case "Preparing":

            progressBar.style.width = "25%";

            step2.className = "timeline-step active";

            statusTitle.innerText = "👨‍🍳 Preparing Your Food";
            statusSubtitle.innerText =
                "Our chefs are preparing your order.";

            break;

        case "Ready":

            progressBar.style.width = "50%";

            step2.className = "timeline-step completed";
            step3.className = "timeline-step active";

            document.getElementById("step2Icon").innerText = "✔";

            statusTitle.innerText = "🍔 Order Ready";
            statusSubtitle.innerText =
                "Your food is ready. Please drive to the pickup lane.";

            lockerBox.style.display = "block";
            lockerText.innerText =
                "Pickup Locker : " + (order.locker || "Locker 1");

            break;

        case "Arrived":

            progressBar.style.width = "75%";

            step2.className = "timeline-step completed";
            step3.className = "timeline-step completed";
            step4.className = "timeline-step active";

            document.getElementById("step2Icon").innerText = "✔";
            document.getElementById("step3Icon").innerText = "✔";

            statusTitle.innerText = "🚗 Vehicle Detected";
            statusSubtitle.innerText =
                "Welcome! Your vehicle has been detected.";

            lockerBox.style.display = "block";
            lockerText.innerText =
                "Locker " + (order.locker || "1") + " Opening...";

            break;

        case "Collected":

            progressBar.style.width = "100%";

            step2.className = "timeline-step completed";
            step3.className = "timeline-step completed";
            step4.className = "timeline-step completed";
            step5.className = "timeline-step active";

            document.getElementById("step2Icon").innerText = "✔";
            document.getElementById("step3Icon").innerText = "✔";
            document.getElementById("step4Icon").innerText = "✔";

            statusTitle.innerText = "✅ Order Collected";
            statusSubtitle.innerText =
                "Thank you for using Smart Drive-Thru.";

            break;

        default:

            statusTitle.innerText = order.status;
            statusSubtitle.innerText = "";

    }

}

CSS

Python
:root{
    --primary-black:#111111;
    --secondary-black:#1E1E1E;
    --pure-white:#FFFFFF;
    --text-muted:#B3B3B3;
    --accent-orange:#FF6F00;
    --accent-yellow:#FFCA28;
    --transition-speed:.3s;
}

/* ---------- Global ---------- */

*{
    margin:0;
    padding:0;
    box-sizing:border-box;
}

body{

    font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
    background:var(--primary-black);
    color:var(--pure-white);

}

/* ---------- Generic Container ---------- */

.container{

    width:90%;
    max-width:1200px;
    margin:auto;
    padding:30px;

}

/* ---------- Headings ---------- */

h1{

    color:var(--accent-orange);

    font-size:48px;

    margin-bottom:20px;

}

h2{

    margin-bottom:15px;

}

p{

    color:var(--text-muted);

    line-height:1.6;

}

/* ---------- Inputs ---------- */

input,
select{

    width:100%;
    padding:14px;

    border:none;
    border-radius:10px;

    font-size:16px;

    margin:10px 0;

    background:white;

    color:black;

}

/* ---------- Buttons ---------- */

button{

    padding:14px 24px;

    border:none;

    border-radius:10px;

    cursor:pointer;

    font-size:16px;

    font-weight:bold;

    background:var(--accent-orange);

    color:white;

    transition:.3s;

}

button:hover{

    background:#e55f00;

}

/* ---------- Links ---------- */

a{

    color:var(--accent-orange);

    text-decoration:none;

}

/* ---------- Cards ---------- */

.card{

    background:white;

    color:black;

    padding:20px;

    border-radius:12px;

    margin:15px 0;

    box-shadow:0 5px 15px rgba(0,0,0,.3);

}

/* ---------- Dashboard Statistics ---------- */

.stats-container{

    display:grid;

    grid-template-columns:repeat(4,1fr);

    gap:20px;

    margin:30px 0;

}

.stat-card{

    background:var(--secondary-black);

    border:1px solid #333;

    border-radius:15px;

    padding:25px;

    text-align:center;

}

.stat-card h2{

    color:var(--accent-orange);

    font-size:42px;

    margin-bottom:10px;

}

.stat-card p{

    color:white;

    font-size:18px;

    font-weight:bold;

}

/* ---------- Responsive ---------- */

@media(max-width:900px){

.stats-container{

grid-template-columns:repeat(2,1fr);

}

}

@media(max-width:600px){

.stats-container{

grid-template-columns:1fr;

}

CART

JavaScript
let cart = JSON.parse(localStorage.getItem("cart")) || [];

const cartContainer = document.getElementById("cartItemsContainer");

function loadCart() {

    cart = JSON.parse(localStorage.getItem("cart")) || [];

    cartContainer.innerHTML = "";

    if(cart.length === 0){

        cartContainer.innerHTML = `
            <h2 style="color:white">
                Your Cart is Empty
            </h2>
        `;

        updateTotal();
        return;
    }

    cart.forEach((item,index)=>{

        if(!item.quantity){
            item.quantity = 1;
        }

        cartContainer.innerHTML += `

        <div class="cart-item">

            <div class="cart-item-details">

                <h3 class="cart-item-name">
                    ${item.name}
                </h3>

                <p class="cart-item-price">
                    ₹${item.price}
                </p>

            </div>

            <div class="cart-item-controls">

                <button class="qty-btn"
                    onclick="changeQty(${index},-1)">
                    -
                </button>

                <span class="qty-display">
                    ${item.quantity}
                </span>

                <button class="qty-btn"
                    onclick="changeQty(${index},1)">
                    +
                </button>

                <button class="remove-btn"
                    onclick="removeItem(${index})">
                    Remove
                </button>

            </div>

        </div>

        `;
    });

    localStorage.setItem("cart",JSON.stringify(cart));

    updateTotal();

}

window.changeQty=function(index,value){

    cart[index].quantity += value;

    if(cart[index].quantity<=0){

        cart.splice(index,1);

    }

    localStorage.setItem("cart",JSON.stringify(cart));

    loadCart();

}

window.removeItem=function(index){

    cart.splice(index,1);

    localStorage.setItem("cart",JSON.stringify(cart));

    loadCart();

}

function updateTotal(){

    let subtotal=0;

    cart.forEach(item=>{

        subtotal += Number(item.price) * item.quantity;

    });

    const tax = subtotal*0.08;

    const total=subtotal+tax;

    document.querySelectorAll(".summary-line span")[1].innerText =
        "₹"+subtotal.toFixed(2);

    document.querySelectorAll(".summary-line span")[3].innerText =
        "₹"+tax.toFixed(2);

    document.querySelector(".summary-total span:last-child").innerText =
        "₹"+total.toFixed(2);

}

loadCart();

AUTH

JavaScript
import { app, db } from "./firebaseconfig.js";

import {
  getAuth,
  createUserWithEmailAndPassword
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-auth.js";

import {
  doc,
  setDoc
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

const auth = getAuth(app);

const signupButton = document.getElementById("signupBtn");

signupButton.addEventListener("click", signup);

async function signup() {

    const name = document.getElementById("name").value.trim();
    const email = document.getElementById("email").value.trim();
    const phone = document.getElementById("phone").value.trim();
    const password = document.getElementById("password").value;

    // Simple validation
    if (!name || !email || !phone || !password) {
        alert("Please fill in all fields.");
        return;
    }

   try {

    console.log("Step 1");

    const userCredential = await createUserWithEmailAndPassword(
        auth,
        email,
        password
    );

    console.log("Step 2");

    const user = userCredential.user;

    await setDoc(doc(db, "users", user.uid), {
        name: name,
        email: email,
        phone: phone,
        role: "customer",
        createdAt: new Date().toISOString()
    });

    console.log("Step 3");

    alert("Account Created Successfully!");

    console.log("Step 4");

} catch (error) {

    console.error("ERROR:", error);
    alert(error.message);

}
}

CHECKOUT

JavaScript
import { db } from "./firebaseconfig.js";

import {
    collection,
    addDoc
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

console.log("Checkout JS Loaded");

const vehicleInput = document.getElementById("vehicleNumber");
const arrivalInput = document.getElementById("arrivalTime");

const subtotalDisplay = document.getElementById("subtotal-display");
const grandTotal = document.getElementById("grand-total");

const placeOrderBtn = document.getElementById("placeOrderBtn");

let cart = JSON.parse(localStorage.getItem("cart")) || [];

let subtotal = 0;

cart.forEach(item=>{

    subtotal += Number(item.price) * Number(item.quantity || 1);

});

let addonsTotal = 0;

const addonCheckboxes =
document.querySelectorAll(".premium-addon");

function updateGrandTotal(){

    addonsTotal = 0;

    addonCheckboxes.forEach(box=>{

        if(box.checked){

            addonsTotal += Number(box.dataset.price);

        }

    });

    subtotalDisplay.innerText =
        "₹"+subtotal.toFixed(2);

    grandTotal.innerText =
        "₹"+(subtotal+addonsTotal).toFixed(2);

}

addonCheckboxes.forEach(box=>{

    box.addEventListener("change",updateGrandTotal);

});

updateGrandTotal();



placeOrderBtn.addEventListener("click", async () => {

    const vehicleNumber = vehicleInput.value.trim().toUpperCase();
    const arrivalTime = arrivalInput.value;

if (!arrivalTime) {

    alert("Select arrival time");

    return;

}

let maxCookTime = 0;

cart.forEach(item => {

    if (item.cookTime && Number(item.cookTime) > maxCookTime) {

        maxCookTime = Number(item.cookTime);

    }

});

// Convert HH:MM to minutes
const [hours, minutes] = arrivalTime.split(":").map(Number);

const arrivalMinutes = (hours * 60) + minutes;

let recommendedStart = arrivalMinutes - maxCookTime;

if (recommendedStart < 0) {

    recommendedStart = 0;

}
    

    const selectedAddons = [];

addonCheckboxes.forEach(box => {

    if (box.checked) {

        selectedAddons.push({
            name: box.parentElement.innerText.trim(),
            price: Number(box.dataset.price)
        });

    }

});

    const order = {

    vehicleNumber,

    items: cart,

    total: subtotal + addonsTotal,

    addons: selectedAddons,

    arrivalTime,

    maxCookTime,

    recommendedStart,

    status: "Pending",

    locker: null,

    createdAt: new Date().toISOString()

};

    console.log(order);

    try{

        const docRef=await addDoc(
            collection(db,"orders"),
            order
        );
        console.log("Order Saved");

        console.log("Order Saved:",docRef.id);

        localStorage.setItem(
            "userLicensePlate",
            vehicleNumber
        );

        localStorage.setItem(
            "currentOrderId",
            docRef.id
        );

        // Empty the cart completely
// Clear cart after successful order
cart = [];

localStorage.removeItem("cart");
localStorage.removeItem("cartItems");

console.log("Cart Cleared");

alert("Order Placed Successfully");

window.location.href = "track.html";

    }

    catch(err){

        console.error(err);

        alert(err.message);

    }

});

DASHBOARD

JavaScript
import { db } from "./firebaseconfig.js";

import {
    collection,
    onSnapshot,
    doc,
    updateDoc,
    deleteDoc
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

console.log("Dashboard Loaded");

const container = document.getElementById("dashboardOrdersContainer");

onSnapshot(collection(db, "orders"), (snapshot) => {

    container.innerHTML = "";

    // Statistics
    let pending = 0;
    let preparing = 0;
    let ready = 0;
    let completed = 0;

    if (snapshot.empty) {

        container.innerHTML =
            "<h2 style='color:white;text-align:center;'>No Orders Yet</h2>";

        document.getElementById("pendingCount").innerText = 0;
        document.getElementById("preparingCount").innerText = 0;
        document.getElementById("readyCount").innerText = 0;
        document.getElementById("completedCount").innerText = 0;

        return;
    }

    snapshot.forEach((document) => {

        const order = document.data();
        const orderId = document.id;

        // Count statistics
        switch (order.status) {

            case "Pending":
                pending++;
                break;

            case "Preparing":
                preparing++;
                break;

            case "Ready":
                ready++;
                break;

            case "Collected":
                completed++;
                break;
        }

        let actionButton = "";

        if (order.status === "Pending") {

            actionButton = `
                <button class="action-btn"
                    onclick="updateStatus('${orderId}','Preparing')">
                    Start Preparing
                </button>
            `;

        }

        else if (order.status === "Preparing") {

            actionButton = `
                <button class="action-btn"
                    onclick="updateStatus('${orderId}','Ready')">
                    Mark Ready
                </button>
            `;

        }

        else if (order.status === "Ready") {

            actionButton = `
                <div style="color:#FFD700;font-weight:bold;text-align:center;">
                    🚗 Waiting for Vehicle...
                </div>
            `;

        }

        else if (order.status === "Arrived") {

            actionButton = `
                <div style="color:#00ff66;font-weight:bold;text-align:center;">
                    🔓 Locker Opening...
                    <br>
                    Waiting for Servo...
                </div>
            `;

        }

        else {

            actionButton = `
                <div style="color:lime;font-weight:bold;text-align:center;">
                    ✅ Order Completed
                </div>
            `;

        }

        let itemsHTML = "";

        if (Array.isArray(order.items)) {

            order.items.forEach(item => {

                itemsHTML += `
                    <li>${item.name} - ₹${item.price}</li>
                `;

            });

        }

        container.innerHTML += `

        <div class="order-card">

            <h2>🚗 ${order.vehicleNumber}</h2>

            <p><strong>Status:</strong> ${order.status}</p>

            <p><strong>Arrival:</strong> ${order.arrivalTime}</p>

            <p><strong>Recommended Start:</strong>
            ${order.recommendedStart} min</p>

            <p><strong>Locker:</strong>
            ${order.locker || "Locker 1"}</p>

            <ul>
                ${itemsHTML}
            </ul>

            <h3>Total ₹${order.total}</h3>

            ${actionButton}

<button
class="action-btn"
style="
margin-top:10px;
background:#d32f2f;
"
onclick="deleteOrder('${orderId}')">

🗑 Remove Order

</button>

        </div>

        `;

    });

    // Update statistics
    document.getElementById("pendingCount").innerText = pending;
    document.getElementById("preparingCount").innerText = preparing;
    document.getElementById("readyCount").innerText = ready;
    document.getElementById("completedCount").innerText = completed;

});

window.updateStatus = async function(orderId, newStatus) {

    try {

        let updateData = {
            status: newStatus
        };

        if (newStatus === "Ready") {
            updateData.locker = "Locker 1";
        }

        await updateDoc(
            doc(db, "orders", orderId),
            updateData
        );

        console.log("Updated to", newStatus);

    }

    catch (err) {

        console.error(err);

    }

};
window.deleteOrder = async function(orderId){

    const confirmDelete = confirm(
        "Delete this order?"
    );

    if(!confirmDelete) return;

    try{

        await deleteDoc(
            doc(db,"orders",orderId)
        );

        console.log("Order deleted");

    }

    catch(err){

        console.error(err);

    }

}

FIREBASECONFIG

JavaScript
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.0.0/firebase-app.js";

import { getFirestore } from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

const firebaseconfig = {
    apiKey: "AIzaSyD7CJF3yVa9BG3Vg1qLxprrAym8_EQwkH8",
    authDomain: "drive-thru-eb0a1.firebaseapp.com",
    projectId: "drive-thru-eb0a1",
    storageBucket: "drive-thru-eb0a1.firebasestorage.app",
    messagingSenderId: "849831589482",
    appId: "1:849831589482:web:3e7e3648fd58fe398c7c13"
};

const app = initializeApp(firebaseconfig);

const db = getFirestore(app);

export { app, db };

LOGIN

JavaScript
import { app } from "./firebaseconfig.js";

import {
    getAuth,
    signInWithEmailAndPassword
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-auth.js";

const auth = getAuth(app);

const loginButton = document.getElementById("loginBtn");

loginButton.addEventListener("click", login);

async function login() {

    const email = document.getElementById("email").value.trim();
    const password = document.getElementById("password").value;

    if (!email || !password) {
        alert("Please enter your email and password.");
        return;
    }

    try {

        await signInWithEmailAndPassword(auth, email, password);

        // Clear previous user's session data
        localStorage.clear();

        alert("Login Successful!");

        window.location.href = "menu.html";

    } catch (error) {

        console.error(error);
        alert(error.message);

    }

}

MENU

JavaScript
import { db } from "./firebaseconfig.js";

import {
    collection,
    getDocs
} from "https://www.gstatic.com/firebasejs/12.0.0/firebase-firestore.js";

const menu = [];
const menuContainer = document.getElementById("menuContainer");

// Always start by reading the current cart from localStorage
let cart = JSON.parse(localStorage.getItem("cart")) || [];

document.getElementById("cartCount").innerText = cart.length;

async function loadMenu() {

    menu.length = 0;

    const querySnapshot = await getDocs(collection(db, "menu"));

    querySnapshot.forEach((doc) => {

        menu.push({
            id: doc.id,
            ...doc.data()
        });

    });

    displayMenu();
}

function displayMenu() {

    menuContainer.innerHTML = "";

    menu.forEach(item => {

        menuContainer.innerHTML += `

        <div class="menu-item">

            <img
                src="${item.image}"
                class="menu-image"
                alt="${item.name}"
            >

            <div class="menu-details">

                <h3 class="menu-name">
                    ${item.name}
                </h3>

                <p>
                    ${item.description}
                </p>

                <h4 class="menu-price">
                    ₹${item.price}
                </h4>

                <button
                    class="add-to-cart-btn"
                    onclick="addToCart('${item.id}')">

                    Add to Cart

                </button>

            </div>

        </div>

        `;

    });

}

window.addToCart = function(id){

    let cart = JSON.parse(localStorage.getItem("cart")) || [];

    const item = menu.find(food => food.id === id);

    const existing = cart.find(x => x.id === id);

    if(existing){

        existing.quantity++;

    }else{

        item.quantity = 1;

        cart.push(item);

    }

    localStorage.setItem("cart",JSON.stringify(cart));

    document.getElementById("cartCount").innerText = cart.length;

    alert(item.name + " added to cart");

};

document.getElementById("cartBtn").addEventListener("click", () => {

    window.location.href = "cart.html";

});

loadMenu();

Credits

amrin karodan
1 project • 3 followers
Sana Fathima
1 project • 2 followers
Swetha S Pai
1 project • 2 followers
Electrical Engineering student passionate about AI, IoT, embedded systems, and building smart solutions through innovation.

Comments