intial test version
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# BluettiBLE
|
||||
|
||||
ESP32 library for reading and controlling **Bluetti** power stations over
|
||||
Bluetooth Low Energy (BLE).
|
||||
|
||||
It is a companion to [VictronBLE](https://gitea.sh3d.com.au/Sh3d/VictronBLE) and
|
||||
shares its interface philosophy — a single global object, device registration,
|
||||
one callback, a non-blocking `loop()`, and a flat parsed data struct. The key
|
||||
difference is the transport: Victron broadcasts data in BLE *advertisements*
|
||||
(passive, connectionless), whereas Bluetti requires an active **GATT connection**
|
||||
and a polled, Modbus-style request/response protocol. BluettiBLE therefore runs a
|
||||
`scan → connect → poll → parse` state machine under the hood.
|
||||
|
||||
The protocol is ported from the excellent
|
||||
[Bluetti_ESP32_Bridge](https://github.com/) project.
|
||||
|
||||
## Features
|
||||
|
||||
- Connects to a Bluetti power station by its BLE advertised name — **no pairing
|
||||
or key required**.
|
||||
- Polls the device on a configurable interval and delivers a parsed snapshot
|
||||
through a single callback.
|
||||
- Reads state of charge, AC/DC input & output power, voltages, frequencies,
|
||||
battery pack and per-cell voltages, model and serial number.
|
||||
- Optional control: toggle AC and DC output.
|
||||
- Supports up to `BLUETTI_MAX_DEVICES` registered devices, connecting to one at a
|
||||
time in round-robin.
|
||||
- Zero external dependencies — uses the stock ESP32 Arduino BLE stack.
|
||||
|
||||
## Supported devices
|
||||
|
||||
| Model | Enum | Status (from reference project) |
|
||||
|----------|------------------|---------------------------------|
|
||||
| AC300 | `BLUETTI_AC300` | Tested |
|
||||
| AC200M | `BLUETTI_AC200M` | Tested (per-cell voltages) |
|
||||
| EB3A | `BLUETTI_EB3A` | Tested |
|
||||
| EP500P | `BLUETTI_EP500P` | Tested (per-cell voltages) |
|
||||
| AC500 | `BLUETTI_AC500` | Minimal / untested |
|
||||
| EP500 | `BLUETTI_EP500` | Untested |
|
||||
| EP600 | `BLUETTI_EP600` | Partial (different register map)|
|
||||
|
||||
## Hardware requirements
|
||||
|
||||
- Any ESP32 (ESP32, ESP32-S3, ESP32-C3 all work) with Bluetooth.
|
||||
|
||||
## Installation
|
||||
|
||||
### PlatformIO
|
||||
|
||||
```ini
|
||||
lib_deps =
|
||||
https://gitea.sh3d.com.au/Sh3d/BluettiBLE.git
|
||||
```
|
||||
|
||||
Or drop the folder into your project's `lib/` directory.
|
||||
|
||||
### Arduino IDE
|
||||
|
||||
Copy this folder into your Arduino `libraries/` directory and restart the IDE.
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include <Arduino.h>
|
||||
#include "BluettiBLE.h"
|
||||
|
||||
BluettiBLE bluetti;
|
||||
|
||||
void onBluettiData(const BluettiDevice* dev) {
|
||||
const BluettiData& d = dev->data;
|
||||
Serial.printf("\n=== %s (%s) ===\n", dev->name, d.model);
|
||||
Serial.printf("SoC: %u%%\n", d.totalBatteryPercent);
|
||||
Serial.printf("AC out: %u W DC out: %u W\n", d.acOutputPower, d.dcOutputPower);
|
||||
Serial.printf("AC in: %u W DC in: %u W\n", d.acInputPower, d.dcInputPower);
|
||||
Serial.printf("AC output: %s DC output: %s\n",
|
||||
d.acOutputOn ? "ON" : "off", d.dcOutputOn ? "ON" : "off");
|
||||
Serial.printf("RSSI: %d dBm\n", dev->rssi);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
bluetti.begin();
|
||||
bluetti.setDebug(false);
|
||||
bluetti.setCallback(onBluettiData);
|
||||
|
||||
// The BLE name is what the unit advertises, e.g. "AC3001234567890".
|
||||
bluetti.addDevice("Shed AC300", "AC3001234567890", BLUETTI_AC300);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
bluetti.loop(); // non-blocking state machine
|
||||
}
|
||||
```
|
||||
|
||||
### Finding your device's BLE name
|
||||
|
||||
Use any BLE scanner app (nRF Connect, LightBlue) and look for a device whose name
|
||||
begins with your model (e.g. `AC300…`, `AC200M…`, `EB3A…`). That full advertised
|
||||
name is the second argument to `addDevice()`.
|
||||
|
||||
## API reference
|
||||
|
||||
```cpp
|
||||
BluettiBLE();
|
||||
|
||||
bool begin();
|
||||
bool addDevice(const char* name, const char* bleName, BluettiModel model);
|
||||
void setCallback(BluettiCallback cb);
|
||||
void setDebug(bool enable);
|
||||
void setPollInterval(uint32_t ms); // default 3000
|
||||
bool isConnected() const;
|
||||
size_t getDeviceCount() const;
|
||||
void loop(); // call every loop iteration
|
||||
|
||||
// Control (acts on the currently connected device)
|
||||
bool setACOutput(bool on);
|
||||
bool setDCOutput(bool on);
|
||||
```
|
||||
|
||||
The callback is a plain function pointer:
|
||||
|
||||
```cpp
|
||||
typedef void (*BluettiCallback)(const BluettiDevice* device);
|
||||
```
|
||||
|
||||
It fires once per completed poll cycle for the connected device.
|
||||
|
||||
## Data structures
|
||||
|
||||
`BluettiDevice` describes the device and carries the latest snapshot:
|
||||
|
||||
```cpp
|
||||
struct BluettiDevice {
|
||||
char name[32]; // your label
|
||||
char bleName[32]; // advertised name matched on
|
||||
BluettiModel model;
|
||||
int8_t rssi;
|
||||
uint32_t lastUpdate; // millis() of last update
|
||||
bool connected;
|
||||
bool dataValid;
|
||||
BluettiData data;
|
||||
};
|
||||
```
|
||||
|
||||
`BluettiData` is a flat superset of values across models. Fields a given model
|
||||
does not report stay at `0`:
|
||||
|
||||
```cpp
|
||||
struct BluettiData {
|
||||
char model[16];
|
||||
uint64_t serialNumber;
|
||||
float armVersion, dspVersion;
|
||||
uint8_t totalBatteryPercent; // %
|
||||
uint16_t dcInputPower, acInputPower; // W
|
||||
uint16_t acOutputPower, dcOutputPower; // W
|
||||
float powerGeneration; // kWh total
|
||||
bool acOutputOn, dcOutputOn;
|
||||
float acInputVoltage, acInputFrequency;
|
||||
float internalAcVoltage, internalAcFrequency;
|
||||
float internalDcInputVoltage, internalDcInputCurrent;
|
||||
float packVoltage;
|
||||
uint8_t packNum, packNumMax, packBatteryPercent;
|
||||
float cellVoltage[16]; // V, 0 if unsupported
|
||||
};
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Scan** — active BLE scan for a device advertising the Bluetti service UUID
|
||||
`0000ff00-…` whose advertised name matches a registered device.
|
||||
2. **Connect** — open a GATT client, negotiate MTU 517, resolve the write
|
||||
(`0000ff02-…`) and notify (`0000ff01-…`) characteristics, subscribe to notify.
|
||||
3. **Poll** — every `pollInterval`, send an 8-byte read command
|
||||
(`prefix, cmd=0x03, page, offset, count, CRC-16/MODBUS`) for the next register
|
||||
range in the model's poll table.
|
||||
4. **Parse** — notification responses are decoded against the model's register
|
||||
map (big-endian uint16, decimals, version, serial, strings) into a rolling
|
||||
snapshot; a completed cycle fires the callback.
|
||||
|
||||
Control writes use the same frame with `cmd=0x06` and the value placed
|
||||
big-endian in the length field.
|
||||
|
||||
## Examples
|
||||
|
||||
- **BasicRead** — connect to one 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).
|
||||
|
||||
## Adding a new model
|
||||
|
||||
Copy a header in `src/devices/`, port the register/poll/command tables from the
|
||||
Bluetti_ESP32_Bridge project's `Device_*.h`, add a `BluettiModel` enum value, and
|
||||
register it in `getModelTables()` in `BluettiBLE.cpp`.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user