Improved attempts, still need encyption

This commit is contained in:
2026-06-04 22:32:27 +10:00
parent 4ea1e12ee9
commit 890feae060
8 changed files with 733 additions and 7 deletions
+75 -1
View File
@@ -27,6 +27,23 @@ The protocol is ported from the excellent
time in round-robin.
- Zero external dependencies — uses the stock ESP32 Arduino BLE stack.
## Two modes: connection vs. advertisement
Bluetti exposes two different BLE channels, and this library has a class for each:
| | `BluettiBLE` (connection) | `BluettiADV` (advertisement) |
|---|---|---|
| Transport | GATT connect + Modbus poll | passive advertisement scan |
| Data | full register set + **control** | monitoring snapshot, **read-only** |
| Encryption | none (older models only) | **AES-128-CTR**, key from the app |
| Works on | AC300, AC200M, EB3A, EP500P, … (older/plaintext generation) | newer **encrypted** generation: Elite / V2 / EP600, etc. |
If `BluettiBLE` connects but never returns data and the unit disconnects after a
few seconds, your device is the newer encrypted generation — use **`BluettiADV`**
instead (see [Advertisement mode](#advertisement-mode-bluettiadv) below). The
connection/control channel on those models is locked behind a proprietary
handshake and is not supported; the advertisement channel is open and documented.
## Supported devices
| Model | Enum | Status (from reference project) |
@@ -182,11 +199,68 @@ struct BluettiData {
Control writes use the same frame with `cmd=0x06` and the value placed
big-endian in the length field.
## Advertisement mode (`BluettiADV`)
Newer Bluetti units (Elite, *V2, EP600, AC180, AC200L, …) refuse the plaintext
connection but **broadcast** an encrypted monitoring snapshot in their BLE
advertisements — no connection, no pairing, multiple listeners, low power. This is
the same scheme Victron uses. `BluettiADV` reads it.
You need two things from the owner's side:
1. The **16-byte AES key** (32 hex chars) — copy it from the BLUETTI app or the
device's Webserver (Bluetooth-data / developer section).
2. The device's **BLE MAC address** — a scanner app shows it; confirm the unit
broadcasts manufacturer data starting `06 0F` (company ID `0x0F06`).
```cpp
#include <Arduino.h>
#include "BluettiADV.h"
BluettiADV bluetti;
void onAdv(const BluettiAdvDevice* dev) {
if (dev->lastRecordType == BLUETTI_ADV_MONITORING) {
const auto& m = dev->monitoring;
Serial.printf("SoC %u%% in %u W out %u W\n",
m.soc, m.inputPower, m.outputPower);
}
}
void setup() {
Serial.begin(115200);
bluetti.begin(5);
bluetti.setDebug(true);
bluetti.setCallback(onAdv);
bluetti.addDevice("My Elite", "AA:BB:CC:DD:EE:FF",
"112233445566778899aabbccddeeff00");
}
void loop() { bluetti.loop(); }
```
The device cycles through several **record types**; each callback sets
`dev->lastRecordType` and refreshes one of the sub-structs:
- `BLUETTI_ADV_MONITORING` (`0x80`) — SoC, in/out power, charge state, event flags
- `BLUETTI_ADV_BATTERY` (`0x02`) — pack voltage, current, temperature, SoC
- `BLUETTI_ADV_INVERTER` (`0x0B`) — battery V/A, AC output, PV power, yield
- `BLUETTI_ADV_CONFIG` (`0x81`) — modes (inverter, ECO, charging), settings
This mode is **read-only** — there is no advertisement-based control.
> Note: the advertisement bit-layout is implemented from Bluetti's official BLE
> ADV spec (V1.0, 2025-07-10). The exact AES nonce/counter construction and a few
> field offsets are best confirmed against a real packet — run with
> `setDebug(true)` (it dumps the decrypted payload) and sanity-check a value or
> two. Please report corrections.
## Examples
- **BasicRead** — connect to one device and print all values.
- **BasicRead** — connect to one (older/plaintext) device and print all values.
- **Control** — read, then toggle AC/DC output on a schedule.
- **Logger** — print only when values change (snapshot/change-detection pattern).
- **AdvMonitor** — read a newer (encrypted) device via `BluettiADV` advertisements.
## Adding a new model