Skip to main content

Async Python client for Besen EV chargers over BLE

Project description

besen

release CI license

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.state returns the latest BesenData snapshot.
  • client.is_connected reports 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

besen-0.3.1.tar.gz (32.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

besen-0.3.1-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file besen-0.3.1.tar.gz.

File metadata

  • Download URL: besen-0.3.1.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

Hashes for besen-0.3.1.tar.gz
Algorithm Hash digest
SHA256 28e2d802ac44c1bf6dffa94fc9bc49cd963142d8dd535ce62293baa959792844
MD5 0bf6d3f64a15276289a6d6ad3b64cb2d
BLAKE2b-256 e9b5c38e81f0f8e82a38cba84aa036b94383b1a16648ab1556285cbe27b74d30

See more details on using hashes here.

Provenance

The following attestation bundles were made for besen-0.3.1.tar.gz:

Publisher: release.yml on moryoav/ha_besen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file besen-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: besen-0.3.1-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

Hashes for besen-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7bdf1a4aea19c271a4a12a8065857dbc3537a469e50fdbd0cfc23d13089fd250
MD5 0fb40dcf2b9a41d44a4effd90db55e7c
BLAKE2b-256 fa4ceea22081cea2eaa09d8a6a6d87d1d3ddcb8d46c8c301cfeb647f6d79a6e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for besen-0.3.1-py3-none-any.whl:

Publisher: release.yml on moryoav/ha_besen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page