ESP32-based BLE bridge to control an Elite Aria cycling fan from Home Assistant via MQTT.
This project allows you to control an Elite Aria smart cycling fan using an ESP32 (M5Stack ATOM Lite) that acts as a BLE client and MQTT bridge. The fan can be controlled via Home Assistant or the fan's own physical controls - all states sync automatically.
Firmware Version: This implementation was developed and tested against an Aria fan running firmware version 9. Protocol details, UUIDs, and command formats may differ on other firmware versions.
The ESP32 acts as a bridge between Home Assistant and the Aria fan:
Home Assistant (MQTT) ← WiFi → ESP32 ← BLE → Aria Fan
Both connections are required for operation:
- Commands from HA require MQTT (to receive) + BLE (to send to fan)
- Status updates require BLE (to receive from fan) + MQTT (to send to HA)
- If either connection drops, the ESP32 automatically reconnects and restores the desired state
The fan's physical controls work independently and any changes are synced back to Home Assistant.
- ESP32 Board (tested with M5Stack ATOM Lite ESP32-PICO-D4)
- Any ESP32 with BLE support should work
- Optional: RGB LED for status indication (default GPIO 27)
- Note: Physical button (GPIO 39) is not used in this implementation
- Elite Aria Fan (with BLE support)
- Home Assistant with MQTT broker (Mosquitto)
- ✅ Home Assistant Integration - Full speed and mode control via MQTT
- ✅ Fan Physical Controls - Mode button and speed controls on the fan itself
- ✅ Bi-directional Sync - Changes from any control method update all interfaces
- ✅ Real-time Status - Fan speed and mode updates reflected in Home Assistant
- ✅ Sensor Modes - Speed/Power/HR readings from ANT+ sensors automatically update HA
- ✅ Auto-reconnect - Automatically reconnects to fan if BLE connection drops
- ✅ State Restoration - Restores desired fan speed/mode after BLE reconnection
- ✅ MQTT Keepalive - Reconnects to MQTT broker if network issues occur
- ✅ Visual Feedback - LED indicates connection status
- ✅ Web-based Logs - View last 30 log entries via web browser
- ✅ Status Endpoint - JSON diagnostics with heap usage and connection status
- ✅ MQTT Logging - Logs published to Home Assistant for long-term history
- ✅ Event Tracking - Logs all speed/mode changes with source (MQTT/BLE/Reconnect)
- ✅ Memory Safeguards - Graceful degradation when heap is critically low
The Aria fan supports 5 operating modes:
| Mode | Description | Sensor Type |
|---|---|---|
| Manual | Direct speed control (0-100%) | None |
| Speed | Speed-based fan control | ANT+ Speed sensor |
| Power | Power-based fan control | ANT+ Power meter |
| HR | Heart rate-based fan control | ANT+ or BLE HR monitor |
| Temp | Temperature-based control | BLE Temp sensor ( |
Note: Temp mode uses BLE and will temporarily disconnect the ESP32 (auto-reconnects in 10-15s).
| Color | Status |
|---|---|
| 🔴 Red | Disconnected from fan |
| 🟡 Yellow | Scanning for fan |
| 🟢 Green | Connected to fan |
Service UUID: 347B0001-7635-408B-8918-8FF3949CE592
Characteristic UUIDs:
| UUID | Type | Purpose |
|---|---|---|
347B0012-7635-408B-8918-8FF3949CE592 |
Write | Initial commands (not used) |
347B0013-7635-408B-8918-8FF3949CE592 |
Read | Configuration blob (variable size, not used) |
347B0040-7635-408B-8918-8FF3949CE592 |
Write + Notify | Control commands |
347B0042-7635-408B-8918-8FF3949CE592 |
Notify | Status updates |
Note: These UUIDs are from a single Aria fan and may be device-specific rather than universal to all Aria fans. If your fan doesn't connect, use the nRF Connect app to discover the correct UUIDs for your device.
// Set Mode: [0x05, mode]
// 0x00=Manual, 0x01=Speed, 0x02=Power, 0x03=HR, 0x04=Temp
setAriaMode(0x00); // Manual mode
// Set Speed: [0x03, 0x01, percentage]
setFanSpeed(50); // 50% speedFormat: [mode, speed, 0x00, 0x02]
Example: 00 32 00 02 = Manual mode (0x00), 50% speed (0x32)
Board Support:
- ESP32 by Espressif Systems - version 3.3.2
- Install via: Tools → Board → Boards Manager → search "esp32"
- Includes BLE libraries (BLEDevice, BLEUtils, BLEClient, etc.)
- Includes WebServer library for diagnostic web interface
Required Libraries (install via Sketch → Include Library → Manage Libraries):
| Library | Purpose | Tested Version |
|---|---|---|
| FastLED | LED control | 3.10.3 |
| PubSubClient | MQTT client | 2.8 |
Create secrets.h:
#ifndef SECRETS_H
#define SECRETS_H
const char* WIFI_SSID = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";
const char* MQTT_SERVER = "homeassistant.local";
const int MQTT_PORT = 1883;
const char* MQTT_USER = "mqtt";
const char* MQTT_PASSWORD = "your_mqtt_password";
#endifEnable the MQTT integration in Home Assistant and configure credentials. See the official MQTT integration documentation for setup instructions.
Important: Use the same MQTT username and password in your secrets.h file (step 2 above).
- Connect ESP32 board via USB
- Select appropriate board in Arduino IDE (e.g., ESP32 Pico Kit for ATOM Lite)
- Upload
AriaFanBLE.ino - If using a different LED pin, update
LED_PINin the code
Add to configuration.yaml:
# MQTT Devices
mqtt:
fan:
- name: "Aria Fan"
unique_id: "aria_fan_ble"
command_topic: "aria/on/set"
state_topic: "aria/on/state"
percentage_command_topic: "aria/speed/set"
percentage_state_topic: "aria/speed/state"
percentage_value_template: "{{ value }}"
speed_range_min: 1
speed_range_max: 100
optimistic: false
select:
- name: "Aria Fan Mode"
unique_id: "aria_fan_mode"
command_topic: "aria/mode/set"
state_topic: "aria/mode/state"
options:
- "Manual"
- "Speed"
- "Power"
- "HR"
- "Temp"
optimistic: falseRestart Home Assistant after saving.
| Topic | Direction | Description |
|---|---|---|
aria/on/set |
Command | Turn fan ON/OFF |
aria/on/state |
State | Fan ON/OFF state |
aria/speed/set |
Command | Set fan speed (0-100) |
aria/speed/state |
State | Current fan speed (0-100) |
aria/mode/set |
Command | Set mode (Manual/Speed/Power/HR/Temp) |
aria/mode/state |
State | Current mode |
aria/log/info |
State | Diagnostic log messages |
- Fan Control - Use the fan entity (
fan.aria_fan) with speed slider - Mode Selection - Use the select entity (
select.aria_fan_mode) - Automations - Trigger fan based on workout start/stop, time, etc.
Example automation:
automation:
- alias: "Start fan on workout"
trigger:
- platform: state
entity_id: binary_sensor.workout_active
to: "on"
action:
- service: select.select_option
target:
entity_id: select.aria_fan_mode
data:
option: "HR"
- service: fan.turn_on
target:
entity_id: fan.aria_fanUse the mode and speed buttons on the fan itself - changes sync to Home Assistant automatically.
The controller includes comprehensive logging for debugging and monitoring.
Access logs and diagnostics via web browser:
- Visit
http://<esp32-ip-address>/logs- View last 30 log entries with timestamps - Visit
http://<esp32-ip-address>/status- JSON endpoint with heap and connection status - Dark theme for readability
- Logs survive until ESP32 reboot
- Returns 503 error if heap is critically low (prevents crashes)
Log format: [timestamp] message
Timestamps are synchronized via NTP (falls back to uptime if NTP not yet synced).
Example logs during normal operation (BLE reconnect, fan may have reset):
[2025-10-13 14:32:15] MQTT command: Set speed to 75%
[2025-10-13 14:32:16] BLE notification: Speed changed to 75%
[2025-10-13 15:10:05] BLE disconnected - will attempt reconnection
[2025-10-13 15:10:20] BLE reconnected - target state: mode 0x00, speed 75%
[2025-10-13 15:10:21] Fan current state after reconnection: mode Manual (0x00), speed 25%
[2025-10-13 15:10:21] Scheduling state restore: mode 0x00, speed 75%
[2025-10-13 15:10:21] Restoring target state: mode 0x00, speed 75%
[2025-10-13 15:10:22] BLE notification: Speed changed to 75%
After ESP32 power cycle (first boot, fan state unchanged):
[+10s] BLE reconnected - target state: mode 0x00, speed 25%
[+11s] Fan current state after reconnection: mode Manual (0x00), speed 60%
[+11s] First connection after boot - accepting fan state (not restoring)
Before NTP sync, logs use uptime format: [+123s] message
The /status endpoint provides JSON-formatted diagnostics:
{
"version": "1.0.0",
"heap_free": 21540,
"heap_size": 234812,
"heap_min_free": 5872,
"psram_free": 0,
"psram_size": 0,
"cpu_freq_mhz": 240,
"uptime_sec": 458,
"wifi_connected": true,
"mqtt_connected": true,
"ble_connected": true,
"log_count": 30,
"log_capacity": 30
}Key metrics:
heap_free- Current available heap memoryheap_min_free- Minimum free heap since boot (indicates memory pressure)log_count/log_capacity- Current vs maximum log entries
Memory constraints: The ESP32 has limited heap (~235KB total). After WiFi, MQTT, and BLE initialization, approximately 20-40KB remains free. The BLE stack alone consumes ~94KB. If heap_min_free drops below 10KB, consider reducing log activity or checking for memory leaks.
Add this sensor to configuration.yaml to store logs in HA database:
mqtt:
sensor:
- name: "Aria Fan Log"
state_topic: "aria/log/info"
value_template: "{{ value }}"
icon: "mdi:text-box"Then view logs in:
- History panel:
sensor.aria_fan_logentity history - Developer Tools → MQTT: Subscribe to
aria/log/info - Logbook: Shows all log events with timestamps
Logs are retained according to your Home Assistant recorder settings (default: 10 days).
All events are logged with their source:
| Event Type | Log Message Example |
|---|---|
| MQTT Command | MQTT command: Set speed to 50% |
| BLE Notification | BLE notification: Speed changed to 60% |
| Mode Change | BLE notification: Mode changed to HR (0x03) |
| Disconnection | BLE disconnected - will attempt reconnection |
| Reconnection | BLE reconnected - target state: mode 0x00, speed 0% |
| State Restore | Restoring target state: mode 0x00, speed 0% |
This helps identify the source of unexpected fan behavior (e.g., if fan randomly changes speed, check if it's from MQTT or the fan itself).
- Ensure fan is powered on and in range
- Check Serial Monitor for scan results
- LED should be yellow when scanning
- Verify credentials in
secrets.h - Check MQTT broker is running in Home Assistant
- Look for error code in Serial Monitor:
-4= Connection timeout4= Bad username/password5= Not authorized
- Expected behavior - Temp mode uses BLE exclusively
- ESP32 will auto-reconnect in 10-15 seconds
- Consider removing "Temp" from Home Assistant options if unused
- Ensure ANT+ sensor is paired with the fan via Elite app
- Speed updates come from the sensor, not the ESP32
- ESP32 correctly reads and reports the fan's actual speed
Issue: Earlier versions experienced phantom button presses caused by WiFi power save mode glitching GPIO39 on the M5Stack ATOM Lite.
Solution: Physical button functionality has been removed from the code since the device is rack-mounted and controlled exclusively via Home Assistant/MQTT. This eliminates the phantom press issue entirely.
Reference: ESPHome Issue #3403 documents this GPIO glitch issue on M5Stack ATOM hardware.
The BLE protocol was discovered through observation and testing:
- nRF Connect - Inspected BLE services and characteristics
- BLE Sniffer - ESP32 sketch to capture Elite app commands
- Testing - Validated command formats with the fan
Implementation notes:
- Status notifications use format
[mode, speed, 0x00, 0x02]with mode in byte 0 - Control commands are sent to characteristic 347B0040
- Implementation uses 500ms delay after mode changes for stability
- BLE scan runs every 10 seconds when disconnected
- Old BLE clients are properly cleaned up before reconnection
- 500ms delay after mode changes for stability
AriaFanBLE.ino # Main ESP32 client code
secrets.h # WiFi/MQTT credentials (not in git)
README.md # This documentation
Note: The BLE sniffer tool used for protocol discovery is maintained as a separate sketch.
- Elite - Aria smart fan manufacturer
- M5Stack - ATOM Lite hardware
- FastLED - LED control library
- PubSubClient - MQTT library
This project uses Semantic Versioning. See CHANGELOG.md for release history.
Current version: 1.0.0
MIT License - See LICENSE file for details.
This is an unofficial implementation and is not affiliated with or endorsed by Elite S.r.l.
Developed with: Claude Code (Anthropic) Hardware: M5Stack ATOM Lite (ESP32) Target Device: Elite Aria Smart Fan