Victron MPPT Solar Monitoring via ESPHome BLE — No Cloud Required

Victron SmartSolar MPPT 100/20 with LiFePO4 battery and solar panel fuse board

Overview

Most Victron MPPT tutorials either use the official VRM cloud platform or a USB-to-serial cable. This guide shows a completely different approach: reading Victron SmartSolar MPPT 100/20 data directly via Bluetooth Low Energy (BLE) using an ESP32 and ESPHome, feeding it into Home Assistant entirely locally — no Victron account, no cloud, no cables required.

The result is a real-time solar monitoring system that stores full historical data in Home Assistant and works even when the internet is down.

What you’ll get:

  • PV Power (W)
  • Battery Voltage (V)
  • Battery Current (A)
  • Load Current (A)
  • Yield Today (kWh)
  • Cumulative Solar Energy Produced (Wh) — with full historical statistics
  • MPPT State (Off / Bulk / Absorption / Float)
  • MPPT Error and Fault status

Hardware Required

  • Any ESP32 board with Bluetooth — ESP32-S3 recommended for range and stability
  • Victron SmartSolar MPPT with Bluetooth enabled — most models from 2018 onwards
  • Nothing else — no cables, no adaptors, no Victron dongle

The ESP32 can be placed anywhere within Bluetooth range of the MPPT — typically 5-10 metres through walls.


How It Works

Victron SmartSolar MPPTs broadcast encrypted BLE advertisements continuously. The ESPHome victron_ble external component decrypts these using a device-specific bindkey, extracts the sensor data, and sends it to Home Assistant via the native ESPHome API.

The key point is that the MPPT broadcasts constantly — the ESP32 listens passively, decrypts, and forwards. No Bluetooth pairing required, no connection established.


Step 1: Get Your Bindkey and MAC Address

The bindkey is a 32-character hex encryption key unique to your MPPT. You need it to decrypt the BLE data.

Via the Victron Connect app:

  1. Open Victron Connect on your phone
  2. Connect to your MPPT
  3. Go to Product Info
  4. Find “Encryption key” and copy the 32-character hex string

Alternative — Python method (if the key is not visible in the app):

Some firmware versions do not expose the key in the app. On a computer with Bluetooth and Python:

pip install victron-ble
python -m victron_ble scan

This scans for nearby Victron BLE devices and displays their data including the encryption key.

You also need the MAC address of your MPPT — visible in Victron Connect under Product Info, or shown during the Python scan.


Step 2: Basic ESPHome Configuration — API Only

This is the simplest working configuration. The native ESPHome API handles everything — Home Assistant discovers all sensors automatically with no additional setup.

esphome:
  name: victron-reader          # Choose a name — do not change it later (see Gotchas)
  friendly_name: Victron Reader
  min_version: 2024.11.0
  name_add_mac_suffix: false

esp32:
  board: esp32-s3-devkitc-1     # Adjust for your specific board
  framework:
    type: esp-idf

logger:

api:                             # Native ESPHome API — all that is needed for HA

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

bluetooth_proxy:
  active: true                   # Optional but useful — extends BLE range for HA

button:
  - platform: restart
    name: "Victron Reader Restart"  # Always use a unique descriptive name here

external_components:
  - source: github://Fabian-Schmidt/esphome-victron_ble

esp32_ble_tracker:

victron_ble:
  - id: MySmartSolar
    mac_address: "XX:XX:XX:XX:XX:XX"           # Your MPPT MAC address
    bindkey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # Your 32-character bindkey

sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Voltage"
    type: BATTERY_VOLTAGE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Current"
    type: BATTERY_CURRENT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Yield Today"
    type: YIELD_TODAY

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "PV Power"
    id: pv_power
    type: PV_POWER

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Load Current"
    type: LOAD_CURRENT

  # Integrates PV Power over time to give cumulative energy produced
  - platform: integration
    name: "Solar Energy Produced"
    sensor: pv_power
    unit_of_measurement: "Wh"
    time_unit: h
    accuracy_decimals: 2
    state_class: total_increasing
    device_class: energy

  # WiFi signal in dBm
  - platform: wifi_signal
    name: "Victron Reader WiFi Signal"
    update_interval: 60s

  # WiFi signal as percentage
  # device_class must be overridden — % is not valid for signal_strength (see Gotchas)
  - platform: wifi_signal
    name: "Victron Reader WiFi Signal Percent"
    update_interval: 60s
    device_class: ""
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"

binary_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT is in Fault state"
    type: DEVICE_STATE_FAULT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT has Error"
    type: CHARGER_ERROR

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT in FLOAT"
    type: DEVICE_STATE_FLOAT

text_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT state"
    type: DEVICE_STATE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT Error reason"
    type: CHARGER_ERROR

  - platform: wifi_info
    ip_address:
      name: "Victron Reader IP"
    ssid:
      name: "Victron Reader SSID"
    bssid:
      name: "Victron Reader BSSID"
    mac_address:
      name: "Victron Reader MAC"

Flash this to your ESP32, add the device in Home Assistant under Settings → Devices & Services → ESPHome, and all sensors will appear automatically.


Step 3: Optional — Add MQTT for Multi-Device Publishing

If you want other ESPHome devices (such as display boards) to subscribe to the Victron data directly without going through Home Assistant, add MQTT to the configuration.

This is not required for basic HA monitoring — only add it if you have a specific reason such as feeding data to ESPHome displays.

Add this section to your yaml:

mqtt:
  broker: !secret mqtt_broker
  username: !secret mqtt_username
  password: !secret mqtt_password
  discovery_unique_id_generator: mac  # Essential — prevents duplicate entity ID errors

Other ESPHome devices can then subscribe to topics like:

victron-reader/sensor/pv_power/state
victron-reader/sensor/battery_voltage/state
victron-reader/sensor/yield_today/state
victron-reader/sensor/mppt_state/state

Where victron-reader matches your ESPHome device name.


Step 4: Utility Meters for Daily/Monthly/Yearly Tracking

The Solar Energy Produced sensor accumulates total Wh produced since the device was first flashed. To track daily, monthly and yearly yield add utility meters to configuration.yaml:

utility_meter:
  solar_daily:
    source: sensor.victron_reader_solar_energy_produced
    cycle: daily
  solar_monthly:
    source: sensor.victron_reader_solar_energy_produced
    cycle: monthly
  solar_yearly:
    source: sensor.victron_reader_solar_energy_produced
    cycle: yearly

Adjust the entity ID to match your device name. These reset automatically at midnight, month end, and year end.


Step 5: Historical Top 10 Solar Days Dashboard Card

Because Solar Energy Produced has state_class: total_increasing, Home Assistant writes hourly statistics to the statistics database table which is never purged regardless of your recorder settings. This means you can query years of historical data directly from the SQLite database.

Create the Python script at /config/solar_top10.py:

import sqlite3

db = sqlite3.connect('/config/home-assistant_v2.db')
rows = db.execute('''
    SELECT DATE(start_ts,'unixepoch','localtime'),
           ROUND((MAX(sum)-MIN(sum))/1000,3)
    FROM statistics
    JOIN statistics_meta ON statistics.metadata_id=statistics_meta.id
    WHERE statistic_id='sensor.victron_reader_solar_energy_produced'
    AND sum IS NOT NULL
    GROUP BY DATE(start_ts,'unixepoch','localtime')
    ORDER BY (MAX(sum)-MIN(sum)) DESC
    LIMIT 10
''').fetchall()
print(';'.join(r[0]+'|'+str(r[1])+' kWh' for r in rows))

Adjust the statistic_id to match your actual entity ID.

Add a command_line sensor to configuration.yaml:

command_line:
  - sensor:
      name: "Solar Top 10"
      command: "python3 /config/solar_top10.py"
      scan_interval: 3600
      value_template: "{{ value }}"

Add a markdown card to your dashboard:

type: markdown
title: "☀️ Top 10 Solar Days"
content: >
  {% set data = states('sensor.solar_top_10') %}
  {% if data and data != 'unknown' %}
  {% set rows = data.split(';') %}
  | # | Date | kWh |
  |---|------|-----|
  {% for row in rows %}
  {% set parts = row.split('|') %}
  | {{ loop.index }} | {{ parts[0] }} | {{ parts[1] }} |
  {% endfor %}
  {% else %}
  Loading...
  {% endif %}

Note: Home Assistant converts spaces to underscores in entity IDs. “Solar Top 10” becomes sensor.solar_top_10.


Dashboard Examples

Solar load and yield statistics cards

Dashboard gauge grid showing solar output, battery voltage, house power and current

Daily yield versus total load as a bar chart using the built-in statistics-graph card:

chart_type: bar
period: day
type: statistics-graph
entities:
  - entity: sensor.esphome_web_78378c_yield_today
    name: Total Yield
  - entity: sensor.total_load_energy
    name: Total Load
stat_types:
  - change
grid_options:
  columns: full
logarithmic_scale: false

Daily solar yield vs total load bar chart

PV power output over 5 days showing daily generation curves

Top 10 solar days markdown table


Gotchas and Common Mistakes

Do not change the ESPHome device name

Once Home Assistant has discovered the device and started recording statistics, never change the ESPHome device name. Statistics are stored against the entity ID which is derived from the device name. Renaming creates a new entity with no history — the old data still exists in the database under the old name but is no longer attached to the current entity.

WiFi signal percent device_class error

ESPHome automatically assigns device_class: signal_strength to all wifi_signal platform sensors. This device class only accepts dBm or dB units — not percent. Without the device_class: "" override on the percent sensor, Home Assistant will log this error on every restart:

Entity is using native unit of measurement '%' which is not a valid unit 
for the device class 'signal_strength'

Fix: add device_class: "" to the percent sensor as shown in the config above.

Always give restart buttons a unique name

If you have multiple ESPHome devices all using name: "Restart" on their restart button, MQTT discovery generates duplicate entity IDs and HA logs errors on every restart. Always give restart buttons a unique device-specific name such as "Victron Reader Restart".

If using MQTT — always add discovery_unique_id_generator: mac

Without it, ESPHome generates short generic IDs like ESPsensorpv_power which clash with other devices. The mac generator prefixes every ID with the device MAC address making them globally unique.

Stale retained MQTT messages

MQTT discovery messages are published with the retain flag. If you rename sensors or remove them, the old discovery messages remain in the broker and replay every time HA restarts causing errors. Use MQTT Explorer (free desktop app) to view and delete stale retained messages under the homeassistant/ topic tree.

Statistics entity ID mismatch after device rename or migration

If you rename the ESPHome device or migrate from a different integration, the entity IDs change. HA treats the new entity as brand new — previous statistics are orphaned under the old ID. You can verify what historical data exists by querying the statistics_meta table directly via SSH:

sqlite3 /config/home-assistant_v2.db \
"SELECT statistic_id, DATE(MIN(start_ts),'unixepoch','localtime') as earliest \
FROM statistics \
JOIN statistics_meta ON statistics.metadata_id=statistics_meta.id \
WHERE statistic_id LIKE '%solar%' \
GROUP BY statistic_id;"

Entities Created in Home Assistant

Entity Type Description
sensor.X_pv_power sensor Live PV power in W
sensor.X_battery_voltage sensor Battery voltage in V
sensor.X_battery_current sensor Battery current in A
sensor.X_load_current sensor Load current in A
sensor.X_yield_today sensor kWh produced today, resets at midnight
sensor.X_solar_energy_produced sensor Cumulative Wh, never resets
sensor.X_mppt_state text sensor Off / Bulk / Absorption / Float
binary_sensor.X_mppt_in_float binary True when battery full
binary_sensor.X_mppt_has_error binary True if MPPT error present
binary_sensor.X_mppt_is_in_fault_state binary True if MPPT fault present

Where X is your ESPHome device friendly name with spaces replaced by underscores.


Tested With

  • Victron SmartSolar MPPT 100/20
  • ESP32-S3-DevKitC-1
  • ESPHome 2026.3.x
  • Home Assistant 2026.4.x

Credits

ESPHome external component by Fabian-Schmidt


fletcher@gingineers.com