A wireless tire pressure monitoring system (TPMS) for motorcycles and cars built on ESP32-C3 with an LCD display powered by LVGL.
- Real-time monitoring of front and rear tire pressure and temperature
- BLE scanning for TPMS sensors (compatible with standard BLE TPMS sensors)
- Visual alerts with color-coded pressure indicators (green/yellow/red)
- Battery monitoring for each sensor
- Temperature-based UI - bar colors change when temperature drops below 10°C
- Auto-update - UI refreshes automatically when new sensor data arrives
- WiFi configuration mode - web-based setup interface
- Pairing mode - guided on-screen sensor pairing process
- Configurable ideal pressures for front and rear tires
- Pressure unit selection - PSI or BAR
- Brightness control - 5 levels (10%, 30%, 50%, 75%, 100%)
- Persistent settings - all configuration stored in NVS (Non-Volatile Storage)
- Touch-free operation - single button control (GPIO9/BOOT button)
- Short press (< 2s): cycle brightness levels
- Long press (2-5s): enter sensor pairing mode
- Very long press (> 5s): enter WiFi configuration mode
- Automatic screen transitions with splash screen on startup
- Version display - shows git version on splash screen
- WiFi AP Mode - creates "TPMS-Config" access point (password: tpms1234)
- Configuration portal - accessible at http://192.168.4.1
- Live sensor view - real-time monitoring of detected sensors
- Configuration management - set sensor addresses, ideal pressures, pressure unit
- Factory reset - clear all configuration and restart
- Board: ESP32-242412N (non-touch version)
- MCU: ESP32-C3 (single core, RISC-V architecture)
- Flash: 4MB (3MB for firmware, ~960KB for SPIFFS storage)
- Display: LCD with Lovyan GFX driver support
- Sensors: BLE TPMS sensors (front and rear wheels)
- Button: GPIO9 (BOOT button)
.
├── main/
│ ├── main.cpp - Entry point (app_main)
│ ├── Application.cpp/h - Main application logic and control flow
│ ├── UIController.cpp/h - LVGL timing and screen transition management
│ ├── UIBikeController.cpp/h - Main-screen UI (motorcycle mode, 2 sensors)
│ ├── UICarController.cpp/h - Main-screen UI (car mode, 4 sensors)
│ ├── DisplayManager.cpp/h - LCD initialization (Lovyan GFX)
│ ├── State.cpp/h - Global state management (singleton)
│ ├── ConfigManager.cpp/h - NVS configuration handling
│ ├── PairController.cpp/h - Sensor pairing logic
│ ├── WiFiManager.cpp/h - WiFi AP mode management
│ ├── WebServer.cpp/h - HTTP server for web interface
│ ├── TPMSScanCallbacks.cpp/h - BLE scan callbacks
│ ├── TPMSUtil.cpp/h - TPMS data parsing utilities
│ ├── LGFX_driver.h - Lovyan GFX display configuration
│ ├── index_html.h - Embedded HTML for web interface
│ └── UI/ - SquareLine Studio generated UI
│ ├── ui.c/h - Main UI code
│ ├── screens/ - Screen definitions
│ ├── components/ - UI components
│ ├── images/ - Image assets
│ └── fonts/ - Custom fonts
├── components/
│ ├── esp-nimble-cpp/ - NimBLE C++ wrapper for BLE
│ ├── lovyan-gfx/ - Display driver library
│ └── lvgl-custom/ - LVGL graphics library (v8)
├── squareline/ - SquareLine Studio project files
├── CMakeLists.txt - Main build configuration
└── sdkconfig - ESP-IDF configuration
The application follows a clean separation of concerns with a singleton-based architecture:
- Application: Singleton managing initialization, BLE setup, button logic, screen transitions, and mode switching (normal/pairing/config)
- UIController: Responsible for LVGL tick/timer lifecycle and managing screen transitions (splash/main/pair)
- UIBikeController: Responsible for main screen LVGL updates for motorcycle mode (pressure, temperature, battery, alerts)
- State: Singleton storing global sensor data (pressure, temperature, battery, signal strength)
- ConfigManager: Persistent storage interface for NVS (addresses, pressures, brightness)
- PairController: State machine for guided sensor pairing process
- WiFiManager: Manages WiFi AP mode with event handlers
- WebServer: HTTP server with REST API and OTA update support
- DisplayManager: Initializes and configures the LCD display
- TPMSScanCallbacks: BLE advertisement parsing and sensor discovery
- UIController (singleton): LVGL tick/timer lifecycle and top-level screen transition control (splash, main, pair).
- UIBikeController (singleton): Handles main-screen UI updates for motorcycle mode — the widgets that display pressure, temperature, battery, icons and blinking states.
- UICarController (singleton): Handles car-mode main-screen (4 sensors) UI updates — pressure, temperature, battery, icons and blinking states for 4 sensors.
Example usage (call from application control loop via LVGL async calls):
// Initialize main-screen labels after UI is created:
// For motorcycle mode:
lv_async_call([](void *arg){ (void)arg; UIBikeController::instance().initializeLabels(); }, nullptr);
// For car:
lv_async_call([](void *arg){ (void)arg; UICarController::instance().initializeLabels(); }, nullptr);
// Periodic update of sensor UI (on sorted LVGL thread):
lv_async_call([](void *arg){ (void)arg;
uint32_t currentTime = esp_timer_get_time() / 1000;
// Motorcycle example
UIBikeController::instance().updateAlertBlinkState(currentTime);
UIBikeController::instance().updateSensorUI(frontSensor, rearSensor, frontIdeal, rearIdeal, currentTime);
// Car example (front-left, front-right, rear-left, rear-right)
UICarController::instance().updateAlertBlinkState(currentTime);
// Order of parameters: FrontLeft, RearLeft, FrontRight, RearRight (mapped to C1..C4 UI)
UICarController::instance().updateSensorUI(s_fl, s_rl, s_fr, s_rr, ideal_fl, ideal_rl, ideal_fr, ideal_rr, currentTime);
}, nullptr);- BLE scan callbacks detect TPMS sensors and parse advertisements
- Sensor data is stored in the global State singleton
- Application triggers UI updates via LVGL async callbacks
- UIBikeController reads from State and updates main-screen LVGL widgets (pressure, temp, battery, icons) for motorcycle mode. UIController maintains LVGL tick/task and triggers screen transitions.
- Button presses are handled in Application control task
- Configuration changes are persisted via ConfigManager
- ESP-IDF: v5.3.1
- CMake: 3.16 or higher
- Python: 3.8+ (for ESP-IDF tools)
- Git: For version tagging
# Set target (first time only)
idf.py set-target esp32c3
# Configure (optional - opens menuconfig)
idf.py menuconfig
# Build
idf.py build
# Flash
idf.py -p COM_PORT flash
# Monitor serial output
idf.py -p COM_PORT monitor
# Flash and monitor in one command
idf.py -p COM_PORT flash monitor# PowerShell
$env:IDF_PATH = 'C:/Espressif/frameworks/esp-idf-v5.3.1/'
idf.py buildConfiguration is stored in Non-Volatile Storage and persists across reboots. Current configuration format (mode, addresses array, ideal_psi array):
{
"mode": 0, // 0 = Motorcycle mode, 1 = Car mode
"addresses": [
"80:ea:ca:10:05:32",
"81:ea:ca:20:04:10",
"", "" // car mode would use up to 4 addresses
],
"ideal_psi": [36.0, 42.0, 36.0, 42.0],
"brightness_index": 4,
"pressure_unit": "PSI"
}Access the web interface to configure settings:
- Long press BOOT button (> 5 seconds) to enter WiFi config mode
- Connect to WiFi network:
TPMS-Config(password:tpms1234) - Open browser to:
http://192.168.4.1 - Available endpoints:
GET /- Web interfaceGET /api/sensors- Current sensor data (JSON)GET /api/config- Current configuration (JSON)POST /api/config- Update configuration (addresses, ideal PSI, pressure unit)POST /api/clear- Factory resetPOST /api/restart- Restart device
Guided sensor pairing with on-screen instructions:
- Long press BOOT button (2-5 seconds) to enter pairing mode
- Scan for front sensor (60 second timeout)
- Press button to confirm when sensor is detected
- Scan for rear sensor (60 second timeout)
- Press button to confirm when sensor is detected
- Configuration is saved and device restarts automatically
The device operates in three distinct modes:
- Displays real-time tire pressure and temperature
- Monitors both front and rear TPMS sensors
- Short button press: cycle brightness
- Long press: enter pairing mode
- Very long press: enter WiFi config mode
- Guides user through sensor discovery
- Shows scanning status and found sensors
- Button confirms selection and advances to next step
- Auto-saves configuration on completion
- Starts WiFi AP: TPMS-Config
- Runs HTTP server on 192.168.4.1
- Provides web interface for configuration:
- View discovered TPMS sensors
- Configure sensor addresses (front/rear)
- Set ideal tire pressures
- Select pressure unit (PSI/BAR)
- Factory reset option
- Long press button again to exit and return to normal mode
- Displays app name and version (from git tag)
- Shows for 3 seconds on startup
- Version format:
v1.0.0or git describe output
- Front tire: Pressure (PSI), temperature (°C), battery (%), signal strength
- Rear tire: Pressure (PSI), temperature (°C), battery (%), signal strength
- Color coding:
- Green: Pressure within ±3 PSI of ideal
- Yellow: Pressure ±3-5 PSI from ideal
- Red: Pressure > 5 PSI from ideal or < 10°C temperature
- Status indicators: Last update time, connection status
- Current pairing step (front/rear)
- Scanning animation
- List of discovered sensors
- Confirmation prompts
- Timeout warnings
The project is optimized to fit within the ESP32-C3's flash constraints:
- Compiler optimization:
-Os(size optimization) - Log levels: INFO level globally, WARN for bootloader
- Flash partition layout:
- NVS: 20KB (0x9000 - 0xE000)
- PHY: 4KB (0xE000 - 0x10000)
- Factory (App): 3MB (0x10000 - 0x310000) - no OTA, single large firmware partition
- SPIFFS: ~960KB (0x310000 - 0x400000) - increased storage for large configurations
- BLE configuration:
- Reduced connection limits
- Optimized buffer sizes
- LVGL configuration:
- Custom configuration via
lv_conf.h - Limited widget set
- Optimized rendering
- Custom configuration via
UI is designed using SquareLine Studio (project files in squareline/):
- Export to
main/UI/ - Includes screens, components, images, and fonts
- LVGL v8 compatible
- Custom theme with pressure-aware colors
- Version is automatically extracted from git tags during build
- Format:
git describe --tags --always --dirty - Displayed on splash screen
- Fallback:
1.0.0-devif git is unavailable
- Update
TPMSUtil.cppto parse new sensor format - Add sensor type detection in
TPMSScanCallbacks.cpp - Test pairing and data parsing
- Update web API if needed
- Ensure tires are moving or recently moved (sensors may sleep)
- Check battery level in paired sensors
- Verify BLE is not blocked by other devices
- Try re-pairing using pairing mode
- Check
LGFX_driver.hfor correct pin configuration - Verify SPI connection and power supply
- Adjust brightness if screen is too dark
- Ensure you held button for > 5 seconds
- Look for "TPMS-Config" WiFi network
- Try connecting with password:
tpms1234 - Check device is not in pairing mode instead
- Ensure ESP-IDF v5.3.1 is installed
- Run
idf.py cleanbefore rebuilding - Check submodules are initialized:
git submodule update --init --recursive
This project is provided as-is for educational and personal use.
GitHub: screemerpl/bike_pressure_monitor (repository; project name: Universal Pressure Monitor) Branch: develop
Created for universal tire pressure monitoring (motorcycle & car) with ESP32-C3.
Last Updated: November 2025
Features:
- BLE TPMS sensor monitoring with real-time pressure/temperature display
- WiFi-based configuration (no OTA - uses larger single firmware partition)
- LVGL-based touchscreen UI with color-coded pressure indicators
- Persistent configuration via NVS
- Up to 3MB available for firmware (no OTA overhead)