492 lines
17 KiB
Markdown
492 lines
17 KiB
Markdown
# VictronBLE
|
|
|
|
A portable Arduino library for reading Victron Energy device data via Bluetooth Low Energy (BLE) advertisements — runs on both **ESP32** and **nRF52840**.
|
|
|
|
v0.6 adds **multi-platform support** (ESP32 + nRF52840) via a hardware-abstracted BLE backend and dependency-free bundled crypto. (v0.5 brought the decoding accuracy fixes and AC charger support; v0.4 reworked the internals — function-pointer callback API, reduced memory usage, non-blocking scanning.) See [VERSIONS](VERSIONS) for full details. A stable **v1.0** release with a consistent, long-term API is coming soon.
|
|
|
|
---
|
|
|
|
Why another library? Most of the Victron BLE examples are built into other frameworks (e.g. ESPHome) or are locked to a single chip. The goal here is one library that works across ESP32 and nRF52 (and is easy to extend to more), usable standalone or inside ESPHome and other frameworks, with a long-term plan to move others onto it and improve the code with many eyes.
|
|
|
|
Supports **ESP32** (original, S and C series — tested on older ESP32, ESP32-S3 and ESP32-C3) and **nRF52840** (Adafruit/Seeed Bluefruit core, e.g. Seeed XIAO nRF52840). All decoding and decryption is shared; only a thin BLE scanning backend is platform-specific (`src/esp32/`, `src/nrf52/`), so other chipsets can be added by implementing one more backend.
|
|
|
|
## Features
|
|
|
|
- ✅ **Multi-Platform**: One API for ESP32 and nRF52840; backend chosen at compile time
|
|
- ✅ **No External Dependencies**: Bundled AES-128-CTR — no mbedTLS or crypto library needed
|
|
- ✅ **Multiple Device Support**: Monitor multiple Victron devices simultaneously
|
|
- ✅ **All Device Types**: Solar chargers, battery monitors, inverters, DC-DC converters, AC chargers
|
|
- ✅ **Framework Friendly**: Works with Arduino (and ESP-IDF on ESP32)
|
|
- ✅ **Clean API**: Simple, intuitive interface with callback support
|
|
- ✅ **No Pairing Required**: Reads BLE advertisement data directly
|
|
- ✅ **Low Power**: Uses passive BLE scanning
|
|
- ✅ **Full Data Access**: Battery voltage, current, SOC, power, alarms, and more
|
|
- ✅ **Production Ready**: Error handling, data validation, debug logging
|
|
|
|
## Supported Devices
|
|
|
|
- **Solar Chargers**: SmartSolar MPPT, BlueSolar MPPT (with BLE dongle)
|
|
- **Battery Monitors**: SmartShunt, BMV-712 Smart, BMV-700 series
|
|
- **Inverters**: MultiPlus, Quattro, Phoenix (with VE.Bus BLE dongle)
|
|
- **DC-DC Converters**: Orion Smart, Orion XS
|
|
- **AC Chargers**: Blue Smart IP22 / IP65 / IP67 chargers
|
|
- **Others**: Smart Battery Protect, Lynx Smart BMS, Smart Lithium batteries
|
|
|
|
## Hardware Requirements
|
|
|
|
- An ESP32 (original / S / C series) **or** an nRF52840 board (Adafruit/Seeed
|
|
Bluefruit core — e.g. Seeed XIAO nRF52840)
|
|
- Victron devices with BLE "Instant Readout" enabled
|
|
|
|
The BLE backend is selected automatically at compile time from the board's
|
|
architecture — no code changes are needed to switch platforms.
|
|
|
|
## Installation
|
|
|
|
### PlatformIO
|
|
|
|
1. Add to `platformio.ini` (recommended — installs from the PlatformIO registry):
|
|
```ini
|
|
lib_deps =
|
|
scottp/victronble
|
|
```
|
|
|
|
Or install directly from git. Note the **`.git` suffix is required** — the bare
|
|
repository URL is not accepted by PlatformIO:
|
|
```ini
|
|
lib_deps =
|
|
https://gitea.sh3d.com.au/Sh3d/VictronBLE.git
|
|
```
|
|
|
|
2. Or clone into your project's `lib` folder:
|
|
```bash
|
|
cd lib
|
|
git clone https://gitea.sh3d.com.au/Sh3d/VictronBLE.git
|
|
```
|
|
|
|
#### nRF52840 board note
|
|
|
|
The nRF52 backend uses the **Bluefruit** library from the Adafruit/Seeed nRF52
|
|
core, so pick a board that uses that core. For the Seeed XIAO nRF52840, the
|
|
board files live in a community platform fork — use the `*_adafruit` variant
|
|
(the plain `xiaoble` variant uses the mbed core, which has no Bluefruit):
|
|
```ini
|
|
[env:xiao_nrf52840]
|
|
platform = https://github.com/maxgerhardt/platform-nordicnrf52
|
|
board = xiaoble_adafruit ; XIAO nRF52840 Sense: xiaoblesense_adafruit
|
|
framework = arduino
|
|
lib_deps = scottp/victronble
|
|
```
|
|
The Adafruit Feather nRF52840 (`board = adafruit_feather_nrf52840`) works out of
|
|
the box with the stock PlatformIO `nordicnrf52` platform. The `MultiDevice`
|
|
example's `platformio.ini` includes ready-made ESP32 and nRF52 environments.
|
|
|
|
### Arduino IDE
|
|
|
|
1. Download or clone this repository
|
|
2. Move the `VictronBLE` folder to your Arduino libraries directory
|
|
3. Restart Arduino IDE
|
|
|
|
## Quick Start
|
|
|
|
### 1. Get Your Encryption Keys
|
|
|
|
Use the VictronConnect app to get your device's encryption key:
|
|
|
|
1. Open VictronConnect
|
|
2. Connect to your device
|
|
3. Go to **Settings** → **Product Info**
|
|
4. Enable **"Instant readout via Bluetooth"**
|
|
5. Click **"Show"** next to **"Instant readout details"**
|
|
6. Copy the **encryption key** (32 hexadecimal characters)
|
|
7. Note your device's **MAC address**
|
|
|
|
### 2. Basic Example
|
|
|
|
```cpp
|
|
#include <Arduino.h>
|
|
#include "VictronBLE.h"
|
|
|
|
VictronBLE victron;
|
|
|
|
// Callback — receives a VictronDevice*, switch on deviceType
|
|
void onVictronData(const VictronDevice* dev) {
|
|
if (dev->deviceType == DEVICE_TYPE_SOLAR_CHARGER) {
|
|
Serial.printf("Solar %s: %.2fV %.2fA %dW\n",
|
|
dev->name,
|
|
dev->solar.batteryVoltage,
|
|
dev->solar.batteryCurrent,
|
|
(int)dev->solar.panelPower);
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
victron.begin(5); // 5 second scan duration
|
|
victron.setCallback(onVictronData);
|
|
|
|
// Add your device (replace with your MAC and key)
|
|
victron.addDevice(
|
|
"My MPPT", // Name
|
|
"AA:BB:CC:DD:EE:FF", // MAC address
|
|
"0123456789abcdef0123456789abcdef", // Encryption key
|
|
DEVICE_TYPE_SOLAR_CHARGER // Device type (optional, auto-detected)
|
|
);
|
|
}
|
|
|
|
void loop() {
|
|
victron.loop(); // Non-blocking, returns immediately
|
|
}
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### VictronBLE Class
|
|
|
|
#### Initialization
|
|
|
|
```cpp
|
|
bool begin(uint32_t scanDuration = 5);
|
|
```
|
|
Initialize BLE scanning. Returns `true` on success.
|
|
|
|
**Parameters:**
|
|
- `scanDuration`: BLE scan window in seconds (default: 5)
|
|
|
|
#### Device Management
|
|
|
|
```cpp
|
|
bool addDevice(const char* name, const char* mac, const char* hexKey,
|
|
VictronDeviceType type = DEVICE_TYPE_UNKNOWN);
|
|
```
|
|
Add a device to monitor (max 8 devices).
|
|
|
|
**Parameters:**
|
|
- `name`: Friendly name for the device
|
|
- `mac`: Device MAC address (format: `"AA:BB:CC:DD:EE:FF"` or `"aabbccddeeff"`)
|
|
- `hexKey`: 32-character hex encryption key from VictronConnect
|
|
- `type`: Device type (optional, auto-detected from BLE advertisement)
|
|
|
|
**Returns:** `true` on success
|
|
|
|
```cpp
|
|
size_t getDeviceCount() const;
|
|
```
|
|
Get the number of configured devices.
|
|
|
|
#### Callback
|
|
|
|
```cpp
|
|
void setCallback(VictronCallback cb);
|
|
```
|
|
Set a function pointer callback. Called when new data arrives from a device. The callback receives a `const VictronDevice*` — switch on `deviceType` to access the appropriate data union member.
|
|
|
|
```cpp
|
|
typedef void (*VictronCallback)(const VictronDevice* device);
|
|
```
|
|
|
|
#### Configuration
|
|
|
|
```cpp
|
|
void setMinInterval(uint32_t ms);
|
|
```
|
|
Set minimum callback interval per device (default: 1000ms). Callbacks are also suppressed when the device nonce hasn't changed (data unchanged).
|
|
|
|
```cpp
|
|
void setDebug(bool enable);
|
|
```
|
|
Enable/disable debug output to Serial.
|
|
|
|
#### Main Loop
|
|
|
|
```cpp
|
|
void loop();
|
|
```
|
|
Call in your main loop. Non-blocking — returns immediately if a scan is already running. Scan restarts automatically when it completes.
|
|
|
|
### Data Structures
|
|
|
|
#### VictronDevice (main struct)
|
|
|
|
All device types share this struct. Access type-specific data via the union member matching `deviceType`.
|
|
|
|
```cpp
|
|
struct VictronDevice {
|
|
char name[32];
|
|
char mac[13]; // 12 hex chars + null
|
|
VictronDeviceType deviceType;
|
|
int8_t rssi; // Signal strength (dBm)
|
|
uint32_t lastUpdate; // millis() of last update
|
|
bool dataValid;
|
|
union {
|
|
VictronSolarData solar;
|
|
VictronBatteryData battery;
|
|
VictronInverterData inverter;
|
|
VictronDCDCData dcdc;
|
|
};
|
|
};
|
|
```
|
|
|
|
#### VictronSolarData
|
|
|
|
```cpp
|
|
struct VictronSolarData {
|
|
uint8_t chargeState; // SolarChargerState enum
|
|
uint8_t errorCode;
|
|
float batteryVoltage; // V
|
|
float batteryCurrent; // A
|
|
float panelPower; // W
|
|
uint16_t yieldToday; // Wh
|
|
float loadCurrent; // A (if load output present)
|
|
};
|
|
```
|
|
|
|
**Charge States** (`chargeState` values):
|
|
`CHARGER_OFF`, `CHARGER_LOW_POWER`, `CHARGER_FAULT`, `CHARGER_BULK`, `CHARGER_ABSORPTION`, `CHARGER_FLOAT`, `CHARGER_STORAGE`, `CHARGER_EQUALIZE`, `CHARGER_INVERTING`, `CHARGER_POWER_SUPPLY`, `CHARGER_EXTERNAL_CONTROL`
|
|
|
|
#### VictronBatteryData
|
|
|
|
```cpp
|
|
struct VictronBatteryData {
|
|
float voltage; // V
|
|
float current; // A (+ charging, - discharging)
|
|
float temperature; // C (0 if aux is voltage)
|
|
float auxVoltage; // V (0 if aux is temperature)
|
|
uint16_t remainingMinutes;
|
|
float consumedAh; // Ah
|
|
float soc; // State of charge %
|
|
bool alarmLowVoltage;
|
|
bool alarmHighVoltage;
|
|
bool alarmLowSOC;
|
|
bool alarmLowTemperature;
|
|
bool alarmHighTemperature;
|
|
};
|
|
```
|
|
|
|
#### VictronInverterData
|
|
|
|
```cpp
|
|
struct VictronInverterData {
|
|
float batteryVoltage; // V
|
|
float batteryCurrent; // A
|
|
float acPower; // W (+ inverting, - charging)
|
|
uint8_t state;
|
|
bool alarmLowVoltage;
|
|
bool alarmHighVoltage;
|
|
bool alarmHighTemperature;
|
|
bool alarmOverload;
|
|
};
|
|
```
|
|
|
|
#### VictronDCDCData
|
|
|
|
```cpp
|
|
struct VictronDCDCData {
|
|
float inputVoltage; // V
|
|
float outputVoltage; // V
|
|
float outputCurrent; // A
|
|
uint8_t chargeState;
|
|
uint8_t errorCode;
|
|
};
|
|
```
|
|
|
|
#### VictronACChargerData
|
|
|
|
```cpp
|
|
struct VictronACChargerData {
|
|
uint8_t chargeState; // SolarChargerState enum (shared charger states)
|
|
uint8_t errorCode;
|
|
float voltage1; // V (output 1)
|
|
float current1; // A (output 1)
|
|
float voltage2; // V (output 2, 0 if absent)
|
|
float current2; // A (output 2, 0 if absent)
|
|
float voltage3; // V (output 3, 0 if absent)
|
|
float current3; // A (output 3, 0 if absent)
|
|
float temperature; // C (0 if not available)
|
|
float acCurrent; // A (0 if not available)
|
|
};
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
### Multiple Devices
|
|
|
|
```cpp
|
|
void setup() {
|
|
victron.begin(5);
|
|
victron.setCallback(onVictronData);
|
|
|
|
// Add multiple devices (type is auto-detected from BLE advertisements)
|
|
victron.addDevice("MPPT 1", "AA:BB:CC:DD:EE:01", "key1...");
|
|
victron.addDevice("MPPT 2", "AA:BB:CC:DD:EE:02", "key2...");
|
|
victron.addDevice("SmartShunt", "AA:BB:CC:DD:EE:03", "key3...");
|
|
victron.addDevice("Inverter", "AA:BB:CC:DD:EE:04", "key4...");
|
|
}
|
|
```
|
|
|
|
### Handling Multiple Device Types
|
|
|
|
```cpp
|
|
void onVictronData(const VictronDevice* dev) {
|
|
switch (dev->deviceType) {
|
|
case DEVICE_TYPE_SOLAR_CHARGER:
|
|
Serial.printf("%s: %.2fV %dW\n", dev->name,
|
|
dev->solar.batteryVoltage, (int)dev->solar.panelPower);
|
|
break;
|
|
case DEVICE_TYPE_BATTERY_MONITOR:
|
|
Serial.printf("%s: %.2fV %.1f%%\n", dev->name,
|
|
dev->battery.voltage, dev->battery.soc);
|
|
break;
|
|
case DEVICE_TYPE_INVERTER:
|
|
Serial.printf("%s: %dW\n", dev->name, (int)dev->inverter.acPower);
|
|
break;
|
|
case DEVICE_TYPE_DCDC_CONVERTER:
|
|
Serial.printf("%s: %.2fV -> %.2fV\n", dev->name,
|
|
dev->dcdc.inputVoltage, dev->dcdc.outputVoltage);
|
|
break;
|
|
case DEVICE_TYPE_AC_CHARGER:
|
|
Serial.printf("%s: %.2fV %.2fA %.0fC\n", dev->name,
|
|
dev->acCharger.voltage1, dev->acCharger.current1,
|
|
dev->acCharger.temperature);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Callback Throttling
|
|
|
|
```cpp
|
|
void setup() {
|
|
victron.begin(5);
|
|
victron.setCallback(onVictronData);
|
|
victron.setMinInterval(2000); // Callback at most every 2 seconds per device
|
|
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### No Data Received
|
|
|
|
1. **Check encryption key**: Must be exactly 32 hex characters from VictronConnect
|
|
2. **Verify MAC address**: Use correct format (AA:BB:CC:DD:EE:FF)
|
|
3. **Enable Instant Readout**: Must be enabled in VictronConnect settings
|
|
4. **Check range**: BLE range is typically 10-30 meters
|
|
5. **Disconnect VictronConnect**: App must be disconnected from device
|
|
6. **Enable debug**: `victron.setDebug(true);` to see detailed logs
|
|
|
|
### Decryption Failures
|
|
|
|
- Encryption key must match exactly
|
|
- Victron may have multiple keys per device; use the current one
|
|
- Keys are case-insensitive hex
|
|
|
|
### Poor Performance
|
|
|
|
- Reduce `scanDuration` for faster updates (minimum 1 second)
|
|
- Increase `scanDuration` for better reliability (5-10 seconds recommended)
|
|
- Ensure good signal strength (RSSI > -80 dBm)
|
|
|
|
## Protocol Details
|
|
|
|
This library implements the Victron BLE Advertising protocol:
|
|
|
|
- **Encryption**: AES-128-CTR
|
|
- **Update Rate**: ~1 Hz from Victron devices
|
|
- **No Pairing**: Reads broadcast advertisements
|
|
- **No Connection**: Extremely low power consumption
|
|
|
|
Based on official [Victron BLE documentation](https://www.victronenergy.com/live/vedirect_protocol:faq).
|
|
|
|
## Architecture & Portability
|
|
|
|
The library keeps everything platform-independent except the BLE radio:
|
|
|
|
```
|
|
src/
|
|
├── VictronBLE.{h,cpp} Common API, device management, payload decoding
|
|
├── crypto/vble_aes.{h,c} Bundled AES-128-CTR (no external dependency)
|
|
├── esp32/ ESP32 backend — Bluedroid BLEScan
|
|
└── nrf52/ nRF52 backend — Bluefruit passive scan
|
|
```
|
|
|
|
- **One BLE HAL.** Each backend extracts the manufacturer data, MAC and RSSI
|
|
from a scan result and calls the shared `onAdvertisement()`. All decryption and
|
|
decoding is common code. The correct backend is selected automatically at
|
|
compile time from the board architecture (`ARDUINO_ARCH_ESP32` /
|
|
`ARDUINO_ARCH_NRF52`) — there is nothing platform-specific in your sketch.
|
|
- **No external crypto.** AES-128-CTR is bundled (a trimmed, NIST-verified
|
|
tiny-AES), so the library no longer depends on mbedTLS or any crypto library
|
|
and builds identically on every target.
|
|
- **Adding a platform** means implementing one more backend (scan → extract →
|
|
`onAdvertisement`); the rest is reused unchanged.
|
|
|
|
> The data callback runs in the BLE event context (the scan task on ESP32, the
|
|
> SoftDevice/Bluefruit handler on nRF52). Keep work in the callback light — copy
|
|
> what you need and process it from `loop()`.
|
|
|
|
## Examples
|
|
|
|
See the `examples/` directory for:
|
|
|
|
- **MultiDevice**: Monitor multiple devices with callbacks. One sketch, multiple
|
|
PlatformIO environments — builds for ESP32 (`esp32dev`, …) and nRF52840
|
|
(`xiao_nrf52840`, `adafruit_feather_nrf52840`).
|
|
- **Logger**: Change-detection logging for Solar Charger data
|
|
- **Repeater**: Collect BLE data and re-transmit via ESPNow broadcast
|
|
- **Receiver**: Receive ESPNow packets from a Repeater and display data
|
|
- **FakeRepeater**: Generate test ESPNow packets without real Victron hardware
|
|
|
|
## Contributing
|
|
|
|
The primary repository is hosted on [Gitea](https://gitea.sh3d.com.au/Sh3d/VictronBLE),
|
|
with a mirror on **GitHub at <https://github.com/SH3D/VictronBLE>**. Since the Gitea
|
|
instance does not currently allow public sign-ups, please raise **issues and pull
|
|
requests on the GitHub mirror**.
|
|
|
|
Contributions welcome! Please:
|
|
|
|
1. Fork the [GitHub mirror](https://github.com/SH3D/VictronBLE)
|
|
2. Create a feature branch
|
|
3. Test thoroughly on real hardware
|
|
4. Submit a pull request
|
|
|
|
## Credits
|
|
|
|
This library was inspired by and builds upon excellent prior work:
|
|
|
|
- **[hoberman's Victron BLE Advertising examples](https://github.com/hoberman/Victron_BLE_Advertising_example)**: ESP32 examples demonstrating Victron BLE protocol implementation
|
|
- **[keshavdv's victron-ble Python library](https://github.com/keshavdv/victron-ble)**: Comprehensive Python implementation of Victron BLE protocol
|
|
- Protocol documentation and specifications by Victron Energy
|
|
|
|
## License
|
|
|
|
MIT License - see LICENSE file for details
|
|
|
|
Copyright (c) 2025 Scott Penrose <scottp@dd.com.au>
|
|
|
|
* https://www.sh3d.com.au/ - Sh3d
|
|
* https://www.dd.com.au/ - Digital Dimensions
|
|
|
|
## Disclaimer
|
|
|
|
This library is not officially supported by Victron Energy. Use at your own risk.
|
|
|
|
## Version History
|
|
|
|
See [VERSIONS](VERSIONS) file for detailed changelog and release history.
|
|
|
|
## Support
|
|
|
|
- 📫 Report issues on the [GitHub mirror](https://github.com/SH3D/VictronBLE/issues)
|
|
(the Gitea instance does not currently allow public sign-ups). Bug reports, device
|
|
decode problems and new device requests are all welcome — debug log output is very
|
|
helpful.
|
|
- 📖 Check the examples directory
|
|
- 🔧 Enable debug mode for diagnostics
|
|
- 📚 See [Victron documentation](https://www.victronenergy.com/live/)
|