CAN bus telemetry system: read frames on an ESP32, stream them over MQTT, store sessions in PostgreSQL/TimescaleDB, and view live charts plus recorded history in a web dashboard.
Built as a portfolio project to practice embedded C++, a .NET backend, and a React frontend working together on one data pipeline.
- A CAN sender (ESP32) publishes test frames on the bus.
- A CAN gateway (ESP32-S3) reads frames, buffers them when WiFi is down, and sends binary batches over MQTT when online.
- The API subscribes to MQTT, decodes signals using uploaded DBC files, pushes live data through SignalR, and writes recorded sessions to the database.
- The web app shows a live chart, lets you start/stop recording, and opens past sessions in an analytics view with zoom and shareable URLs.
Live dashboard — real-time chart while recording a session
Session history — past recordings on the dashboard
Analytics view — recorded session with zoom
flowchart LR
Sender[TracePoint.Sender<br/>ESP32 CAN TX]
Bus[(CAN Bus 125 kbps)]
Gateway[TracePoint.Gateway<br/>ESP32-S3]
MQTT[(Mosquitto<br/>telemetry/binary)]
API[TracePoint.Api<br/>.NET 10]
DB[(PostgreSQL / TimescaleDB)]
Web[tracepoint-web<br/>Next.js]
Sender --> Bus
Bus --> Gateway
Gateway --> MQTT
MQTT --> API
API --> DB
API <-->|SignalR| Web
Web -->|REST| API
| Layer | Technologies |
|---|---|
| Firmware | C++, Arduino, PlatformIO, ESP32 / ESP32-S3, TWAI CAN, PubSubClient |
| Backend | ASP.NET Core, SignalR, MQTTnet, EF Core, Dapper, MediatR, DbcParserLib |
| Database | PostgreSQL with TimescaleDB (time_bucket for downsampling) |
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS, uPlot, SignalR client |
| Messaging | Mosquitto MQTT broker |
TracePoint/
├── src/
│ ├── TracePoint.Api/ # Backend API, MQTT bridge, SignalR hub
│ ├── TracePoint.Shared/ # Shared DTOs and EF models
│ ├── TracePoint.Simulator/ # Console app for testing without hardware
│ ├── tracepoint-web/ # Next.js dashboard
│ ├── TracePoint.Gateway/ # ESP32-S3 CAN → MQTT gateway
│ └── TracePoint.Sender/ # ESP32 CAN frame generator (test traffic)
├── TracePoint.Client/ # Blazor template (not part of main flow)
└── TracePoint.slnx
Each main component has its own README with setup details:
- .NET 10 SDK
- Node.js 20+
- Docker (PostgreSQL/TimescaleDB and optional pgAdmin)
- Mosquitto MQTT broker on port
1883 - PlatformIO (only if you work with the ESP32 boards)
Start the local database container (adjust names if yours differ):
docker start tracepoint-db
# optional UI:
docker start pgadmin-dashboardDefault connection (see src/TracePoint.Api/appsettings.json):
- Host:
localhost, port:5432 - Database:
tracepoint - User / password:
postgres/postgres
Apply migrations from the API project:
cd src/TracePoint.Api
dotnet ef database updateRun Mosquitto locally on port 1883. The gateway publishes to topic telemetry/binary.
cd src/TracePoint.Api
dotnet runAPI listens on http://localhost:5247 (see Properties/launchSettings.json).
cd src/tracepoint-web
npm install
npm run devOpen http://localhost:3000.
Flash Sender and Gateway with PlatformIO, wire CAN TX/RX (125 kbps), and set WiFi/MQTT targets in the gateway firmware. See the firmware README.
Without boards, you can run TracePoint.Simulator to push mock measurements into SignalR:
cd src/TracePoint.Simulator
dotnet runSerial monitor (Linux example):
pio device monitor --port /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_* --baud 115200- Offline-first gateway: CAN frames go into a PSRAM-backed deque when MQTT is unavailable; backlog drains automatically on reconnect.
- Binary MQTT payload: fixed-size packed structs (16 bytes per frame) instead of JSON — less overhead on the ESP32.
- DBC decoding at ingest: upload a
.dbcfile from the UI; the API maps raw CAN bytes to physical values (factor/offset). - Live vs storage path: 100% of frames go to the DB buffer during recording; only every 10th frame is sent to the live chart to keep the UI responsive.
- Time-series queries: historical charts use TimescaleDB
time_bucketwith ~2000 buckets, and zoom level is reflected in the URL (?min=&max=).


