bluetti-bt-connect-lib
This is a fork of bluetti-bt-lib by Patrick762, extended with additional device support, register corrections, and new writable fields for the Bluetti EP2000. All credit for the original protocol reverse-engineering, architecture, and core library design goes to Patrick762 and the project's other contributors. This fork exists to track device-specific fixes and additions on a faster iteration cycle; where possible, improvements are intended to be contributed back upstream.
Original repository: https://github.com/Patrick762/bluetti-bt-lib
Additional credit to atiweb/hassio-bluetti-bt, a separate fork of the original project, cross-referenced for two specific fixes: correcting
consumption_power_all,pv_input_power_all, andgrid_power_allfrom incorrectly-assumed 32-bit fields to plain 16-bit fields (validated against real captured data), and the pattern for proper Modbus response validation (CRC checking and exception-response detection) now used indevice_reader.py.
Inofficial Library for basic communication to bluetti powerstations. Core functions based on https://github.com/warhammerkid/bluetti_mqtt
Disclaimer
This library is provided without any warranty or support by Bluetti. I do not take responsibility for any problems it may cause in all cases. Use it at your own risk.
⚠️ EP2000: grid/mode controls carry real risk - read before using
AC Output, Charge From Grid, Grid Export, Working Mode, and all four grid import/export power and current limits remain writable in this build. Before you rely on any of them, please understand what we found while investigating this device.
The core problem: writes into this device's grid-related register block can get a clean, protocol-valid acknowledgment from the device - and then the change does not actually take effect. This was confirmed at the raw byte level, ruling out a simple wrong-address or scaling issue. In practice this means: you may set a limit in Home Assistant, see it accepted with no error, and the device may still be running on its old setting. Nothing in the app or the integration will reliably tell you a write didn't stick - the failure is silent. Do not assume a control has taken effect on the device just because Home Assistant shows no error; if you change one of these settings, verify the result independently (in the Bluetti app, or against real grid behavior) before relying on it.
Why we believe this happens: a research pass across comparable BLE-connected solar/battery projects (EcoFlow, Renogy, Growatt, Deye/Sunsynk, Anker SOLIX, Marstek, several open-source BMS forks) found that grid-facing settings - export/import limits, working mode, grid protection - are consistently gated behind some form of vendor authentication across the entire industry, not just on Bluetti hardware: a cloud-account round-trip, a licensed BLE encryption handshake keyed to the device's serial number, or a support-issued password. The EP2000 fits this pattern - there's a genuine "Pro Mode" authentication layer here, and while we confirmed the universal technician password Bluetti documents publicly ("88888888"), simply knowing that password did not make writes persist, which points at a session or encryption-level gate underneath it that hasn't been reverse-engineered.
Why we haven't removed these controls outright: unlike the grid-compliance layer itself, some of these fields (notably the AC Output and Grid Export switches, and Max Grid Export Current) were live-tested and confirmed to actually take effect on this specific unit at various points during development. Reliability may vary by field, by firmware version, and possibly by unit - which is exactly why blanket trust in any of them is the wrong approach. Treat every write as unverified until you've checked its real-world effect yourself.
Grid export and grid-protection parameters are regulated for interconnection safety in most jurisdictions (anti-islanding, voltage/frequency ride-through). Changing them, even successfully, may have real compliance implications depending on where you live and how your system is set up. This is worth knowing regardless of whether a given write actually persists.
If you're an advanced user or researcher: the full research writeup and the exact register-level evidence behind the above are referenced in this version's release notes. The realistic next step for confirming or defeating the authentication gate isn't more register-guessing - it's capturing the official app's authenticated write sequence directly (Android Bluetooth HCI snoop log, or hooking the app's write call with Frida).
Projects using this library
- Bluetti BT Connect - Home Assistant Integration (this fork's companion integration)
- Original Home Assistant Integration
- UPS Server (NUT compatible)
Supported Powerstations and data
Validated
| Device Name | total_battery_percent | dc_input_power | ac_input_power | dc_output_power | ac_output_power |
|---|---|---|---|---|---|
| AC70 | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC180 | ✅ | ✅ | ✅ | ✅ | ✅ |
| EB3A | ✅ | ✅ | ✅ | ✅ | ✅ |
| EP600 | ✅ | PV | Grid | ❌ | AC Phases |
| EP2000 | ✅ | PV | Grid | ❌ | AC Phases, writable grid/mode fields (⚠️ see warning above) |
| Handsfree 1 | ✅ | ✅ | ✅ | ✅ | ✅ |
Added and mostly validated by contributors (some are moved here from the HA Integration https://github.com/Patrick762/hassio-bluetti-bt):
| Device Name | Contributor | total_battery_percent | dc_input_power | ac_input_power | dc_output_power | ac_output_power |
|---|---|---|---|---|---|---|
| AC2A | @ruanmed | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC50B | @goetzc | ✅ | ❌ | ✅ | ✅ | ✅ |
| AC60 | @mzpwr | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC60P | @mzpwr | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC70P | @matthewpucc | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC180P | @Patrick762 | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC200L | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC200M | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC200PL | @0x4E4448 | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC300 | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| AC500 | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| AP300 | @seaburger, @sidieje | ✅ | ✅ | ✅ | ✅ | ✅ |
| EL30V2 | @dgudim | ✅ | ✅ | ✅ | ✅ | ✅ |
| EL100V2 | @seaburger | ✅ | ✅ | ✅ | ✅ | ✅ |
| EP500 | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| EP500P | bluetti-mqtt | ✅ | ✅ | ✅ | ✅ | ✅ |
| EP760 | @Apfuntimes | ✅ | PV | Grid | ❌ | AC Phases |
| EP800 | @jhagenk | ✅ | ❌ | ❌ | ❌ | ❌ |
| PR30V2 | @gentoo90 | ✅ | ✅ | ✅ | ✅ | ✅ |
| PR100V2 | shares PR30V2 register layout (pending validation) | ✅ | ✅ | ✅ | ✅ | ✅ |
Controls
Validated:
| Device Name | ctrl_ac | ctrl_dc |
|---|---|---|
| EB3A | ✅ | ✅ |
Added and mostly validated by contributors:
| Device Name | Contributor | ctrl_ac | ctrl_dc | ctrl_ups_mode | soc_range_start | soc_range_end |
|---|---|---|---|---|---|---|
| AC200L | bluetti-mqtt, @seaburger | ✅ | ✅ | ✅ | ❌ | ❌ |
| EL30V2 | @x3ccd4828 | ✅ | ✅ | ❌ | ❌ | ❌ |
Battery pack data
| Device Name | voltage | battery_soc | cell_voltages |
|---|---|---|---|
| AC300 | ✅ | ✅ | ✅ |
Installation
pip install bluetti-bt-connect-lib
Commands for testing
Commands included in this library should only be used for testing.
Scan for supported devices
usage: bluetti-scan [-h] [-r REGEX] [-s SCAN_TIME]
Detect bluetti devices by bluetooth name
options:
-h, --help show this help message and exit
-r REGEX, --regex REGEX
Custom regex to match device name
-s SCAN_TIME, --scan-time SCAN_TIME
How long to scan for devices (seconds)
Example output: ['EB3A', '00:00:00:00:00:00']
Detect device type by mac address
usage: bluetti-detect [-h] mac
Detect bluetti devices
positional arguments:
mac Mac-address of the powerstation
options:
-h, --help show this help message and exit
Example:
bluetti-detect 00:00:00:00:00:00
Example output: Device type is 'EB3A' with iot version 1 and serial 0000000000000. Full name: EB3A0000000000000
Read device data for supported devices
usage: bluetti-read [-h] [-m MAC] [-t TYPE] [-e ENCRYPTION]
Detect bluetti devices
options:
-h, --help show this help message and exit
-m MAC, --mac MAC Mac-address of the powerstation
-t TYPE, --type TYPE Type of the powerstation (AC70 f.ex.)
-e ENCRYPTION, --encryption ENCRYPTION
Add this if encryption is needed
Example:
bluetti-read -m 00:00:00:00:00:00 -t EB3A
Example output:
FieldName.DEVICE_TYPE: EB3A
FieldName.DEVICE_SN: 0000000000000
FieldName.BATTERY_SOC: 92%
FieldName.DC_INPUT_POWER: 0W
FieldName.AC_INPUT_POWER: 0W
FieldName.AC_OUTPUT_POWER: 0W
FieldName.DC_OUTPUT_POWER: 0W
FieldName.CTRL_AC: False
FieldName.CTRL_DC: True
FieldName.CTRL_LED_MODE: LedMode.OFF
FieldName.CTRL_POWER_OFF: False
FieldName.CTRL_ECO: False
FieldName.CTRL_ECO_TIME_MODE: EcoMode.HOURS1
FieldName.CTRL_CHARGING_MODE: ChargingMode.STANDARD
FieldName.CTRL_POWER_LIFTING: False
Write to supported device
INFO: Devices with encryption are currently not supported!
usage: bluetti-write [-h] [-m MAC] [-t TYPE] [--on ON] [--off OFF] [-v VALUE] [-e ENCRYPTION] field
Write to bluetti device
positional arguments:
field Field name (ctrl_dc f.ex.)
options:
-h, --help show this help message and exit
-m MAC, --mac MAC Mac-address of the powerstation
-t TYPE, --type TYPE Type of the powerstation (AC70 f.ex.)
--on ON Value to write
--off OFF Value to write
-v VALUE, --value VALUE
Value to write (integer, see enum for value)
-e ENCRYPTION, --encryption ENCRYPTION
Add this if encryption is needed
Example:
bluetti-write -m 00:00:00:00:00:00 -t EB3A --on on ctrl_ac
Adding fields
To add new fields, you can use the bluetti-detect command to first find out which version of iot protocol is used and if it uses encryption.
After you got this information, you can use the bluetti-readall command to read every registry and save the data to a json file. You should also note all values you see in the app to later compare the data.
Here's how to use the bluetti-readall command:
usage: bluetti-readall [-h] [-m MAC] [-v VERSION] [-e ENCRYPTION]
Detect bluetti devices
options:
-h, --help show this help message and exit
-m MAC, --mac MAC Mac-address of the powerstation
-v VERSION, --version VERSION
IoT protocol version
-e ENCRYPTION, --encryption ENCRYPTION
Add this if encryption is needed
With the separate tool at bluetti-bt-raw-reader you can view those values in a more understandable way.
You can also share the output with me using this form
To test added fields with the created json file, use bluetti-parse:
usage: bluetti-parse [-h] file
Parse readall output files
positional arguments:
file JSON file of the powerstation readall output
options:
-h, --help show this help message and exit
Metadata
Release files for bluetti-bt-connect-lib 1.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bluetti_bt_connect_lib-1.6.0.tar.gz | 43.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bluetti_bt_connect_lib-1.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 114.3 kB
Release files / bluetti_bt_connect_lib-1.6.0.tar.gz
| Download URL | bluetti_bt_connect_lib-1.6.0.tar.gz |
|---|---|
| Size | 43.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
062d5b19ea719ad568ae7d2627d7ff4b9bcd048d2850c8fbefe94c2086aff610
|
|
BLAKE2b-256 checksum How to use checksums |
24f5cd14b0b7d591e340c8fbb25de4ce0dc1534714c9da7884a463099e36e25c
|
| 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 20, 2026.
Transparency logRelease files / bluetti_bt_connect_lib-1.6.0-py3-none-any.whl
| Download URL | bluetti_bt_connect_lib-1.6.0-py3-none-any.whl |
|---|---|
| Size | 70.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bb90b61e116618d2b0ead1ce731e82e625d202175be95857f448f5f6c8829ac7
|
|
BLAKE2b-256 checksum How to use checksums |
09ff89e114a790689937657378670c7c62f62c40a7f5d14f82a997814e72b836
|
| 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 20, 2026.
Transparency log