Skip to main content
The S-Parking IoT layer consists of ESP32 microcontrollers equipped with VL53L0X Time-of-Flight (ToF) laser sensors. This guide covers firmware installation, configuration, and troubleshooting.

Hardware Requirements

  • ESP32 Development Board (ESP32-WROOM-32 recommended)
  • VL53L0X ToF Laser Sensor (I2C interface)
  • RGB LED (Common Anode) for visual feedback
  • Resistors: 3x 220Ω (for LED current limiting)
  • Jumper Wires and breadboard
  • Micro USB Cable for programming

Wiring Diagram

ComponentESP32 PinNotes
VL53L0X SDAGPIO 21I2C Data
VL53L0X SCLGPIO 22I2C Clock
VL53L0X VIN3.3VPower
VL53L0X GNDGNDGround
LED RedGPIO 13Via 220Ω resistor
LED GreenGPIO 12Via 220Ω resistor
LED BlueGPIO 14Via 220Ω resistor
LED Common3.3VCommon Anode
The S-Parking firmware assumes a Common Anode RGB LED (long pin to 3.3V). For Common Cathode LEDs, invert the logic in setColor() function (lines 196-202 in S-Parking.ino).

Setup Development Environment

1

Install Arduino IDE

Download from arduino.cc
  • Version 2.0+ recommended
2

Add ESP32 Board Support

  1. Open File → Preferences
  2. Add to Additional Board Manager URLs:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  1. Go to Tools → Board → Board Manager
  2. Search for “esp32” and install esp32 by Espressif Systems
3

Install Required Libraries

Go to Sketch → Include Library → Manage Libraries and install:
  • Adafruit VL53L0X (by Adafruit)
  • ArduinoJson (by Benoit Blanchon) - Version 6.x
Dependencies auto-install:
  • Adafruit BusIO
  • Adafruit Unified Sensor
4

Select Board

Tools → Board → ESP32 Arduino → ESP32 Dev ModuleConfigure board settings:
  • Upload Speed: 921600
  • CPU Frequency: 240MHz
  • Flash Frequency: 80MHz
  • Flash Mode: QIO
  • Flash Size: 4MB (32Mb)
  • Partition Scheme: Default 4MB with spiffs

Option 2: PlatformIO (Advanced Users)

1

Install PlatformIO

  • VS Code Extension: Search for “PlatformIO IDE” in Extensions
  • Standalone: platformio.org
2

Create Project

platformio init --board esp32dev
3

Configure platformio.ini

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps = 
    adafruit/Adafruit VL53L0X@^1.2.0
    bblanchon/ArduinoJson@^6.21.0
4

Build and Upload

platformio run --target upload
platformio device monitor

Configure Firmware

1. Create Secrets File

Copy the example secrets template:
cd firmware/S-Parking
cp arduino_secrets.example.h arduino_secrets.h

2. Edit Configuration

Open arduino_secrets.h and configure:
arduino_secrets.h
#pragma once

// --- CREDENCIALES WIFI ---
#define SECRET_SSID "YourWiFiNetwork"
#define SECRET_PASS "YourWiFiPassword"

// --- TUS URLs DE GOOGLE CLOUD ---

// 1. URL para ENVIAR datos (ingest-parking-data)
#define SECRET_GCP_URL_INGEST "https://ingest-parking-data-[hash].run.app"

// 2. URL para LEER estado (get-parking-status)
// NOTA: Esta funcion devuelve TODOS los puestos. El ESP32 buscará su ID en la lista.
#define SECRET_GCP_URL_GET "https://get-parking-status-[hash].run.app"

// 3. Identificación del Puesto
#define SECRET_SPOT_ID "A-01"

Configuration Parameters

ParameterDescriptionExample
SECRET_SSIDWiFi network name"ParkingLot_WiFi"
SECRET_PASSWiFi password"SecurePass123"
SECRET_GCP_URL_INGESTCloud Run ingest endpointhttps://ingest-[hash].run.app
SECRET_GCP_URL_GETCloud Run status endpointhttps://get-status-[hash].run.app
SECRET_SPOT_IDUnique spot identifier"A-01", "B-12", etc.
Spot ID Naming Convention: Use format [ZONE]-[NUMBER] (e.g., A-01, B-12, VIPA-03). This must match the spot IDs created in the dashboard.

3. Calibrate Sensor Threshold

The default detection threshold is 400mm (line 78 in S-Parking.ino):
if (measure.RangeStatus != 4 && measure.RangeMilliMeter < 400) {
    nuevoEstadoSensor = 0; // Ocupado fisicamente
} else {
    nuevoEstadoSensor = 1; // Disponible fisicamente
}
Adjust based on installation height:
  • Ceiling-mounted (2.5m): 400-500mm
  • Pole-mounted (1.5m): 300-400mm
  • Ground-level test: 200-300mm
Test sensor readings before deployment:
Serial.print("Distance: ");
Serial.print(measure.RangeMilliMeter);
Serial.println(" mm");
Measure distance with and without a vehicle present, then set threshold midway between values.

Upload Firmware

1

Connect ESP32

2

Select Port

Arduino IDE: Tools → Port → Select COM port (Windows) or /dev/ttyUSB0 (Linux)PlatformIO: Auto-detects port
3

Open Firmware

Arduino IDE: File → Open → Select firmware/S-Parking/S-Parking.ino
4

Compile and Upload

Click Upload button or press Ctrl+UExpected output:
Connecting........_____.....
Writing at 0x00001000... (100%)
Leaving...
Hard resetting via RTS pin...
5

Open Serial Monitor

Tools → Serial Monitor (set baud rate to 115200)Verify output:
Conectando a WiFi: YourWiFiNetwork
........
¡WiFi Conectado!
IP asignada: 192.168.1.42

Firmware Behavior

The S-Parking firmware implements intelligent self-healing logic:

Polling Intervals

const long sensorInterval = 500;    // Read sensor every 0.5s
const long cloudPollInterval = 15000; // Check cloud every 15s
  • Sensor reading: 500ms (fast response to vehicle arrival/departure)
  • Cloud sync: 15s (periodic validation of reservation status)

LED Status Indicators

LED ColorStatusDescription
🟢 GreenAvailableNo vehicle, no reservation
🟡 Amber (Red+Green)ReservedNo vehicle, but reserved in system
🔴 RedOccupiedVehicle detected (priority over reservation)

Self-Healing Logic

The firmware auto-corrects desynchronization (lines 120-136): Scenario A: Sensor shows Available (Green), Cloud shows Occupied
  • Cause: Network packet loss when vehicle departed
  • Action: Force-send “Available” status to cloud
Scenario B: Sensor shows Occupied (Red), Cloud shows Available/Reserved
  • Cause: Network packet loss when vehicle arrived
  • Action: Force-send “Occupied” status to cloud
The physical sensor reading always takes priority over cloud state. This ensures visual LED feedback is accurate for drivers on-site.

Test Firmware

1. Verify WiFi Connection

Serial Monitor output should show:
Conectando a WiFi: YourNetwork
¡WiFi Conectado!
IP asignada: 192.168.1.42

2. Test Sensor Detection

Place an object (or hand) under the sensor:
Cambio físico detectado: 0
Enviado a ingest. Codigo: 200
Remove object:
Cambio físico detectado: 1
Enviado a ingest. Codigo: 200

3. Verify Cloud Sync

Every 15 seconds:
Sincronizando. Local: 1 | Nube: 1
If desynchronization occurs:
¡Desincronización detectada! Forzando actualización a DISPONIBLE...
Enviado a ingest. Codigo: 200

4. Check Dashboard

Open S-Parking web dashboard and verify:
  • Spot appears with correct ID
  • Status updates in real-time (under 2s latency)
  • LED color matches dashboard indicator

Troubleshooting

Issue: “Error al iniciar VL53L0X”

Cause: Sensor not detected on I2C bus Solution:
  1. Verify wiring (SDA → GPIO 21, SCL → GPIO 22)
  2. Check sensor power (3.3V, not 5V!)
  3. Test I2C scanner:
    Wire.begin();
    Wire.beginTransmission(0x29);
    byte error = Wire.endTransmission();
    Serial.println(error == 0 ? "Sensor found" : "Sensor not found");
    

Issue: “Fallo al conectar WiFi. Reiniciando…”

Cause: Incorrect SSID/password or weak signal Solution:
  • Verify credentials in arduino_secrets.h
  • Move ESP32 closer to router during testing
  • Check WiFi is 2.4GHz (ESP32 doesn’t support 5GHz)

Issue: HTTP Response Code 404/500

Cause: Incorrect Cloud Run URL or service not deployed Solution:
# Verify Cloud Run services are running
gcloud run services list

# Test endpoint manually
curl -X POST https://your-ingest-url.run.app \
  -H "Content-Type: application/json" \
  -d '{"spot_id":"A-01","status":1}'

Issue: LED not lighting up

Cause: Incorrect LED type (Common Cathode vs. Common Anode) Solution: Invert logic in setColor() function:
// For Common Cathode LED:
digitalWrite(LED_RED_PIN, red ? HIGH : LOW);
digitalWrite(LED_GREEN_PIN, green ? HIGH : LOW);
digitalWrite(LED_BLUE_PIN, blue ? HIGH : LOW);

Issue: Sensor reads 8190mm constantly

Cause: VL53L0X out of range or measurement error Solution:
  • Check measure.RangeStatus (should be 0 for valid reading)
  • Ensure target is within 0-1200mm range
  • Adjust sensor angle (perpendicular to ground)

Issue: High latency (>5s) in status updates

Cause: Network congestion or Cloud Run cold start Solution:
  • Reduce cloudPollInterval to 10000ms for critical spots
  • Set Cloud Run minimum instances to 1:
    gcloud run services update ingest-parking-data --min-instances 1
    

Production Deployment Tips

1

Unique Spot IDs

Use a consistent naming scheme:
  • A-01 to A-20 (Zone A)
  • B-01 to B-15 (Zone B)
  • VIP-01 to VIP-05 (VIP section)
2

Label Devices

Use physical labels on ESP32 boards with spot IDs for easy maintenance.
3

Power Supply

Use 5V/2A USB power adapters or PoE (with voltage regulator to 5V for ESP32).
4

Weatherproofing

For outdoor installations:
  • IP65-rated enclosures
  • Silicone seal around sensor
  • Waterproof USB connectors
5

Batch Programming

For multiple devices, create a script:
#!/bin/bash
for spot in A-01 A-02 A-03; do
  sed -i "s/SECRET_SPOT_ID \".*\"/SECRET_SPOT_ID \"$spot\"/" arduino_secrets.h
  platformio run --target upload
  echo "Flashed $spot"
  sleep 5
done

Firmware Update Over-The-Air (Future)

S-Parking roadmap includes OTA updates via ESP32’s built-in OTA capabilities:
#include <ArduinoOTA.h>

ArduinoOTA.setHostname("S-Parking-A01");
ArduinoOTA.setPassword("secure_ota_pass");
ArduinoOTA.begin();
This allows wireless firmware updates without physical access.

Next Steps

Environment Configuration

Manage secrets and API endpoints across all system components

Deploy Cloud Run

Set up backend services for IoT data ingestion

Firebase Hosting

Deploy the web dashboard to production

Build docs developers (and LLMs) love