solarflow-ble
Async Python library for Zendure SolarFlow devices over Bluetooth Low Energy. The protocol work builds on esphome-solarflow-ble and reverse engineering of the Zendure Android app.
The library has been tested with a SolarFlow 2400AC through a Home Assistant Connect AUX-2 Bluetooth proxy. It requires Python 3.14 or newer.
Install
uv add solarflow-ble
Library usage
SolarFlowClient accepts an injected BleTransport. Tests can use a fake
transport without Bluetooth hardware.
import asyncio
from bleak.backends.device import BLEDevice
from solarflow_ble import BleakTransport, SolarFlowClient
async def main() -> None:
device = BLEDevice("AA:BB:CC:DD:EE:FF", "SolarFlow", {})
client = SolarFlowClient(BleakTransport(device))
try:
await client.connect()
print(client.device_id)
print(client.state)
finally:
await client.disconnect()
asyncio.run(main())
The client waits for BLESPP, sends BLESPP_OK, requests getInfo, then
sends a read request for getAll. It keeps the session updated with report
messages. Always disconnect the client.
Control methods are disabled unless allow_control=True. Validated ranges are:
- input and output limits:
0..2400 W - minimum SOC:
0..50% - target SOC:
70..100% - AC mode:
1or2
Probe
The developer-only probe captures raw GATT traffic through an ESPHome Bluetooth proxy. It never sends device-setting writes.
List advertisements:
uv run scripts/probe_solarflow.py \
--proxy "the-ip-of-your-esphome-bluetooth-proxy" \
--noise-psk "the-encryption-key-of-your-esphome-bluetooth-proxy" \
--list-advertisements \
--show-identities
Capture a target by address or SolarFlow advertisement identifier:
uv run scripts/probe_solarflow.py \
--proxy "the-ip-of-your-esphome-bluetooth-proxy" \
--noise-psk "the-encryption-key-of-your-esphome-bluetooth-proxy" \
--identifier "DEVICE_IDENTIFIER" \
--capture-seconds 30 \
--output /tmp/solarflow.jsonl
Use --no-handshake for passive notification capture. Addresses, identifiers,
device IDs, product keys, pack serials, and credentials are redacted from
capture files. --show-identities affects logs only.
The probe uses the same SolarFlowClient protocol flow as the library in
normal mode. Passive mode connects directly to the notification characteristic
without sending protocol writes.
Standalone library test
scripts/test_solarflow_client.py exercises the library directly. It does not
use the probe script. The default run is read-only and requires exactly one of
--address or --identifier.
uv run scripts/test_solarflow_client.py \
--proxy "the-ip-of-your-esphome-bluetooth-proxy" \
--noise-psk "the-encryption-key-of-your-esphome-bluetooth-proxy" \
--identifier "DEVICE_IDENTIFIER" \
--duration 30 \
--output /tmp/solarflow-library-test.jsonl
You can put the connection settings in the ignored file
scripts/test_solarflow_client.local.json:
{
"proxy": "the-ip-of-your-esphome-bluetooth-proxy",
"noise_psk": "the-encryption-key-of-your-esphome-bluetooth-proxy",
"identifier": "DEVICE_IDENTIFIER"
}
Then run:
uv run scripts/test_solarflow_client.py --duration 30
The script prints decoded updates to stdout and always redacts optional JSONL
output. Controls require both --controls and --confirm-controls. Never
commit the local config or a real PSK.
Development
uv sync
uv run python -m scripts.check
Run focused tests:
uv run pytest tests/test_solarflow.py
uv run pytest tests/test_solarflow_client_script.py
The full gate runs format, Ruff, mypy, branch-covered tests, coverage, and
uv build in that order.
License
MIT. See LICENSE.
Metadata
Release files for solarflow-ble 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| solarflow_ble-0.1.2.tar.gz | 8.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| solarflow_ble-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 19.5 kB
Release files / solarflow_ble-0.1.2.tar.gz
| Download URL | solarflow_ble-0.1.2.tar.gz |
|---|---|
| Size | 8.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd7d963c085dff0b1c864ecf3854018db13fa969ad7b7d1ee094205384c2336f
|
|
BLAKE2b-256 checksum How to use checksums |
c5153c7405d8c72a1fe4ff5bb472c9a7bc48703349a71ffc708a2a54ad946d19
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / solarflow_ble-0.1.2-py3-none-any.whl
| Download URL | solarflow_ble-0.1.2-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b86ebea8449408eee16a805af6460ed7619da4e238c1723ba6ec34b102e5ddc
|
|
BLAKE2b-256 checksum How to use checksums |
9bf5dbf29ad6bdd0146234658e3972083cf10f7b21476b3f4a3fb07d8c670c4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency log