Open-source Python client + Home Assistant integration for the E-Badge E87 / L8 round-screen Bluetooth pin (the one that normally pairs with the Zrun app).
- 🖼️ Static images (JPEG/PNG upload)
- 📝 Rendered text
- 🎞️ Multi-image slideshows (MJPG AVI)
- 🖼️ Animated GIFs
- 🧧 Danmaku — scrolling text with custom colours
Built on top of:
- Reverse-engineered JieLi RCSP framing and mutual-auth cipher
- Upstream hybridherbst/web-bluetooth-e87 (MIT) for protocol flow + AVI container
- Home Assistant's
habluetooth→ ESPHomebluetooth_proxypath so a single badge is reachable from anywhere in the house
See docs/protocol.md for the full wire-level protocol write-up.
pip install git+https://github.com/jumpingmushroom/e87_badge@v0.1.0Usage:
e87 discover # scan for nearby badges
e87 image my-photo.png # upload a still image
e87 text "Hello" --size 96 --colour white # rendered text
e87 slideshow a.png b.png c.png --ms 600 # multi-image slideshow
e87 gif pulse.gif # animated GIF
e87 danmaku "Welcome!" --fg red --bg yellow # scrolling textPass --address AA:BB:CC:DD:EE:FF to target a specific badge (otherwise discovery picks the first one).
Library API:
import asyncio
from e87_badge import E87Client
async def main():
async with E87Client("46:8D:00:01:2C:25") as badge:
await badge.send_image("welcome.png")
await badge.send_text("Hi")
await badge.send_slideshow(["a.png", "b.png", "c.png"], frame_ms=500)
await badge.send_gif("party.gif")
await badge.send_danmaku("breaking news!", fg="red", bg="black")
asyncio.run(main())E87Client accepts either a MAC-address string or a pre-resolved bleak.BLEDevice. The BLEDevice form is what Home Assistant uses to route through whichever Bluetooth proxy is currently closest.
The custom component lives under custom_components/e87_badge/ and installs the library automatically via its manifest.json requirements.
HACS (recommended):
- HACS → Integrations → ⋮ → Custom repositories
- Add
https://github.com/jumpingmushroom/e87_badgeas an Integration - Install "E87 Smart Digital Badge", restart Home Assistant
Manual:
cd /config
git clone --branch v0.1.0 https://github.com/jumpingmushroom/e87_badge
ln -s e87_badge/custom_components/e87_badge custom_components/e87_badgeRestart HA. When the badge advertises, it will appear under Settings → Devices & Services → Discovered as "E87 Smart Digital Badge". Add it once and you get:
- One sensor entity showing connection status (
last_sent_at,last_sent_type,rssi,proxy_sourceattributes) - Five services:
| Service | Fields |
|---|---|
e87_badge.send_image |
image (path, URL, or base64) |
e87_badge.send_text |
text, optional font, size, colour, bg |
e87_badge.send_slideshow |
images (list), optional frame_ms |
e87_badge.send_gif |
image (GIF path/URL/base64), optional max_fps |
e87_badge.send_danmaku |
text, optional fg, bg, font, font_size, speed, fps |
Example automation:
- alias: Welcome badge on arrival
trigger:
- platform: state
entity_id: person.johnny
to: "home"
action:
- service: e87_badge.send_image
target:
entity_id: sensor.e87_badge_status
data:
image: /config/www/badges/welcome.pngBump connection_slots on the ESPHome bluetooth_proxy nearest to the badge. The badge's upload protocol uses a single long-lived GATT connection plus several short command round-trips per send; on a proxy at the default connection_slots: 3, a previous failed session that hasn't fully released its slot can block the next send with Could not subscribe to the badge's notification channel.
Safe default — Bluedroid (stock ESPHome):
bluetooth_proxy:
active: true
connection_slots: 4
esp32_ble_tracker:
scan_parameters:
active: true # needed for the "E87" local_name matcher4 works on default Bluedroid builds. Going to 5+ fails at boot with Failed due to no resources. Try to reduce number of BLE clients in config.
Do not try to switch the proxy to NimBLE to raise the cap — ESPHome's esp32_ble_tracker and bluetooth_proxy components depend on Bluedroid headers (esp_bt_defs.h etc.) and don't compile under NimBLE. Stick with connection_slots: 4 on Bluedroid; in practice that's enough once you avoid leaking slots on every failed attempt (which v0.1.11+ handles).
If sends still fail from a specific proxy, reboot it once — this clears any stale internal state. Long-term, distribute proxies so each is within ~3 m of the badge in typical use.
Uploads take ~5–15 seconds for a small still image, 30–60 seconds for a slideshow/GIF/danmaku. Service calls don't time out at HA's end, but keep this in mind when writing automations — don't fire consecutive sends in rapid succession.
If a send fails mid-transfer, v0.1.12+ sends a CMD_STOP to the badge so the next attempt starts clean. Earlier versions required waiting a few minutes for the badge to time itself out. If you're on an older version and hit this, the workaround is to power-cycle the badge or wait ~3 minutes.
- Linux or HA OS host with Bluetooth, or an ESPHome
bluetooth_proxy - Python 3.11+ (HA itself requires 3.12+)
bleak,bleak-retry-connector,pillow(pulled in automatically)
MIT. See LICENSE. The JieLi auth cipher tables and AVI builder are ported from web-bluetooth-e87 (© 2026 Felix Herbst, MIT).