Self-hosted fleet tracking for bus/car fleets. Runs on a personal laptop with no cloud costs.
Stack: ESP32 + NEO-6M GPS + SIM800L (hardware) · Node.js + Express (server) · SQLite (database) · PWA (driver app) · Cloudflare Tunnel (remote access)
npm install
node server/server.js
# → Running at http://localhost:3000
# → Dashboard at http://localhost:3000/dashboard# Download cloudflared from https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
# Testing (random URL, no account needed):
cloudflared tunnel --url http://localhost:3000
# Production (permanent URL):
cloudflared login
cloudflared tunnel create fleet
cloudflared tunnel run --url http://localhost:3000 fleetDrivers open the Cloudflare URL on their phone. Tap "Add to Home Screen" for an app-like experience. Works offline — trips queue and sync automatically.
- Open
firmware/fleet_tracker.inoin Arduino IDE - Install libraries: TinyGPS++ (via Library Manager)
- Edit the config block at the top of the file:
WIFI_SSID/WIFI_PASS— depot WiFiSERVER_URL— your Cloudflare tunnel URLVEHICLE_ID— e.g.BUS-01APN— your SIM card APN (Airtel:airtelgprs.com, BSNL:bsnlnet)
- Flash to ESP32 (board: ESP32 Dev Module)
| Component | Pin | ESP32 Pin |
|---|---|---|
| NEO-6M | TX | GPIO 16 |
| NEO-6M | RX | GPIO 17 |
| SIM800L | TX | GPIO 26 |
| SIM800L | RX | GPIO 27 |
| Trip button | Signal | GPIO 0 |
| Status LED | Anode (+) | GPIO 2 |
⚠️ SIM800L power: Must be powered from a dedicated 4.1V regulator (e.g. AMS1117-4.0) capable of 2A. Do NOT power from ESP32's 3.3V rail.
| ELM327 | ESP32 |
|---|---|
| TX | GPIO 18 |
| RX | GPIO 19 |
Connect to vehicle OBD-II port (usually under dashboard near steering column).
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/trip/start | Begin a trip, returns trip_id |
| POST | /api/trip/hardware | ESP32 GPS + OBD payload |
| POST | /api/trip/manual | Driver PWA form (fuel, score, etc.) |
| GET | /api/trips | All trips (filterable) |
| GET | /api/stats | Today's summary stats |
| GET | /api/vehicles | Vehicle registry |
| GET | /api/drivers | Driver list with aggregated stats |
| GET | /api/report | Fleet efficiency report |
| GET | /api/export/csv | Full CSV export |
fleet-tracking/
├── server/
│ └── server.js # Express API + DB setup
├── public/
│ ├── index.html # Driver PWA
│ ├── dashboard.html # Owner dashboard
│ ├── manifest.json # PWA manifest
│ ├── sw.js # Service Worker (offline support)
│ ├── css/
│ │ ├── pwa.css
│ │ └── dashboard.css
│ └── js/
│ ├── pwa.js
│ └── dashboard.js
├── firmware/
│ └── fleet_tracker.ino # ESP32 Arduino sketch
├── docs/
│ └── fleet_tracking_spec.md
├── package.json
└── README.md
Edit the seed block in server/server.js, or insert directly into SQLite:
# Using sqlite3 CLI:
sqlite3 server/fleet.db
INSERT INTO vehicles VALUES ('BUS-03','bus','Tata','LP 913',2021,'diesel',200,1,datetime('now'));
INSERT INTO drivers VALUES ('DRV-04','Sunil Verma',NULL,1,datetime('now'));The schema already supports cars. Just add vehicles with type = 'car':
INSERT INTO vehicles VALUES ('CAR-01','car','Maruti','Swift',2022,'petrol',37,1,datetime('now'));The dashboard filters by type automatically. OBD-II fuel data is significantly more reliable on cars.
Your entire database is one file: server/fleet.db
Simple backup:
cp server/fleet.db backups/fleet-$(date +%Y%m%d).dbSet this as a daily scheduled task (Windows Task Scheduler / Linux cron / Mac launchd).
| When | Upgrade |
|---|---|
| Laptop reliability is poor | Migrate to Supabase (swap SQLite driver) |
| Fleet > 30 vehicles | Migrate to PostgreSQL |
| Need live map | Add Leaflet.js + GPS stream endpoint |
| Need SMS alerts | Use SIM800L AT+CMGS for delay alerts |
| Need better fuel accuracy | Add fuel float sensor or OBD PID 0x5E |
MIT — free to use, modify, and deploy.