evsemaster
Unofficial Async Python client for EVSE chargers that speak the EVSEMaster app's UDP protocol, as used by Besen, Telestar, evseODM, Morec, Deltacom and other rebrands of the same hardware.
Using Home Assistant? You probably want the EVSEMaster integration instead.
It is built on this library and installs through HACS: sensors, start/stop, scheduled sessions and automatic discovery, with no code to write.
This library is the plumbing underneath it: one UDP socket, several chargers, parsed events. It is deliberately smaller than the original TypeScript project it is based on, emproto by johnwoo-nl.
Compatible devices
Confirmed by users of the Home Assistant integration:
- Telestar EC311S & EC311S6
- Besen B20
- Morec MC20CAPP / MC20AAPP
Chargers differ in small ways the protocol cannot be asked about, so per-model quirks live in
capabilities.py. An unrecognised charger gets the permissive default and normally just works.
Report a working charger here
to get it listed.
Installation
Published on PyPI as evsemaster; requires Python 3.14 or newer.
pip install evsemaster
Usage
One EvseListener owns the UDP socket; every charger is an EvseDevice added to it. Incoming packets
are routed by the device serial in the packet header, so several chargers share the one socket.
import asyncio
from evsemaster import EvseListener
def on_event(event_type, data):
print(f"Event: {event_type}, Data: {data}")
def on_discovery(discovered):
print(f"Found an unconfigured charger: {discovered}")
async def main():
listener = EvseListener(on_discovery=on_discovery)
await listener.start()
evse = await listener.async_add_device(
host="10.0.0.1", # IP address of the EVSE
password="123456", # 6 digit password of the EVSE
on_event=on_event, # called with every parsed update
)
if await evse.login():
await evse.request_status()
await evse.start_charging()
await evse.stop_charging()
await listener.stop()
asyncio.run(main())
Updates arrive through on_event(event_type, data), where event_type is the model name:
"EvseStatus", "ChargingStatus" or "EvseDeviceInfo".
Scheduling a session
from datetime import timedelta
from evsemaster import now_aware
await evse.start_charging(
max_amps=16,
start_date=now_aware() + timedelta(hours=2), # must be timezone aware
duration_minutes=180,
)
Chargers interpret every timestamp as Asia/Shanghai local time, and some firmware has a clock that
drifts by weeks. The library converts and compensates for both, so pass normal aware datetimes and
read evse.time_delta if you want to see the skew it corrected.
Networking
Chargers broadcast their presence on UDP port 28376, which is how the library learns a charger's send port, follows it across DHCP changes and logs back in after a session drops. Those broadcasts have to reach your host: a separate VLAN or Docker bridge networking blocks them.
listener.probe() broadcasts a password-less login request for chargers that stay quiet, so discovery
works without a real password crossing the network.
A device counts as logged in while the charger keeps talking to it: evse.is_logged_in needs a
successful login plus a packet other than an announcement within the last 120 seconds
(evse.last_alive).
Implemented
- Multiple chargers on one UDP socket, routed by device serial
- Discovery of chargers on the network, including ones that must be asked
- Device info, status and charging status as parsed pydantic models
- Start/stop charging, including reservations with a start time and a duration
- Get/set nickname
- Get/set current limit, refused on models that silently ignore it mid-charge
- Device time, with correction for on-device clock drift
Not planned
- Home Assistant or MQTT integration, see evsemaster-homeassistant
- Recurring charge schedules; single reservations are covered by
start_charging - Bluetooth setup (getting the charger onto wifi). The EVSEMaster app is unpleasant, but you only need it once.
Development
poetry install
poetry run poe check # ruff check and pytest
evsemaster.testing.FakeEvse is a charger that speaks enough of the protocol to drive a real client
over loopback. It is public API, so downstream projects can use it in their own tests.
For poking at real hardware there is an interactive script that takes one or more chargers:
poetry run python test.py 10.0.0.1:123456 10.0.0.2:654321
Shortcuts: status, start [amps] [YYYY-MM-DDTHH:MM:SS] [minutes], stop, discover, use <n>, all.
Credits
The protocol was reverse engineered by @johnwoo-nl in emproto. Packet structures, command codes and the communication patterns all come from that work; this library is a Python take on it.
Metadata
Release files for evsemaster 2.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| evsemaster-2.0.3.tar.gz | 21.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| evsemaster-2.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.1 kB
Release files / evsemaster-2.0.3.tar.gz
| Download URL | evsemaster-2.0.3.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3dac3e8ecdaafe03ef4e6ea422a8ae11552c210a5c2f19d895b11a3069ae8ae6
|
|
BLAKE2b-256 checksum How to use checksums |
2e6e4a996d88eee31749e114e21e75ddddcbc28e4198225d21f7529a4ffef9e2
|
| 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 19, 2026.
Transparency logRelease files / evsemaster-2.0.3-py3-none-any.whl
| Download URL | evsemaster-2.0.3-py3-none-any.whl |
|---|---|
| Size | 23.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f26111130a238d8a4c624884e3903f6835a6a4a5d60d888bb41a2e5a9e2b149
|
|
BLAKE2b-256 checksum How to use checksums |
3d41bb7bd05c45adb1249398ae8d6ae80175ca311fb4616356fa3bbe14019794
|
| 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 19, 2026.
Transparency logSecurity for evsemaster 2.0.3
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for evsemaster-2.0.3.tar.gz
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 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.