Skip to main content

Quick Start Guide

This guide will walk you through setting up your first parking spot sensor, from hardware assembly to seeing live data on the web dashboard.
Time Required: ~30 minutes
Difficulty: Intermediate (basic electronics and cloud platform knowledge required)

Prerequisites

Before you begin, ensure you have:
1

Hardware Components

  • ESP32 development board (any variant)
  • Adafruit VL53L0X Time-of-Flight laser distance sensor
  • RGB LED (common anode) with appropriate resistors
  • Breadboard and jumper wires
  • USB cable for programming
2

Software Tools

  • Arduino IDE or PlatformIO
  • Node.js v20+ and npm
  • Google Cloud SDK installed and configured
  • Firebase CLI (npm install -g firebase-tools)
3

Cloud Accounts

  • Google Cloud Platform account with billing enabled
  • Firebase project created

Step 1: Deploy the Backend Services

1.1 Deploy Cloud Run Service (Ingest Data)

First, deploy the data ingestion service that receives sensor readings:
# Navigate to the ingest service directory
cd gcp-functions/services_ingest-parking-data_*

# Build and deploy to Cloud Run
gcloud builds submit --tag gcr.io/YOUR-PROJECT-ID/ingest-parking-data

gcloud run deploy ingest-parking-data \
  --image gcr.io/YOUR-PROJECT-ID/ingest-parking-data \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated
Save the Cloud Run URL from the output - you’ll need it for the firmware configuration.

1.2 Deploy Cloud Functions

Deploy the supporting Cloud Functions for status retrieval and reservations:
# Deploy get-parking-status function
cd gcp-functions/get-parking-status_function-source
gcloud functions deploy get-parking-status \
  --runtime nodejs20 \
  --trigger-http \
  --allow-unauthenticated

# Deploy reserve-parking-spot function
cd ../reserve-parking-spot_function-source
gcloud functions deploy reserve-parking-spot \
  --runtime nodejs20 \
  --trigger-http \
  --allow-unauthenticated

# Deploy release-parking-spot function
cd ../release-parking-spot_function-source
gcloud functions deploy release-parking-spot \
  --runtime nodejs20 \
  --trigger-http \
  --allow-unauthenticated
In production, remove --allow-unauthenticated and implement proper authentication using Firebase Auth or API keys.

1.3 Initialize Firestore Database

The backend services automatically create collections on first write, but you can manually create them:
# Initialize Firestore in Native mode
gcloud firestore databases create --location=us-central1
The system uses these collections:
  • parking_spots - Current state of all parking spaces
  • parking_zones - Zone definitions for organizing spots
  • hourly_snapshots - Historical occupancy data for analytics

Step 2: Hardware Assembly

2.1 Wiring Diagram

Connect the components to your ESP32: VL53L0X Sensor (I2C):
  • VCC → 3.3V
  • GND → GND
  • SDA → GPIO 21 (default I2C)
  • SCL → GPIO 22 (default I2C)
RGB LED (Common Anode):
  • Common pin → 3.3V
  • Red → GPIO 13 (via 220Ω resistor)
  • Green → GPIO 12 (via 220Ω resistor)
  • Blue → GPIO 14 (via 220Ω resistor)
The firmware uses common anode logic where LOW = ON. If using common cathode, modify the setColor() function in the firmware.

2.2 Install Required Libraries

In Arduino IDE, install these libraries via Library Manager:
- Adafruit VL53L0X
- ArduinoJson (v6+)
- WiFi (included with ESP32 board support)
- HTTPClient (included with ESP32 board support)

Step 3: Configure and Flash Firmware

3.1 Create Secrets File

Copy the example secrets file and configure it with your credentials:
cd firmware/S-Parking
cp arduino_secrets.example.h arduino_secrets.h
Edit arduino_secrets.h with your actual values:
#pragma once

// --- WIFI CREDENTIALS ---
#define SECRET_SSID "Your_WiFi_Network"
#define SECRET_PASS "Your_WiFi_Password"

// --- GOOGLE CLOUD URLS ---

// 1. URL to SEND data (from Step 1.1)
#define SECRET_GCP_URL_INGEST "https://ingest-parking-data-xxxxx-uc.a.run.app"

// 2. URL to READ status (from Step 1.2)
#define SECRET_GCP_URL_GET "https://us-central1-YOUR-PROJECT.cloudfunctions.net/get-parking-status"

// 3. Unique Spot Identifier (choose any ID like "A-01", "NORTH-12", etc.)
#define SECRET_SPOT_ID "A-01"
The SECRET_SPOT_ID must be unique for each sensor. Use a logical naming scheme like zone-number (e.g., “A-01”, “B-15”).

3.2 Flash the Firmware

Open S-Parking.ino in Arduino IDE:
  1. Select your ESP32 board from Tools → Board
  2. Select the correct COM port from Tools → Port
  3. Click Upload (or press Ctrl+U)

3.3 Monitor Serial Output

Open Serial Monitor (115200 baud) to verify the connection:
Conectando a WiFi: Your_WiFi_Network
........
¡WiFi Conectado!
IP asignada: 192.168.1.100
Cambio físico detectado: 1
Enviado a ingest. Codigo: 200
HTTP 200 response means the data was successfully sent to Cloud Run and written to Firestore.

Step 4: Configure the Web Dashboard

4.1 Set Up Configuration File

Navigate to the web dashboard directory:
cd web-dashboard/js/config
cp config.example.js config.js
Edit config.js with your API endpoints:
export const CONFIG = {
    DEBUG: true,
    
    PERFORMANCE: {
        POLLING_INTERVAL: 20000,         // Poll every 20 seconds
        CACHE_PARKING_STATUS: 15000,     // Cache for 15 seconds
        LAZY_RENDER: true
    },
    
    // Google Maps Credentials
    GOOGLE_MAPS_API_KEY: "YOUR_MAPS_API_KEY_HERE", 
    GOOGLE_MAPS_ID: "YOUR_MAP_ID_HERE",
    
    // Backend URLs (from Step 1)
    GET_STATUS_API_URL: "https://us-central1-YOUR-PROJECT.cloudfunctions.net/get-parking-status",
    RESERVATION_API_URL: "https://us-central1-YOUR-PROJECT.cloudfunctions.net/reserve-parking-spot",
    RELEASE_API_URL: "https://us-central1-YOUR-PROJECT.cloudfunctions.net/release-parking-spot",
    CREATE_SPOT_URL: "https://us-central1-YOUR-PROJECT.cloudfunctions.net/create-parking-spot",
    DELETE_SPOT_URL: "https://us-central1-YOUR-PROJECT.cloudfunctions.net/delete-parking-spot",
    
    // Firebase Config
    FIREBASE: {
        apiKey: "YOUR_FIREBASE_API_KEY",
        authDomain: "your-project.firebaseapp.com",
        projectId: "your-project-id",
        storageBucket: "your-project.appspot.com",
        messagingSenderId: "123456789012",
        appId: "1:123456789012:web:abcdef123456"
    }
};

4.2 Deploy to Firebase Hosting

# From the web-dashboard directory
firebase login
firebase init hosting

# Deploy
firebase deploy --only hosting
Your dashboard will be live at https://YOUR-PROJECT.web.app

Step 5: Test the System

1

Access the Dashboard

Open your Firebase Hosting URL in a browser. You should see the Google Maps interface.
2

Verify Sensor Data

The sensor you configured (e.g., “A-01”) should appear on the map automatically when it sends its first reading.LED Behavior:
  • 🟢 Green: Spot is available (no vehicle detected)
  • 🔴 Red: Spot is occupied (vehicle within 400mm)
  • 🟡 Amber (Red+Green): Spot is reserved but vacant
3

Trigger Occupancy Change

Place your hand or an object within 40cm (400mm) of the VL53L0X sensor. Within 500ms, you should see:
  1. LED turns red immediately
  2. Serial monitor shows: Cambio físico detectado: 0
  3. Dashboard updates to show spot as occupied (within 2 seconds)
4

Test Self-Healing

Simulate a network failure:
  1. Temporarily disconnect WiFi router
  2. Change sensor state (cover/uncover)
  3. Reconnect WiFi
  4. Sensor will poll cloud state and detect desynchronization
  5. Automatic correction message: ¡Desincronización detectada! Forzando actualización...

Understanding Sensor Logic

The ESP32 firmware implements intelligent state management:

Detection Threshold

if (measure.RangeStatus != 4 && measure.RangeMilliMeter < 400) {
    nuevoEstadoSensor = 0; // Occupied
} else {
    nuevoEstadoSensor = 1; // Available
}
400mm threshold means any object closer than 40cm triggers occupancy. Adjust this based on your sensor mounting height.

State Synchronization

The sensor reads locally every 500ms but only sends updates on state changes:
if (nuevoEstadoSensor != estadoLocalSensor) {
    estadoLocalSensor = nuevoEstadoSensor;
    sendStateToCloud(estadoLocalSensor);
}
Every 15 seconds, it polls the cloud to verify consistency:
if (estadoLocalSensor == 1 && estadoNube == 0) {
    Serial.println("¡Desincronización detectada! Forzando actualización a DISPONIBLE...");
    sendStateToCloud(1);
}
This self-healing mechanism ensures the cloud state always reflects physical reality, even after network interruptions.

Troubleshooting

Sensor Not Appearing on Dashboard

  1. Check Serial Monitor for HTTP response codes (should be 200)
  2. Verify SECRET_GCP_URL_INGEST matches your Cloud Run URL exactly
  3. Check Firestore console - a document with your spot_id should exist in parking_spots collection

LED Not Changing Colors

  1. Verify wiring - common anode LEDs require LOW to turn on
  2. Check GPIO pin definitions match your physical connections
  3. Test individual LEDs with simple blink sketch

Dashboard Shows Stale Data

  1. Check browser console for errors (F12)
  2. Verify GET_STATUS_API_URL in config.js
  3. Clear localStorage: localStorage.clear() in browser console
  4. Check Page Visibility API isn’t pausing updates (click on the tab)

WiFi Connection Fails

// The firmware auto-restarts on WiFi failure:
if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Fallo al conectar WiFi. Reiniciando...");
    ESP.restart();
}
Check SSID and password in arduino_secrets.h.

Next Steps

Add More Sensors

Clone your working sensor, change the SECRET_SPOT_ID, and flash new ESP32s. Each sensor operates independently.

Create Zones

Use the dashboard’s Builder Mode to organize spots into zones (North Lot, VIP Section, etc.)

Enable Reservations

Configure Firebase Authentication to allow users to reserve spots for specific durations.

View Analytics

Deploy the save-hourly-snapshot Cloud Function with Cloud Scheduler to build historical occupancy data.

Congratulations! You now have a working IoT parking sensor streaming real-time data to the cloud. Ready to dive deeper? Check out the Architecture Guide to understand how all the pieces fit together.

Build docs developers (and LLMs) love