Async Python client for Besen EV chargers over BLE
Project description
besen
besen is an async Python client for Besen EV chargers over Bluetooth Low Energy.
It provides the BLE connection management, login flow, protocol parsing, typed state
models, and charger control commands needed by applications such as Home Assistant
integrations.
The library has been verified with a Besen BS20 charger. Other Besen chargers that
advertise as ACP#... and use the same BLE protocol may also work.
Installation
pip install besen
Python 3.12 or newer is required.
Basic Usage
Applications provide the BLE device lookup function. This keeps discovery policy
outside the library, so callers can use bleak, Home Assistant Bluetooth helpers,
or another BLE stack integration.
import asyncio
import logging
from bleak import BleakScanner
from besen import BesenClient, BesenData
ADDRESS = "AA:BB:CC:DD:EE:FF"
PIN = "123456"
async def main() -> None:
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("besen")
device = await BleakScanner.find_device_by_address(ADDRESS, timeout=10.0)
if device is None:
raise RuntimeError("Charger was not found")
def device_provider():
return device
client = BesenClient(
address=ADDRESS,
pin=PIN,
ble_device_provider=device_provider,
logger=logger,
)
def handle_update(data: BesenData) -> None:
print(
"available=",
data.available,
"charging=",
data.charge.charger_status,
"amps=",
data.config.charge_amps,
)
remove_listener = client.add_listener(handle_update)
try:
await client.async_start()
await client.async_start_charging(amps=8)
await asyncio.sleep(5)
await client.async_stop_charging()
finally:
remove_listener()
await client.async_stop()
asyncio.run(main())
Client API
Create one BesenClient per charger:
client = BesenClient(
address="AA:BB:CC:DD:EE:FF",
pin="123456",
ble_device_provider=device_provider,
logger=logger,
advertised_name="ACP#Garage",
sync_clock=True,
)
The BLE device provider is called before connection attempts and reconnects. It
must return a connectable bleak.backends.device.BLEDevice or None when no
connectable path is available.
Lifecycle methods:
await client.async_start()connects, subscribes to notifications, and completes the charger login flow.await client.async_stop()cancels background tasks and disconnects.client.add_listener(callback)registers a synchronous state callback and returns a function that removes it.client.statereturns the latestBesenDatasnapshot.client.is_connectedreports whether the underlying BLE connection is open.
Control methods:
await client.async_start_charging(amps=None)await client.async_stop_charging()await client.async_set_charge_amps(amps)await client.async_refresh_charge_amps()await client.async_set_lcd_brightness(brightness)await client.async_set_temperature_unit(unit)await client.async_set_language(language)await client.async_set_device_name(name)await client.async_refresh_config()
State Model
State updates are immutable dataclasses. Every listener receives a full BesenData
snapshot.
Important fields:
BesenData.available: whether the latest BLE state is usable.BesenData.authenticated: whether the PIN login flow completed.BesenData.info: charger metadata such as serial, model, phases, firmware, and board revision.BesenData.config: configuration values such as charge amps, device name, language, temperature unit, LCD brightness, and RSSI.BesenData.charge: live charging state such as voltage, amperage, energy, temperature, plug state, output state, and charger status.BesenData.last_command: last parsed command response.BesenData.last_error: last connection, protocol, or command error string.
Exceptions
All library-specific errors inherit from BesenError.
CannotConnect: the charger could not be reached or login timed out.NoConnectablePath: no active BLE path is available.InvalidAuth: the charger rejected the configured PIN.ProtocolError: malformed charger data was received.CommandFailed: a charger command could not be sent or was invalid.
Bluetooth Notes
Besen chargers normally allow only one active BLE client connection. Stop other tools or apps that may already be connected to the charger before starting this client.
The client keeps one active BLE connection open, listens for notifications, replies to heartbeats, and schedules reconnects when notifications stop. The caller remains responsible for device discovery, adapter/proxy selection, and deciding when to start or stop the client.
Home Assistant
This package is the reusable Python communication library used by the Besen Home Assistant integration. Home Assistant user-facing setup and troubleshooting notes are kept separately in docs/home-assistant-custom-integration.md.
Safety
EV charging equipment controls real electrical hardware. This library is not a safety controller. Keep charger hardware, breaker sizing, wiring, and local electrical code protections correct independently of any software using this package. Use conservative defaults and manual supervision when automating charging.
Attribution
The Bluetooth protocol implementation is based on the MIT-licensed work in slespersen/evseMQTT, with MQTT-specific runtime behavior replaced by a reusable async Python client.
Additional attribution details are maintained in NOTICE.md.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file besen-0.3.2.tar.gz.
File metadata
- Download URL: besen-0.3.2.tar.gz
- Upload date:
- Size: 32.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
532a9a5d9d11ab17a862d199daef6e7da4cef11569164cfec5a7d4c3e3fc95d1
|
|
| MD5 |
675cc7da96df4654dcc11829492d1ee1
|
|
| BLAKE2b-256 |
a12575c7f3758ce270ac27def670066011aa086436a46fc074aa217997efddda
|
Provenance
The following attestation bundles were made for besen-0.3.2.tar.gz:
Publisher:
release.yml on moryoav/ha_besen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
besen-0.3.2.tar.gz -
Subject digest:
532a9a5d9d11ab17a862d199daef6e7da4cef11569164cfec5a7d4c3e3fc95d1 - Sigstore transparency entry: 2088005480
- Sigstore integration time:
-
Permalink:
moryoav/ha_besen@678cfd13525e688dcb71d98bc9e434b266269ef9 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/moryoav
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@678cfd13525e688dcb71d98bc9e434b266269ef9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file besen-0.3.2-py3-none-any.whl.
File metadata
- Download URL: besen-0.3.2-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
415afb840a6c1e0748bacdda4e778e3e0cd9070b4888ee57f8e0ac1291972d33
|
|
| MD5 |
0133ebcbb53c0e435339de11ef97c429
|
|
| BLAKE2b-256 |
976ace13cf10e3a73c233fd48a0196f2207879004a5fbf3d043707f9cce1bfce
|
Provenance
The following attestation bundles were made for besen-0.3.2-py3-none-any.whl:
Publisher:
release.yml on moryoav/ha_besen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
besen-0.3.2-py3-none-any.whl -
Subject digest:
415afb840a6c1e0748bacdda4e778e3e0cd9070b4888ee57f8e0ac1291972d33 - Sigstore transparency entry: 2088005634
- Sigstore integration time:
-
Permalink:
moryoav/ha_besen@678cfd13525e688dcb71d98bc9e434b266269ef9 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/moryoav
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@678cfd13525e688dcb71d98bc9e434b266269ef9 -
Trigger Event:
push
-
Statement type: